diff --git a/.vscode/superviseur.code-workspace b/.vscode/superviseur.code-workspace index 7b369b4..deab3fc 100644 --- a/.vscode/superviseur.code-workspace +++ b/.vscode/superviseur.code-workspace @@ -11,6 +11,21 @@ }, { "path": "../examples" + }, + { + "path": "../sdk/deno" + }, + { + "path": "../sdk/gleam" + }, + { + "path": "../sdk/go" + }, + { + "path": "../sdk/node" + }, + { + "path": "../sdk/rust" } ], "settings": {} diff --git a/Cargo.lock b/Cargo.lock index d094cc6..db05246 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -810,6 +810,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colored_json" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5373cd259cdfce1f5fc3d1d577c1fad8c597828f25b9ae70032b095f03825ae9" +dependencies = [ + "is-terminal", + "serde", + "serde_json", + "yansi", +] + [[package]] name = "concurrent-queue" version = "2.1.0" @@ -1189,6 +1201,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -1535,6 +1558,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1707,6 +1736,18 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix 0.37.3", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1832,6 +1873,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" + [[package]] name = "local-channel" version = "0.1.3" @@ -2565,10 +2612,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" +dependencies = [ + "bitflags", + "errno 0.3.1", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.6", "windows-sys 0.45.0", ] @@ -2639,9 +2700,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -2809,6 +2870,7 @@ dependencies = [ "bat", "chrono", "clap", + "colored_json", "dirs", "dyn-clone", "futures", @@ -2827,6 +2889,7 @@ dependencies = [ "prost", "rust-embed", "serde", + "serde_json", "sha256", "slab", "sysinfo", @@ -2950,7 +3013,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix", + "rustix 0.36.8", "windows-sys 0.42.0", ] @@ -2969,7 +3032,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a" dependencies = [ - "rustix", + "rustix 0.36.8", "windows-sys 0.45.0", ] @@ -3551,13 +3614,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", ] [[package]] @@ -3566,7 +3629,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.1", +] + +[[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.0", ] [[package]] @@ -3575,13 +3647,28 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -3590,42 +3677,84 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winnow" version = "0.3.3" @@ -3644,6 +3773,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index c66273b..e4995c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,8 @@ notify = "5.1.0" indexmap = { version = "1.9.2", features = ["serde"] } async-trait = "0.1.68" dyn-clone = "1.0.11" +serde_json = "1.0.96" +colored_json = "3.1.0" [build-dependencies] tonic-build = "0.8" diff --git a/build.rs b/build.rs index 137b56b..93776a8 100644 --- a/build.rs +++ b/build.rs @@ -1,12 +1,25 @@ fn main() -> Result<(), Box> { - tonic_build::configure().out_dir("src/api").compile( - &[ - "proto/objects/v1alpha1/service.proto", - "proto/superviseur/v1alpha1/control.proto", - "proto/superviseur/v1alpha1/core.proto", - "proto/superviseur/v1alpha1/logging.proto", - ], - &["proto"], - )?; + tonic_build::configure() + .type_attribute("objects.v1alpha1.Project", "#[derive(serde::Serialize)]") + .type_attribute("objects.v1alpha1.Service", "#[derive(serde::Serialize)]") + .type_attribute( + "superviseur.v1alpha1.ListProjectsResponse", + "#[derive(serde::Serialize)]", + ) + .type_attribute( + "superviseur.v1alpha1.GetProjectResponse", + "#[derive(serde::Serialize)]", + ) + .out_dir("src/api") + .compile( + &[ + "proto/objects/v1alpha1/service.proto", + "proto/superviseur/v1alpha1/control.proto", + "proto/superviseur/v1alpha1/core.proto", + "proto/superviseur/v1alpha1/logging.proto", + "proto/superviseur/v1alpha1/project.proto", + ], + &["proto"], + )?; Ok(()) } diff --git a/examples/angular/Superfile.hcl b/examples/angular/Superfile.hcl index 1807e46..9c6635b 100644 --- a/examples/angular/Superfile.hcl +++ b/examples/angular/Superfile.hcl @@ -13,6 +13,7 @@ services = [ "namespace" = "demo_namespace" "stdout" = "/tmp/angular-stdout.log" "stderr" = "/tmp/angular-stderr.log" + "port" = 4200 flox = { "environment" = ".#angular" } diff --git a/examples/deno-fresh/deno.lock b/examples/deno-fresh/deno.lock index e2d2411..fc878fc 100644 --- a/examples/deno-fresh/deno.lock +++ b/examples/deno-fresh/deno.lock @@ -102,26 +102,26 @@ "https://deno.land/x/ts_morph@16.0.0/mod.ts": "adba9b82f24865d15d2c78ef6074b9a7457011719056c9928c800f130a617c93", "https://deno.land/x/ts_morph@16.0.0/ts_morph.d.ts": "38668b0e3780282a56a805425494490b0045d1928bd040c47a94095749dab8c3", "https://deno.land/x/ts_morph@16.0.0/ts_morph.js": "9fc0f3d6a3997c2df023fabc4e529d2117d214ffd4fd04247ca2f56c4e9cd470", - "https://esm.sh/*preact-render-to-string@5.2.4": "5c965103e5039039a89c5de1f551a690aecd3918afe53b5d967ce5d96d939584", - "https://esm.sh/preact@10.11.0": "e888b244446037c56f1881173fb51d1f5fa7aae5599e6c5154619346a6a5094e", + "https://esm.sh/*preact-render-to-string@5.2.4": "ea3c2017616ad84afc9e1aa55e1d40d692d191dd0e232c4063c200a458740c71", + "https://esm.sh/preact@10.11.0": "7a2563b2c3ec030072836f2d2746ffda575fbddd9bb573ec8f9ebdcdefabf4fa", "https://esm.sh/preact@10.11.0/hooks": "2b8ec155eb8b87501663f074acff1d55a9114fa7d88f0b39da06c940af1ff736", "https://esm.sh/preact@10.11.0/jsx-runtime": "5c123264f19799ab243211132dded45f6d42d594b5c78dd585f947d07bf20eae", - "https://esm.sh/stable/preact@10.11.0/deno/hooks.js": "32a891b0e7abdfbf30dc7c274dbcbd46f2ea04d6637174451c50c41c12429805", - "https://esm.sh/stable/preact@10.11.0/deno/jsx-runtime.js": "0310ceb4c83643ee994baa3104675f08834300731a838ddecf5078853492e8d3", - "https://esm.sh/stable/preact@10.11.0/deno/preact.js": "08219fcb5b92ccdc709233a070ab22478c91521866c819aea679144847e4e87a", - "https://esm.sh/twind@0.16.17": "519a5a4d20ff4f797ddf747af70a5600b3581c50a6edf38a4d77e8d7b4caff74", - "https://esm.sh/twind@0.16.17/sheets": "5a2b275c294ceffb1b09f2cc367b386172b6ccaa14273dbde473865d4d3f6721", - "https://esm.sh/v111/csstype@3.1.1/index.d.ts": "1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383", - "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.js": "7651121e4bb5ef2a48f840dba3f0c13087293ca2eb825aa23ffea3a87bd463b3", - "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0", - "https://esm.sh/v111/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", - "https://esm.sh/v111/preact@10.11.0/jsx-runtime/src/index.d.ts": "e153460ed2b3fe2ad8b93696ecd48fbf73cd628b0b0ea6692b71804a3af69dfd", - "https://esm.sh/v111/preact@10.11.0/src/index.d.ts": "1a5c331227be54be6515b0c92a469d352834fa413963ae84a39a05a3177111f6", - "https://esm.sh/v111/preact@10.11.0/src/jsx.d.ts": "c423715fd7992b2e1446fea11d2d04e8adbd66c1edca1ce5e85f90e0d26a2eb2", - "https://esm.sh/v111/style-vendorizer@2.2.3/deno/style-vendorizer.js": "4823723c1cd5b34a60b4a1dfdf272a821d367fc472c23aeed6c3bcd7af5b7a7b", - "https://esm.sh/v111/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db", - "https://esm.sh/v111/twind@0.16.17/deno/twind.js": "076dfc344b9507b3f5ee8a20d0b1e6eeb2ea3c6688649026ed0e13e4de158ff2", - "https://esm.sh/v111/twind@0.16.17/sheets/sheets.d.ts": "103ecfb19311b86eb5f7a4899f6a3b1192a84cfb73b6ff9a8b84d78b053e6704", - "https://esm.sh/v111/twind@0.16.17/twind.d.ts": "35492f009a6d8695748d121d0b3b210b0819a663eafc77d68296d3110d6ec27f" + "https://esm.sh/stable/preact@10.11.0/deno/hooks.js": "da007f66891314678a699e81043726a60ccc7981a8c65a6c5b2a83099dd4eb8c", + "https://esm.sh/stable/preact@10.11.0/deno/jsx-runtime.js": "dd51d3a9e2006fc844e355973253755ef3d869729d44746dc69a2a4c14771f78", + "https://esm.sh/stable/preact@10.11.0/deno/preact.mjs": "ad6f5591bcf99d1a3f92079b2215ee7d0b00aed264afde30b4290159142328dd", + "https://esm.sh/twind@0.16.17": "7cc696d414627c6b6c684a2a6d8ce37e0665aad6ed7363cea67464e8469675e3", + "https://esm.sh/twind@0.16.17/sheets": "7b0edb211c27ed5b0f28ea8c56dd8de5eee777c26ef062ba010d591b0df2cee1", + "https://esm.sh/v117/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", + "https://esm.sh/v117/preact@10.11.0/jsx-runtime/src/index.d.ts": "e153460ed2b3fe2ad8b93696ecd48fbf73cd628b0b0ea6692b71804a3af69dfd", + "https://esm.sh/v117/preact@10.11.0/src/index.d.ts": "1a5c331227be54be6515b0c92a469d352834fa413963ae84a39a05a3177111f6", + "https://esm.sh/v117/preact@10.11.0/src/jsx.d.ts": "c423715fd7992b2e1446fea11d2d04e8adbd66c1edca1ce5e85f90e0d26a2eb2", + "https://esm.sh/v119/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288", + "https://esm.sh/v119/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.mjs": "5ddbdb771669d535c808638acaa03e0866128c4e58462aa34de05a8997d6f635", + "https://esm.sh/v119/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0", + "https://esm.sh/v119/style-vendorizer@2.2.3/deno/style-vendorizer.mjs": "fb725497dd9621a84f552f9b6a4f3df82af5989ff18c40e972de1bdf475c9765", + "https://esm.sh/v119/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db", + "https://esm.sh/v119/twind@0.16.17/deno/twind.mjs": "ad9863faad5a13136a5e3449188648b7600860d5bd206d6778f3515e796e3455", + "https://esm.sh/v119/twind@0.16.17/sheets/sheets.d.ts": "9599f49d7214ac8c2c9b4286485ee033d01b742716f63fee110a8fc5301384f4", + "https://esm.sh/v119/twind@0.16.17/twind.d.ts": "96d3dae0fd2fdc7c826236533c2fc8c0eac3dd4982ca0a045cfe6ec61c1f557d" } } diff --git a/proto/objects/v1alpha1/process.proto b/proto/objects/v1alpha1/process.proto index 44c57bc..5736960 100644 --- a/proto/objects/v1alpha1/process.proto +++ b/proto/objects/v1alpha1/process.proto @@ -18,4 +18,5 @@ message Process { bool auto_restart = 13; repeated string env = 14; string service_id = 15; + int32 port = 16; } \ No newline at end of file diff --git a/proto/objects/v1alpha1/project.proto b/proto/objects/v1alpha1/project.proto new file mode 100644 index 0000000..e36f81e --- /dev/null +++ b/proto/objects/v1alpha1/project.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package objects.v1alpha1; + +import "objects/v1alpha1/service.proto"; + +message Project { + string id = 1; + string name = 2; + string description = 3; + string context = 4; + repeated objects.v1alpha1.Service services = 5; +} \ No newline at end of file diff --git a/proto/objects/v1alpha1/service.proto b/proto/objects/v1alpha1/service.proto index 4ead9e6..9affccb 100644 --- a/proto/objects/v1alpha1/service.proto +++ b/proto/objects/v1alpha1/service.proto @@ -12,4 +12,5 @@ message Service { string status = 7; repeated string depends_on = 8; bool auto_restart = 9; + int32 port = 10; } \ No newline at end of file diff --git a/proto/superviseur/v1alpha1/project.proto b/proto/superviseur/v1alpha1/project.proto new file mode 100644 index 0000000..fbba7a8 --- /dev/null +++ b/proto/superviseur/v1alpha1/project.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package superviseur.v1alpha1; + +import "objects/v1alpha1/project.proto"; + +message ListProjectsRequest { + string filter = 1; +} + +message ListProjectsResponse { + repeated objects.v1alpha1.Project projects = 1; +} + +message GetProjectRequest { + string id = 1; +} + +message GetProjectResponse { + objects.v1alpha1.Project project = 1; +} + +service ProjectService { + rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse); + rpc GetProject(GetProjectRequest) returns (GetProjectResponse); +} \ No newline at end of file diff --git a/sdk/deno/.gitignore b/sdk/deno/.gitignore new file mode 100644 index 0000000..3d8239c --- /dev/null +++ b/sdk/deno/.gitignore @@ -0,0 +1,15 @@ +# Created by https://www.toptal.com/developers/gitignore/api/deno +# Edit at https://www.toptal.com/developers/gitignore?templates=deno + +### Deno ### +/.idea/ +/.vscode/ + +/node_modules + +.env +*.orig +*.pyc +*.swp + +# End of https://www.toptal.com/developers/gitignore/api/deno diff --git a/sdk/deno/client.ts b/sdk/deno/client.ts new file mode 100644 index 0000000..631f9b6 --- /dev/null +++ b/sdk/deno/client.ts @@ -0,0 +1,54 @@ +import Project from "./project.ts"; +import { + gql, + GraphQLClient, +} from "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/mod.ts"; + +class Client { + client: GraphQLClient; + + constructor() { + this.client = new GraphQLClient("http://localhost:5478/graphql"); + } + + newProject(): Project { + return new Project(this); + } + + async project(id: string): Promise { + const query = gql` + query Project($id: ID!) { + project(id: $id) { + id + name + } + } + `; + const { project } = await this.client.request(query, { id }); + + const p = new Project(this); + p.id = project.id; + return p; + } + + async projects() { + const query = gql` + query Projects { + projects { + id + name + } + } + `; + const { projects } = await this.client.request(query, {}); + return projects; + } + + send(query: string, variables: any) { + return this.client.request(query, variables); + } +} + +export const connect = (): Client => new Client(); + +export default Client; diff --git a/sdk/deno/deno.json b/sdk/deno/deno.json new file mode 100644 index 0000000..4711ff3 --- /dev/null +++ b/sdk/deno/deno.json @@ -0,0 +1,3 @@ +{ + "importMap": "import_map.json" +} \ No newline at end of file diff --git a/sdk/deno/deno.lock b/sdk/deno/deno.lock new file mode 100644 index 0000000..32cfc56 --- /dev/null +++ b/sdk/deno/deno.lock @@ -0,0 +1,247 @@ +{ + "version": "2", + "remote": { + "https://deno.land/std@0.186.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.186.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.186.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.186.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.186.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.186.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.186.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.186.0/path/mod.ts": "ee161baec5ded6510ee1d1fb6a75a0f5e4b41f3f3301c92c716ecbdf7dae910d", + "https://deno.land/std@0.186.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.186.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.186.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/GraphQLError.d.ts": "aa0d285b36b47fb1f100d69c8f8943a21e6936ab94a9b5980b6f41fc0857f0ea", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/GraphQLError.js": "18adbba7aa651770e0876d0c7df4e6e2ab647a9f09d4b5c107c57d6fa157be9d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/formatError.d.ts": "cdaad07cbbae178884d9145c9682d43ab0fac4cbf993ff58fc338ec5e0ac1cf9", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/formatError.js": "aec87433c501df6d6272b64974e8edf53b2ed192e66782b827328d635ed55df8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/index.d.ts": "190cf062145a81e0d5e9bdc896cc4288ca6df147623aec4b0a8e2010c4ca3a0f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/index.js": "7557dcea8830550f82dd7b1984fdc216e14327d094f501bd2a03f80bf609a768", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/locatedError.d.ts": "65f6dd69a9c70b7eff68226f1b76af9292be7ebf6c5ef659025b6149a389f168", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/locatedError.js": "65ad5e9246747d2b63cd2ea48fa22db617e1c7c2b796a27b8ce32bfb0f2a401c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/syntaxError.d.ts": "1bb24605698f8d7fc3e82eea42eb6101cd4fb4a6c13caa839604a471efc16b63", + "https://deno.land/x/graphql_deno@v15.0.0/lib/error/syntaxError.js": "9c53411030cf5f4e874e9d1c1926f242e4acc4b469197b395ae1a9a7a9092055", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/execute.d.ts": "d17f525b4af6933d8aebe07f2b28bad0d64fd20fe40e67c9d6a0de08a938cb79", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/execute.js": "2d3114e49268a50195195039cc14629b0fe3dff7afeafed9876f1532a95ed4e7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/index.d.ts": "c5dc291a92b7c1e4cd1bd274680ee8b7deb3e342029a9d4edaea9d0d7d6b7c41", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/index.js": "064615af63e0da1584c557ce130b3ca42acab750289a085ebcc3107aa024ce52", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/values.d.ts": "40d52b064cde6ac57989d59b203336a8266cd8a5c321d48c8f0197fe1e545937", + "https://deno.land/x/graphql_deno@v15.0.0/lib/execution/values.js": "e9a42ff1593db210a47504eebcc145f70f8d97cfe53c4cce345116bb420fbf95", + "https://deno.land/x/graphql_deno@v15.0.0/lib/graphql.d.ts": "9645cbeaa04577f8260a42dc5246a68374c35645e0ecd93e4cdd0ce0630cc6ac", + "https://deno.land/x/graphql_deno@v15.0.0/lib/graphql.js": "c34b19e78a57ad0a0ac9b39ec2766ab755b23e61ea08edf124dda1d60104ae54", + "https://deno.land/x/graphql_deno@v15.0.0/lib/index.d.ts": "0a9ca9f997ba290dfd0f5d53f481a5b65d2d6ac5771b9b65c1abf9ec70408cca", + "https://deno.land/x/graphql_deno@v15.0.0/lib/index.js": "ab428112340017d5b1f74e62f958cf0c50c17b664a33be53974ab8738a531c1c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/Path.d.ts": "9a270d184390b8fe3ab1dc838da770a10663611c644d8e11e4e2c2d4389e3d5b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/Path.js": "28322158c8208f92d376969de58c30ea393aada1beb44223d40584d8d89285c0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/PromiseOrValue.d.ts": "641b9da0622e0225740b5a55f47af9f23f01bf8f4dcbfb81128c16b585900717", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/defineToJSON.js": "5423bbfe56faf7c3b0cda65b515c7fcc0e111bef46da9f76fed93eaba1859149", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/devAssert.js": "7493987e163c294b31f354091b6ca5e87849a132aa3aad8e3c173ac008c8e970", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/didYouMean.js": "9b83a8fe7bdd5b02012c097de14a0fe08a5e33662624ce55eb0019f46067b974", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/identityFunc.js": "204f973b6a5fc6f60ea62641e58eed254f0bb7d76b2b05e9c0bae54dc7cd324a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/inspect.js": "bb756ad067f23b137b63bb2dabc4e50b070255da2ff10068d9f0a174dd5f3aae", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/instanceOf.js": "c2940e2f0c71ed73390ab03ceb04484c40a4f5f28973709702c8279b1b6f96cd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/invariant.js": "b8d3e8438abe0ec591fdc3a3f0f9c2d7750925738ee146ba7a18173a8f2dc1cb", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/isCollection.js": "42ffc930d5982ec031b4a7f29db9fd7f3a9e488ca0ceb73183a51a67ad146e2b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/isObjectLike.js": "96403248d91ae55fa67ce81d339bec098aa51e5fc7132eae3f8dcce91ac340ce", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/isPromise.js": "dbe9979b7d4ffd920d8a608a9ce7127b943b130822cd907d78b9e56e66843509", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/keyMap.js": "4e54790d45a13998d198b0b7b1bc20ae17259e8131c29821d67e64b96dcb249e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/keyValMap.js": "515ebb0c4be9c26f8fa38b7e0db4f2ebf96057196a8fcd52078046f05bf4e4e0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/mapValue.js": "5df0be208d71e372a693ef305287a8f041b061a293cff030aa10bef4c2735981", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/memoize3.js": "4212fbf3f75fc8625a8312b18e0b2907efaf9cf6b8131c78a731a707ba638c74", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/nodejsCustomInspectSymbol.js": "eb85744be5bab6c4511556b095198b45259b2c020ff2f88f3b04197acc9feb01", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/printPathArray.js": "15110572999ddb82bcde2e42a35092d18fa601f5b8312e5823bd43d48f9b773f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/promiseForObject.js": "b82720027d9bf3b81f302baa8d72f80b5d04f2069ebb538875f22993be83ee11", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/promiseReduce.js": "178a7c704a3c124a68a60aa71f29fb078268e403dab310b41937d10f5bb55e01", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/suggestionList.js": "4504e10e0e9b230cd697ebcf1ffae9b2ae420db2927a44dfbab452558e2f4b4a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/jsutils/toObjMap.js": "482c18508c808bca6a8e657ab7e3dfacadbf9f78603ce452700199d02f1b9d32", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/ast.d.ts": "f108571662a720116c326859ee1a4dc9981a3ff7e665b5d88a8ba728380e3d55", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/ast.js": "faa944fc543e4997c82302a6230c190eaf72bfcca1b8db3b29502826bdf9e9bb", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/blockString.js": "413ad9886dee36a96875cea8bbb51763b1ab8f1c3e82b8468330e8a5ca877852", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/directiveLocation.d.ts": "8735909e5cce8f71d782cf65799639858a1d6de548686c26cc0ed8fb785be72e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/directiveLocation.js": "8500c3878eafe0142f1b1255a998e76b20d3f859d9016ef9f7252878926dfdd8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/index.d.ts": "63a58025800ec87d5ac650598d58cd9db430238eda6810ebf8ac5fddb2ed77b6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/index.js": "9289b05fbe173c54e727465996239a3bce6057fb727677ca44af37d60409480d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/kinds.d.ts": "e5c8d10051ae13c168f7bd5de139e266e80da90f5acf3bd4f7091acc6be04631", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/kinds.js": "7bdebb8110e7345fe6c14f255225d7c9b5448fb2dd53ea91d035a4b2e1e8f72b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/lexer.d.ts": "e14731714243ffea9b0a2201f455206963ce031710e3dfe9166155198731bef5", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/lexer.js": "68bf4d832a8d1b4af3a0432ad308bc6f3ffbf60eb1c4e786503b30aff7e17564", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/location.d.ts": "a44431030c273eaf864cd97772c035bd53aeff987f896e3064e0a6310b04bd33", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/location.js": "9994a20bee8d97ff69781b35477f596a3e6bee9ac1d914340e435bc0298922ee", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/parser.d.ts": "0a51bc1dffa7bce1181309558f20c4297a70e6dbf66c3a272e4800acb36beafa", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/parser.js": "53af901714fda7e62137fe2fe9eccecb4945a8648cffbb93ace439a0e445f05e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/predicates.d.ts": "7a13a97f25086dbb1cb6c1f0f4ab1f0a4cfae033bbe6737adf253100532b02f3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/predicates.js": "cfd7e10b724590b618f67845ce1d1ca712b7da5943622b40881e7653f5557ab7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/printLocation.d.ts": "eda03d41b35f094d0017230c45687aeb366de44a8a64f06a6431dedddd066925", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/printLocation.js": "26b21c32abaee20e11a072984c381384dd5645be50bcbbdb59bac435490a60e9", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/printer.d.ts": "c7ca0f9655f7d70a84e41e28bf835a599c840ae1b11ee75b49693a0293f0b4fe", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/printer.js": "a2da61f578725d78182fadfdc0ce809c30ed7d6b8369b98b1606b68b0e949b39", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/source.d.ts": "edd0c6bed787da0201d4dfeb44f7fc1724563ca89042e3f543bd879c433a6bdd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/source.js": "e2a870f2446abf092996453d87f98214f12898e83980b76706cc8160993f16a8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/tokenKind.d.ts": "179ac19e0cedaefa9bad359d7124724838aed1ad9e4d6e3ee02fef158f7c5b78", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/tokenKind.js": "ad57e9dddaca5336e49a715b710c8142d9ff963270f3d4d6b63348a482922a74", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/visitor.d.ts": "bf500c078a942b56907ea707a03a9eb5cf0ecea57fbbde2d44cc1591887760fb", + "https://deno.land/x/graphql_deno@v15.0.0/lib/language/visitor.js": "47b0a0f0d2f1e1edbf18a4b26861d371c41c0bfebf7c3f9ccb2e423663184dcd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/arrayFrom.js": "b57194d34b98ac5926ae83e0a83e884d63ee9ec2d2ae3e846f1783cb26f6dbf8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/find.js": "30ebdb4e2cd0ed5b0e3e4a00877f2bd2ac62de6d70a4c63152021fe724e41911", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/flatMap.js": "29d62564cd9745536e366e09b7f6cbe8860d8423c00ca253ae6cbfba839f2fc6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/isFinite.js": "231e2d149aa58e8288b6cf86f588a1597f3f8d432928a17af9c348b48bd8ddde", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/isInteger.js": "0b68164ae12c46b16e439a5384af0c8c395f5c941e0f03fb71ee2ee9cb45fae6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/objectEntries.js": "c261873f15f88ecb1497427844a9afc541168a92b7d75d69446732a0d381c819", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/objectValues.js": "7c3ed07793a1685cf018fa028275ff556213ba953c43dd2410bb549a86ee801b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/polyfills/symbols.js": "bbe907378637a4264484590815d80563cfbd8a8a5f396ac9d2be4107658e5bf7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/index.d.ts": "e1ca29bcfa8cf8817f2d2d4cca96233df2aadd30b1bc4c8ec94efd7b6a6a8e55", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/index.js": "3c2e03b8225f66a541d870fb6729ad2e3afbd923a0d07713a087c22d134097b3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/mapAsyncIterator.js": "ff581dcfccf8aeedfefe1c42a8f01c7898c1a2b093e5f6fb4d2e62b2f83ab8c8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/subscribe.d.ts": "ab5d678c1283bba4933370dd4bed79ab23464f8c5c1d15411d36fde4b77d7756", + "https://deno.land/x/graphql_deno@v15.0.0/lib/subscription/subscribe.js": "f4d316d4f7e3e60cc3f9f8371e6122396653ff1836a27d44a3e8bf1cfa3d5def", + "https://deno.land/x/graphql_deno@v15.0.0/lib/tsutils/Maybe.d.ts": "2ab1660d333d4915103448124c83b99a7463735c3dc18232689c878591fb1919", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/definition.d.ts": "0c274846aa2e1f26c96a8ea1374ca686e790f7b046d646f29e9d4647f376f5a8", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/definition.js": "94ef1069f5e034f9dc34ca7f25370fe81a58365acbb6e932ab9e0db92efe8287", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/directives.d.ts": "39b27edc4dc1c332c984403f9f47dc0218b174745c94800976be7d7176518f8d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/directives.js": "d4614736de1997413a4970b73865c99d3b01922eda3de1e587c27c25c24e8892", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/index.d.ts": "b16fe38726c315d1f0866e3771c4d46906c8d5e7bdde046e830ab217705bf1e3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/index.js": "4a7d837f30c56cf3074ee527f4b9388d160ad20cd545d4fdebac500acdf102b0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/introspection.d.ts": "5160cf4c0169aec2db90bab10788854e45e91a283177ea1b956c69163bc88341", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/introspection.js": "c63b4c5a578d76f8d159837ce531e1193dd3a2629148aedb895b511141303afd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/scalars.d.ts": "617fc67f38f4b88cdd1541997ff1531083c88be703b873445ea224819d05cc50", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/scalars.js": "d447913dbdcaaa32f2eaa5d86bd39b5342bfcf78d9ddd6b2173ad717ae7fa734", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/schema.d.ts": "ec32dacd14e373e09c536fc94ab01e921954fd9bb3bad89b19dc15a5ae5a2999", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/schema.js": "1c92f80cc2c1f0a14a5b3fcb13d55f215f14e1b2bb7b92c2674ede22f969f033", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/validate.d.ts": "367ce28cc9a026e4c70188da8806711f519fbd118e3e649d5f475a7810a11227", + "https://deno.land/x/graphql_deno@v15.0.0/lib/type/validate.js": "f8300538e4327b46959ff83a7dabd55e42148ef07fa66c79f72b308a46cfd369", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/TypeInfo.d.ts": "b6a17075c9cc27858ebfbb6462f28714e8b2e4d9c8323abc60f4edd39618412c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/TypeInfo.js": "03276253d0ab3d4947f7bf63ca4ccc4fe0073f73063952103530f8193a98e546", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/assertValidName.d.ts": "0a8734e2deaa15228b83bf336c688a5327d358cc61f354226d0d7261cd103e1e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/assertValidName.js": "d6b44f4dddcc50ebe92c7cb9b94ecc9b27627994b3c156ce63d33279c7f7d0e4", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/astFromValue.d.ts": "e082469a3278e06cc816f730baccccc9970d234a3b5b5095f3fb1b76042c5336", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/astFromValue.js": "d460204b065b8e1036e215faa9a617a20c268e687ed424208dabc5cd5f40f2d1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/buildASTSchema.d.ts": "c151cf3700c89b8bdc50205a34d8d2acddbdb9ca8bb13f087e348ff3309c36ef", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/buildASTSchema.js": "d4aeade98d0a1bc6ffdc160fdc78e9edf22a9efdc550cdc6121d1c83ff1e6c84", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/buildClientSchema.d.ts": "d9e5ba6111abe6acecc3d0eac5e6759870c0e09f27e6a0b76f31bbf14719c673", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/buildClientSchema.js": "1ac64f6aacdf57e9b552a0c45e7176c988c0d97310f646d7d048899524856d3a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/coerceInputValue.d.ts": "d828a43fcf4086a9bbf34881bab86f9306df934c07ff47adba41aef3c0ce8805", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/coerceInputValue.js": "ee59bf0ed0857af92c4f40a309365fd57770821b3c788b6b13d0d1051ffec0da", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/concatAST.d.ts": "54e26ca1215c56436a0c32164a1449173d8a33f8c0590e98068447c9b4261966", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/concatAST.js": "bf410b42f7aa015ff0cd938623894906ac19329d9cd9a2298cf97de7cb01442a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/extendSchema.d.ts": "2076431c7b1294d7624e1e95929877df404622581f047a33efde8713757755e1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/extendSchema.js": "802fbd291d21cfa97c1ebea4fa6528ac6cddbbcc27c169942c975ce708a9f295", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/findBreakingChanges.d.ts": "ecd77d35db4fb0594d1e20291e4e53de817caa8bc50a2fe98ac0d37baac735b1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/findBreakingChanges.js": "2c8dfd8acf617e5d8a96e389ba6bccb0845fc4470f680957a1ca64514337574a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/findDeprecatedUsages.d.ts": "53852681df5845fc1f8a3d1faf2193c2875d744a833aa89932a2762e7d740433", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/findDeprecatedUsages.js": "5a5366e6ed3c99e16d73b1235ee9062ac10d3ec16afafc4d6c4f635cc12370f6", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getIntrospectionQuery.d.ts": "3ec8c5ac382f3d29612438382c78c9e3f0c5a92579b8b285b11ce949f74c2754", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getIntrospectionQuery.js": "81125e8afec11871bf532dff9dd523016292c5a57f84d0c04a142a6704ba1572", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getOperationAST.d.ts": "84d0416a6d3870965a182ea6bb6c2c8b7ea186b2f7abb6b492d4a2f545d81c73", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getOperationAST.js": "5fef27e56f708c5336a78e611f08627122a6673d10ea1db6f5466c6f984ad9a2", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getOperationRootType.d.ts": "f00f7ba2394b9cb21da59617eaa4b7abc40b9b91b7d526fe96f0cd5e9d43c656", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/getOperationRootType.js": "58addddae7feabecce40191d836f7e65a9478cf0d9df6c360c4c30e6f5f6f09c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/index.d.ts": "e5965203334960bd425fca4d75943c0812c7c5900cb8b98121f3895471dcfd28", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/index.js": "d0e34dcf95627362527e93e7eaa970caee63b40ed04ff1da320b6426484c8278", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/introspectionFromSchema.d.ts": "c581f78ba3b49d7fcba8fa977233916f1b1cec2e5db19ba4b092e7178a67d411", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/introspectionFromSchema.js": "50a2202f28b25e98bc496f151de8fc6b2feeb81bab9739fee97f66ac30ef8652", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/lexicographicSortSchema.d.ts": "5fc81484e00ec8c7696e56d398985eab9af36fe12795be223358a4fdddeab30b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/lexicographicSortSchema.js": "ad2b935289966fc75700a3b511b86e79130d505399b0c2de052e9915624e2c2a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/printSchema.d.ts": "e1f8e469e1e1c69b15f4b68bc8181cd04e30c489383bf80282aad069c1136b13", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/printSchema.js": "104a4666bf3e745ad1fbd30c227d3e06902a2b255c95e29183dcd6d98fad76be", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/separateOperations.d.ts": "bcb8064d5a437e1b993584f14c54ea6cfe72d329e7bf9a2200b0b69c28edfee4", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/separateOperations.js": "ac7e7789fddc0fcf2b439897fce86601a16c6d3f4e20b7f6837278bcf1c36a56", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/stripIgnoredCharacters.d.ts": "cc99a05af41d14fe6e74d864bd5674c8193ac90ff172a395a2365217bfe78a3c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/stripIgnoredCharacters.js": "be44c652bfa218b18e5d4387a068d2e431d75e770ddc38ced6eb95a0b8854eab", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/typeComparators.d.ts": "5d48b23b9e8c8676e334a85358120e7d01cfad57688ce8812107ee213ee78a9d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/typeComparators.js": "dcda7fffeaf3999bfd993a4faac36547dace3ec3f78d7686f4334f86a6c3aec0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/typeFromAST.d.ts": "dffcc2ba7a2630f6a80ee2dd30fef8c6026a6d45b116783fa09fd2188cfe5bb0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/typeFromAST.js": "99cd615599626259b8e2b9974e22bf8beb495f541b20d78f4a01d48de2f97942", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/valueFromAST.d.ts": "345281466a8a50938b7e64bb1257396565aed7357c9485f586a8bb04af9be8cc", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/valueFromAST.js": "26098b7e5074d408f877f8b4566e6183cd77f076ee2e3f1413e53c4677e150ce", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/valueFromASTUntyped.d.ts": "f03ab21f0b23a2f185a2b2dc5dfe8fb13acae99af0d2a9fc689e817514fc7f21", + "https://deno.land/x/graphql_deno@v15.0.0/lib/utilities/valueFromASTUntyped.js": "9db575f95392635bb02dc417d920e9ae6552b2fa871b19e94ad0cc9ceb83c892", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/ValidationContext.d.ts": "6e3df3fafe1fa6b886fc8600a7601a9ff47bb91af4f8799b77f41f2637a1834c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/ValidationContext.js": "52808fc712f4ef50f79edcb30cb907721e449b55c54225551d2ad09660351dba", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/index.d.ts": "ecdec3ee908833c22a88957ad16b4bed5f62696f856d9cdb470f6e2ff91511ff", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/index.js": "1b91224c90bae4b49c6b8227c190c34cb3600433423c257a672801e12910c06d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ExecutableDefinitionsRule.d.ts": "04be37434f1e2551efa08567708095b45d96d325273ac7c835cc9e41a9763177", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ExecutableDefinitionsRule.js": "f94c0c866335d1c249d08efa55da0dd6e5954844fa7851b6a0d6b3cd86383819", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/FieldsOnCorrectTypeRule.d.ts": "47ae3b0bee6e8ce5ef17af0f2d2fb824edf4aae7bc06d309aa61890f8497b848", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/FieldsOnCorrectTypeRule.js": "587eb8a9e7376e58ea6a0bbb12585ca45621a0be102dc7e6c641adee9652e91d", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/FragmentsOnCompositeTypesRule.d.ts": "fa80f7ee34d5c61590e039e1e686b83eb988a9993bf03d88ee5db9ae662993fd", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/FragmentsOnCompositeTypesRule.js": "6a6172831b4e7e6431af810505e23bdf82b7f46d92cfe719226c549a36a75b92", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownArgumentNamesRule.d.ts": "8f6bf5b0ee7529bf8a3b01e4c43e27a09367f576ee914a5d87ffddfcce39c882", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownArgumentNamesRule.js": "2ab0571f6a6b9c5486b76a09955efde97ef3ff7e9cc06ecfcec8e1f830b8e79b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownDirectivesRule.d.ts": "c7ec3cac2923dc6eded39477ade3514dcc2a5049d27219d59a08e1d25d1dfcb9", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownDirectivesRule.js": "57f4c9df617702d3695c3010f6822641b4c7343c65a4873bab6b29c911424e2c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownFragmentNamesRule.d.ts": "9f39d746d818f23243473c487e681ebce73286b4a9acbc570f27985cb6cedbc3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownFragmentNamesRule.js": "24273c31aa208652113a2e635853c336227a2756fc642ece6e977bd9c4ccb094", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownTypeNamesRule.d.ts": "2b013194d48e9a58018d66ed77517af95a0a09c9446d68a764e4420d51d2ef3f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/KnownTypeNamesRule.js": "b600453083f32688c43f2225399439b4f6312903397572b947ea4fc80ac17b52", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/LoneAnonymousOperationRule.d.ts": "696f7eb563d07642b9af1f37cb4a0a3ca11b189e00c4278215b001420d543411", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/LoneAnonymousOperationRule.js": "ec860c3ce338509b1e407c5cea36aa6e97e34ed207e5cdf719d6f7fa4026398c", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/LoneSchemaDefinitionRule.d.ts": "9f19413c3376e00b9214c1c677b56f16c17d3dd9250873e4b3cd8f5765820148", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/LoneSchemaDefinitionRule.js": "d3e40186003d89bd45c751156a6df69af6970beb5493c891cd79e1eaab734968", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoFragmentCyclesRule.d.ts": "4fcb76b33a00d9bcb88433bd87d6ba6dadf3322acb1d6f67ba5951641fb66ae7", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoFragmentCyclesRule.js": "1445f37ad14819d117371c70acdc725459d89b9a8773dd2c15e131f5d71b92ac", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUndefinedVariablesRule.d.ts": "eb5316d5c66cd6e3556b6a1aac1b6822725e20d49041045962b848bb673af421", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUndefinedVariablesRule.js": "7aaf4409398526fef667fa6cede56b60824166409247b62c2f74b2c4a1ea5497", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUnusedFragmentsRule.d.ts": "ec34966706496784033eeae009e179ec1c4af40563b699d1425a49ca38550027", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUnusedFragmentsRule.js": "9d1c9b11d6287f077cff8661c704f9aa0fdf53de0b4894c4e7fe29597a32a704", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUnusedVariablesRule.d.ts": "e5bbb0fc9d9a658817beb52c737a510cdbc841a95a2995c0a637277f34633130", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/NoUnusedVariablesRule.js": "8d477bdbc19eb02556e7899b97b147511ccb69777ae29136c12e2f49d6c7a264", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/OverlappingFieldsCanBeMergedRule.d.ts": "1fb92fb87bf2c5e3cc90f0863c8c0e05af22c1a02d9d7971568b493a03926e79", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/OverlappingFieldsCanBeMergedRule.js": "5f74a4c076f8c18ce6371bfda2d213c4461655f830fb506569a1cc71faa56e96", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/PossibleFragmentSpreadsRule.d.ts": "b3478d98f395611d40e067004bd1ce9bcc58c8d87f509e9a572df0642861ef92", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/PossibleFragmentSpreadsRule.js": "0bf927e91d4103c630ef3a10b586586ababb1412874917fed1b5b908d40bc54b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/PossibleTypeExtensionsRule.d.ts": "7b20436d4e8815580b4033f281eb10e509ce2192c4ea5454c5f0928e36781198", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/PossibleTypeExtensionsRule.js": "2c1f8a1371656ff8c992d0e4df5500ecb36475286b5f131d9a880018baf00c73", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ProvidedRequiredArgumentsRule.d.ts": "9bd373eafde96d79b2327ed01876491da3330a155cfa2e66a2fb593bf0cd5414", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ProvidedRequiredArgumentsRule.js": "03d3af937d5e08c544a10911770f0660ed6eb18d2b8877acc677aac90583f86e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ScalarLeafsRule.d.ts": "fcf1764e1425df316773549d70d761da4214f6fe4be7ed391619b8908ed4f49e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ScalarLeafsRule.js": "374bba26d4e9e085560ef20179604955ae191e7c68c85f2f1905fb68686bc079", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/SingleFieldSubscriptionsRule.d.ts": "4fe6fd33f4715c707d1ef7a8e2c7cdffee78ea4ceb4aaf93f536832b813fef92", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/SingleFieldSubscriptionsRule.js": "8a748eb20d1b21fee38aaeae7b368893f113b5b35bbd0fd94c22df072d23e0be", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueArgumentNamesRule.d.ts": "d69fca573eae59c0a79feba1a52c48259c9ace78575b8bb28a515f7cab0aa4f0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueArgumentNamesRule.js": "fe7fafd8d4a5efe66f0d690ef868f55f46c78512cf98abc1bd6ab4c175b86d79", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueDirectiveNamesRule.d.ts": "aefc34e427ea12a9f44eade273e400e221ec0b3d35e216f277e8e90de141cd24", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueDirectiveNamesRule.js": "6664f72231dfa4fa4337ffeed4868a95e91970825bd45f77d7d4f92bcb49bb50", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueDirectivesPerLocationRule.d.ts": "c3178a030d6a9ebd445b66cefc7888db8c446685ddc5c4489dc7c4c737e34b5b", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueDirectivesPerLocationRule.js": "0aedfb8194bd8630b84e344a8ff0f6572bb169ad290a18c8391000324c4d8a03", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueEnumValueNamesRule.d.ts": "4b1cafd892e4e3eb1ab2ddb180ce9f9a58e59b87e034dd185ec03d97761b5f7a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueEnumValueNamesRule.js": "fbfec070d1c6ca58514f0e459b7dc5c8fe505d81bd1323509802d65db1463272", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueFieldDefinitionNamesRule.d.ts": "59313c26ec90f73035992f062d596ed0de9810d4994c3d038f453c2a006e4792", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueFieldDefinitionNamesRule.js": "3da7356eaaf7ae41bf5b86c7c55c4049b962bd9d5339252a90cc46538714b694", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueFragmentNamesRule.d.ts": "07b691476403e13bea06322b4d0fa7d7591412fc31d6d196799e513b16ed8342", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueFragmentNamesRule.js": "523f0dfeb1ab059bd396d240376e3b602d32405826dd53d8b2e575aaebd7c055", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueInputFieldNamesRule.d.ts": "9585a9aeabab315d73880b3b3716b999d1d0f4ebef5907d6ddda325b865e2f35", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueInputFieldNamesRule.js": "433e878697932b4b5cd82dc9282f10650d568360afcd68a6130b9a54ef21e7f1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueOperationNamesRule.d.ts": "1dcc0c5872252e38b6673f7897163e04e93a3435430660bf90fa6d96337147f9", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueOperationNamesRule.js": "1de59fc010ff56ef74c856d23f949c523de78f299e9aa7c6d882f1f49959e3be", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueOperationTypesRule.d.ts": "03bc9c4c5cd977f3fe2ff06a2923994b8f0edee71a7cb25c90a14c3d1b0d4b3f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueOperationTypesRule.js": "2faa2b9c0e9c0197f053162e1356dbfa819e02f0c294271873576ac4ea5786fc", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueTypeNamesRule.d.ts": "8210db2f1cb91fc5613d25f900b359d487b638d3e491de93841699fad3a38fb4", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueTypeNamesRule.js": "d01564b48843b9a316b314f1f9079a25d381bbe02bb47bc47dca0a144fdb596f", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueVariableNamesRule.d.ts": "d8591177950f446f6deb5ccddd6cf92a9e89dd8784994ddde33285c1b1e74dc0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/UniqueVariableNamesRule.js": "5522d2278b64aaf70c30d0dc5e37f70bdbde0201bd6c06c136e0d9d50b0a3c4a", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ValuesOfCorrectTypeRule.d.ts": "04132bab1be915a3758a1511a1f48556ff4dca5c68cc4d90eb95aeb59847386e", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/ValuesOfCorrectTypeRule.js": "f50002aac89fa875cfb594c7696d0d6eaa8ad39d699e3d0550cda34799797faa", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/VariablesAreInputTypesRule.d.ts": "993f1293a0be7b5a1c54cc4e34b47dccb2a46b7a935aaa10dc0fb2d002dc63a3", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/VariablesAreInputTypesRule.js": "13337bf58985546627c8060c3ff1201dd21bb6d67e5293819ad0eefa8196a144", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/VariablesInAllowedPositionRule.d.ts": "b7eb9e5df8f14843b2b2ee0a6b8e7a471dc58b254e27838cfeae1fa6720f49b1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/rules/VariablesInAllowedPositionRule.js": "3032c0af7f9287cb10230685807e1460f12775e24ac76f88496769ad0acc43f0", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/specifiedRules.d.ts": "6713c9eb9de291f3ae4ed5659df298840444187496ab7edc7988465fe745fd01", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/specifiedRules.js": "0e2f6954eb79701a15731f528209a5616ee028133cbf7d4fbecb4e88ac5028c5", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/validate.d.ts": "7c5a4440fe0e2d712412096c3dd26d903b4892ad11585a53f53c45df0757a395", + "https://deno.land/x/graphql_deno@v15.0.0/lib/validation/validate.js": "44bf2bc96845af0bf77e4421f0a05ee57cdc2a688abb32ff6270eacc501327d1", + "https://deno.land/x/graphql_deno@v15.0.0/lib/version.d.ts": "fd179d7b68260caf075aaabe202dfd39622403405beec3c7a697dec1df338cb2", + "https://deno.land/x/graphql_deno@v15.0.0/lib/version.js": "a135d0beee3fa1405733453e86f1c887e4771d56357504dd78eaa872c0a2b482", + "https://deno.land/x/graphql_deno@v15.0.0/mod.ts": "c85d6ab2a9c22ff494bfaa5f662507249650419af6c6c27a7a5c8175de61870d", + "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/mod.ts": "a24c6f401b8253bb84c773583bf2b37b1b74eec960639f36eb874ca17972f9e7", + "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/src/createRequestBody.ts": "dd6a053c95a75afb8d6f3e483444bac64be659e23ca9e7bb3519edd0ade70f37", + "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/src/index.ts": "15194aba3fdfc1ba896e7fcf9c11227dd1962b635c7d2101e23ed2e0a57cd900", + "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/src/parseArgs.ts": "d00abde98728be96c902debd7ee5032b05f69fb6bb00f86a2b5adf09159541aa", + "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/src/types.dom.ts": "56432fd7ce0aff31542ec7198ff1a3ea5e3d62fda009510f78bf9c0fc47af13e", + "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/src/types.ts": "63a98d9f8efb1d6a580bbef62861c764f0f0b0d3dda04674f4d9a32d6490440b", + "https://unpkg.com/extract-files@13.0.0/extractFiles.mjs": "77a9fc46352f7f8a762d95d3a9fead8e09515c0afd0425143de8fc892bbe05d7", + "https://unpkg.com/extract-files@13.0.0/isExtractableFile.mjs": "a3a1a872a8b678b67c376407746896c9061e280ed0429a7fdec2a603b542f6e0", + "https://unpkg.com/is-plain-obj@4.1.0/index.d.ts": "07dd810c936f5e4428e1012fa0e8c92ecb4de6fa9966159742f7185245b17f9e", + "https://unpkg.com/is-plain-obj@4.1.0/index.js": "957e14eb5863048b3eb7a97c7cedcf62cbca2bb94745198982d8e278f7b2f89d" + } +} diff --git a/sdk/deno/examples/create_project.ts b/sdk/deno/examples/create_project.ts new file mode 100644 index 0000000..f620c55 --- /dev/null +++ b/sdk/deno/examples/create_project.ts @@ -0,0 +1,22 @@ +import * as path from "https://deno.land/std/path/mod.ts"; +import { connect } from "../client.ts"; +import Service from "../service.ts"; + +if (import.meta.main) { + const deno = new Service() + .withName("deno-fresh") + .withCommand("./dev.ts") + .withEnv({ + PORT: "8000", + }); + + const projectDir = path.resolve("../../examples/deno-fresh"); + + const response = await connect() + .newProject() + .withName("deno-example") + .withContext(projectDir) + .withService(deno) + .stdout(); + console.log(response); +} diff --git a/sdk/deno/examples/list_processes.ts b/sdk/deno/examples/list_processes.ts new file mode 100644 index 0000000..bb73212 --- /dev/null +++ b/sdk/deno/examples/list_processes.ts @@ -0,0 +1,7 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + const processes = await project.processes(); + console.log(processes); +} diff --git a/sdk/deno/examples/list_projects.ts b/sdk/deno/examples/list_projects.ts new file mode 100644 index 0000000..d410919 --- /dev/null +++ b/sdk/deno/examples/list_projects.ts @@ -0,0 +1,6 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const projects = await connect().projects(); + console.log(projects); +} diff --git a/sdk/deno/examples/list_services.ts b/sdk/deno/examples/list_services.ts new file mode 100644 index 0000000..e240c1c --- /dev/null +++ b/sdk/deno/examples/list_services.ts @@ -0,0 +1,7 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + const services = await project.listServices(); + console.log(services); +} diff --git a/sdk/deno/examples/restart_all.ts b/sdk/deno/examples/restart_all.ts new file mode 100644 index 0000000..cb53c24 --- /dev/null +++ b/sdk/deno/examples/restart_all.ts @@ -0,0 +1,6 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + await project.restart(); +} diff --git a/sdk/deno/examples/start_all.ts b/sdk/deno/examples/start_all.ts new file mode 100644 index 0000000..eb1746d --- /dev/null +++ b/sdk/deno/examples/start_all.ts @@ -0,0 +1,6 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + await project.start(); +} diff --git a/sdk/deno/examples/start_service.ts b/sdk/deno/examples/start_service.ts new file mode 100644 index 0000000..9e8a503 --- /dev/null +++ b/sdk/deno/examples/start_service.ts @@ -0,0 +1,6 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + await project.start("deno-fresh"); +} diff --git a/sdk/deno/examples/status.ts b/sdk/deno/examples/status.ts new file mode 100644 index 0000000..630cf43 --- /dev/null +++ b/sdk/deno/examples/status.ts @@ -0,0 +1,7 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + const status = await project.status("happy-poison"); + console.log(status); +} diff --git a/sdk/deno/examples/stop_all.ts b/sdk/deno/examples/stop_all.ts new file mode 100644 index 0000000..f31ae5e --- /dev/null +++ b/sdk/deno/examples/stop_all.ts @@ -0,0 +1,7 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + const response = await project.stop(); + console.log(response); +} diff --git a/sdk/deno/examples/stop_service.ts b/sdk/deno/examples/stop_service.ts new file mode 100644 index 0000000..b9c3b94 --- /dev/null +++ b/sdk/deno/examples/stop_service.ts @@ -0,0 +1,6 @@ +import { connect } from "../client.ts"; + +if (import.meta.main) { + const project = await connect().project("near-suit"); + await project.stop("deno"); +} diff --git a/sdk/deno/import_map.json b/sdk/deno/import_map.json new file mode 100644 index 0000000..a590420 --- /dev/null +++ b/sdk/deno/import_map.json @@ -0,0 +1,7 @@ +{ + "imports": { + "extract-files/": "https://unpkg.com/extract-files@13.0.0/", + "is-plain-obj": "https://unpkg.com/is-plain-obj@4.1.0/index.js", + "is-plain-obj/": "https://unpkg.com/is-plain-obj@4.1.0/" + } +} \ No newline at end of file diff --git a/sdk/deno/mod.ts b/sdk/deno/mod.ts new file mode 100644 index 0000000..ba20321 --- /dev/null +++ b/sdk/deno/mod.ts @@ -0,0 +1,5 @@ +import Client from "./client.ts"; +import Project from "./project.ts"; +import Service from "./service.ts"; + +export { Client, Project, Service }; diff --git a/sdk/deno/project.ts b/sdk/deno/project.ts new file mode 100644 index 0000000..7f62f00 --- /dev/null +++ b/sdk/deno/project.ts @@ -0,0 +1,243 @@ +import Client from "./client.ts"; +import { buildNestedWithServiceQuery } from "./query.ts"; +import Service from "./service.ts"; +import { gql } from "https://raw.githubusercontent.com/ArtCodeStudio/graphql-request/main/mod.ts"; + +class Project { + client: Client; + id?: string; + name: string; + context: string; + services: Service[]; + + constructor(client: Client) { + this.client = client; + this.name = ""; + this.services = []; + this.context = ""; + } + + withContext(context: string): Project { + this.context = context; + return this; + } + + withName(name: string): Project { + this.name = name; + return this; + } + + withService(service: Service): Project { + this.services.push(service); + return this; + } + + async start(serviceId?: string) { + const query = gql` + mutation StartService($id: ID, $projectId: ID!) { + start(id: $id, projectId: $projectId) { + pid + } + } + `; + const response = await this.client.send(query, { + id: serviceId, + projectId: this.id, + }); + return response.start; + } + + async stop(serviceId?: string) { + const query = gql` + mutation StopService($id: ID, $projectId: ID!) { + stop(id: $id, projectId: $projectId) { + pid + } + } + `; + const response = await this.client.send(query, { + id: serviceId, + projectId: this.id, + }); + return response.stop; + } + + async restart(serviceId?: string) { + const query = gql` + mutation RestartService($id: ID, $projectId: ID!) { + restart(id: $id, projectId: $projectId) { + pid + } + } + `; + const response = await this.client.send(query, { + id: serviceId, + projectId: this.id, + }); + return response.restart; + } + + async listServices() { + const query = gql` + query Services($projectId: ID!) { + services(projectId: $projectId) { + id + name + command + status + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + }); + return response.services; + } + + async logs(serviceId: string) { + const query = gql` + query Logs($id: ID!, $projectId: ID!) { + logs(id: $id, projectId: $projectId) { + lines + } + } + `; + const response = await this.client.send(query, { + id: serviceId, + projectId: this.id, + }); + return response.logs; + } + + async status(serviceId: string) { + const query = gql` + query Status($id: ID!) { + status(id: $id) { + name + serviceId + pid + project + state + upTime + } + } + `; + const response = await this.client.send(query, { + id: serviceId, + }); + return response.status; + } + + async ps() { + const query = gql` + query Processes { + processes { + name + pid + project + serviceId + command + upTime + } + } + `; + const response = await this.client.send(query, {}); + return response.processes; + } + + processes = this.ps; + + async stdout() { + if (this.services.length === 0) { + throw new Error("Project must have at least one service"); + } + const nestedQuery = buildNestedWithServiceQuery(this.services); + const query = gql` + mutation NewProject($name: String!, $context: String!) { + newProject(name: $name, context: $context) { + ${nestedQuery} + } + } + `; + const response = await this.client.send(query, { + name: this.name, + context: this.context, + }); + return response.newProject; + } + + async addEnvVariable(serviceId: string, name: string, value: string) { + const query = gql` + mutation CreateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! + ) { + createEnvVar( + projectId: $projectId + id: $id + name: $name + value: $value + ) { + id + env + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + id: serviceId, + name, + value, + }); + return response.createEnvVar; + } + + async removeEnvVariable(serviceId: string, name: string) { + const query = gql` + mutation DeleteEnvVar($projectId: ID!, $id: ID!, $name: String!) { + deleteEnvVar(projectId: $projectId, id: $id, name: $name) { + id + env + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + id: serviceId, + name, + }); + return response.deleteEnvVar; + } + + async updateEnvVariable(serviceId: string, name: string, value: string) { + const query = gql` + mutation UpdateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! + ) { + updateEnvVar( + projectId: $projectId + id: $id + name: $name + value: $value + ) { + id + env + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + id: serviceId, + name, + value, + }); + return response.updateEnvVar; + } +} + +export default Project; diff --git a/sdk/deno/query.ts b/sdk/deno/query.ts new file mode 100644 index 0000000..1e198c5 --- /dev/null +++ b/sdk/deno/query.ts @@ -0,0 +1,42 @@ +const buildNestedWithServiceQuery = (services: any[]): string => { + let query = "id stdout"; + for (let i = services.length - 1; i >= 0; i--) { + const service = services[i]; + query = ` + withService(service: {${buildParams(service)}}) { + ${query} + } + `; + } + return query; +}; + +const buildParams = (params: any): string => { + let query = ""; + for (const key in params) { + const value = params[key]; + + if (params[key] === undefined) continue; + + if (Array.isArray(value)) { + query += `${key}: [${value.map((v) => `"${v}"`).join(", ")}], `; + } + + if (typeof value === "object" && !Array.isArray(value)) { + const array = Object.entries(value).map( + ([key, value]) => `"${key}=${value}"` + ); + query += `${key}: [${array.join(", ")}], `; + } + + if (typeof value === "number") { + query += `${key}: ${value}, `; + } + if (typeof value === "string") { + query += `${key}: "${value}", `; + } + } + return query.slice(0, -2); +}; + +export { buildNestedWithServiceQuery }; diff --git a/sdk/deno/service.ts b/sdk/deno/service.ts new file mode 100644 index 0000000..64c7616 --- /dev/null +++ b/sdk/deno/service.ts @@ -0,0 +1,115 @@ +class Service { + name: string; + command: string; + execType?: string; + dependsOn: string[]; + workingDir?: string; + description?: string; + env: { [key: string]: string }; + autostart: boolean; + autorestart: boolean; + namespace?: string; + stdout?: string; + stderr?: string; + buildCommand?: string; + floxEnvironment?: string; + enableDocker?: boolean; + enableNix?: boolean; + port?: number; + + constructor() { + this.name = ""; + this.command = ""; + this.dependsOn = []; + this.env = {}; + this.autostart = false; + this.autorestart = false; + } + + withName(name: string): Service { + this.name = name; + return this; + } + + withCommand(command: string): Service { + this.command = command; + return this; + } + + withExecType(execType: string): Service { + this.execType = execType; + return this; + } + + withDependsOn(dependsOn: string[]): Service { + this.dependsOn = dependsOn; + return this; + } + + withWorkingDir(workingDir: string): Service { + this.workingDir = workingDir; + return this; + } + + withDescription(description: string): Service { + this.description = description; + return this; + } + + withEnv(env: { [key: string]: string }): Service { + this.env = env; + return this; + } + + withAutostart(autostart: boolean): Service { + this.autostart = autostart; + return this; + } + + withAutorestart(autorestart: boolean): Service { + this.autorestart = autorestart; + return this; + } + + withNamespace(namespace: string): Service { + this.namespace = namespace; + return this; + } + + withStdout(stdout: string): Service { + this.stdout = stdout; + return this; + } + + withStderr(stderr: string): Service { + this.stderr = stderr; + return this; + } + + withBuildCommand(buildCommand: string): Service { + this.buildCommand = buildCommand; + return this; + } + + withFloxEnvironment(floxEnvironment: string): Service { + this.floxEnvironment = floxEnvironment; + return this; + } + + withEnableDocker(enableDocker: boolean): Service { + this.enableDocker = enableDocker; + return this; + } + + withEnableNix(enableNix: boolean): Service { + this.enableNix = enableNix; + return this; + } + + withPort(port: number): Service { + this.port = port; + return this; + } +} + +export default Service; diff --git a/sdk/gleam/.gitignore b/sdk/gleam/.gitignore new file mode 100644 index 0000000..170cca9 --- /dev/null +++ b/sdk/gleam/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +build +erl_crash.dump diff --git a/sdk/gleam/README.md b/sdk/gleam/README.md new file mode 100644 index 0000000..f2567c7 --- /dev/null +++ b/sdk/gleam/README.md @@ -0,0 +1,24 @@ +# superviseur_client + +[![Package Version](https://img.shields.io/hexpm/v/superviseur_client)](https://hex.pm/packages/superviseur_client) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/superviseur_client/) + +A Gleam project + +## Quick start + +```sh +gleam run # Run the project +gleam test # Run the tests +gleam shell # Run an Erlang shell +``` + +## Installation + +If available on Hex this package can be added to your Gleam project: + +```sh +gleam add superviseur_client +``` + +and its documentation can be found at . diff --git a/sdk/gleam/gleam.toml b/sdk/gleam/gleam.toml new file mode 100644 index 0000000..a6c8310 --- /dev/null +++ b/sdk/gleam/gleam.toml @@ -0,0 +1,18 @@ +name = "superviseur_client" +version = "0.1.0" +description = "A Gleam project" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "username", repo = "project" } +# links = [{ title = "Website", href = "https://gleam.run" }] + +[dependencies] +gleam_stdlib = "~> 0.28" +gleam_hackney = "~> 1.0" +gleam_json = "~> 0.5" + +[dev-dependencies] +gleeunit = "~> 0.10" diff --git a/sdk/gleam/manifest.toml b/sdk/gleam/manifest.toml new file mode 100644 index 0000000..5ec9a05 --- /dev/null +++ b/sdk/gleam/manifest.toml @@ -0,0 +1,25 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "certifi", version = "2.9.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "266DA46BDB06D6C6D35FDE799BCB28D36D985D424AD7C08B5BB48F5B5CDD4641" }, + { name = "gleam_hackney", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "B3C1E6BD138D57252F9F9E499C741E9227EE7EE9B017CA650EC8193E02F734E1" }, + { name = "gleam_http", version = "3.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "D034F5CE0639CD142CBA210B7D5D14236C284B0C5772A043D2E22128594573AE" }, + { name = "gleam_json", version = "0.5.1", build_tools = ["gleam"], requirements = ["thoas", "gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "9A805C1E60FB9CD73AF3034EB464268A6B522D937FCD2DF92BD246F2F4B37930" }, + { name = "gleam_stdlib", version = "0.28.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "73F0A89FADE5022CBEF6D6C3551F9ADCE7054AFCE0CB1DC4C6D5AB4CA62D0111" }, + { name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" }, + { name = "hackney", version = "1.18.1", build_tools = ["rebar3"], requirements = ["parse_trans", "certifi", "idna", "ssl_verify_fun", "unicode_util_compat", "metrics", "mimerl"], otp_app = "hackney", source = "hex", outer_checksum = "A4ECDAFF44297E9B5894AE499E9A070EA1888C84AFDD1FD9B7B2BC384950128E" }, + { name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" }, + { name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" }, + { name = "mimerl", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323" }, + { name = "parse_trans", version = "3.3.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B" }, + { name = "ssl_verify_fun", version = "1.1.6", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680" }, + { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, + { name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" }, +] + +[requirements] +gleam_hackney = "~> 1.0" +gleam_json = "~> 0.5" +gleam_stdlib = "~> 0.28" +gleeunit = "~> 0.10" diff --git a/sdk/gleam/src/base.gleam b/sdk/gleam/src/base.gleam new file mode 100644 index 0000000..c2291d8 --- /dev/null +++ b/sdk/gleam/src/base.gleam @@ -0,0 +1,22 @@ +import gleam/http/request.{Request} +import gleam/hackney +import gleam/io + +pub type Client { + Client(request: Request(String)) +} + +pub fn send(client: Client, query: String) { + let request = + client.request + |> request.set_body(query) + case hackney.send(request) { + Ok(response) -> { + io.println("Response:") + io.println(response.body) + } + _ -> { + io.println("Error:") + } + } +} diff --git a/sdk/gleam/src/client.gleam b/sdk/gleam/src/client.gleam new file mode 100644 index 0000000..b5edba3 --- /dev/null +++ b/sdk/gleam/src/client.gleam @@ -0,0 +1,51 @@ +import gleam/io +import project.{Project, new_project} +import base.{Client, send} +import gleam/http.{Http, Post} +import gleam/http/request +import gleam/option.{None} +import gleam/json.{object, string} + +pub fn connect() -> Client { + io.println("Connecting to server...") + let request = + request.new() + |> request.set_method(Post) + |> request.set_scheme(Http) + |> request.set_host("localhost") + |> request.set_port(5478) + |> request.set_path("/graphql") + |> request.set_header("content-type", "application/json") + Client(request) +} + +pub fn with_project(client: Client, name: String) -> Project { + io.println("Connecting to project...") + new_project(name, client) +} + +pub fn get_project(client: Client, id: String) -> Project { + io.println("Fetching project...") + let body = + object([ + #( + "query", + string("query Project($id: ID!) { project(id: $id) { id name } }"), + ), + #("variables", object([#("id", string(id))])), + ]) + |> json.to_string() + client + |> send(body) + Project(id, "", [], client, None) +} + +pub fn projects(client: Client) -> List(String) { + io.println("Fetching projects...") + let body = + object([#("query", string("query { projects { id name } }"))]) + |> json.to_string() + client + |> send(body) + [] +} diff --git a/sdk/gleam/src/examples/create_project.gleam b/sdk/gleam/src/examples/create_project.gleam new file mode 100644 index 0000000..32391d2 --- /dev/null +++ b/sdk/gleam/src/examples/create_project.gleam @@ -0,0 +1,22 @@ +import project.{stdout, with_context, with_service} +import service.{new_service, with_command, with_env, with_name} +import client.{connect, with_project} + +pub fn main() { + let deno_fresh = + new_service() + |> with_name("deno-fresh") + |> with_command("./dev.ts") + |> with_env(["PORT=8000"]) + + let project = + connect() + |> with_project("deno-example") + |> with_context( + "/Users/tsirysandratraina/Documents/GitHub/superviseur/examples/deno-fresh", + ) + |> with_service(deno_fresh) + + project + |> stdout() +} diff --git a/sdk/gleam/src/examples/list_processes.gleam b/sdk/gleam/src/examples/list_processes.gleam new file mode 100644 index 0000000..126c05e --- /dev/null +++ b/sdk/gleam/src/examples/list_processes.gleam @@ -0,0 +1,8 @@ +import project.{ps} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("obese-ants") + |> ps() +} diff --git a/sdk/gleam/src/examples/list_projects.gleam b/sdk/gleam/src/examples/list_projects.gleam new file mode 100644 index 0000000..825805a --- /dev/null +++ b/sdk/gleam/src/examples/list_projects.gleam @@ -0,0 +1,6 @@ +import client.{connect, projects} + +pub fn main() { + connect() + |> projects() +} diff --git a/sdk/gleam/src/examples/list_services.gleam b/sdk/gleam/src/examples/list_services.gleam new file mode 100644 index 0000000..cb3823d --- /dev/null +++ b/sdk/gleam/src/examples/list_services.gleam @@ -0,0 +1,8 @@ +import project.{list_services} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("obese-ants") + |> list_services() +} diff --git a/sdk/gleam/src/examples/restart_all.gleam b/sdk/gleam/src/examples/restart_all.gleam new file mode 100644 index 0000000..d69183c --- /dev/null +++ b/sdk/gleam/src/examples/restart_all.gleam @@ -0,0 +1,8 @@ +import project.{restart_all} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("test") + |> restart_all() +} diff --git a/sdk/gleam/src/examples/start_all.gleam b/sdk/gleam/src/examples/start_all.gleam new file mode 100644 index 0000000..6f6111f --- /dev/null +++ b/sdk/gleam/src/examples/start_all.gleam @@ -0,0 +1,8 @@ +import project.{start_all} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("test") + |> start_all() +} diff --git a/sdk/gleam/src/examples/start_service.gleam b/sdk/gleam/src/examples/start_service.gleam new file mode 100644 index 0000000..0b856f6 --- /dev/null +++ b/sdk/gleam/src/examples/start_service.gleam @@ -0,0 +1,8 @@ +import project.{start} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("test") + |> start("test") +} diff --git a/sdk/gleam/src/examples/status.gleam b/sdk/gleam/src/examples/status.gleam new file mode 100644 index 0000000..c1183bd --- /dev/null +++ b/sdk/gleam/src/examples/status.gleam @@ -0,0 +1,8 @@ +import project.{status} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("obese-ants") + |> status("happy-poison") +} diff --git a/sdk/gleam/src/examples/stop_all.gleam b/sdk/gleam/src/examples/stop_all.gleam new file mode 100644 index 0000000..0504e81 --- /dev/null +++ b/sdk/gleam/src/examples/stop_all.gleam @@ -0,0 +1,8 @@ +import project.{stop_all} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("test") + |> stop_all() +} diff --git a/sdk/gleam/src/examples/stop_service.gleam b/sdk/gleam/src/examples/stop_service.gleam new file mode 100644 index 0000000..eadd871 --- /dev/null +++ b/sdk/gleam/src/examples/stop_service.gleam @@ -0,0 +1,8 @@ +import project.{stop} +import client.{connect, get_project} + +pub fn main() { + connect() + |> get_project("test") + |> stop("deno") +} diff --git a/sdk/gleam/src/flox.gleam b/sdk/gleam/src/flox.gleam new file mode 100644 index 0000000..9cb342f --- /dev/null +++ b/sdk/gleam/src/flox.gleam @@ -0,0 +1,3 @@ +pub type Flox { + Flox(environment: String) +} diff --git a/sdk/gleam/src/project.gleam b/sdk/gleam/src/project.gleam new file mode 100644 index 0000000..42f5462 --- /dev/null +++ b/sdk/gleam/src/project.gleam @@ -0,0 +1,328 @@ +import gleam/io +import base.{Client, send} +import service.{Service} +import query.{build_nested_with_service_query} +import gleam/json.{object, string} +import gleam/option.{None, Option, Some} + +pub type Project { + Project( + id: String, + name: String, + services: List(Service), + client: Client, + context: Option(String), + ) +} + +pub fn new_project(name: String, client: Client) -> Project { + Project("", name, [], client, None) +} + +pub fn with_context(project: Project, ctx: String) -> Project { + Project(..project, context: Some(ctx)) +} + +pub fn with_service(project: Project, service: Service) -> Project { + io.print("Adding service ") + io.println(service.name) + Project(..project, services: [service, ..project.services]) +} + +pub fn start_all(project: Project) { + io.println("Starting all services...") + let body = + object([ + #( + "query", + string( + "mutation StartAll($projectId: ID!) { start(projectId: $projectId) { name pid serviceId command } }", + ), + ), + #("variables", object([#("projectId", string(project.id))])), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn stop_all(project: Project) { + io.println("Stopping all services...") + let body = + object([ + #( + "query", + string( + "mutation StopAll($projectId: ID!) { stop(projectId: $projectId) { name pid serviceId command } }", + ), + ), + #("variables", object([#("projectId", string(project.id))])), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn restart_all(project: Project) { + io.println("Restarting all services...") + let body = + object([ + #( + "query", + string( + "mutation RestartAll($projectId: ID!) { restart(projectId: $projectId) { name pid serviceId command } }", + ), + ), + #("variables", object([#("projectId", string(project.id))])), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn start(project: Project, service: String) { + io.println("Starting service...") + let body = + object([ + #( + "query", + string( + "mutation Start($id: ID, $projectId: ID!) { start(id: $id, projectId: $projectId) { name pid serviceId command } }", + ), + ), + #( + "variables", + object([#("id", string(service)), #("projectId", string(project.id))]), + ), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn stop(project: Project, service: String) { + io.println("Stopping service...") + let body = + object([ + #( + "query", + string( + "mutation Stop($id: ID, $projectId: ID!) { stop(id: $id, projectId: $projectId) { name pid serviceId command } }", + ), + ), + #( + "variables", + object([#("id", string(service)), #("projectId", string(project.id))]), + ), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn restart(project: Project, service: String) { + io.println("Restarting service...") + let body = + object([ + #( + "query", + string( + "mutation Restart($id: ID, $projectId: ID!) { restart(id: $id, projectId: $projectId) { name pid serviceId command } }", + ), + ), + #( + "variables", + object([#("id", string(service)), #("projectId", string(project.id))]), + ), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn status(project: Project, service: String) { + io.println("Checking service status...") + let body = + object([ + #( + "query", + string( + "query Status($id: ID) { status(id: $id) { name pid serviceId command } }", + ), + ), + #("variables", object([#("id", string(service))])), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn list_services(project: Project) { + io.println("Listing services...") + let body = + object([ + #( + "query", + string( + "query Services($projectId: ID!) { services(projectId: $projectId) { id command name status } }", + ), + ), + #("variables", object([#("projectId", string(project.id))])), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn logs(project: Project, service: String) { + io.println("Showing service logs...") + let body = + object([ + #( + "query", + string( + "query Logs($id: ID!, $projectId: ID!) { logs(id: $id, projectId: $projectId) { lines } }", + ), + ), + #( + "variables", + object([#("id", string(service)), #("projectId", string(project.id))]), + ), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn stdout(project: Project) { + let nested_query = build_nested_with_service_query(project.services) + + let context = case project.context { + Some(context) -> context + None -> "default" + } + + let body = + object([ + #( + "query", + string( + "mutation NewProject($name: String!, $context: String!) { newProject(name: $name, context: $context) {" <> nested_query <> " } }", + ), + ), + #( + "variables", + object([#("name", string(project.name)), #("context", string(context))]), + ), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn ps(project: Project) { + io.println("Fetch running services...") + let body = + object([ + #( + "query", + string("query Processes { processes { command pid type upTime } }"), + ), + ]) + |> json.to_string() + project.client + |> send(body) +} + +pub fn processes(project: Project) { + io.println("Fetch running services...") + project + |> ps +} + +pub fn add_env_var( + project: Project, + service_id: String, + name: String, + value: String, +) { + io.println("Adding environment variable...") + let body = + object([ + #( + "query", + string( + "mutation CreateEnvVar($id: ID!, $projectId: ID!, $name: String!, $value: String!) { addEnvVar(id: $id, projectId: $projectId, name: $name, value: $value) { id env } }", + ), + ), + #( + "variables", + object([ + #("id", string(service_id)), + #("projectId", string(project.id)), + #("name", string(name)), + #("value", string(value)), + ]), + ), + ]) + |> json.to_string() + + project.client + |> send(body) +} + +pub fn remove_env_var(project: Project, service_id: String, name: String) { + io.println("Removing environment variable...") + let body = + object([ + #( + "query", + string( + "mutation DeleteEnvVar($id: ID!, $projectId: ID!, $name: String!) { deleteEnvVar(id: $id, projectId: $projectId, name: $name) { id env } }", + ), + ), + #( + "variables", + object([ + #("id", string(service_id)), + #("projectId", string(project.id)), + #("name", string(name)), + ]), + ), + ]) + |> json.to_string() + + project.client + |> send(body) +} + +pub fn update_env_var( + project: Project, + service_id: String, + name: String, + value: String, +) { + io.println("Updating environment variable...") + let body = + object([ + #( + "query", + string( + "mutation UpdateEnvVar($id: ID!, $projectId: ID!, $name: String!, $value: String!) { updateEnvVar(id: $id, projectId: $projectId, name: $name, value: $value) { id env } }", + ), + ), + #( + "variables", + object([ + #("id", string(service_id)), + #("projectId", string(project.id)), + #("name", string(name)), + #("value", string(value)), + ]), + ), + ]) + |> json.to_string() + + project.client + |> send(body) +} diff --git a/sdk/gleam/src/query.gleam b/sdk/gleam/src/query.gleam new file mode 100644 index 0000000..5ab7b48 --- /dev/null +++ b/sdk/gleam/src/query.gleam @@ -0,0 +1,53 @@ +import gleam/json.{object, string} +import gleam/list.{fold} +import gleam/string.{replace} +import service.{Service} + +pub fn encode_body(body: String) -> String { + object([#("query", string(body))]) + |> json.to_string() +} + +pub fn build_nested_with_service_query(services: List(Service)) -> String { + let query = "" + let sub_query = + fold( + services, + query, + fn(acc, service) { + "withService(service: { " <> build_params(service) <> " }) { " <> acc <> " }" + }, + ) + sub_query + |> replace("{ }", "{ id stdout }") +} + +fn build_params(service: Service) -> String { + let params = + "name: \"" <> service.name <> "\", command: \"" <> service.command <> "\"" <> build_depends_on( + service.depends_on, + ) + |> replace(", ]", "]") <> build_env(service.env) + |> replace(", ]", "]") + params +} + +fn build_depends_on(depends_on: List(String)) -> String { + case depends_on { + [] -> "" + _ -> + ", dependsOn: [" <> fold( + depends_on, + "", + fn(acc, d) { acc <> "\"" <> d <> "\", " }, + ) <> "]" + } +} + +fn build_env(env: List(String)) -> String { + case env { + [] -> "" + _ -> + ", env: [" <> fold(env, "", fn(acc, e) { acc <> "\"" <> e <> "\", " }) <> "]" + } +} diff --git a/sdk/gleam/src/service.gleam b/sdk/gleam/src/service.gleam new file mode 100644 index 0000000..1685827 --- /dev/null +++ b/sdk/gleam/src/service.gleam @@ -0,0 +1,108 @@ +import gleam/option.{None, Option, Some} +import flox.{Flox} + +pub type Service { + Service( + name: String, + service_type: String, + command: String, + description: String, + depends_on: List(String), + env: List(String), + auto_restart: Bool, + auto_start: Bool, + namespace: String, + port: Option(Int), + stdout: String, + stderr: String, + flox: Option(Flox), + build_command: String, + enable_docker: Option(Bool), + enable_nix: Option(Bool), + ) +} + +pub fn new_service() -> Service { + Service( + "", + "", + "", + "", + [], + [], + False, + False, + "default", + None, + "", + "", + None, + "", + None, + None, + ) +} + +pub fn with_name(service: Service, name: String) -> Service { + Service(..service, name: name) +} + +pub fn with_type(service: Service, service_type: String) -> Service { + Service(..service, service_type: service_type) +} + +pub fn with_command(service: Service, command: String) -> Service { + Service(..service, command: command) +} + +pub fn with_depends_on(service: Service, depends_on: List(String)) -> Service { + Service(..service, depends_on: depends_on) +} + +pub fn with_description(service: Service, description: String) -> Service { + Service(..service, description: description) +} + +pub fn with_env(service: Service, env: List(String)) -> Service { + Service(..service, env: env) +} + +pub fn with_auto_restart(service: Service, auto_restart: Bool) -> Service { + Service(..service, auto_restart: auto_restart) +} + +pub fn with_auto_start(service: Service, auto_start: Bool) -> Service { + Service(..service, auto_start: auto_start) +} + +pub fn with_namespace(service: Service, namespace: String) -> Service { + Service(..service, namespace: namespace) +} + +pub fn with_port(service: Service, port: Int) -> Service { + Service(..service, port: Some(port)) +} + +pub fn with_stdout(service: Service, stdout: String) -> Service { + Service(..service, stdout: stdout) +} + +pub fn with_stderr(service: Service, stderr: String) -> Service { + Service(..service, stderr: stderr) +} + +pub fn with_flox(service: Service, flox: Flox) -> Service { + Service(..service, flox: Some(flox)) +} + +pub fn with_build_command(service: Service, build_command: String) -> Service { + Service(..service, build_command: build_command) +} + +pub fn with_enable_docker(service: Service, enable_docker: Bool) -> Service { + Service(..service, enable_docker: Some(enable_docker)) +} + +pub fn with_enable_nix(service: Service, enable_nix: Bool) -> Service { + Service(..service, enable_nix: Some(enable_nix)) +} diff --git a/sdk/gleam/src/superviseur_client.gleam b/sdk/gleam/src/superviseur_client.gleam new file mode 100644 index 0000000..586f781 --- /dev/null +++ b/sdk/gleam/src/superviseur_client.gleam @@ -0,0 +1,50 @@ +import gleam/io +import project.{ + list_services, logs, restart_all, start_all, status, stdout, stop_all, + with_service, +} +import service.{new_service, with_name} +import client.{connect, with_project} + +pub fn main() { + io.println("Hello from superviseur_client!") + let service1 = + new_service() + |> with_name("my-service-1") + + let service2 = + new_service() + |> with_name("my-service-2") + + let service3 = + new_service() + |> with_name("my-service-3") + + let project = + connect() + |> with_project("my-project") + |> with_service(service1) + |> with_service(service2) + |> with_service(service3) + + project + |> stdout() + + project + |> start_all() + + project + |> logs("my-service-1") + + project + |> list_services() + + project + |> status("my-service-1") + + project + |> restart_all() + + project + |> stop_all() +} diff --git a/sdk/gleam/test/superviseur_client_test.gleam b/sdk/gleam/test/superviseur_client_test.gleam new file mode 100644 index 0000000..3831e7a --- /dev/null +++ b/sdk/gleam/test/superviseur_client_test.gleam @@ -0,0 +1,12 @@ +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +// gleeunit test functions end in `_test` +pub fn hello_world_test() { + 1 + |> should.equal(1) +} diff --git a/sdk/go/.gitignore b/sdk/go/.gitignore new file mode 100644 index 0000000..d19c362 --- /dev/null +++ b/sdk/go/.gitignore @@ -0,0 +1,27 @@ +# Created by https://www.toptal.com/developers/gitignore/api/go +# Edit at https://www.toptal.com/developers/gitignore?templates=go + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# End of https://www.toptal.com/developers/gitignore/api/go diff --git a/sdk/go/client.go b/sdk/go/client.go new file mode 100644 index 0000000..e931377 --- /dev/null +++ b/sdk/go/client.go @@ -0,0 +1,87 @@ +package superviseur + +import ( + "context" + + "github.com/machinebox/graphql" + "github.com/mitchellh/mapstructure" + "github.com/tsirysndr/superviseur-go/types" +) + +type Client struct { + client *graphql.Client +} + +func Connect() *Client { + client := graphql.NewClient("http://localhost:5478/graphql") + return &Client{ + client, + } +} + +func (c *Client) NewProject() *Project { + return &Project{ + ID: nil, + Name: "", + services: []Service{}, + client: c, + } +} + +func (c *Client) Project(ID string) *Project { + req := graphql.NewRequest(` + query Project($id: ID!) { + project(id: $id) { + id + name + } + } + `) + req.Var("id", ID) + var responseData map[string]interface{} + ctx := context.Background() + + if err := c.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var p types.Project + + if err := mapstructure.Decode(responseData["project"], &p); err != nil { + panic(err) + } + + return &Project{ + ID: &p.ID, + Name: p.Name, + services: []Service{}, + client: c, + } +} + +func (c *Client) Projects() []types.Project { + req := graphql.NewRequest(` + query Projects { + projects { + id + name + } + } + `) + + ctx := context.Background() + + var responseData map[string]interface{} + + if err := c.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var projects []types.Project + + if err := mapstructure.Decode(responseData["projects"], &projects); err != nil { + panic(err) + } + + return projects +} diff --git a/sdk/go/examples/examples_test.go b/sdk/go/examples/examples_test.go new file mode 100644 index 0000000..2e4c601 --- /dev/null +++ b/sdk/go/examples/examples_test.go @@ -0,0 +1,88 @@ +package examples + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + sdk "github.com/tsirysndr/superviseur-go" +) + +func TestCreateProject(t *testing.T) { + deno := sdk.NewService(). + WithName("deno-fresh"). + WithCommand("./dev.ts"). + WithPort(8000). + WithEnv(map[string]string{ + "PORT": "8000", + }) + + projectDir, err := filepath.Abs("../../../examples/deno-fresh") + if err != nil { + panic(err) + } + + client := sdk.Connect() + client.NewProject(). + WithName("deno-example"). + WithContext(projectDir). + WithService(deno). + Stdout() + assert.Nil(nil, nil) +} + +func TestListProcesses(t *testing.T) { + client := sdk.Connect() + processes := client.Project("obese-ants").Processes() + fmt.Printf("%#v\n", processes) +} + +func TestListProjects(t *testing.T) { + client := sdk.Connect() + projects := client.Projects() + fmt.Printf("%#v\n", projects) +} + +func TestListServices(t *testing.T) { + client := sdk.Connect() + services := client.Project("obese-ants").Services() + fmt.Printf("%#v\n", services) +} + +func TestRestartAll(t *testing.T) { + client := sdk.Connect() + client.Project("obese-ants").RestartAll() +} + +func TestStartAll(t *testing.T) { + client := sdk.Connect() + client.Project("obese-ants").StartAll() +} + +func TestStartService(t *testing.T) { + client := sdk.Connect() + client.Project("obese-ants").Start("happy-poison") +} + +func TestStatus(t *testing.T) { + client := sdk.Connect() + process := client.Project("obese-ants").Status("happy-poison") + fmt.Printf("%#v\n", process) +} + +func TestStopAll(t *testing.T) { + client := sdk.Connect() + client.Project("obese-ants").StopAll() +} + +func TestStopService(t *testing.T) { + client := sdk.Connect() + client.Project("obese-ants").Stop("happy-poison") +} + +func TestLogs(t *testing.T) { + client := sdk.Connect() + logs := client.Project("obese-ants").Logs("happy-poison") + fmt.Printf("%#v\n", logs) +} diff --git a/sdk/go/go.mod b/sdk/go/go.mod new file mode 100644 index 0000000..9b34448 --- /dev/null +++ b/sdk/go/go.mod @@ -0,0 +1,16 @@ +module github.com/tsirysndr/superviseur-go + +go 1.20 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dghubble/sling v1.4.1 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/machinebox/graphql v0.2.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/sdk/go/go.sum b/sdk/go/go.sum new file mode 100644 index 0000000..3cc086f --- /dev/null +++ b/sdk/go/go.sum @@ -0,0 +1,29 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dghubble/sling v1.4.1 h1:AxjTubpVyozMvbBCtXcsWEyGGgUZutC5YGrfxPNVOcQ= +github.com/dghubble/sling v1.4.1/go.mod h1:QoMB1KL3GAo+7HsD8Itd6S+6tW91who8BGZzuLvpOyc= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo= +github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/go/project.go b/sdk/go/project.go new file mode 100644 index 0000000..9152bb7 --- /dev/null +++ b/sdk/go/project.go @@ -0,0 +1,435 @@ +package superviseur + +import ( + "context" + "fmt" + + "github.com/machinebox/graphql" + "github.com/mitchellh/mapstructure" + "github.com/tsirysndr/superviseur-go/types" +) + +type Project struct { + ID *string + Name string + services []Service + client *Client + context string +} + +func (p *Project) WithName(name string) *Project { + p.Name = name + return p +} + +func (p *Project) WithContext(context string) *Project { + p.context = context + return p +} + +func (p *Project) WithService(service *Service) *Project { + p.services = append(p.services, *service) + return p +} + +func (p *Project) Stdout() { + nestedQuery := BuildNestedQuery(p.services) + req := graphql.NewRequest(fmt.Sprintf(` + mutation NewProject($name: String!, $context: String!) { + newProject(name: $name, context: $context) { + %s + } + } + `, nestedQuery)) + req.Var("name", p.Name) + req.Var("context", p.context) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + +} + +func (p *Project) Start(service string) types.Process { + req := graphql.NewRequest(` + mutation StartService($id: ID, $projectId: ID!) { + start(id: $id, projectId: $projectId) { + pid + } + } + `) + req.Var("id", service) + req.Var("projectId", p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["start"], &process); err != nil { + panic(err) + } + + return process +} + +func (p *Project) Stop(service string) types.Process { + req := graphql.NewRequest(` + mutation StopService($id: ID, $projectId: ID!) { + stop(id: $id, projectId: $projectId) { + pid + } + } + `) + req.Var("id", service) + req.Var("projectId", p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["stop"], &process); err != nil { + panic(err) + } + return process +} + +func (p *Project) Restart(service string) types.Process { + req := graphql.NewRequest(` + mutation RestartService($id: ID, $projectId: ID!) { + restart(id: $id, projectId: $projectId) { + pid + } + } + `) + req.Var("id", service) + req.Var("projectId", p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["restart"], &process); err != nil { + panic(err) + } + + return process +} + +func (p *Project) Status(service string) types.Process { + req := graphql.NewRequest(` + query Status($id: ID!) { + status(id: $id) { + pid + project + name + serviceId + state + command + upTime + } + } + `) + req.Var("id", service) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["status"], &process); err != nil { + panic(err) + } + + return process +} + +func (p *Project) StartAll() types.Process { + req := graphql.NewRequest(` + mutation StartAll($projectId: ID!) { + start(projectId: $projectId) { + name + pid + serviceId + command + } + } + `) + req.Var("projectId", *p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["start"], &process); err != nil { + panic(err) + } + + return process +} + +func (p *Project) StopAll() types.Process { + req := graphql.NewRequest(` + mutation StopAll($projectId: ID!) { + stop(projectId: $projectId) { + name + pid + serviceId + command + } + } + `) + req.Var("projectId", *p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["stop"], &process); err != nil { + panic(err) + } + + return process +} + +func (p *Project) RestartAll() types.Process { + req := graphql.NewRequest(` + mutation RestartAll($projectId: ID!) { + restart(projectId: $projectId) { + name + pid + serviceId + command + } + } + `) + req.Var("projectId", *p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var process types.Process + if err := mapstructure.Decode(responseData["restart"], &process); err != nil { + panic(err) + } + + return process +} + +func (p *Project) Logs(service string) types.Logs { + req := graphql.NewRequest(` + query Logs($id: ID!, $projectId: ID!) { + logs(id: $id, projectId: $projectId) { + lines + } + } + `) + req.Var("id", service) + req.Var("projectId", *p.ID) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var logs types.Logs + + if err := mapstructure.Decode(responseData["logs"], &logs); err != nil { + panic(err) + } + + return logs + +} + +func (p *Project) Processes() []types.Process { + req := graphql.NewRequest(` + query Processes { + processes { + name + pid + serviceId + command + upTime + } + } + `) + ctx := context.Background() + + var responseData map[string][]interface{} + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var processes []types.Process + if err := mapstructure.Decode(responseData["processes"], &processes); err != nil { + panic(err) + } + + return processes +} + +func (p *Project) Services() []types.Service { + req := graphql.NewRequest(` + query Services($projectId: ID!) { + services(projectId: $projectId) { + id + name + command + status + } + } + `) + req.Var("projectId", *p.ID) + ctx := context.Background() + + var responseData map[string][]interface{} + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var services []types.Service + if err := mapstructure.Decode(responseData["services"], &services); err != nil { + panic(err) + } + + return services +} + +func (p *Project) AddEnvVar(serviceID, name, value string) types.Service { + req := graphql.NewRequest(` + mutation CreateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! + ) { + createEnvVar( + projectId: $projectId + id: $id + name: $name + value: $value + ) { + id + env + } + } + `) + req.Var("projectId", *p.ID) + req.Var("id", serviceID) + req.Var("name", name) + req.Var("value", value) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var service types.Service + + if err := mapstructure.Decode(responseData["createEnvVar"], &service); err != nil { + panic(err) + } + + return service +} + +func (p *Project) RemoveEnvVar(serviceID, name string) types.Service { + req := graphql.NewRequest(` + mutation DeleteEnvVar($projectId: ID!, $id: ID!, $name: String!) { + deleteEnvVar(projectId: $projectId, id: $id, name: $name) { + id + env + } + } + `) + req.Var("projectId", *p.ID) + req.Var("id", serviceID) + req.Var("name", name) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var service types.Service + + if err := mapstructure.Decode(responseData["deleteEnvVar"], &service); err != nil { + panic(err) + } + + return service +} + +func (p *Project) UpdateEnvVar(serviceID, name, value string) types.Service { + req := graphql.NewRequest(` + mutation UpdateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! + ) { + updateEnvVar( + projectId: $projectId + id: $id + name: $name + value: $value + ) { + id + env + } + } + `) + req.Var("projectId", *p.ID) + req.Var("id", serviceID) + req.Var("name", name) + req.Var("value", value) + ctx := context.Background() + + var responseData map[string]interface{} + + if err := p.client.client.Run(ctx, req, &responseData); err != nil { + panic(err) + } + + var service types.Service + + if err := mapstructure.Decode(responseData["updateEnvVar"], &service); err != nil { + panic(err) + } + + return service +} diff --git a/sdk/go/query.go b/sdk/go/query.go new file mode 100644 index 0000000..b16d772 --- /dev/null +++ b/sdk/go/query.go @@ -0,0 +1,55 @@ +package superviseur + +import "fmt" + +func BuildNestedQuery(service []Service) string { + query := "id stdout" + for _, service := range service { + query = fmt.Sprintf(` + withService(service: { %s }) { + %s + } + `, BuildParams(service), query) + } + + return query +} + +func BuildParams(service Service) string { + params := "" + + if service.Name != "" { + params = fmt.Sprintf(`name: "%s", `, service.Name) + } + + if service.Command != "" { + params = fmt.Sprintf(`%s command: "%s", `, params, service.Command) + } + + if service.Port != nil { + params = fmt.Sprintf(`%s port: %d, `, params, *service.Port) + } + + if len(service.Env) > 0 { + env := "" + for key, value := range service.Env { + env = fmt.Sprintf(`"%s=%s", `, key, value) + } + + env = env[:len(env)-2] + params = fmt.Sprintf(`%s env: [%s], `, params, env) + } + + if len(service.DependsOn) > 0 { + dependsOn := "" + for _, value := range service.DependsOn { + dependsOn += fmt.Sprintf(`"%s", `, value) + } + dependsOn = dependsOn[:len(dependsOn)-2] + params = fmt.Sprintf(` + %s dependsOn: [%s], + `, params, dependsOn) + } + params = params[:len(params)-2] + return params +} diff --git a/sdk/go/service.go b/sdk/go/service.go new file mode 100644 index 0000000..b52621b --- /dev/null +++ b/sdk/go/service.go @@ -0,0 +1,105 @@ +package superviseur + +type Service struct { + Name string + Command string + ExecType *string + WorkingDir *string + Description *string + Env map[string]string + DependsOn []string + AutoStart *bool + AutoRestart *bool + Namespace *string + Stdout *string + Stderr *string + BuildCommand *string + FloxEnvrionment *string + EnableDocker *bool + EnableNix *bool + Port *int +} + +func NewService() *Service { + return &Service{} +} + +func (s *Service) WithName(name string) *Service { + s.Name = name + return s +} + +func (s *Service) WithCommand(command string) *Service { + s.Command = command + return s +} + +func (s *Service) WithDescription(description string) *Service { + s.Description = &description + return s +} + +func (s *Service) WithExecType(execType string) *Service { + s.ExecType = &execType + return s +} + +func (s *Service) WithWorkingDir(workingDir string) *Service { + s.WorkingDir = &workingDir + return s +} + +func (s *Service) WithEnv(env map[string]string) *Service { + s.Env = env + return s +} + +func (s *Service) WithAutoStart(autoStart bool) *Service { + s.AutoStart = &autoStart + return s +} + +func (s *Service) WithAutoRestart(autoRestart bool) *Service { + s.AutoRestart = &autoRestart + return s +} + +func (s *Service) WithNamespace(namespace string) *Service { + s.Namespace = &namespace + return s +} + +func (s *Service) WithStdout(stdout string) *Service { + s.Stdout = &stdout + return s +} + +func (s *Service) WithStderr(stderr string) *Service { + s.Stderr = &stderr + return s +} + +func (s *Service) WithBuildCommand(buildCommand string) *Service { + s.BuildCommand = &buildCommand + return s +} + +func (s *Service) WithFloxEnvironment(floxEnvironment *string) *Service { + s.FloxEnvrionment = floxEnvironment + return s +} + +func (s *Service) WithEnableDocker(enableDocker *bool) *Service { + s.EnableDocker = enableDocker + return s +} + +func (s *Service) WithEnableNix(enableNix *bool) *Service { + s.EnableNix = enableNix + return s +} + +func (s *Service) WithPort(port int) *Service { + s.Port = &port + return s +} diff --git a/sdk/go/types/logs.go b/sdk/go/types/logs.go new file mode 100644 index 0000000..631e1db --- /dev/null +++ b/sdk/go/types/logs.go @@ -0,0 +1,5 @@ +package types + +type Logs struct { + Lines []string `json:"lines"` +} diff --git a/sdk/go/types/process.go b/sdk/go/types/process.go new file mode 100644 index 0000000..ba4afca --- /dev/null +++ b/sdk/go/types/process.go @@ -0,0 +1,11 @@ +package types + +type Process struct { + PID *int64 `json:"pid"` + Name string `json:"name"` + Project string `json:"project"` + ServiceID string `json:"serviceId"` + Command string `json:"command"` + UpTime string `json:"upTime"` + State string `json:"state"` +} diff --git a/sdk/go/types/project.go b/sdk/go/types/project.go new file mode 100644 index 0000000..4cef196 --- /dev/null +++ b/sdk/go/types/project.go @@ -0,0 +1,8 @@ +package types + +type Project struct { + ID string `json:"id"` + Name string `json:"name"` + Services []Service `json:"services"` + Context string `json:"context"` +} diff --git a/sdk/go/types/service.go b/sdk/go/types/service.go new file mode 100644 index 0000000..9b98976 --- /dev/null +++ b/sdk/go/types/service.go @@ -0,0 +1,9 @@ +package types + +type Service struct { + ID string `json:"id"` + Name string `json:"name"` + Command string `json:"command"` + Status string `json:"status"` + Env []string `json:"env"` +} diff --git a/sdk/node/.gitignore b/sdk/node/.gitignore new file mode 100644 index 0000000..3502ef7 --- /dev/null +++ b/sdk/node/.gitignore @@ -0,0 +1,144 @@ +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node diff --git a/sdk/node/bun.lockb b/sdk/node/bun.lockb new file mode 100755 index 0000000..213247f Binary files /dev/null and b/sdk/node/bun.lockb differ diff --git a/sdk/node/package.json b/sdk/node/package.json new file mode 100644 index 0000000..18cbc64 --- /dev/null +++ b/sdk/node/package.json @@ -0,0 +1,33 @@ +{ + "name": "@superviseur/sdk", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "test": "echo \"Error: no test specified\" && exit 1", + "ex:create_project": "ts-node src/examples/create_project.ts", + "ex:list_processes": "ts-node src/examples/list_processes.ts", + "ex:list_projects": "ts-node src/examples/list_projects.ts", + "ex:list_services": "ts-node src/examples/list_services.ts", + "ex:restart_all": "ts-node src/examples/restart_all.ts", + "ex:start_all": "ts-node src/examples/start_all.ts", + "ex:start_service": "ts-node src/examples/start_service.ts", + "ex:status": "ts-node src/examples/status.ts", + "ex:stop_all": "ts-node src/examples/stop_all.ts", + "ex:stop_service": "ts-node src/examples/stop_service.ts" + }, + "author": "Tsiry Sandratraina", + "license": "MIT", + "devDependencies": { + "@types/node": "^18.16.3", + "esbuild": "^0.17.18", + "prettier": "^2.8.8", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "dependencies": { + "graphql": "^16.6.0", + "graphql-request": "^6.0.0" + } +} \ No newline at end of file diff --git a/sdk/node/src/client.ts b/sdk/node/src/client.ts new file mode 100644 index 0000000..28aa423 --- /dev/null +++ b/sdk/node/src/client.ts @@ -0,0 +1,51 @@ +import Project from "./project"; +import { gql, GraphQLClient } from "graphql-request"; + +class Client { + client: GraphQLClient; + + constructor() { + this.client = new GraphQLClient("http://localhost:5478/graphql"); + } + + newProject(): Project { + return new Project(this); + } + + async project(id: string) { + const query = gql` + query Project($id: ID!) { + project(id: $id) { + id + name + } + } + `; + const response = await this.client.request(query, { id }); + const p = new Project(this); + p.id = id; + return p; + } + + async projects() { + const query = gql` + query Projects { + projects { + id + name + } + } + `; + const projects = await this.client.request(query, {}); + return projects; + } + + send = async (query: any, variables: any) => { + const response = await this.client.request(query, variables); + return response; + }; +} + +export const connect = (): Client => new Client(); + +export default Client; diff --git a/sdk/node/src/examples/create_project.ts b/sdk/node/src/examples/create_project.ts new file mode 100644 index 0000000..0fa123b --- /dev/null +++ b/sdk/node/src/examples/create_project.ts @@ -0,0 +1,24 @@ +import Project from "../project"; +import Service from "../service"; +import path from "path"; +import { connect } from "../client"; + +function main() { + const deno = new Service() + .withName("deno-fresh") + .withCommand("./dev.ts") + .withEnv({ + PORT: "8000", + }); + + const projectDir = path.resolve("../../examples/deno-fresh"); + + connect() + .newProject() + .withName("deno-example") + .withContext(projectDir) + .withService(deno) + .stdout(); +} + +main(); diff --git a/sdk/node/src/examples/env.ts b/sdk/node/src/examples/env.ts new file mode 100644 index 0000000..4d79d69 --- /dev/null +++ b/sdk/node/src/examples/env.ts @@ -0,0 +1,8 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + await addEnvVariable("", "", ""); +} + +main(); diff --git a/sdk/node/src/examples/list_processes.ts b/sdk/node/src/examples/list_processes.ts new file mode 100644 index 0000000..34c3875 --- /dev/null +++ b/sdk/node/src/examples/list_processes.ts @@ -0,0 +1,8 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + await project.processes(); +} + +main(); diff --git a/sdk/node/src/examples/list_projects.ts b/sdk/node/src/examples/list_projects.ts new file mode 100644 index 0000000..0599dd0 --- /dev/null +++ b/sdk/node/src/examples/list_projects.ts @@ -0,0 +1,8 @@ +import { connect } from "../client"; + +async function main() { + const projects = await connect().projects(); + console.log(projects); +} + +main(); diff --git a/sdk/node/src/examples/list_services.ts b/sdk/node/src/examples/list_services.ts new file mode 100644 index 0000000..c080d2c --- /dev/null +++ b/sdk/node/src/examples/list_services.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const services = await project.listServices(); + console.log(services); +} + +main(); diff --git a/sdk/node/src/examples/restart_all.ts b/sdk/node/src/examples/restart_all.ts new file mode 100644 index 0000000..4663bc3 --- /dev/null +++ b/sdk/node/src/examples/restart_all.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const response = await project.restart(); + console.log(response); +} + +main(); diff --git a/sdk/node/src/examples/start_all.ts b/sdk/node/src/examples/start_all.ts new file mode 100644 index 0000000..9812dfe --- /dev/null +++ b/sdk/node/src/examples/start_all.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const response = await project.start(); + console.log(response); +} + +main(); diff --git a/sdk/node/src/examples/start_service.ts b/sdk/node/src/examples/start_service.ts new file mode 100644 index 0000000..78b9080 --- /dev/null +++ b/sdk/node/src/examples/start_service.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const response = await project.start("deno"); + console.log(response); +} + +main(); diff --git a/sdk/node/src/examples/status.ts b/sdk/node/src/examples/status.ts new file mode 100644 index 0000000..929b461 --- /dev/null +++ b/sdk/node/src/examples/status.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const response = await project.status("happy-poison"); + console.log(response); +} + +main(); diff --git a/sdk/node/src/examples/stop_all.ts b/sdk/node/src/examples/stop_all.ts new file mode 100644 index 0000000..519359a --- /dev/null +++ b/sdk/node/src/examples/stop_all.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const response = await project.stop(); + console.log(response); +} + +main(); diff --git a/sdk/node/src/examples/stop_service.ts b/sdk/node/src/examples/stop_service.ts new file mode 100644 index 0000000..73059af --- /dev/null +++ b/sdk/node/src/examples/stop_service.ts @@ -0,0 +1,9 @@ +import { connect } from "../client"; + +async function main() { + const project = await connect().project("obese-ants"); + const response = await project.stop("deno"); + console.log(response); +} + +main(); diff --git a/sdk/node/src/index.ts b/sdk/node/src/index.ts new file mode 100644 index 0000000..c232cfa --- /dev/null +++ b/sdk/node/src/index.ts @@ -0,0 +1,4 @@ +import Service from "./service"; +import Project from "./project"; + +export { Service, Project }; diff --git a/sdk/node/src/project.ts b/sdk/node/src/project.ts new file mode 100644 index 0000000..0ee0ae4 --- /dev/null +++ b/sdk/node/src/project.ts @@ -0,0 +1,233 @@ +import Service from "./service"; +import Client from "./client"; +import { buildNestedWithServiceQuery } from "./query"; +import { gql } from "graphql-request"; + +class Project { + id?: string; + name: string; + client: Client; + context: string; + services: Service[]; + + constructor(client: Client) { + this.name = ""; + this.services = []; + this.context = ""; + this.client = client; + } + + withName(name: string) { + this.name = name; + return this; + } + + withContext(context: string) { + this.context = context; + return this; + } + + withService(service: Service): Project { + this.services.push(service); + return this; + } + + async listServices() { + const query = gql` + query Services($projectId: ID!) { + services(projectId: $projectId) { + id + name + command + status + } + } + `; + const response = await this.client.send(query, { projectId: this.id }); + return response.services; + } + + async logs(service: string) { + const query = gql` + query Logs($id: ID!, $projectId: ID!) { + logs(id: $id, projectId: $projectId) { + lines + } + } + `; + const response = await this.client.send(query, { + id: service, + projectId: this.id, + }); + return response.logs; + } + + async start(service?: string) { + const query = gql` + mutation StartService($id: ID, $projectId: ID!) { + start(id: $id, projectId: $projectId) { + pid + } + } + `; + const response = await this.client.send(query, { + id: service, + projectId: this.id, + }); + return response.start; + } + + async stop(service?: string) { + const query = gql` + mutation StopService($id: ID, $projectId: ID!) { + stop(id: $id, projectId: $projectId) { + pid + } + } + `; + const response = await this.client.send(query, { + id: service, + projectId: this.id, + }); + return response.stop; + } + + async restart(service?: string) { + const query = gql` + mutation RestartService($id: ID, $projectId: ID!) { + restart(id: $id, projectId: $projectId) { + pid + } + } + `; + const response = await this.client.send(query, { + id: service, + projectId: this.id, + }); + return response.restart; + } + + async status(service: string) { + const query = gql` + query Status($id: ID!) { + status(id: $id) { + state + } + } + `; + const response = await this.client.send(query, { id: service }); + return response.status; + } + + async ps() { + const query = gql` + query Processes { + processes { + name + pid + serviceId + command + upTime + } + } + `; + const response = await this.client.send(query, {}); + return response.processes; + } + + processes = this.ps; + + async stdout() { + if (this.services.length === 0) { + throw new Error("Project must have at least one service"); + } + const nestedQuery = buildNestedWithServiceQuery(this.services); + const query = gql` + mutation NewProject($name: String!, $context: String!) { + newProject(name: $name, context: $context) { + ${nestedQuery} + } + } + `; + const response = await this.client.send(query, { + name: this.name, + context: this.context, + }); + return response.newProject; + } + + async addEnvVariable(serviceId: string, name: string, value: string) { + const query = gql` + mutation CreateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! + ) { + createEnvVar( + projectId: $projectId + id: $id + name: $name + value: $value + ) { + id + env + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + id: serviceId, + name, + value, + }); + return response.createEnvVar; + } + + async removeEnvVariable(serviceId: string, name: string) { + const query = gql` + mutation DeleteEnvVar($projectId: ID!, $id: ID!, $name: String!) { + deleteEnvVar(projectId: $projectId, id: $id, name: $name) { + id + env + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + id: serviceId, + name, + }); + return response.deleteEnvVar; + } + + async updateEnvVariable(serviceId: string, name: string, value: string) { + const query = gql` + mutation UpdateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! + ) { + updateEnvVar( + projectId: $projectId + id: $id + name: $name + value: $value + ) { + id + env + } + } + `; + const response = await this.client.send(query, { + projectId: this.id, + id: serviceId, + name, + value, + }); + return response.updateEnvVar; + } +} + +export default Project; diff --git a/sdk/node/src/query.ts b/sdk/node/src/query.ts new file mode 100644 index 0000000..1e198c5 --- /dev/null +++ b/sdk/node/src/query.ts @@ -0,0 +1,42 @@ +const buildNestedWithServiceQuery = (services: any[]): string => { + let query = "id stdout"; + for (let i = services.length - 1; i >= 0; i--) { + const service = services[i]; + query = ` + withService(service: {${buildParams(service)}}) { + ${query} + } + `; + } + return query; +}; + +const buildParams = (params: any): string => { + let query = ""; + for (const key in params) { + const value = params[key]; + + if (params[key] === undefined) continue; + + if (Array.isArray(value)) { + query += `${key}: [${value.map((v) => `"${v}"`).join(", ")}], `; + } + + if (typeof value === "object" && !Array.isArray(value)) { + const array = Object.entries(value).map( + ([key, value]) => `"${key}=${value}"` + ); + query += `${key}: [${array.join(", ")}], `; + } + + if (typeof value === "number") { + query += `${key}: ${value}, `; + } + if (typeof value === "string") { + query += `${key}: "${value}", `; + } + } + return query.slice(0, -2); +}; + +export { buildNestedWithServiceQuery }; diff --git a/sdk/node/src/service.ts b/sdk/node/src/service.ts new file mode 100644 index 0000000..2d091d6 --- /dev/null +++ b/sdk/node/src/service.ts @@ -0,0 +1,76 @@ +class Service { + name: string; + command: string; + execType?: string; + workingDir?: string; + description?: string; + env: { [key: string]: string }; + dependsOn?: string[]; + autostart?: boolean; + autorestart?: boolean; + namespace?: string; + stdout?: string; + stderr?: string; + buildCommand?: string; + floxEnvironment?: string; + enableDocker?: boolean; + enableNix?: boolean; + port?: number; + + constructor() { + this.name = ""; + this.command = ""; + } + + withName(name: string) { + this.name = name; + return this; + } + + withCommand(command: string) { + this.command = command; + return this; + } + + withDescription(description: string) { + this.description = description; + return this; + } + + withAutoRestart(autoRestart: boolean) { + this.autorestart = autoRestart; + return this; + } + + withDependsOn(dependsOn: string[]) { + this.dependsOn = dependsOn; + return this; + } + + withWorkingDir(workingDir: string) { + this.workingDir = workingDir; + return this; + } + + withEnv(env: { [key: string]: string }) { + this.env = env; + return this; + } + + withEnableDocker(enableDocker: boolean) { + this.enableDocker = enableDocker; + return this; + } + + withEnableNix(enableNix: boolean) { + this.enableNix = enableNix; + return this; + } + + withPort(port: number) { + this.port = port; + return this; + } +} + +export default Service; diff --git a/sdk/node/tsconfig.json b/sdk/node/tsconfig.json new file mode 100644 index 0000000..ae4949d --- /dev/null +++ b/sdk/node/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "dist", + "lib": ["ES2020", "DOM"], + "module": "nodenext", /* Specify what module code is generated. */ + "target": "ES2020", + "moduleResolution": "node", + "skipLibCheck": true, + "allowImportingTsExtensions": false, + "emitDeclarationOnly": false, + "declaration": true + } +} \ No newline at end of file diff --git a/sdk/rust/.gitignore b/sdk/rust/.gitignore new file mode 100644 index 0000000..389159a --- /dev/null +++ b/sdk/rust/.gitignore @@ -0,0 +1,20 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust +# Edit at https://www.toptal.com/developers/gitignore?templates=rust + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/rust diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml new file mode 100644 index 0000000..f6a8d9f --- /dev/null +++ b/sdk/rust/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "superviseur-client" +version = "0.1.0" +edition = "2021" + +[[example]] +name = "create_project" +path = "examples/create_project.rs" + +[[example]] +name = "list_processes" +path = "examples/list_processes.rs" + +[[example]] +name = "list_projects" +path = "examples/list_projects.rs" + +[[example]] +name = "list_services" +path = "examples/list_services.rs" + +[[example]] +name = "logs" +path = "examples/logs.rs" + +[[example]] +name = "restart_services" +path = "examples/restart_services.rs" + +[[example]] +name = "start_all" +path = "examples/start_all.rs" + +[[example]] +name = "start_service" +path = "examples/start_service.rs" + +[[example]] +name = "status" +path = "examples/status.rs" + +[[example]] +name = "stop_all" +path = "examples/stop_all.rs" + +[[example]] +name = "stop_service" +path = "examples/stop_service.rs" + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.71" +chrono = "0.4.24" +graphql_client = "0.12.0" +serde = "1.0.160" +surf = "2.3.2" +tokio = { version = "1.28.0", features = ["tokio-macros", "macros", "rt", "rt-multi-thread"] } diff --git a/sdk/rust/examples/create_project.rs b/sdk/rust/examples/create_project.rs new file mode 100644 index 0000000..12bb866 --- /dev/null +++ b/sdk/rust/examples/create_project.rs @@ -0,0 +1,21 @@ +use std::fs::canonicalize; +use superviseur_client::{client::connect, service::new_service}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let deno_fresh = new_service() + .with_name("deno-fresh") + .with_command("./dev.ts") + .with_env("PORT", "8000"); + + let project_dir = canonicalize("../../examples/deno-fresh")?; + + connect() + .new_project("deno-example") + .with_context(project_dir.to_str().unwrap()) + .with_service(deno_fresh) + .stdout() + .await?; + + Ok(()) +} diff --git a/sdk/rust/examples/list_processes.rs b/sdk/rust/examples/list_processes.rs new file mode 100644 index 0000000..2c8ee27 --- /dev/null +++ b/sdk/rust/examples/list_processes.rs @@ -0,0 +1,9 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + let processes = project.processes().await?; + println!("{:#?}", processes); + Ok(()) +} diff --git a/sdk/rust/examples/list_projects.rs b/sdk/rust/examples/list_projects.rs new file mode 100644 index 0000000..a0a50a8 --- /dev/null +++ b/sdk/rust/examples/list_projects.rs @@ -0,0 +1,14 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let projects = connect().projects().await?; + println!( + "{:#?}", + projects + .into_iter() + .map(|p| (p.name, p.id)) + .collect::>() + ); + Ok(()) +} diff --git a/sdk/rust/examples/list_services.rs b/sdk/rust/examples/list_services.rs new file mode 100644 index 0000000..a2916ea --- /dev/null +++ b/sdk/rust/examples/list_services.rs @@ -0,0 +1,9 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + let services = project.services().await?; + println!("{:#?}", services); + Ok(()) +} diff --git a/sdk/rust/examples/logs.rs b/sdk/rust/examples/logs.rs new file mode 100644 index 0000000..75f8488 --- /dev/null +++ b/sdk/rust/examples/logs.rs @@ -0,0 +1,9 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + let logs = project.logs("happy-poison").await?; + println!("{:#?}", logs); + Ok(()) +} diff --git a/sdk/rust/examples/restart_all.rs b/sdk/rust/examples/restart_all.rs new file mode 100644 index 0000000..7c7e21d --- /dev/null +++ b/sdk/rust/examples/restart_all.rs @@ -0,0 +1,8 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + project.restart_all().await?; + Ok(()) +} diff --git a/sdk/rust/examples/start_all.rs b/sdk/rust/examples/start_all.rs new file mode 100644 index 0000000..7c7e21d --- /dev/null +++ b/sdk/rust/examples/start_all.rs @@ -0,0 +1,8 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + project.restart_all().await?; + Ok(()) +} diff --git a/sdk/rust/examples/start_service.rs b/sdk/rust/examples/start_service.rs new file mode 100644 index 0000000..f6b0774 --- /dev/null +++ b/sdk/rust/examples/start_service.rs @@ -0,0 +1,8 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + project.start("").await?; + Ok(()) +} diff --git a/sdk/rust/examples/status.rs b/sdk/rust/examples/status.rs new file mode 100644 index 0000000..e03040c --- /dev/null +++ b/sdk/rust/examples/status.rs @@ -0,0 +1,8 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + project.status("").await?; + Ok(()) +} diff --git a/sdk/rust/examples/stop_all.rs b/sdk/rust/examples/stop_all.rs new file mode 100644 index 0000000..6ad057d --- /dev/null +++ b/sdk/rust/examples/stop_all.rs @@ -0,0 +1,8 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + project.stop_all().await?; + Ok(()) +} diff --git a/sdk/rust/examples/stop_service.rs b/sdk/rust/examples/stop_service.rs new file mode 100644 index 0000000..6ad057d --- /dev/null +++ b/sdk/rust/examples/stop_service.rs @@ -0,0 +1,8 @@ +use superviseur_client::client::connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let project = connect().project("obese-ants").await?; + project.stop_all().await?; + Ok(()) +} diff --git a/sdk/rust/src/client.rs b/sdk/rust/src/client.rs new file mode 100644 index 0000000..57cf28a --- /dev/null +++ b/sdk/rust/src/client.rs @@ -0,0 +1,94 @@ +use std::time::Duration; + +use crate::graphql::query::project_query::ProjectQueryProject; +use graphql_client::{GraphQLQuery, Response}; +use serde::{de::DeserializeOwned, Serialize}; +use surf::{Config, Error, Url}; + +const BASE_URL: &str = "http://localhost:5478"; + +use crate::{ + graphql::query::{self, project_query, projects_query}, + project::Project, +}; + +#[derive(Debug, Serialize)] +pub struct RawQuery { + pub query: String, +} + +#[derive(Default, Clone)] +pub struct Client { + pub http_client: surf::Client, +} + +impl Client { + pub fn new_project(&self, name: &str) -> Project { + Project { + client: self.clone(), + name: name.to_string(), + ..Default::default() + } + } + + pub async fn project(&self, name: &str) -> Result { + let variables = project_query::Variables { + id: name.to_string(), + }; + let body = query::ProjectQuery::build_query(variables); + let response_body = self + .execute_query::>(&body) + .await?; + let p: ProjectQueryProject = response_body.data.expect("missing response data").project; + Ok(Project { + client: self.clone(), + ..Project::from(p) + }) + } + + pub async fn projects(&self) -> Result, Error> { + let variables = projects_query::Variables {}; + let body = query::ProjectsQuery::build_query(variables); + let response_body = self + .execute_query::>(&body) + .await?; + let projects = response_body.data.expect("missing response data").projects; + + Ok(projects + .into_iter() + .map(|p| Project { + client: self.clone(), + ..Project::from(p) + }) + .collect()) + } + + pub async fn execute_query( + &self, + body: &impl Serialize, + ) -> Result { + let mut response = self.http_client.post("/graphql").body_json(&body)?.await?; + let response_body = response.body_json::().await?; + Ok(response_body) + } + + pub async fn send_query(&self, body: &str) -> Result { + let mut response = self + .http_client + .post("/graphql") + .body_json(&RawQuery { + query: body.to_string(), + })? + .await?; + Ok(response.body_string().await?) + } +} + +pub fn connect() -> Client { + let http_client = Config::new() + .set_base_url(Url::parse(BASE_URL).unwrap()) + .set_timeout(Some(Duration::from_secs(5))) + .try_into() + .unwrap(); + Client { http_client } +} diff --git a/sdk/rust/src/graphql/mod.rs b/sdk/rust/src/graphql/mod.rs new file mode 100644 index 0000000..67350db --- /dev/null +++ b/sdk/rust/src/graphql/mod.rs @@ -0,0 +1 @@ +pub mod query; diff --git a/sdk/rust/src/graphql/queries/add_env_variable.graphql b/sdk/rust/src/graphql/queries/add_env_variable.graphql new file mode 100644 index 0000000..6be76e5 --- /dev/null +++ b/sdk/rust/src/graphql/queries/add_env_variable.graphql @@ -0,0 +1,11 @@ +mutation CreateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! +) { + createEnvVar(projectId: $projectId, id: $id, name: $name, value: $value) { + id + env + } +} diff --git a/sdk/rust/src/graphql/queries/get_project.graphql b/sdk/rust/src/graphql/queries/get_project.graphql new file mode 100644 index 0000000..478f8a3 --- /dev/null +++ b/sdk/rust/src/graphql/queries/get_project.graphql @@ -0,0 +1,6 @@ +query ProjectQuery($id: ID!) { + project(id: $id) { + id + name + } +} diff --git a/sdk/rust/src/graphql/queries/list_projects.graphql b/sdk/rust/src/graphql/queries/list_projects.graphql new file mode 100644 index 0000000..1a65135 --- /dev/null +++ b/sdk/rust/src/graphql/queries/list_projects.graphql @@ -0,0 +1,6 @@ +query ProjectsQuery { + projects { + id + name + } +} diff --git a/sdk/rust/src/graphql/queries/list_services.graphql b/sdk/rust/src/graphql/queries/list_services.graphql new file mode 100644 index 0000000..a262365 --- /dev/null +++ b/sdk/rust/src/graphql/queries/list_services.graphql @@ -0,0 +1,8 @@ +query ServicesQuery($projectId: ID!) { + services(projectId: $projectId) { + id + name + command + status + } +} diff --git a/sdk/rust/src/graphql/queries/logs.graphql b/sdk/rust/src/graphql/queries/logs.graphql new file mode 100644 index 0000000..2abbc6e --- /dev/null +++ b/sdk/rust/src/graphql/queries/logs.graphql @@ -0,0 +1,5 @@ +query LogsQuery($id: ID!, $projectId: ID!) { + logs(id: $id, projectId: $projectId) { + lines + } +} diff --git a/sdk/rust/src/graphql/queries/processes.graphql b/sdk/rust/src/graphql/queries/processes.graphql new file mode 100644 index 0000000..e7d1256 --- /dev/null +++ b/sdk/rust/src/graphql/queries/processes.graphql @@ -0,0 +1,10 @@ +query ProcessesQuery { + processes { + name + pid + project + serviceId + command + upTime + } +} diff --git a/sdk/rust/src/graphql/queries/remove_env_variable.graphql b/sdk/rust/src/graphql/queries/remove_env_variable.graphql new file mode 100644 index 0000000..43f7f01 --- /dev/null +++ b/sdk/rust/src/graphql/queries/remove_env_variable.graphql @@ -0,0 +1,6 @@ +mutation DeleteEnvVar($projectId: ID!, $id: ID!, $name: String!) { + deleteEnvVar(projectId: $projectId, id: $id, name: $name) { + id + env + } +} diff --git a/sdk/rust/src/graphql/queries/restart_all.graphql b/sdk/rust/src/graphql/queries/restart_all.graphql new file mode 100644 index 0000000..c13306a --- /dev/null +++ b/sdk/rust/src/graphql/queries/restart_all.graphql @@ -0,0 +1,5 @@ +mutation RestartAllServices($projectId: ID!) { + restart(projectId: $projectId) { + pid + } +} diff --git a/sdk/rust/src/graphql/queries/restart_service.graphql b/sdk/rust/src/graphql/queries/restart_service.graphql new file mode 100644 index 0000000..3fe60f4 --- /dev/null +++ b/sdk/rust/src/graphql/queries/restart_service.graphql @@ -0,0 +1,5 @@ +mutation RestartService($id: ID, $projectId: ID!) { + restart(id: $id, projectId: $projectId) { + pid + } +} diff --git a/sdk/rust/src/graphql/queries/start_all.graphql b/sdk/rust/src/graphql/queries/start_all.graphql new file mode 100644 index 0000000..a3e670d --- /dev/null +++ b/sdk/rust/src/graphql/queries/start_all.graphql @@ -0,0 +1,5 @@ +mutation StartAllServices($projectId: ID!) { + start(projectId: $projectId) { + pid + } +} diff --git a/sdk/rust/src/graphql/queries/start_service.graphql b/sdk/rust/src/graphql/queries/start_service.graphql new file mode 100644 index 0000000..af88469 --- /dev/null +++ b/sdk/rust/src/graphql/queries/start_service.graphql @@ -0,0 +1,5 @@ +mutation StartService($id: ID, $projectId: ID!) { + start(id: $id, projectId: $projectId) { + pid + } +} diff --git a/sdk/rust/src/graphql/queries/status.graphql b/sdk/rust/src/graphql/queries/status.graphql new file mode 100644 index 0000000..3af8b80 --- /dev/null +++ b/sdk/rust/src/graphql/queries/status.graphql @@ -0,0 +1,10 @@ +query StatusQuery($id: ID!) { + status(id: $id) { + name + serviceId + pid + project + state + upTime + } +} diff --git a/sdk/rust/src/graphql/queries/stop_all.graphql b/sdk/rust/src/graphql/queries/stop_all.graphql new file mode 100644 index 0000000..72413d1 --- /dev/null +++ b/sdk/rust/src/graphql/queries/stop_all.graphql @@ -0,0 +1,5 @@ +mutation StopAllServices($projectId: ID!) { + stop(projectId: $projectId) { + pid + } +} diff --git a/sdk/rust/src/graphql/queries/stop_service.graphql b/sdk/rust/src/graphql/queries/stop_service.graphql new file mode 100644 index 0000000..212d099 --- /dev/null +++ b/sdk/rust/src/graphql/queries/stop_service.graphql @@ -0,0 +1,5 @@ +mutation StopService($id: ID, $projectId: ID!) { + stop(id: $id, projectId: $projectId) { + pid + } +} diff --git a/sdk/rust/src/graphql/queries/update_env_variable.graphql b/sdk/rust/src/graphql/queries/update_env_variable.graphql new file mode 100644 index 0000000..fadb0a7 --- /dev/null +++ b/sdk/rust/src/graphql/queries/update_env_variable.graphql @@ -0,0 +1,11 @@ +mutation UpdateEnvVar( + $projectId: ID! + $id: ID! + $name: String! + $value: String! +) { + updateEnvVar(projectId: $projectId, id: $id, name: $name, value: $value) { + id + env + } +} diff --git a/sdk/rust/src/graphql/query.rs b/sdk/rust/src/graphql/query.rs new file mode 100644 index 0000000..d58bc53 --- /dev/null +++ b/sdk/rust/src/graphql/query.rs @@ -0,0 +1,121 @@ +use graphql_client::GraphQLQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/get_project.graphql", + response_derives = "Debug" +)] +pub struct ProjectQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/list_projects.graphql", + response_derives = "Debug" +)] +pub struct ProjectsQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/list_services.graphql", + response_derives = "Debug" +)] +pub struct ServicesQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/add_env_variable.graphql", + response_derives = "Debug" +)] +pub struct CreateEnvVar; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/update_env_variable.graphql", + response_derives = "Debug" +)] +pub struct UpdateEnvVar; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/remove_env_variable.graphql", + response_derives = "Debug" +)] +pub struct DeleteEnvVar; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/processes.graphql", + response_derives = "Debug" +)] +pub struct ProcessesQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/logs.graphql", + response_derives = "Debug" +)] +pub struct LogsQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/start_service.graphql", + response_derives = "Debug" +)] +pub struct StartService; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/stop_service.graphql", + response_derives = "Debug" +)] +pub struct StopService; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/restart_service.graphql", + response_derives = "Debug" +)] +pub struct RestartService; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/status.graphql", + response_derives = "Debug" +)] +pub struct StatusQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/start_all.graphql", + response_derives = "Debug" +)] +pub struct StartAllServices; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/stop_all.graphql", + response_derives = "Debug" +)] +pub struct StopAllServices; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/schema.json", + query_path = "src/graphql/queries/restart_all.graphql", + response_derives = "Debug" +)] +pub struct RestartAllServices; diff --git a/sdk/rust/src/graphql/schema.json b/sdk/rust/src/graphql/schema.json new file mode 100644 index 0000000..3629057 --- /dev/null +++ b/sdk/rust/src/graphql/schema.json @@ -0,0 +1,3072 @@ +{ + "data": { + "__schema": { + "directives": [ + { + "args": [ + { + "defaultValue": null, + "description": "Included when true.", + "name": "if", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + } + ], + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "name": "include" + }, + { + "args": [ + { + "defaultValue": null, + "description": "Skipped when true.", + "name": "if", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + } + ], + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "name": "skip" + } + ], + "mutationType": { + "name": "Mutation" + }, + "queryType": { + "name": "Query" + }, + "subscriptionType": { + "name": "Subscription" + }, + "types": [ + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "AllServicesRestarted", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "AllServicesStarted", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "AllServicesStopped", + "possibleTypes": null + }, + { + "description": "The `Boolean` scalar type represents `true` or `false`.", + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "Boolean", + "possibleTypes": null + }, + { + "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).", + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "Float", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "ID", + "possibleTypes": null + }, + { + "description": "The `Int` scalar type represents non-fractional whole numeric values.", + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "Int", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "lines", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Log", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "line", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "LogStream", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "context", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "newProject", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ProjectConfiguration", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "start", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "stop", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "restart", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "value", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "createEnvVar", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "deleteEnvVar", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "value", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "updateEnvVar", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Mutation", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "serviceId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "pid", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "ppid", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "command", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "workingDirectory", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "project", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "type", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "logFile", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "stderrFile", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "autoRestart", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "env", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "state", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "upTime", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Process", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "configPath", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Project", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "service", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "ServiceConfiguration", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "withService", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ProjectConfiguration", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "stdout", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ProjectConfiguration", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "status", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "projects", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + } + } + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "services", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "project", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "processes", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "service", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "numLines", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "tail", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Log", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "logs", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Log", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Query", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "command", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "namespace", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "type", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "status", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "dependsOn", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "env", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "autoRestart", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "workingDirectory", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "logFile", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "stderrFile", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "port", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Service", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": null, + "inputFields": [ + { + "defaultValue": null, + "description": null, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "command", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "workingDirectory", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "type", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "logFile", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "stderrFile", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "autoRestart", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "autoStart", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "env", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + } + ], + "interfaces": null, + "kind": "INPUT_OBJECT", + "name": "ServiceConfiguration", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "process", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ServiceRestarted", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "process", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ServiceStarted", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "process", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ServiceStarting", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "process", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ServiceStopped", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "payload", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Service", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "process", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Process", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "ServiceStopping", + "possibleTypes": null + }, + { + "description": "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.", + "enumValues": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "SCALAR", + "name": "String", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "tail", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TailLogStream", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "projectId", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "logs", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "LogStream", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onStarting", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ServiceStarting", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onStopping", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ServiceStopping", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onStart", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ServiceStarted", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onStop", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ServiceStopped", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onRestart", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ServiceRestarted", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onStartAll", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AllServicesStarted", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onStopAll", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AllServicesStopped", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "onRestartAll", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AllServicesRestarted", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "Subscription", + "possibleTypes": null + }, + { + "description": null, + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "line", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "TailLogStream", + "possibleTypes": null + }, + { + "description": "A Directive provides a way to describe alternate runtime execution and type\nvalidation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution\nbehavior in ways field arguments will not suffice, such as conditionally\nincluding or skipping a field. Directives provide this by describing\nadditional information to the executor.", + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "locations", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "args", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "isRepeatable", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "__Directive", + "possibleTypes": null + }, + { + "description": "A Directive can be adjacent to many parts of the GraphQL language, a\n__DirectiveLocation describes one such possible adjacencies.", + "enumValues": [ + { + "deprecationReason": null, + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "name": "QUERY" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "name": "MUTATION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "name": "SUBSCRIPTION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a field.", + "isDeprecated": false, + "name": "FIELD" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "name": "FRAGMENT_DEFINITION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "name": "FRAGMENT_SPREAD" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "name": "INLINE_FRAGMENT" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a variable definition.", + "isDeprecated": false, + "name": "VARIABLE_DEFINITION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "name": "SCHEMA" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "name": "SCALAR" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "name": "OBJECT" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "name": "FIELD_DEFINITION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "name": "ARGUMENT_DEFINITION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "name": "INTERFACE" + }, + { + "deprecationReason": null, + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "name": "UNION" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "name": "ENUM" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "name": "ENUM_VALUE" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "name": "INPUT_OBJECT" + }, + { + "deprecationReason": null, + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "name": "INPUT_FIELD_DEFINITION" + } + ], + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "ENUM", + "name": "__DirectiveLocation", + "possibleTypes": null + }, + { + "description": "One possible value for a given Enum. Enum values are unique values, not a\nplaceholder for a string or numeric value. However an Enum value is returned\nin a JSON response as a string.", + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "isDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "deprecationReason", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "__EnumValue", + "possibleTypes": null + }, + { + "description": "Object and Interface types are described by a list of Fields, each of which\nhas a name, potentially a list of arguments, and a return type.", + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "args", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "type", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "isDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "deprecationReason", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "__Field", + "possibleTypes": null + }, + { + "description": "Arguments provided to Fields or Directives and the input fields of an\nInputObject are represented as Input Values which describe their type and\noptionally a default value.", + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "type", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "defaultValue", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "__InputValue", + "possibleTypes": null + }, + { + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes\nall available types and directives on the server, as well as the entry\npoints for query, mutation, and subscription operations.", + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": "A list of all types supported by this server.", + "isDeprecated": false, + "name": "types", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": "The type that query operations will be rooted at.", + "isDeprecated": false, + "name": "queryType", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": "If this server supports mutation, the type that mutation operations will\nbe rooted at.", + "isDeprecated": false, + "name": "mutationType", + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": "If this server support subscription, the type that subscription\noperations will be rooted at.", + "isDeprecated": false, + "name": "subscriptionType", + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": "A list of all directives supported by this server.", + "isDeprecated": false, + "name": "directives", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "__Schema", + "possibleTypes": null + }, + { + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds\nof types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about\nthat type. Scalar types provide no information beyond a name and\ndescription, while Enum types provide their values. Object and Interface\ntypes provide the fields they describe. Abstract types, Union and Interface,\nprovide the Object types possible at runtime. List and NonNull types compose\nother types.", + "enumValues": null, + "fields": [ + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "kind", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "name", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "description", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [ + { + "defaultValue": "false", + "description": null, + "name": "includeDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "fields", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "interfaces", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "possibleTypes", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + { + "args": [ + { + "defaultValue": "false", + "description": null, + "name": "includeDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "enumValues", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "inputFields", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "ofType", + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "specifiedByURL", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "isOneOf", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "__Type", + "possibleTypes": null + }, + { + "description": "An enum describing what kind of type a given `__Type` is.", + "enumValues": [ + { + "deprecationReason": null, + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "name": "SCALAR" + }, + { + "deprecationReason": null, + "description": "Indicates this type is an object. `fields` and `interfaces` are valid\nfields.", + "isDeprecated": false, + "name": "OBJECT" + }, + { + "deprecationReason": null, + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are\nvalid fields.", + "isDeprecated": false, + "name": "INTERFACE" + }, + { + "deprecationReason": null, + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "name": "UNION" + }, + { + "deprecationReason": null, + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "name": "ENUM" + }, + { + "deprecationReason": null, + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "name": "INPUT_OBJECT" + }, + { + "deprecationReason": null, + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "name": "LIST" + }, + { + "deprecationReason": null, + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "name": "NON_NULL" + } + ], + "fields": null, + "inputFields": null, + "interfaces": null, + "kind": "ENUM", + "name": "__TypeKind", + "possibleTypes": null + } + ] + } + } +} \ No newline at end of file diff --git a/sdk/rust/src/lib.rs b/sdk/rust/src/lib.rs new file mode 100644 index 0000000..d195dae --- /dev/null +++ b/sdk/rust/src/lib.rs @@ -0,0 +1,21 @@ +pub mod client; +pub mod graphql; +pub mod project; +pub mod query; +pub mod service; +pub mod types; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/sdk/rust/src/project.rs b/sdk/rust/src/project.rs new file mode 100644 index 0000000..bd9de58 --- /dev/null +++ b/sdk/rust/src/project.rs @@ -0,0 +1,288 @@ +use crate::graphql::query::project_query::ProjectQueryProject; +use crate::graphql::query::projects_query::ProjectsQueryProjects; +use crate::graphql::query::{ + self, create_env_var, delete_env_var, logs_query, processes_query, restart_all_services, + restart_service, services_query, start_all_services, start_service, status_query, + stop_all_services, stop_service, update_env_var, +}; +use crate::types::{self, Logs, Process}; +use crate::{client::Client, query::build_nested_with_service_query, service::Service}; +use graphql_client::{GraphQLQuery, Response}; +use surf::Error; + +#[derive(Default)] +pub struct Project { + pub client: Client, + pub id: String, + pub name: String, + pub context: String, + pub services: Vec, +} + +impl Project { + pub fn new(client: Client) -> Project { + Project { + client, + ..Default::default() + } + } + + pub fn with_context(mut self, context: &str) -> Self { + self.context = context.to_string(); + self + } + + pub fn with_name(mut self, name: &str) -> Self { + self.name = name.to_string(); + self + } + + pub fn with_service(mut self, service: Service) -> Self { + self.services.push(service); + self + } + + pub async fn start(self, service_id: &str) -> Result { + let variables = start_service::Variables { + id: Some(service_id.to_string()), + project_id: self.id.clone(), + }; + let body = query::StartService::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").start); + Ok(process) + } + + pub async fn stop(self, service_id: &str) -> Result { + let variables = stop_service::Variables { + id: Some(service_id.to_string()), + project_id: self.id.clone(), + }; + let body = query::StopService::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").stop); + Ok(process) + } + + pub async fn restart(self, service_id: &str) -> Result { + let variables = restart_service::Variables { + id: Some(service_id.to_string()), + project_id: self.id.clone(), + }; + let body = query::RestartService::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").restart); + Ok(process) + } + + pub async fn status(self, service_id: &str) -> Result { + let variables = status_query::Variables { + id: service_id.to_string(), + }; + let body = query::StatusQuery::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").status); + Ok(process) + } + + pub async fn start_all(self) -> Result { + let variables = start_all_services::Variables { + project_id: self.id.clone(), + }; + let body = query::StartAllServices::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").start); + Ok(process) + } + + pub async fn stop_all(self) -> Result { + let variables = stop_all_services::Variables { + project_id: self.id.clone(), + }; + let body = query::StopAllServices::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").stop); + Ok(process) + } + + pub async fn restart_all(self) -> Result { + let variables = restart_all_services::Variables { + project_id: self.id.clone(), + }; + let body = query::RestartAllServices::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let process = Process::from(response.data.expect("missing response data").restart); + Ok(process) + } + + pub async fn services(self) -> Result, Error> { + let variables = services_query::Variables { + project_id: self.id.clone(), + }; + let body = query::ServicesQuery::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let services = response + .data + .expect("missing response data") + .services + .into_iter() + .map(types::Service::from) + .collect(); + Ok(services) + } + + pub async fn processes(self) -> Result, Error> { + let variables = processes_query::Variables {}; + let body = query::ProcessesQuery::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let processes = response + .data + .expect("missing response data") + .processes + .into_iter() + .map(Process::from) + .collect(); + Ok(processes) + } + + pub async fn logs(self, service_id: &str) -> Result { + let variables = logs_query::Variables { + id: service_id.to_string(), + project_id: self.id.clone(), + }; + let body = query::LogsQuery::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let logs = Logs::from(response.data.expect("missing response data").logs); + Ok(logs) + } + + pub async fn stdout(self) -> Result<(), Error> { + let nested_query = build_nested_with_service_query(self.services); + let query = format!( + r#" + mutation {{ + newProject(name: "{}", context: "{}") {{ + {} + }} + }} + "#, + self.name, self.context, nested_query + ); + self.client.send_query(&query).await?; + Ok(()) + } + + pub async fn add_env_var( + self, + service_id: &str, + name: &str, + value: &str, + ) -> Result { + let variables = create_env_var::Variables { + id: service_id.to_string(), + project_id: self.id.clone(), + name: name.to_string(), + value: value.to_string(), + }; + let body = query::CreateEnvVar::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let service = + types::Service::from(response.data.expect("missing response data").create_env_var); + Ok(service) + } + + pub async fn remove_env_var( + self, + service_id: &str, + name: &str, + ) -> Result { + let variables = delete_env_var::Variables { + id: service_id.to_string(), + project_id: self.id.clone(), + name: name.to_string(), + }; + let body = query::DeleteEnvVar::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let service = + types::Service::from(response.data.expect("missing response data").delete_env_var); + Ok(service) + } + + pub async fn update_env_var( + self, + service_id: &str, + name: &str, + value: &str, + ) -> Result { + let variables = update_env_var::Variables { + id: service_id.to_string(), + project_id: self.id.clone(), + name: name.to_string(), + value: value.to_string(), + }; + let body = query::UpdateEnvVar::build_query(variables); + let response = self + .client + .execute_query::>(&body) + .await?; + let service = + types::Service::from(response.data.expect("missing response data").update_env_var); + Ok(service) + } +} + +impl From for Project { + fn from(project: ProjectQueryProject) -> Self { + Project { + id: project.id, + name: project.name, + ..Default::default() + } + } +} + +impl From for Project { + fn from(project: ProjectsQueryProjects) -> Self { + Project { + id: project.id, + name: project.name, + ..Default::default() + } + } +} diff --git a/sdk/rust/src/query.rs b/sdk/rust/src/query.rs new file mode 100644 index 0000000..77574a6 --- /dev/null +++ b/sdk/rust/src/query.rs @@ -0,0 +1,74 @@ +use crate::service::Service; + +pub fn build_nested_with_service_query(services: Vec) -> String { + let mut query = "id stdout".to_string(); + for service in services { + query = format!( + r#" + withService(service: {{ {} }}) {{ + {} + }} + "#, + build_params(service), + query + ); + } + query +} + +fn build_params(service: Service) -> String { + let mut params = String::new(); + + if !service.name.is_empty() { + params = format!(r#"name: "{}", "#, service.name); + } + + if !service.command.is_empty() { + params = format!(r#"{} command: "{}", "#, params, service.command); + } + + if service.port.is_some() { + params = format!(r#"{} port: {}, "#, params, service.port.unwrap()); + } + + if !service.r#type.is_empty() { + params = format!(r#"{} type: "{}", "#, params, service.r#type); + } + + if !service.working_dir.is_empty() { + params = format!(r#"{} workingDir: "{}", "#, params, service.working_dir); + } + + if !service.description.is_empty() { + params = format!(r#"{} description: "{}", "#, params, service.description); + } + + if !service.depends_on.is_empty() { + params = format!( + "{} dependsOn: [{}], ", + params, + service + .depends_on + .iter() + .map(|s| format!(r#""{}""#, s)) + .collect::>() + .join(", ") + ); + } + + if !service.env.is_empty() { + params = format!( + "{} env: [{}], ", + params, + service + .env + .iter() + .map(|(k, v)| format!(r#""{}={}""#, k, v)) + .collect::>() + .join(", ") + ); + } + + params.truncate(params.len() - 2); + params +} diff --git a/sdk/rust/src/service.rs b/sdk/rust/src/service.rs new file mode 100644 index 0000000..015e5e9 --- /dev/null +++ b/sdk/rust/src/service.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; + +#[derive(Debug, Default)] +pub struct Service { + pub name: String, + pub command: String, + pub r#type: String, + pub working_dir: String, + pub description: String, + pub depends_on: Vec, + pub env: HashMap, + pub auto_start: bool, + pub auto_restart: bool, + pub namespace: String, + pub stdout: String, + pub stderr: String, + pub build_command: String, + pub flox_enviroment: Option, + pub enable_docker: Option, + pub enable_nix: Option, + pub port: Option, +} + +impl Service { + pub fn with_name(mut self, name: &str) -> Service { + self.name = name.to_string(); + self + } + + pub fn with_command(mut self, command: &str) -> Service { + self.command = command.to_string(); + self + } + + pub fn with_type(mut self, r#type: &str) -> Service { + self.r#type = r#type.to_string(); + self + } + + pub fn with_working_dir(mut self, working_dir: &str) -> Service { + self.working_dir = working_dir.to_string(); + self + } + + pub fn with_description(mut self, description: &str) -> Service { + self.description = description.to_string(); + self + } + + pub fn with_depends_on(mut self, depends_on: Vec) -> Service { + self.depends_on = depends_on; + self + } + + pub fn with_env(mut self, name: &str, value: &str) -> Service { + self.env.insert(name.to_string(), value.to_string()); + self + } + + pub fn with_auto_start(mut self, auto_start: bool) -> Service { + self.auto_start = auto_start; + self + } + + pub fn with_auto_restart(mut self, auto_restart: bool) -> Service { + self.auto_restart = auto_restart; + self + } + + pub fn with_namespace(mut self, namespace: &str) -> Service { + self.namespace = namespace.to_string(); + self + } + + pub fn with_stdout(mut self, stdout: &str) -> Service { + self.stdout = stdout.to_string(); + self + } + + pub fn with_stderr(mut self, stderr: &str) -> Service { + self.stderr = stderr.to_string(); + self + } + + pub fn with_build_command(mut self, build_command: &str) -> Service { + self.build_command = build_command.to_string(); + self + } + + pub fn with_flox_enviroment(mut self, flox_enviroment: &str) -> Service { + self.flox_enviroment = Some(flox_enviroment.to_string()); + self + } + + pub fn with_enable_docker(mut self, enable_docker: bool) -> Service { + self.enable_docker = Some(enable_docker); + self + } + + pub fn with_enable_nix(mut self, enable_nix: bool) -> Service { + self.enable_nix = Some(enable_nix); + self + } +} + +pub fn new_service() -> Service { + Service { + ..Default::default() + } +} diff --git a/sdk/rust/src/types/logs.rs b/sdk/rust/src/types/logs.rs new file mode 100644 index 0000000..69b5dfa --- /dev/null +++ b/sdk/rust/src/types/logs.rs @@ -0,0 +1,12 @@ +use crate::graphql::query::logs_query::LogsQueryLogs; + +#[derive(Debug)] +pub struct Logs { + pub lines: Vec, +} + +impl From for Logs { + fn from(logs: LogsQueryLogs) -> Self { + Logs { lines: logs.lines } + } +} diff --git a/sdk/rust/src/types/mod.rs b/sdk/rust/src/types/mod.rs new file mode 100644 index 0000000..baeedbc --- /dev/null +++ b/sdk/rust/src/types/mod.rs @@ -0,0 +1,7 @@ +pub mod logs; +pub mod process; +pub mod service; + +pub type Process = process::Process; +pub type Service = service::Service; +pub type Logs = logs::Logs; diff --git a/sdk/rust/src/types/process.rs b/sdk/rust/src/types/process.rs new file mode 100644 index 0000000..a29e60c --- /dev/null +++ b/sdk/rust/src/types/process.rs @@ -0,0 +1,98 @@ +use crate::graphql::query::processes_query::ProcessesQueryProcesses; +use crate::graphql::query::restart_all_services::RestartAllServicesRestart; +use crate::graphql::query::restart_service::RestartServiceRestart; +use crate::graphql::query::start_all_services::StartAllServicesStart; +use crate::graphql::query::start_service::StartServiceStart; +use crate::graphql::query::status_query::StatusQueryStatus; +use crate::graphql::query::stop_all_services::StopAllServicesStop; +use crate::graphql::query::stop_service::StopServiceStop; + +use chrono::{DateTime, Utc}; + +#[derive(Debug, Default)] +pub struct Process { + pub name: String, + pub pid: Option, + pub project: String, + pub service_id: String, + pub command: String, + pub up_time: DateTime, +} + +impl From for Process { + fn from(process: ProcessesQueryProcesses) -> Self { + Process { + name: process.name, + pid: process.pid, + project: process.project, + service_id: process.service_id, + command: process.command, + up_time: DateTime::parse_from_rfc3339(&process.up_time) + .unwrap() + .with_timezone(&Utc), + } + } +} + +impl From for Process { + fn from(process: StartServiceStart) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} + +impl From for Process { + fn from(process: RestartServiceRestart) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} + +impl From for Process { + fn from(process: StopServiceStop) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} + +impl From for Process { + fn from(process: StartAllServicesStart) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} + +impl From for Process { + fn from(process: RestartAllServicesRestart) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} + +impl From for Process { + fn from(process: StopAllServicesStop) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} + +impl From for Process { + fn from(process: StatusQueryStatus) -> Self { + Process { + pid: process.pid, + ..Default::default() + } + } +} diff --git a/sdk/rust/src/types/service.rs b/sdk/rust/src/types/service.rs new file mode 100644 index 0000000..36ec63b --- /dev/null +++ b/sdk/rust/src/types/service.rs @@ -0,0 +1,55 @@ +use crate::graphql::query::create_env_var::CreateEnvVarCreateEnvVar; +use crate::graphql::query::delete_env_var::DeleteEnvVarDeleteEnvVar; +use crate::graphql::query::services_query::ServicesQueryServices; +use crate::graphql::query::update_env_var::UpdateEnvVarUpdateEnvVar; + +#[derive(Debug, Default)] +pub struct Service { + pub id: String, + pub name: String, + pub command: String, + pub status: String, + pub env: Vec, +} + +impl From for Service { + fn from(service: ServicesQueryServices) -> Self { + Service { + id: service.id, + name: service.name, + command: service.command, + status: service.status, + ..Default::default() + } + } +} + +impl From for Service { + fn from(service: CreateEnvVarCreateEnvVar) -> Self { + Service { + id: service.id, + env: service.env, + ..Default::default() + } + } +} + +impl From for Service { + fn from(service: UpdateEnvVarUpdateEnvVar) -> Self { + Service { + id: service.id, + env: service.env, + ..Default::default() + } + } +} + +impl From for Service { + fn from(service: DeleteEnvVarDeleteEnvVar) -> Self { + Service { + id: service.id, + env: service.env, + ..Default::default() + } + } +} diff --git a/src/api/objects.v1alpha1.rs b/src/api/objects.v1alpha1.rs index bd8675e..fc0fd5e 100644 --- a/src/api/objects.v1alpha1.rs +++ b/src/api/objects.v1alpha1.rs @@ -1,3 +1,4 @@ +#[derive(serde::Serialize)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Service { @@ -19,6 +20,8 @@ pub struct Service { pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(bool, tag = "9")] pub auto_restart: bool, + #[prost(int32, tag = "10")] + pub port: i32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -53,4 +56,21 @@ pub struct Process { pub env: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, tag = "15")] pub service_id: ::prost::alloc::string::String, + #[prost(int32, tag = "16")] + pub port: i32, +} +#[derive(serde::Serialize)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Project { + #[prost(string, tag = "1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub description: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub context: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "5")] + pub services: ::prost::alloc::vec::Vec, } diff --git a/src/api/superviseur.v1alpha1.rs b/src/api/superviseur.v1alpha1.rs index 5661d38..c48506e 100644 --- a/src/api/superviseur.v1alpha1.rs +++ b/src/api/superviseur.v1alpha1.rs @@ -1447,3 +1447,330 @@ pub mod logging_service_server { const NAME: &'static str = "superviseur.v1alpha1.LoggingService"; } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListProjectsRequest { + #[prost(string, tag = "1")] + pub filter: ::prost::alloc::string::String, +} +#[derive(serde::Serialize)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListProjectsResponse { + #[prost(message, repeated, tag = "1")] + pub projects: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetProjectRequest { + #[prost(string, tag = "1")] + pub id: ::prost::alloc::string::String, +} +#[derive(serde::Serialize)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetProjectResponse { + #[prost(message, optional, tag = "1")] + pub project: ::core::option::Option, +} +/// Generated client implementations. +pub mod project_service_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct ProjectServiceClient { + inner: tonic::client::Grpc, + } + impl ProjectServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: std::convert::TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl ProjectServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> ProjectServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + ProjectServiceClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + pub async fn list_projects( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/superviseur.v1alpha1.ProjectService/ListProjects", + ); + self.inner.unary(request.into_request(), path, codec).await + } + pub async fn get_project( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/superviseur.v1alpha1.ProjectService/GetProject", + ); + self.inner.unary(request.into_request(), path, codec).await + } + } +} +/// Generated server implementations. +pub mod project_service_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with ProjectServiceServer. + #[async_trait] + pub trait ProjectService: Send + Sync + 'static { + async fn list_projects( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + async fn get_project( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + } + #[derive(Debug)] + pub struct ProjectServiceServer { + inner: _Inner, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + } + struct _Inner(Arc); + impl ProjectServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + } + impl tonic::codegen::Service> for ProjectServiceServer + where + T: ProjectService, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/superviseur.v1alpha1.ProjectService/ListProjects" => { + #[allow(non_camel_case_types)] + struct ListProjectsSvc(pub Arc); + impl< + T: ProjectService, + > tonic::server::UnaryService + for ListProjectsSvc { + type Response = super::ListProjectsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { + (*inner).list_projects(request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = ListProjectsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/superviseur.v1alpha1.ProjectService/GetProject" => { + #[allow(non_camel_case_types)] + struct GetProjectSvc(pub Arc); + impl< + T: ProjectService, + > tonic::server::UnaryService + for GetProjectSvc { + type Response = super::GetProjectResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).get_project(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetProjectSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for ProjectServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } + impl tonic::server::NamedService for ProjectServiceServer { + const NAME: &'static str = "superviseur.v1alpha1.ProjectService"; + } +} diff --git a/src/cmd/config.rs b/src/cmd/config.rs index 96dc64d..b7746a0 100644 --- a/src/cmd/config.rs +++ b/src/cmd/config.rs @@ -1,3 +1,3 @@ -pub fn execute_config(name: &str) { +pub fn execute_config(_name: &str) { todo!("config") } diff --git a/src/cmd/init.rs b/src/cmd/init.rs index dd1793a..f733ed5 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,5 +1,5 @@ use crate::types::configuration::ConfigFormat; -pub fn execute_init(cfg_format: ConfigFormat) { +pub fn execute_init(_cfg_format: ConfigFormat) { todo!("init") } diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 4728550..795b2fd 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -4,6 +4,8 @@ pub mod init; pub mod list; pub mod log; pub mod new; +pub mod preview; +pub mod project; pub mod ps; pub mod restart; pub mod start; diff --git a/src/cmd/new.rs b/src/cmd/new.rs index 38b4f97..e316410 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -10,6 +10,7 @@ pub fn execute_new(cfg_format: ConfigFormat) { let config = ConfigurationData { project: "demo".to_string(), + context: None, services: vec![Service { id: None, name: "demo".to_string(), diff --git a/src/cmd/preview.rs b/src/cmd/preview.rs new file mode 100644 index 0000000..5debddb --- /dev/null +++ b/src/cmd/preview.rs @@ -0,0 +1,59 @@ +use anyhow::Error; +use owo_colors::OwoColorize; +use tokio::net::UnixStream; +use tonic::transport::{ Endpoint, Uri}; +use tower::service_fn; +use crate::{ + api::superviseur::v1alpha1::{ + control_service_client::ControlServiceClient, LoadConfigRequest, StatusRequest, + }, + types::{ UNIX_SOCKET_PATH, SUPERFILE}, config::verify_if_config_file_is_present, +}; + +pub async fn execute_preview(name: &str) -> Result<(), Error> { + verify_if_config_file_is_present()?; + let current_dir = std::env::current_dir()?; + let config = std::fs::read_to_string(current_dir.join(SUPERFILE))?; + let channel = Endpoint::try_from("http://[::]:50051")? + .connect_with_connector(service_fn(move |_: Uri| UnixStream::connect( UNIX_SOCKET_PATH))) + .await + .map_err(|_| + Error::msg(format!("Cannot connect to the Superviseur daemon at unix:{}. Is the superviseur daemon running?", UNIX_SOCKET_PATH)))?; + + // let mut client = ControlServiceClient::connect("http://127.0.0.1:5476").await?; + let mut client = ControlServiceClient::new(channel); + + let request = tonic::Request::new(LoadConfigRequest { + config, + file_path: current_dir.to_str().unwrap().to_string(), + }); + + client.load_config(request).await?; + + let request = tonic::Request::new(StatusRequest { + name: name.to_string(), + config_file_path: current_dir.to_str().unwrap().to_string(), + }); + + let response = client.status(request).await?; + let response = response.into_inner(); + + match response.process { + Some(process) => { + match process.state.as_str() { + "Running" => { + open::that(format!("http://localhost:{}", process.port))?; + println!("Previewing {} at http://localhost:{}", process.name.bright_green(), process.port); + } + _ => { + println!("{} is not running", process.name); + }, + }; + } + None => { + println!("{} is not running", name); + } + } + + Ok(()) +} diff --git a/src/cmd/project.rs b/src/cmd/project.rs new file mode 100644 index 0000000..f7ba56f --- /dev/null +++ b/src/cmd/project.rs @@ -0,0 +1,41 @@ +use anyhow::Error; +use colored_json::ToColoredJson; +use tokio::net::UnixStream; +use tonic::transport::{ Endpoint, Uri}; +use tower::service_fn; + +use crate::{api::superviseur::v1alpha1::{project_service_client::ProjectServiceClient, GetProjectRequest, ListProjectsRequest}, types::UNIX_SOCKET_PATH}; + +pub async fn execute_get_project(project_id: &str)-> Result<(), Error> { + let channel = Endpoint::try_from("http://[::]:50051")? + .connect_with_connector(service_fn(move |_: Uri| UnixStream::connect(UNIX_SOCKET_PATH))) + .await + .map_err(|_| + Error::msg(format!("Cannot connect to the Superviseur daemon at unix:{}. Is the superviseur daemon running?", UNIX_SOCKET_PATH)))?; + + let mut client = ProjectServiceClient::new(channel); + let request = tonic::Request::new(GetProjectRequest { + id: project_id.to_string(), + }); + let response = client.get_project(request).await?; + let response = response.into_inner(); + println!("{}", serde_json::to_string_pretty(&response)?.to_colored_json_auto()?); + Ok(()) +} + +pub async fn execute_list_projects() -> Result<(), Error> { + let channel = Endpoint::try_from("http://[::]:50051")? + .connect_with_connector(service_fn(move |_: Uri| UnixStream::connect(UNIX_SOCKET_PATH))) + .await + .map_err(|_| + Error::msg(format!("Cannot connect to the Superviseur daemon at unix:{}. Is the superviseur daemon running?", UNIX_SOCKET_PATH)))?; + + let mut client = ProjectServiceClient::new(channel); + let request = tonic::Request::new(ListProjectsRequest { + filter: "".to_string(), + }); + let response = client.list_projects(request).await?; + let response = response.into_inner(); + println!("{}", serde_json::to_string_pretty(&response)?.to_colored_json_auto()?); + Ok(()) +} diff --git a/src/graphql/schema/control.rs b/src/graphql/schema/control.rs index c6ec086..e9e4bb5 100644 --- a/src/graphql/schema/control.rs +++ b/src/graphql/schema/control.rs @@ -7,6 +7,7 @@ use std::{ use async_graphql::{Context, Error, Object, Subscription, ID}; use futures_util::Stream; +use names::Generator; use tokio::sync::mpsc; use crate::{ @@ -18,6 +19,7 @@ use crate::{ use super::objects::{ process::Process, project::Project, + project_configuration::ProjectConfiguration, service::Service, subscriptions::{ AllServicesRestarted, AllServicesStarted, AllServicesStopped, ServiceRestarted, @@ -60,13 +62,11 @@ impl ControlQuery { let projects = config_map .iter() .map(|(id, config)| { - let config_path = project_map - .clone() - .into_iter() - .find(|(_, v)| v == id) - .unwrap() - .0 - .clone(); + let config_path = match project_map.clone().into_iter().find(|(_, v)| v == id) { + Some((k, _)) => Some(k), + None => None, + }; + Project { id: ID(id.clone()), name: config.project.clone(), @@ -128,13 +128,14 @@ impl ControlQuery { match config_map.get(&project_id) { Some(config) => { - let config_path = project_map + let config_path = match project_map .clone() .into_iter() .find(|(_, v)| v == &project_id) - .unwrap() - .0 - .clone(); + { + Some((k, _)) => Some(k), + None => None, + }; Ok(Project { id: ID(project_id), @@ -215,6 +216,44 @@ pub struct ControlMutation; #[Object] impl ControlMutation { + async fn new_project( + &self, + ctx: &Context<'_>, + name: String, + context: String, + ) -> Result { + let config_map = ctx + .data::>>>() + .unwrap(); + + let mut generator = Generator::default(); + let id = generator.next().unwrap(); + let config = ConfigurationData { + project: name.clone(), + services: vec![], + context: Some(context.clone()), + }; + + let mut config_map = config_map.lock().unwrap(); + // check if project already exists by verifying if the context is already used + if config_map + .values() + .any(|c| c.context == Some(context.clone())) + { + return Err(Error::new("Project already exists with this context")); + } + + config_map.insert(id.clone(), config.clone()); + drop(config_map); + + return Ok(ProjectConfiguration { + id: ID(id), + name, + context, + ..Default::default() + }); + } + async fn start( &self, ctx: &Context<'_>, diff --git a/src/graphql/schema/logging.rs b/src/graphql/schema/logging.rs index 2be663a..fbee10e 100644 --- a/src/graphql/schema/logging.rs +++ b/src/graphql/schema/logging.rs @@ -13,14 +13,12 @@ use tokio_stream::StreamExt; use crate::{ graphql::{schema::objects::subscriptions::TailLogStream, simple_broker::SimpleBroker}, types::configuration::ConfigurationData, + util::read_lines, }; -use super::{ - get_project_id, - objects::{ - log::Log, - subscriptions::{self, LogStream}, - }, +use super::objects::{ + log::Log, + subscriptions::{self, LogStream}, }; #[derive(Default, Clone)] @@ -90,15 +88,7 @@ impl LoggingQuery { .find(|s| s.id == Some(id.to_string())) .ok_or_else(|| Error::new("Service not found"))?; - let log_file = File::open(&service.stdout).map_err(|e| Error::new(e.to_string()))?; - - let reader = BufReader::new(log_file); - - let mut lines = vec![]; - for line in reader.lines() { - let line = line.map_err(|e| Error::new(e.to_string()))?; - lines.push(line); - } + let lines = read_lines(&service.stdout)?; Ok(Log { lines }) } diff --git a/src/graphql/schema/objects/mod.rs b/src/graphql/schema/objects/mod.rs index dc0d63f..b437f5d 100644 --- a/src/graphql/schema/objects/mod.rs +++ b/src/graphql/schema/objects/mod.rs @@ -1,5 +1,7 @@ pub mod log; pub mod process; pub mod project; +pub mod project_configuration; pub mod service; +pub mod service_configuration; pub mod subscriptions; diff --git a/src/graphql/schema/objects/process.rs b/src/graphql/schema/objects/process.rs index a537d66..338ffca 100644 --- a/src/graphql/schema/objects/process.rs +++ b/src/graphql/schema/objects/process.rs @@ -19,6 +19,7 @@ pub struct Process { pub env: Vec, pub state: String, pub up_time: String, + pub port: Option, } #[Object] diff --git a/src/graphql/schema/objects/project.rs b/src/graphql/schema/objects/project.rs index 974f3b8..2609cd8 100644 --- a/src/graphql/schema/objects/project.rs +++ b/src/graphql/schema/objects/project.rs @@ -4,7 +4,7 @@ use async_graphql::{Object, ID}; pub struct Project { pub id: ID, pub name: String, - pub config_path: String, + pub config_path: Option, } #[Object] @@ -17,7 +17,7 @@ impl Project { &self.name } - async fn config_path(&self) -> &str { - &self.config_path + async fn config_path(&self) -> Option<&str> { + self.config_path.as_deref() } } diff --git a/src/graphql/schema/objects/project_configuration.rs b/src/graphql/schema/objects/project_configuration.rs new file mode 100644 index 0000000..b7e8ebb --- /dev/null +++ b/src/graphql/schema/objects/project_configuration.rs @@ -0,0 +1,166 @@ +use std::{ + collections::HashMap, + path::Path, + sync::{Arc, Mutex}, + thread::sleep, + time::Duration, +}; + +use async_graphql::{Context, Error, Object, ID}; +use names::Generator; +use tokio::sync::mpsc; + +use crate::{ + graphql::simple_broker::SimpleBroker, + superviseur::core::SuperviseurCommand, + types::configuration::{ConfigurationData, Service}, + util::{convert_dir_path_to_absolute_path, read_lines}, +}; + +use super::{service_configuration::ServiceConfiguration, subscriptions::AllServicesStarted}; + +#[derive(Default, Clone, Debug)] +pub struct ProjectConfiguration { + pub id: ID, + pub name: String, + pub description: Option, + pub context: String, +} + +#[Object] +impl ProjectConfiguration { + async fn id(&self) -> &ID { + &self.id + } + + async fn name(&self) -> &str { + &self.name + } + + async fn description(&self) -> Option<&str> { + self.description.as_deref() + } + + async fn with_service( + &self, + ctx: &Context<'_>, + service: ServiceConfiguration, + ) -> Result<&ProjectConfiguration, Error> { + let working_directory = convert_dir_path_to_absolute_path( + &service.working_directory.unwrap_or("./".to_string()), + &self.context, + )?; + if !Path::new(&working_directory).exists() { + return Err(Error::new(format!( + "Working directory {} does not exist", + working_directory + ))); + } + + let config_map = ctx + .data::>>>() + .unwrap(); + + let mut config_map = config_map.lock().unwrap(); + let config = config_map.get_mut(&self.id.to_string()).unwrap(); + + let mut generator = Generator::default(); + let service_id = generator.next().unwrap(); + let env = match service.env { + Some(env) => env + .iter() + .map(|e| { + let mut split = e.split('='); + let key = split.next().unwrap().to_string(); + let value = split.next().unwrap().to_string(); + (key, value) + }) + .collect::>(), + None => HashMap::new(), + }; + + let service = Service { + id: Some(service_id.clone()), + name: service.name, + description: service.description, + r#type: service.r#type.unwrap_or("exec".to_string()), + command: service.command, + working_dir: working_directory, + stdout: service + .log_file + .unwrap_or(format!("/tmp/stdout-{}.log", service_id.clone())), + stderr: service + .stderr_file + .unwrap_or(format!("/tmp/stderr-{}.log", service_id)), + autorestart: service.auto_restart.unwrap_or(false), + autostart: service.auto_start.unwrap_or(false), + env, + depends_on: service.depends_on.unwrap_or(vec![]), + port: service.port, + ..Default::default() + }; + config.services.push(service); + + Ok(&self) + } + + async fn stdout(&self, ctx: &Context<'_>) -> Result, Error> { + let cmd_tx = ctx + .data::>() + .unwrap(); + + let config_map = ctx + .data::>>>() + .unwrap(); + + let mut config_map = config_map.lock().unwrap(); + let config = config_map.get(&self.id.to_string()).unwrap(); + + cmd_tx + .send(SuperviseurCommand::LoadConfig( + config.clone(), + config.project.clone(), + )) + .unwrap(); + + let config = config_map.get_mut(&self.id.to_string()).unwrap(); + + let services = config.services.clone(); + let mut services = services.into_iter(); + + // convert services dependencies to ids + for service in &mut config.services { + let mut dependencies = vec![]; + for dependency in &service.depends_on { + match services.find(|s| s.name == *dependency) { + Some(service) => { + dependencies.push(service.id.clone().unwrap()); + } + None => { + return Err(Error::new(format!("Service {} not found", dependency))); + } + } + } + service.dependencies = dependencies; + } + + for service in services.into_iter() { + cmd_tx.send(SuperviseurCommand::Load(service, config.project.clone()))?; + } + + cmd_tx.send(SuperviseurCommand::StartAll(config.project.clone()))?; + + futures::executor::block_on_stream(SimpleBroker::::subscribe()).next(); + + let mut stdout = vec![]; + for service in &config.services { + // loop while the file does not exist + while !Path::new(&service.stdout).exists() { + sleep(Duration::from_secs(2)); + } + let lines = read_lines(&service.stdout).unwrap(); + stdout.extend(lines); + } + Ok(stdout) + } +} diff --git a/src/graphql/schema/objects/service_configuration.rs b/src/graphql/schema/objects/service_configuration.rs new file mode 100644 index 0000000..8703aef --- /dev/null +++ b/src/graphql/schema/objects/service_configuration.rs @@ -0,0 +1,21 @@ +use async_graphql::InputObject; + +#[derive(InputObject)] +pub struct ServiceConfiguration { + pub name: String, + pub description: Option, + pub command: String, + pub depends_on: Option>, + pub working_directory: Option, + pub r#type: Option, + pub log_file: Option, + pub stderr_file: Option, + pub auto_restart: Option, + pub auto_start: Option, + pub env: Option>, + pub enable_docker: Option, + pub enable_flox: Option, + pub enable_nix: Option, + pub flox_enviroment: Option, + pub port: Option, +} diff --git a/src/lib.rs b/src/lib.rs index 150c911..46edefd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ pub mod api { depends_on: self.depends_on, command: self.command, r#type: self.r#type, + port: Some(self.port as u32), ..Default::default() } } @@ -45,6 +46,7 @@ pub mod api { depends_on: service.depends_on, command: service.command, r#type: service.r#type, + port: service.port.unwrap_or_default() as i32, ..Default::default() } } @@ -76,6 +78,7 @@ pub mod api { auto_restart: self.auto_restart, env, service_id: self.service_id, + port: Some(self.port as u32), ..Default::default() } } @@ -102,6 +105,7 @@ pub mod api { auto_restart: process.auto_restart, env, service_id: process.service_id, + port: process.port.unwrap_or_default() as i32, ..Default::default() } } diff --git a/src/main.rs b/src/main.rs index 841b4af..1cfd8dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,21 @@ use anyhow::Error; -use clap::{arg, Command}; +use clap::{arg, Command, SubCommand}; use superviseur::{ cmd::{ - build::execute_build, config::execute_config, init::execute_init, list::execute_list, - log::execute_log, new::execute_new, ps::execute_ps, restart::execute_restart, - start::execute_start, status::execute_status, stop::execute_stop, tail::execute_tail, + build::execute_build, + config::execute_config, + init::execute_init, + list::execute_list, + log::execute_log, + new::execute_new, + preview::execute_preview, + project::{execute_get_project, execute_list_projects}, + ps::execute_ps, + restart::execute_restart, + start::execute_start, + status::execute_status, + stop::execute_stop, + tail::execute_tail, ui::execute_ui, }, server, @@ -93,6 +104,25 @@ A simple process supervisor"#, .subcommand(Command::new("build") .arg(arg!([name] "The name of the service to build, if not specified, all services will be built")) .about("Build all services or a specific one")) + .subcommand( + Command::new("project") + .subcommand( + Command::new("details") + .arg(arg!( "The id of the project to get the details of")) + .about("Get the details of a project"), + ) + .subcommand( + Command::new("list") + .alias("ls") + .about("List all projects"), + ) + .about("Manage projects") + ) + .subcommand( + Command::new("preview") + .arg(arg!( "The name of the service to preview")) + .about("Open URL of a service in the browser"), + ) } #[tokio::main] @@ -155,6 +185,18 @@ async fn main() -> Result<(), Error> { let name = args.value_of("name"); execute_build(name).await?; } + Some(("project", args)) => match args.subcommand() { + Some(("details", args)) => { + let id = args.value_of("id").unwrap(); + execute_get_project(id).await?; + } + Some(("list", _)) => execute_list_projects().await?, + _ => SubCommand::with_name("project").print_help()?, + }, + Some(("preview", args)) => { + let name = args.value_of("name"); + execute_preview(name.unwrap()).await?; + } _ => cli().print_help()?, } Ok(()) diff --git a/src/server/control.rs b/src/server/control.rs index 8c8051f..02b63ea 100644 --- a/src/server/control.rs +++ b/src/server/control.rs @@ -99,6 +99,9 @@ impl ControlService for Control { let mut config: ConfigurationData = hcl::from_str(&config).map_err(|e| tonic::Status::internal(e.to_string()))?; + // get directory of the config file + config.context = Some(path.clone()); + let (project_id, is_new_config) = self.insert_config_and_get_project_id(path.clone(), config.clone()); let mut generator = Generator::default(); @@ -393,6 +396,7 @@ impl ControlService for Control { auto_restart: service.autorestart, stdout: service.stdout.clone(), stderr: service.stderr.clone(), + port: service.port, ..Default::default() }); Ok(Response::new(StatusResponse { diff --git a/src/server/mod.rs b/src/server/mod.rs index e6005ee..232a648 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -8,9 +8,9 @@ use std::{ use crate::{ api::superviseur::v1alpha1::{ control_service_server::ControlServiceServer, core_service_server::CoreServiceServer, - logging_service_server::LoggingServiceServer, + logging_service_server::LoggingServiceServer, project_service_server::ProjectServiceServer, }, - server::{control::Control, logging::Logging}, + server::{control::Control, logging::Logging, project::Project}, superviseur::{core::Superviseur, dependencies::DependencyGraph}, types::{configuration::Service, process::Process, BANNER, UNIX_SOCKET_PATH}, }; @@ -23,6 +23,7 @@ use tonic::transport::Server; pub mod control; pub mod core; pub mod logging; +pub mod project; pub async fn exec(port: u16, serve: bool) -> Result<(), Error> { let addr: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap(); @@ -92,9 +93,15 @@ pub async fn exec(port: u16, serve: bool) -> Result<(), Error> { cloned_project_map.clone(), )))) .add_service(tonic_web::enable(CoreServiceServer::new(core::Core::new( - cloned_cmd_tx, + cloned_cmd_tx.clone(), cloned_event_tx, cloned_superviseur, + cloned_processes.clone(), + cloned_config_map.clone(), + cloned_project_map.clone(), + )))) + .add_service(tonic_web::enable(ProjectServiceServer::new(Project::new( + cloned_cmd_tx, cloned_processes, cloned_config_map, cloned_project_map, @@ -123,9 +130,15 @@ pub async fn exec(port: u16, serve: bool) -> Result<(), Error> { project_map.clone(), )))) .add_service(tonic_web::enable(CoreServiceServer::new(core::Core::new( - cmd_tx, + cmd_tx.clone(), event_tx, superviseur, + processes.clone(), + config_map.clone(), + project_map.clone(), + )))) + .add_service(tonic_web::enable(ProjectServiceServer::new(Project::new( + cmd_tx, processes, config_map, project_map, diff --git a/src/server/project.rs b/src/server/project.rs new file mode 100644 index 0000000..18bfab1 --- /dev/null +++ b/src/server/project.rs @@ -0,0 +1,94 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use tokio::sync::mpsc; + +use crate::{ + api::objects::v1alpha1::Project as ProjectProto, + api::objects::v1alpha1::Service as ServiceProto, + api::superviseur::v1alpha1::{ + project_service_server::ProjectService, GetProjectRequest, GetProjectResponse, + ListProjectsRequest, ListProjectsResponse, + }, + superviseur::core::SuperviseurCommand, + types::{ + configuration::{ConfigurationData, Service}, + process::Process, + }, +}; + +pub struct Project { + cmd_tx: mpsc::UnboundedSender, + processes: Arc>>, + config_map: Arc>>, + project_map: Arc>>, +} + +impl Project { + pub fn new( + cmd_tx: mpsc::UnboundedSender, + processes: Arc>>, + config_map: Arc>>, + project_map: Arc>>, + ) -> Self { + Self { + cmd_tx, + processes, + config_map, + project_map, + } + } +} + +#[tonic::async_trait] +impl ProjectService for Project { + async fn list_projects( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + let config_map = self.config_map.lock().unwrap(); + + let projects = config_map + .iter() + .map(|(id, config)| ProjectProto { + id: id.clone(), + name: config.project.clone(), + context: config + .context + .as_ref() + .map(|x| x.to_string()) + .unwrap_or_default(), + ..Default::default() + }) + .collect(); + + Ok(tonic::Response::new(ListProjectsResponse { projects })) + } + + async fn get_project( + &self, + request: tonic::Request, + ) -> Result, tonic::Status> { + let request = request.into_inner(); + let id = request.id; + let config_map = self.config_map.lock().unwrap(); + let project = config_map.get(&id).map(|x| ProjectProto { + id: id.clone(), + name: x.project.clone(), + context: x + .context + .as_ref() + .map(|x| x.to_string()) + .unwrap_or_default(), + services: x + .services + .iter() + .map(|service| ServiceProto::from(service.clone())) + .collect(), + ..Default::default() + }); + Ok(tonic::Response::new(GetProjectResponse { project })) + } +} diff --git a/src/superviseur/core.rs b/src/superviseur/core.rs index 20a3502..2da99d0 100644 --- a/src/superviseur/core.rs +++ b/src/superviseur/core.rs @@ -210,6 +210,7 @@ impl SuperviseurInternal { process.auto_restart = service.autorestart; process.stdout = service.stdout; process.stderr = service.stderr; + process.port = service.port; return Ok(()); } @@ -335,6 +336,8 @@ impl SuperviseurInternal { .next() .ok_or(anyhow::anyhow!("Project {} not found", project))?; graph.start_services(); + let services = self.get_project_services(&project)?; + SimpleBroker::publish(AllServicesStarted { payload: services }); Ok(()) } diff --git a/src/superviseur/dependencies.rs b/src/superviseur/dependencies.rs index 0b75a2c..8954286 100644 --- a/src/superviseur/dependencies.rs +++ b/src/superviseur/dependencies.rs @@ -31,7 +31,7 @@ struct Vertex { autostart: bool, autorestart: bool, namespace: Option, - port: Option, + port: Option, stdout: String, stderr: String, driver: Box, diff --git a/src/superviseur/drivers/nix/mod.rs b/src/superviseur/drivers/nix/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/types/configuration.rs b/src/types/configuration.rs index a80a3cd..878e39c 100644 --- a/src/types/configuration.rs +++ b/src/types/configuration.rs @@ -27,7 +27,7 @@ pub struct Service { pub autorestart: bool, pub namespace: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub port: Option, + pub port: Option, pub stdout: String, pub stderr: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -41,6 +41,7 @@ pub struct Service { #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ConfigurationData { pub project: String, + pub context: Option, pub services: Vec, } diff --git a/src/types/process.rs b/src/types/process.rs index 7f1750e..4ce9654 100644 --- a/src/types/process.rs +++ b/src/types/process.rs @@ -102,8 +102,6 @@ pub struct Process { #[tabled(skip)] pub working_dir: String, #[tabled(skip)] - pub port: Option, - #[tabled(skip)] pub env: HashMap, #[tabled(skip)] pub project: String, @@ -117,6 +115,8 @@ pub struct Process { pub stderr: String, #[tabled(rename = "SERVICE_ID")] pub service_id: String, + #[tabled(rename = "PORT", display_with = "display_port")] + pub port: Option, } fn display_option(value: &Option) -> String { @@ -156,3 +156,10 @@ pub fn format_duration(duration: Duration) -> String { let days = duration.num_days(); format!("{} {} ago", days, if days == 1 { "day" } else { "days" }) } + +fn display_port(port: &Option) -> String { + match port { + Some(port) => port.clone().to_string(), + None => "-".to_string(), + } +} diff --git a/src/types/service.rs b/src/types/service.rs index 4074ebc..b30ee3d 100644 --- a/src/types/service.rs +++ b/src/types/service.rs @@ -16,6 +16,8 @@ pub struct Service { pub description: String, #[tabled(skip)] pub depends_on: Vec, + #[tabled(rename = "PORT", display_with = "display_port")] + pub port: Option, } fn display_command(command: &str) -> String { @@ -25,3 +27,10 @@ fn display_command(command: &str) -> String { format!("\"{}\"", command.to_string()) } } + +fn display_port(port: &Option) -> String { + match port { + Some(port) => port.clone().to_string(), + None => "-".to_string(), + } +} diff --git a/src/util.rs b/src/util.rs index ad5f9fb..bd79219 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,9 @@ -use std::path::PathBuf; - use anyhow::Error; +use std::{ + fs::File, + io::{BufRead, BufReader}, + path::PathBuf, +}; pub fn convert_dir_path_to_absolute_path(dir: &str, current_dir: &str) -> Result { let current_dir = PathBuf::from(current_dir); @@ -15,3 +18,16 @@ pub fn convert_dir_path_to_absolute_path(dir: &str, current_dir: &str) -> Result } Err(Error::msg("Invalid directory")) } + +pub fn read_lines(path: &str) -> Result, Error> { + let file = File::open(path).map_err(|e| Error::msg(e.to_string()))?; + + let reader = BufReader::new(file); + + let mut lines = vec![]; + for line in reader.lines() { + let line = line.map_err(|e| Error::msg(e.to_string()))?; + lines.push(line); + } + Ok(lines) +}