From f299caadd260237c1c9fbe9da0ec2a9bb45a5d97 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Fri, 4 Jul 2025 01:14:25 -0700 Subject: [PATCH 01/21] feat: wip - new templates working --- Cargo.lock | 502 +-- Cargo.toml | 4 +- README.md | 174 +- packages/ftl-cli/build.rs | 46 +- .../examples/javascript-test/.gitignore | 6 + .../ftl-cli/examples/javascript-test/Makefile | 20 + .../examples/javascript-test/README.md | 85 + .../ftl-cli/examples/javascript-test/ftl.toml | 24 + .../javascript-test/handler/package-lock.json | 3678 ++++++++++++++++ .../javascript-test/handler/package.json | 21 + .../javascript-test/handler/src/index.js | 104 + .../handler/test/handler.test.js | 38 + .../javascript-test/handler/vitest.config.js | 13 + .../javascript-test/handler/wit/mcp.wit | 82 + .../examples/javascript-test/spin.toml | 19 + .../test-templates/rust-test/.gitignore | 5 + .../test-templates/rust-test/Makefile | 16 + .../test-templates/rust-test/README.md | 95 + .../test-templates/rust-test/ftl.toml | 23 + .../rust-test/handler/Cargo.toml | 24 + .../rust-test/handler/src/lib.rs | 110 + .../rust-test/handler/wit/mcp.wit | 82 + .../test-templates/rust-test/spin.toml | 19 + .../rust-test/test-call-object.json | 9 + .../test-templates/rust-test/test-call.json | 9 + .../rust-test/test-mcp-spec.json | 11 + .../examples/typescript-test/.gitignore | 7 + .../ftl-cli/examples/typescript-test/Makefile | 20 + .../examples/typescript-test/README.md | 89 + .../ftl-cli/examples/typescript-test/ftl.toml | 24 + .../typescript-test/handler/package-lock.json | 3711 +++++++++++++++++ .../typescript-test/handler/package.json | 24 + .../typescript-test/handler/src/index.ts | 82 + .../handler/test/handler.test.ts | 44 + .../typescript-test/handler}/tsconfig.json | 23 +- .../typescript-test/handler/vitest.config.ts | 13 + .../typescript-test/handler/wit/mcp.wit | 82 + .../examples/typescript-test/spin.toml | 19 + packages/ftl-cli/src/commands/build.rs | 89 +- packages/ftl-cli/src/commands/new.rs | 77 +- packages/ftl-cli/src/commands/toolkit.rs | 7 +- packages/ftl-cli/src/commands/validate.rs | 137 +- packages/ftl-cli/src/language/javascript.rs | 131 +- packages/ftl-cli/src/language/rust.rs | 13 +- packages/ftl-cli/src/language/typescript.rs | 135 +- packages/ftl-cli/src/main.rs | 1 - packages/ftl-cli/src/manifest.rs | 2 + packages/ftl-cli/src/templates.rs | 345 -- .../templates/javascript/content/.gitignore | 6 + .../src/templates/javascript/content/Makefile | 25 + .../templates/javascript/content/README.md | 85 + .../src/templates/javascript/content/ftl.toml | 24 + .../javascript/content/handler/package.json | 21 + .../javascript/content/handler/src/index.js | 104 + .../content/handler/test/handler.test.js | 38 + .../content/handler/vitest.config.js | 13 + .../javascript/content/handler/wit/mcp.wit | 82 + .../templates/javascript/content/spin.toml | 20 + .../src/templates/javascript/ftl.toml.hbs | 11 - .../src/templates/javascript/index.js.hbs | 48 - .../metadata/snippets/component.txt | 12 + .../javascript/metadata/spin-template.toml | 13 + .../src/templates/javascript/package.json.hbs | 24 - .../src/templates/javascript/tool.test.js.hbs | 33 - .../src/templates/javascript/vitest.config.js | 11 - .../templates/javascript/webpack.config.js | 32 - .../templates/mcp-server/content/.gitignore | 21 + .../src/templates/mcp-server/content/Makefile | 37 + .../templates/mcp-server/content/README.md | 109 + .../mcp-server/content/handler/Cargo.toml | 16 + .../mcp-server/content/handler/src/lib.rs | 109 + .../mcp-server/content/handler/wit/mcp.wit | 82 + .../templates/mcp-server/content/spin.toml | 19 + .../metadata/snippets/component.txt | 11 + .../mcp-server/metadata/spin-template.toml | 16 + .../src/templates/rust/content/.gitignore | 5 + .../src/templates/rust/content/Makefile | 21 + .../src/templates/rust/content/README.md | 95 + .../src/templates/rust/content/ftl.toml | 23 + .../templates/rust/content/handler/Cargo.toml | 27 + .../templates/rust/content/handler/src/lib.rs | 110 + .../rust/content/handler/wit/mcp.wit | 82 + .../src/templates/rust/content/spin.toml | 20 + .../rust/metadata/snippets/component.txt | 12 + .../rust/metadata/spin-template.toml | 13 + .../templates/typescript/content/.gitignore | 7 + .../src/templates/typescript/content/Makefile | 25 + .../templates/typescript/content/README.md | 89 + .../src/templates/typescript/content/ftl.toml | 24 + .../typescript/content/handler/package.json | 24 + .../typescript/content/handler/src/index.ts | 83 + .../content/handler/test/handler.test.ts | 44 + .../typescript/content/handler/tsconfig.json | 26 + .../content/handler/vitest.config.ts | 13 + .../typescript/content/handler/wit/mcp.wit | 82 + .../templates/typescript/content/spin.toml | 20 + .../src/templates/typescript/ftl.toml.hbs | 11 - .../src/templates/typescript/index.ts.hbs | 48 - .../metadata/snippets/component.txt | 12 + .../typescript/metadata/spin-template.toml | 13 + .../src/templates/typescript/package.json.hbs | 27 - .../src/templates/typescript/tool.test.ts.hbs | 33 - .../src/templates/typescript/tsconfig.json | 17 - .../src/templates/typescript/vitest.config.ts | 11 - .../templates/typescript/webpack.config.js | 41 - packages/ftl-sdk-rs/Cargo.toml | 23 - packages/ftl-sdk-rs/README.md | 65 - packages/ftl-sdk-rs/src/gateway.rs | 326 -- packages/ftl-sdk-rs/src/gateway_test.rs | 136 - packages/ftl-sdk-rs/src/lib.rs | 30 - packages/ftl-sdk-rs/src/mcp.rs | 167 - packages/ftl-sdk-rs/src/server.rs | 263 -- packages/ftl-sdk-rs/src/spin.rs | 205 - packages/ftl-sdk-rs/src/tool.rs | 76 - packages/ftl-sdk-rs/src/types.rs | 215 - packages/ftl-sdk-ts/README.md | 42 - packages/ftl-sdk-ts/package-lock.json | 3041 -------------- packages/ftl-sdk-ts/package.json | 46 - packages/ftl-sdk-ts/src/index.ts | 82 - packages/ftl-sdk-ts/src/mcp-server.ts | 170 - packages/ftl-sdk-ts/src/tool.ts | 83 - packages/ftl-sdk-ts/src/types.ts | 101 - packages/ftl-sdk-ts/tests/tool.test.ts | 124 - packages/ftl-sdk-ts/tests/types.test.ts | 69 - packages/ftl-sdk-ts/vitest.config.js | 7 - 125 files changed, 10683 insertions(+), 6962 deletions(-) create mode 100644 packages/ftl-cli/examples/javascript-test/.gitignore create mode 100644 packages/ftl-cli/examples/javascript-test/Makefile create mode 100644 packages/ftl-cli/examples/javascript-test/README.md create mode 100644 packages/ftl-cli/examples/javascript-test/ftl.toml create mode 100644 packages/ftl-cli/examples/javascript-test/handler/package-lock.json create mode 100644 packages/ftl-cli/examples/javascript-test/handler/package.json create mode 100644 packages/ftl-cli/examples/javascript-test/handler/src/index.js create mode 100644 packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js create mode 100644 packages/ftl-cli/examples/javascript-test/handler/vitest.config.js create mode 100644 packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/examples/javascript-test/spin.toml create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/.gitignore create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/Makefile create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/README.md create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/ftl.toml create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/spin.toml create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/test-call.json create mode 100644 packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json create mode 100644 packages/ftl-cli/examples/typescript-test/.gitignore create mode 100644 packages/ftl-cli/examples/typescript-test/Makefile create mode 100644 packages/ftl-cli/examples/typescript-test/README.md create mode 100644 packages/ftl-cli/examples/typescript-test/ftl.toml create mode 100644 packages/ftl-cli/examples/typescript-test/handler/package-lock.json create mode 100644 packages/ftl-cli/examples/typescript-test/handler/package.json create mode 100644 packages/ftl-cli/examples/typescript-test/handler/src/index.ts create mode 100644 packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts rename packages/{ftl-sdk-ts => ftl-cli/examples/typescript-test/handler}/tsconfig.json (57%) create mode 100644 packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts create mode 100644 packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/examples/typescript-test/spin.toml delete mode 100644 packages/ftl-cli/src/templates.rs create mode 100644 packages/ftl-cli/src/templates/javascript/content/.gitignore create mode 100644 packages/ftl-cli/src/templates/javascript/content/Makefile create mode 100644 packages/ftl-cli/src/templates/javascript/content/README.md create mode 100644 packages/ftl-cli/src/templates/javascript/content/ftl.toml create mode 100644 packages/ftl-cli/src/templates/javascript/content/handler/package.json create mode 100644 packages/ftl-cli/src/templates/javascript/content/handler/src/index.js create mode 100644 packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js create mode 100644 packages/ftl-cli/src/templates/javascript/content/handler/vitest.config.js create mode 100644 packages/ftl-cli/src/templates/javascript/content/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/src/templates/javascript/content/spin.toml delete mode 100644 packages/ftl-cli/src/templates/javascript/ftl.toml.hbs delete mode 100644 packages/ftl-cli/src/templates/javascript/index.js.hbs create mode 100644 packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt create mode 100644 packages/ftl-cli/src/templates/javascript/metadata/spin-template.toml delete mode 100644 packages/ftl-cli/src/templates/javascript/package.json.hbs delete mode 100644 packages/ftl-cli/src/templates/javascript/tool.test.js.hbs delete mode 100644 packages/ftl-cli/src/templates/javascript/vitest.config.js delete mode 100644 packages/ftl-cli/src/templates/javascript/webpack.config.js create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/.gitignore create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/Makefile create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/README.md create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/src/templates/mcp-server/content/spin.toml create mode 100644 packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt create mode 100644 packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml create mode 100644 packages/ftl-cli/src/templates/rust/content/.gitignore create mode 100644 packages/ftl-cli/src/templates/rust/content/Makefile create mode 100644 packages/ftl-cli/src/templates/rust/content/README.md create mode 100644 packages/ftl-cli/src/templates/rust/content/ftl.toml create mode 100644 packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml create mode 100644 packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs create mode 100644 packages/ftl-cli/src/templates/rust/content/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/src/templates/rust/content/spin.toml create mode 100644 packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt create mode 100644 packages/ftl-cli/src/templates/rust/metadata/spin-template.toml create mode 100644 packages/ftl-cli/src/templates/typescript/content/.gitignore create mode 100644 packages/ftl-cli/src/templates/typescript/content/Makefile create mode 100644 packages/ftl-cli/src/templates/typescript/content/README.md create mode 100644 packages/ftl-cli/src/templates/typescript/content/ftl.toml create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/package.json create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/vitest.config.ts create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit create mode 100644 packages/ftl-cli/src/templates/typescript/content/spin.toml delete mode 100644 packages/ftl-cli/src/templates/typescript/ftl.toml.hbs delete mode 100644 packages/ftl-cli/src/templates/typescript/index.ts.hbs create mode 100644 packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt create mode 100644 packages/ftl-cli/src/templates/typescript/metadata/spin-template.toml delete mode 100644 packages/ftl-cli/src/templates/typescript/package.json.hbs delete mode 100644 packages/ftl-cli/src/templates/typescript/tool.test.ts.hbs delete mode 100644 packages/ftl-cli/src/templates/typescript/tsconfig.json delete mode 100644 packages/ftl-cli/src/templates/typescript/vitest.config.ts delete mode 100644 packages/ftl-cli/src/templates/typescript/webpack.config.js delete mode 100644 packages/ftl-sdk-rs/Cargo.toml delete mode 100644 packages/ftl-sdk-rs/README.md delete mode 100644 packages/ftl-sdk-rs/src/gateway.rs delete mode 100644 packages/ftl-sdk-rs/src/gateway_test.rs delete mode 100644 packages/ftl-sdk-rs/src/lib.rs delete mode 100644 packages/ftl-sdk-rs/src/mcp.rs delete mode 100644 packages/ftl-sdk-rs/src/server.rs delete mode 100644 packages/ftl-sdk-rs/src/spin.rs delete mode 100644 packages/ftl-sdk-rs/src/tool.rs delete mode 100644 packages/ftl-sdk-rs/src/types.rs delete mode 100644 packages/ftl-sdk-ts/README.md delete mode 100644 packages/ftl-sdk-ts/package-lock.json delete mode 100644 packages/ftl-sdk-ts/package.json delete mode 100644 packages/ftl-sdk-ts/src/index.ts delete mode 100644 packages/ftl-sdk-ts/src/mcp-server.ts delete mode 100644 packages/ftl-sdk-ts/src/tool.ts delete mode 100644 packages/ftl-sdk-ts/src/types.ts delete mode 100644 packages/ftl-sdk-ts/tests/tool.test.ts delete mode 100644 packages/ftl-sdk-ts/tests/types.test.ts delete mode 100644 packages/ftl-sdk-ts/vitest.config.js diff --git a/Cargo.lock b/Cargo.lock index 7d24df26..55d6ea39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,21 +37,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.6.19" @@ -117,29 +102,12 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "backtrace" version = "0.3.75" @@ -268,20 +236,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - [[package]] name = "cipher" version = "0.4.4" @@ -320,10 +274,10 @@ version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -465,7 +419,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.104", + "syn", ] [[package]] @@ -476,7 +430,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -502,7 +456,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -523,7 +477,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -533,7 +487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.104", + "syn", ] [[package]] @@ -589,7 +543,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -728,33 +682,6 @@ dependencies = [ "zip", ] -[[package]] -name = "ftl-sdk-rs" -version = "0.0.18" -dependencies = [ - "serde", - "serde_json", - "spin-sdk", - "talc", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -762,7 +689,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -771,17 +697,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.31" @@ -796,7 +711,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -817,7 +732,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -911,15 +825,6 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -1062,30 +967,6 @@ dependencies = [ "windows-registry", ] -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "2.0.0" @@ -1172,12 +1053,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - [[package]] name = "ident_case" version = "1.0.1" @@ -1232,7 +1107,6 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", - "serde", ] [[package]] @@ -1351,12 +1225,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - [[package]] name = "libc" version = "0.2.174" @@ -1392,16 +1260,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.27" @@ -1549,15 +1407,6 @@ dependencies = [ "num-modular", ] -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "object" version = "0.36.7" @@ -1602,7 +1451,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1682,7 +1531,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -1879,16 +1728,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "routefinder" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0971d3c8943a6267d6bd0d782fdc4afa7593e7381a92a3df950ff58897e066b5" -dependencies = [ - "smartcow", - "smartstring", -] - [[package]] name = "rustc-demangle" version = "0.1.25" @@ -1984,12 +1823,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "security-framework" version = "2.11.1" @@ -2039,7 +1872,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2145,26 +1978,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "smartcow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" -dependencies = [ - "smartstring", -] - -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - [[package]] name = "socket2" version = "0.5.10" @@ -2175,74 +1988,12 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spdx" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193" -dependencies = [ - "smallvec", -] - -[[package]] -name = "spin-executor" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d11baf86ca52100e8742ea43d2c342cf4d75b94f8a85454cf44fd108cdd71d5" -dependencies = [ - "futures", - "once_cell", - "wit-bindgen", -] - -[[package]] -name = "spin-macro" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "988ffe27470862bf28fe9b4f0268361040d4732cd86bcaebe45aa3d3b3e3d896" -dependencies = [ - "anyhow", - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "spin-sdk" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f845e889d8431740806e04704ac5aa619466dfaef626f3c15952ecf823913e01" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "chrono", - "form_urlencoded", - "futures", - "http", - "once_cell", - "routefinder", - "serde", - "serde_json", - "spin-executor", - "spin-macro", - "thiserror 1.0.69", - "wit-bindgen", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -2255,17 +2006,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.104" @@ -2294,7 +2034,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2318,15 +2058,6 @@ dependencies = [ "libc", ] -[[package]] -name = "talc" -version = "4.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ae828aa394de34c7de08f522d1b86bd1c182c668d27da69caadda00590f26d" -dependencies = [ - "lock_api", -] - [[package]] name = "tar" version = "0.4.44" @@ -2377,7 +2108,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2388,7 +2119,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2454,7 +2185,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2595,7 +2326,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -2661,24 +2392,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-width" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unit-prefix" version = "0.5.1" @@ -2788,7 +2507,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn", "wasm-bindgen-shared", ] @@ -2823,7 +2542,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2837,40 +2556,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.38.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.41.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f97a5d8318f908dded23594188a90bcd09365986b1163e66d70170e5287ae" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-metadata" -version = "0.10.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18ebaa7bd0f9e7a5e5dd29b9a998acf21c4abed74265524dd7e85934597bfb10" -dependencies = [ - "anyhow", - "indexmap", - "serde", - "serde_derive", - "serde_json", - "spdx", - "wasm-encoder 0.41.2", - "wasmparser 0.121.2", -] - [[package]] name = "wasm-streams" version = "0.4.2" @@ -2884,27 +2569,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.118.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f1154f1ab868e2a01d9834a805faca7bf8b50d041b4ca714d005d0dab1c50c" -dependencies = [ - "indexmap", - "semver", -] - -[[package]] -name = "wasmparser" -version = "0.121.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" -dependencies = [ - "bitflags 2.9.1", - "indexmap", - "semver", -] - [[package]] name = "web-sys" version = "0.3.77" @@ -2968,41 +2632,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - [[package]] name = "windows-link" version = "0.1.3" @@ -3274,27 +2903,6 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" -[[package]] -name = "wit-bindgen" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b76f1d099678b4f69402a421e888bbe71bf20320c2f3f3565d0e7484dbe5bc20" -dependencies = [ - "bitflags 2.9.1", - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d55e1a488af2981fb0edac80d8d20a51ac36897a1bdef4abde33c29c1b6d0d" -dependencies = [ - "anyhow", - "wit-component", - "wit-parser", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -3304,70 +2912,6 @@ dependencies = [ "bitflags 2.9.1", ] -[[package]] -name = "wit-bindgen-rust" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01ff9cae7bf5736750d94d91eb8a49f5e3a04aff1d1a3218287d9b2964510f8" -dependencies = [ - "anyhow", - "heck 0.4.1", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804a98e2538393d47aa7da65a7348116d6ff403b426665152b70a168c0146d49" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn 2.0.104", - "wit-bindgen-core", - "wit-bindgen-rust", - "wit-component", -] - -[[package]] -name = "wit-component" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a35a2a9992898c9d27f1664001860595a4bc99d32dd3599d547412e17d7e2" -dependencies = [ - "anyhow", - "bitflags 2.9.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.38.1", - "wasm-metadata", - "wasmparser 0.118.2", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316b36a9f0005f5aa4b03c39bc3728d045df136f8c13a73b7db4510dec725e08" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", -] - [[package]] name = "writeable" version = "0.6.1" @@ -3413,7 +2957,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "synstructure", ] @@ -3434,7 +2978,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", "synstructure", ] @@ -3455,7 +2999,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -3488,7 +3032,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 79b9b510..bb873ecc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = ["packages/ftl-cli", "packages/ftl-sdk-rs"] -exclude = ["demo_hash", "toolkit-*"] +members = ["packages/ftl-cli"] +exclude = ["demo_hash", "toolkit-*", "packages/ftl-cli/src/templates/**"] resolver = "2" # Don't build CLI for wasm target diff --git a/README.md b/README.md index 6c9cc5a3..6125ab01 100644 --- a/README.md +++ b/README.md @@ -36,38 +36,50 @@ ftl new my-tool --language rust This creates a new directory with: - `ftl.toml` - Tool manifest -- `Cargo.toml` - Rust dependencies -- `src/lib.rs` - Tool implementation +- `spin.toml` - Spin application configuration +- `handler/` - Tool implementation directory + - `Cargo.toml` - Rust dependencies + - `src/lib.rs` - Tool implementation + - `wit/mcp.wit` - WebAssembly interface definition ```rust -use ftl_sdk_rs::prelude::*; +use serde_json::{json, Value}; -#[derive(Clone)] -struct MyTool; - -impl Tool for MyTool { - fn name(&self) -> &'static str { "my-tool" } - fn description(&self) -> &'static str { "My tool description" } - - fn input_schema(&self) -> serde_json::Value { - json!({ - "type": "object", - "properties": { - "input": {"type": "string"} - }, - "required": ["input"] - }) +wit_bindgen::generate!({ + world: "mcp-handler", + path: "./wit", + exports: { + "component:mcp/handler": Component + } +}); + +use exports::component::mcp::handler::{Guest, Tool, ToolResult, Error as McpError}; + +struct Component; + +impl Guest for Component { + fn list_tools() -> Vec { + vec![Tool { + name: "my_tool".to_string(), + description: "My tool description".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "input": { "type": "string" } + }, + "required": ["input"] + }).to_string(), + }] } - fn call(&self, args: &serde_json::Value) -> Result { - let input = args["input"].as_str() - .ok_or(ToolError::InvalidArguments("input required".into()))?; - - Ok(ToolResult::text(format!("Processed: {}", input))) + fn call_tool(name: String, arguments: String) -> ToolResult { + let args: Value = serde_json::from_str(&arguments).unwrap(); + let input = args["input"].as_str().unwrap_or("No input"); + ToolResult::Text(format!("Processed: {}", input)) } + + // ... other required methods } - -ftl_sdk_rs::ftl_mcp_server!(MyTool); ``` @@ -80,37 +92,44 @@ ftl new my-tool --language typescript This creates a new directory with: - `ftl.toml` - Tool manifest -- `package.json` - Node dependencies -- `tsconfig.json` - TypeScript configuration -- `src/index.ts` - Tool implementation +- `spin.toml` - Spin application configuration +- `handler/` - Tool implementation directory + - `package.json` - Node dependencies + - `tsconfig.json` - TypeScript configuration + - `src/index.ts` - Tool implementation + - `wit/mcp.wit` - WebAssembly interface definition ```typescript -import { Tool, ToolResult, ToolError } from '@fastertools/ftl-sdk-ts'; - -export default class MyTool extends Tool { - get name(): string { return 'my-tool'; } - get description(): string { return 'My tool description'; } - - get inputSchema() { +import { handler } from '../generated/mcp'; + +const { Tool, ToolResult } = handler; + +export const Handler = { + listTools(): handler.Tool[] { + return [{ + name: 'my-tool', + description: 'My tool description', + inputSchema: JSON.stringify({ + type: 'object', + properties: { + input: { type: 'string' } + }, + required: ['input'] + }) + }]; + }, + + callTool(name: string, argumentsStr: string): handler.ToolResult { + const args = JSON.parse(argumentsStr); + const input = args.input || 'No input'; return { - type: 'object', - properties: { - input: { type: 'string' } - }, - required: ['input'] + tag: 'text', + val: `Processed: ${input}` }; } - execute(args: { input: string }): ToolResult { - const { input } = args; - - if (!input) { - throw ToolError.invalidArguments('input required'); - } - - return ToolResult.text(`Processed: ${input}`); - } -} + // ... other required methods +}; ``` @@ -126,36 +145,43 @@ ftl new my-tool --language javascript This creates a new directory with: - `ftl.toml` - Tool manifest -- `package.json` - Node dependencies -- `src/index.js` - Tool implementation +- `spin.toml` - Spin application configuration +- `handler/` - Tool implementation directory + - `package.json` - Node dependencies + - `src/index.js` - Tool implementation + - `wit/mcp.wit` - WebAssembly interface definition ```javascript -import { Tool, ToolResult, ToolError } from '@fastertools/ftl-sdk-ts'; - -export default class MyTool extends Tool { - get name() { return 'my-tool'; } - get description() { return 'My tool description'; } - - get inputSchema() { +import { handler } from '../generated/mcp.js'; + +const { Tool, ToolResult } = handler; + +export const Handler = { + listTools() { + return [{ + name: 'my-tool', + description: 'My tool description', + inputSchema: JSON.stringify({ + type: 'object', + properties: { + input: { type: 'string' } + }, + required: ['input'] + }) + }]; + }, + + callTool(name, argumentsStr) { + const args = JSON.parse(argumentsStr); + const input = args.input || 'No input'; return { - type: 'object', - properties: { - input: { type: 'string' } - }, - required: ['input'] + tag: 'text', + val: `Processed: ${input}` }; } - execute(args) { - const { input } = args; - - if (!input) { - throw ToolError.invalidArguments('input required'); - } - - return ToolResult.text(`Processed: ${input}`); - } -} + // ... other required methods +}; ``` diff --git a/packages/ftl-cli/build.rs b/packages/ftl-cli/build.rs index c21c6ec8..829e6dd4 100644 --- a/packages/ftl-cli/build.rs +++ b/packages/ftl-cli/build.rs @@ -1,44 +1,4 @@ -use std::{env, fs, path::Path}; - fn main() { - println!("cargo:rerun-if-changed=../../Cargo.toml"); - println!("cargo:rerun-if-changed=../ftl-sdk-ts/package.json"); - - // Read ftl-sdk-rs version from workspace Cargo.toml - let workspace_toml_path = Path::new("../../Cargo.toml"); - let ftl_sdk_rs_version = if workspace_toml_path.exists() { - let content = fs::read_to_string(workspace_toml_path).unwrap(); - let workspace_toml: toml::Value = toml::from_str(&content).unwrap(); - - workspace_toml - .get("workspace") - .and_then(|w| w.get("package")) - .and_then(|p| p.get("version")) - .and_then(|v| v.as_str()) - .unwrap_or("0.0.9") - .to_string() - } else { - // Fallback for when building outside the workspace - env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.9".to_string()) - }; - - // Read @fastertools/ftl-sdk-ts version from package.json - let sdk_ts_package_json = Path::new("../ftl-sdk-ts/package.json"); - let ftl_sdk_ts_version = if sdk_ts_package_json.exists() { - let content = fs::read_to_string(sdk_ts_package_json).unwrap(); - let package_json: serde_json::Value = serde_json::from_str(&content).unwrap(); - - package_json - .get("version") - .and_then(|v| v.as_str()) - .unwrap() - .to_string() - } else { - // Use same version as Rust SDK as fallback - ftl_sdk_rs_version.clone() - }; - - // Set environment variables that will be available at compile time - println!("cargo:rustc-env=FTL_SDK_RS_VERSION={ftl_sdk_rs_version}"); - println!("cargo:rustc-env=FTL_SDK_TS_VERSION={ftl_sdk_ts_version}"); -} + // Empty build script for now + // Can be used in the future for other build-time operations +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/.gitignore b/packages/ftl-cli/examples/javascript-test/.gitignore new file mode 100644 index 00000000..a418fe81 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.ftl/ +*.wasm +.spin/ +handler/src/generated/ +handler.wasm \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/Makefile b/packages/ftl-cli/examples/javascript-test/Makefile new file mode 100644 index 00000000..d58450ec --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/Makefile @@ -0,0 +1,20 @@ +.PHONY: build test clean install + +install: + cd handler && npm install + +build: install + cd handler && npm run build + +test: install + cd handler && npm test + +clean: + cd handler && npm run clean + cd handler && rm -rf node_modules + +serve: + ftl serve + +deploy: + ftl deploy \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/README.md b/packages/ftl-cli/examples/javascript-test/README.md new file mode 100644 index 00000000..722306a1 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/README.md @@ -0,0 +1,85 @@ +# javascript-test + +An MCP tool written in JavaScript + +## Structure + +This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. + +- `handler/` - The JavaScript implementation of your MCP handler +- `ftl.toml` - FTL configuration file +- `spin.toml` - Spin application manifest + +## Development + +### Prerequisites + +- Node.js >= 20.0.0 +- FTL CLI + +### Building + +```bash +ftl build +# or +make build +``` + +### Testing + +```bash +ftl test +# or +make test +``` + +### Running Locally + +```bash +ftl serve +# or +make serve +``` + +The tool will be available at `http://localhost:3000/mcp` + +### Example Usage + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "javascript_test", + "arguments": { + "input": "Hello, world!" + } + }, + "id": 1 + }' +``` + +## Deployment + +```bash +ftl deploy +# or +make deploy +``` + +## Implementing Your Tool + +Edit `handler/src/index.js` to implement your tool's functionality: + +1. Modify `listTools()` to define your tools +2. Implement the tool logic in `callTool()` +3. Optionally implement resources and prompts + +## Configuration + +Edit `ftl.toml` to configure: +- Allowed external hosts +- Build optimization flags +- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/ftl.toml b/packages/ftl-cli/examples/javascript-test/ftl.toml new file mode 100644 index 00000000..6ee30115 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/ftl.toml @@ -0,0 +1,24 @@ +[tool] +name = "javascript-test" +version = "0.1.0" +description = "An MCP tool written in JavaScript" + +[build] +# Build commands are run from the handler directory +commands = [ + "cd handler && npm install", + "cd handler && npm run build" +] + +[optimization] +# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test +flags = [ + "-O4", + "-Oz", +] + +[runtime] +# List of external hosts this tool is allowed to make HTTP requests to. +# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). +# Leave empty to deny all external requests. +allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/package-lock.json b/packages/ftl-cli/examples/javascript-test/handler/package-lock.json new file mode 100644 index 00000000..b7a519d1 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/handler/package-lock.json @@ -0,0 +1,3678 @@ +{ + "name": "javascript-test-handler", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "javascript-test-handler", + "version": "0.1.0", + "devDependencies": { + "@bytecodealliance/jco": "^1.8.1", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@bytecodealliance/componentize-js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/componentize-js/-/componentize-js-0.17.0.tgz", + "integrity": "sha512-FDgO5UPipfjyq5OghSB4JW313LkQJK3Sl647WH1jvIuYAyCq1j+bMt+Q66c3UF6IVs6PneNTGfGSjYgzID/k0w==", + "dev": true, + "workspaces": [ + "." + ], + "dependencies": { + "@bytecodealliance/jco": "^1.9.1", + "@bytecodealliance/weval": "^0.3.3", + "@bytecodealliance/wizer": "^7.0.5", + "es-module-lexer": "^1.6.0" + } + }, + "node_modules/@bytecodealliance/jco": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.11.3.tgz", + "integrity": "sha512-Ynv0GDj9iAp+cS03UGg+ss4uo/Hx5e4FyjcFDBfkRt9qJsqbL2AnPqgO/ORNlSXg9zu9PTNC0uGgE7/Vklcn/w==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/componentize-js": "^0.17.0", + "@bytecodealliance/preview2-shim": "^0.17.2", + "binaryen": "^123.0.0", + "chalk-template": "^1", + "commander": "^12", + "mkdirp": "^3", + "ora": "^8", + "terser": "^5" + }, + "bin": { + "jco": "src/jco.js" + } + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.2.tgz", + "integrity": "sha512-mNm/lblgES8UkVle8rGImXOz4TtL3eU3inHay/7TVchkKrb/lgcVvTK0+VAw8p5zQ0rgQsXm1j5dOlAAd+MeoA==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/weval": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@bytecodealliance/weval/-/weval-0.3.4.tgz", + "integrity": "sha512-+GCKtZXhPj7qyDl5pR0F3PTEk8tWINV8dv1tUbKMjMXvqjHIkvzalkWo5ZL2kCKwh8Bwn8rWpSmyvRC/Nlu9nQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/lzma": "^1.1.2", + "decompress": "^4.2.1", + "decompress-tar": "^4.1.1", + "decompress-unzip": "^4.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@bytecodealliance/wizer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-7.0.5.tgz", + "integrity": "sha512-xIbLzKxmUNaPwDWorcGtdxh1mcgDiXI8fe9KiDaSICKfCl9VtUKVyXIc3ix+VpwFczBbdhek+TlMiiCf+9lpOQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wizer": "wizer.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@bytecodealliance/wizer-darwin-arm64": "7.0.5", + "@bytecodealliance/wizer-darwin-x64": "7.0.5", + "@bytecodealliance/wizer-linux-arm64": "7.0.5", + "@bytecodealliance/wizer-linux-s390x": "7.0.5", + "@bytecodealliance/wizer-linux-x64": "7.0.5", + "@bytecodealliance/wizer-win32-x64": "7.0.5" + } + }, + "node_modules/@bytecodealliance/wizer-darwin-arm64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-7.0.5.tgz", + "integrity": "sha512-Tp0SgVQR568SVPvSfyWDT00yL4ry/w9FS2qy8ZwaP0EauYyjFSZojj6mESX6x9fpYkEnQdprgfdvhw5h1hTwCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "wizer-darwin-arm64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-darwin-x64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-7.0.5.tgz", + "integrity": "sha512-HYmG5Q9SpQJnqR7kimb5J3VAh6E62b30GLG/E+6doS/UwNhSpSmYjaggVfuJvgFDbUxsnD1l36qZny0xMwxikA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "wizer-darwin-x64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-arm64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-7.0.5.tgz", + "integrity": "sha512-01qqaiIWrYXPt2bjrfiluSSOmUL/PMjPtJlYa/XqZgK75g3RVn3sRkSflwoCXtXMRbHdb03qNrJ9w81+F17kvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-arm64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-s390x": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-7.0.5.tgz", + "integrity": "sha512-smGfD4eJou81g6yDlV7MCRoKgKlqd4SQL00pHxQGrNfUPnfYKhZ4z80N9J9T2B++uo2FM14BFilsRrV5UevKlA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-s390x": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-x64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-7.0.5.tgz", + "integrity": "sha512-lxMb25jLd6n+hhjPhlqRBnBdGRumKkcEavqJ3p4OAtjr6pEPdbSfSVmYDt9LnvtqmqQSnUCtFRRr5J2BmQ3SkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-x64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-win32-x64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-7.0.5.tgz", + "integrity": "sha512-eUY9a82HR20qIfyEffWdJZj7k4GH2wGaZpr70dinDy8Q648LeQayL0Z6FW5nApoezjy+CIBj0Mv+rHUASV9Jzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "wizer-win32-x64": "wizer" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.4.3.tgz", + "integrity": "sha512-uBjLLoUM9ll03jL/bP7XjyPg0vTU0vQ35N1vVqQHbzlK/fVZyuF2B1p/A6kqPsFFhaoBKgO6oaxsuerv091RtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/lzma-android-arm-eabi": "1.4.3", + "@napi-rs/lzma-android-arm64": "1.4.3", + "@napi-rs/lzma-darwin-arm64": "1.4.3", + "@napi-rs/lzma-darwin-x64": "1.4.3", + "@napi-rs/lzma-freebsd-x64": "1.4.3", + "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.3", + "@napi-rs/lzma-linux-arm64-gnu": "1.4.3", + "@napi-rs/lzma-linux-arm64-musl": "1.4.3", + "@napi-rs/lzma-linux-ppc64-gnu": "1.4.3", + "@napi-rs/lzma-linux-riscv64-gnu": "1.4.3", + "@napi-rs/lzma-linux-s390x-gnu": "1.4.3", + "@napi-rs/lzma-linux-x64-gnu": "1.4.3", + "@napi-rs/lzma-linux-x64-musl": "1.4.3", + "@napi-rs/lzma-wasm32-wasi": "1.4.3", + "@napi-rs/lzma-win32-arm64-msvc": "1.4.3", + "@napi-rs/lzma-win32-ia32-msvc": "1.4.3", + "@napi-rs/lzma-win32-x64-msvc": "1.4.3" + } + }, + "node_modules/@napi-rs/lzma-android-arm-eabi": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.3.tgz", + "integrity": "sha512-XpjRUZ/EbWtVbMvW+ucon5Ykz7PjMoX65mIlUdAiVnaPGykzFAUrl8dl6Br5bfqnhQQfDjjUIgTAwWl3G++n1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-android-arm64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.3.tgz", + "integrity": "sha512-Bve6BF/4pnlO6HotIgRWgmUT3rbbW/QH471RF/GBA29GfEeUOPEdfQWC7tlzrLYsVFNX2KCWKd+XlxQNz9sRaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-arm64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.3.tgz", + "integrity": "sha512-UxTb56kL6pSVTsZ1ShibnqLSwJZLTWtPU5TNYuyIjVNQYAIG8JQ5Yxz35azjwBCK7AjD8pBdpWLYUSyJRGAVAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-x64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.3.tgz", + "integrity": "sha512-ps6HiwGKS1P4ottyV2/hVboZ0ugdM1Z1qO9YFpcuKweORfxAkxwJ6S8jOt7G27LQiWiiQHVwsUCODTHDFhOUPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-freebsd-x64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.3.tgz", + "integrity": "sha512-W49h41U3+vLnbthbPzvJX1fQtTG+1jyUlfB+wX3oxILvIur06PjJRdMXrFtOZpWkFsihK9gO2DRkQYQJIIgTZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.3.tgz", + "integrity": "sha512-11PNPiMGuwwxIxd9yPZY3Ek6RFGFRFQb/AtMStJIwlmJ6sM/djEknClLJVbVXbC/nqm7htVZEr+qmYgoDy0fAw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.3.tgz", + "integrity": "sha512-XzlxZjSXTcrWFHbvvv2xbV5+bSV5IJqCJ8CCksc7xV3uWEAso9yBPJ8VSRD3GPc7ZoBDRqJmgCb/HQzHpLBekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-musl": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.3.tgz", + "integrity": "sha512-k4fWiI4Pm61Esj8hnm7NWIbpZueTtP2jlJqmMhTqJyjqW3NUxbTHjSErZOZKIFRF1B3if4v5Tyzo7JL2X+BaSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.3.tgz", + "integrity": "sha512-tTIfk+TYZYbFySxaCMuzp4Zz1T3I6OYVYNAm+IrCSkZDLmUKUzBK3+Su+mT+PjcTNsAiHBa5NVjARXC7b7jmgQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.3.tgz", + "integrity": "sha512-HPyLYOYhkN7QYaWiKWhSnsLmx/l0pqgiiyaYeycgxCm9dwL8ummFWxveZqYjqdbUUvG7Mgi1jqgRe+55MVdyZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-s390x-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.3.tgz", + "integrity": "sha512-YkcV+RSZZIMM3D5sPZqvo2Q7/tHXBhgJWBi+6ceo46pTlqgn/nH+pVz+CzsDmLWz5hqNSXyv5IAhOcg2CH6rAg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.3.tgz", + "integrity": "sha512-ep6PLjN1+g4P12Hc7sLRmVpXXaHX22ykqxnOzjXUoj1KTph5XgM4+fUCyE5dsYI+lB4/tXqFuf9ZeFgHk5f00A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-musl": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.3.tgz", + "integrity": "sha512-QkCO6rVw0Z7eY0ziVc4aCFplbOTMpt0UBLPXWxsPd2lXtkAlRChzqaHOxdcL/HoLmBsqdCxmG0EZuHuAP/vKZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.3.tgz", + "integrity": "sha512-+rMamB0xaeDyVt4OP4cV888cnmso+m78iUebNhGcrL/WXIziwql50KQrmj7PBdBCza/W7XEcraZT8pO8gSDGcg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.10" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/lzma-win32-arm64-msvc": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.3.tgz", + "integrity": "sha512-6gQ+R6ztw11hswdsEu0jsOOXXnJPwhOA1yHRjqfuFemhf6esMd8l9b0uh3BfLBNe7qumtrH4KLrHu8yC9pSY3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-ia32-msvc": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.3.tgz", + "integrity": "sha512-+AJeJQoGE+QtZKlwM4VzDkfLmUa+6DsGOO5zdbIPlRCB6PEstRCXxp8lkMiQBNgk9f/IO0UEkRcJSZ+Hhqd8zw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-x64-msvc": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.3.tgz", + "integrity": "sha512-66dFCX9ACpVUyTTom89nxhllc88yJyjxGFHO0M2olFcrSJArulfbE9kNIATgh04NDAe/l8VsDhnAxWuvJY1GuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binaryen": { + "version": "123.0.0", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-123.0.0.tgz", + "integrity": "sha512-/hls/a309aZCc0itqP6uhoR+5DsKSlJVfB8Opd2BY9Ndghs84IScTunlyidyF4r2Xe3lQttnfBNIDjaNpj6mTw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wasm-as": "bin/wasm-as", + "wasm-ctor-eval": "bin/wasm-ctor-eval", + "wasm-dis": "bin/wasm-dis", + "wasm-merge": "bin/wasm-merge", + "wasm-metadce": "bin/wasm-metadce", + "wasm-opt": "bin/wasm-opt", + "wasm-reduce": "bin/wasm-reduce", + "wasm-shell": "bin/wasm-shell", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/ftl-cli/examples/javascript-test/handler/package.json b/packages/ftl-cli/examples/javascript-test/handler/package.json new file mode 100644 index 00000000..51749bf9 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/handler/package.json @@ -0,0 +1,21 @@ +{ + "name": "javascript-test-handler", + "version": "0.1.0", + "type": "module", + "description": "An MCP tool written in JavaScript", + "main": "src/index.js", + "scripts": { + "build": "npm run build:types && npm run build:component", + "build:types": "jco types wit/mcp.wit -o src/generated", + "build:component": "jco componentize src/index.js --wit wit/mcp.wit --world-name mcp-handler --out ../handler.wasm --disable all", + "test": "vitest", + "clean": "rm -rf generated ../handler.wasm" + }, + "devDependencies": { + "@bytecodealliance/jco": "^1.8.1", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=20.0.0" + } +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/src/index.js b/packages/ftl-cli/examples/javascript-test/handler/src/index.js new file mode 100644 index 00000000..5c605294 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/handler/src/index.js @@ -0,0 +1,104 @@ +// Export the handler implementation for componentize-js +export const handler = { + listTools() { + return [{ + name: 'javascript_test', + description: 'An MCP tool written in JavaScript', + inputSchema: JSON.stringify({ + type: 'object', + properties: { + input: { + type: 'string', + description: 'Input to process' + } + }, + required: ['input'] + }) + }]; + }, + + callTool(name, argumentsStr) { + let args; + try { + args = JSON.parse(argumentsStr); + } catch (e) { + return { + tag: 'error', + val: { + code: -32602, + message: `Invalid JSON arguments: ${e}`, + data: undefined + } + }; + } + + switch (name) { + case 'javascript_test': { + const input = args.input || 'No input provided'; + // TODO: Implement your tool logic here + return { + tag: 'text', + val: `Processed: ${input}` + }; + } + default: + return { + tag: 'error', + val: { + code: -32601, + message: `Unknown tool: ${name}`, + data: undefined + } + }; + } + }, + + listResources() { + return [ + // Add resources here + // Example: + // { + // uri: 'example://resource', + // name: 'Example Resource', + // description: 'An example resource', + // mimeType: 'text/plain' + // } + ]; + }, + + readResource(uri) { + // According to WIT, this returns ResourceContents, not a Result + // So we need to return a valid ResourceContents or throw + throw { + code: -32601, + message: `Resource not found: ${uri}`, + data: undefined + }; + }, + + listPrompts() { + return [ + // Add prompts here + // Example: + // { + // name: 'greeting', + // description: 'Generate a greeting', + // arguments: [{ + // name: 'name', + // description: 'Name to greet', + // required: true + // }] + // } + ]; + }, + + getPrompt(name, argumentsStr) { + // According to WIT, this returns Array, not a Result + // So we need to return an array or throw + throw { + code: -32601, + message: `Prompt not found: ${name}`, + data: undefined + }; + } +}; \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js b/packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js new file mode 100644 index 00000000..fe8ce82b --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { Handler } from '../src/index.js'; + +describe('Handler', () => { + describe('listTools', () => { + it('should return tool metadata', () => { + const tools = Handler.listTools(); + expect(tools).toHaveLength(1); + expect(tools[0].name).toBe('javascript_test'); + expect(tools[0].description).toBe('An MCP tool written in JavaScript'); + }); + }); + + describe('callTool', () => { + it('should process input correctly', () => { + const args = JSON.stringify({ input: 'test input' }); + const result = Handler.callTool('javascript_test', args); + + expect(result.tag).toBe('text'); + expect(result.val).toContain('test input'); + }); + + it('should handle invalid JSON', () => { + const result = Handler.callTool('javascript_test', 'invalid json'); + + expect(result.tag).toBe('error'); + expect(result.val.code).toBe(-32602); + }); + + it('should handle unknown tool', () => { + const args = JSON.stringify({ input: 'test' }); + const result = Handler.callTool('unknown_tool', args); + + expect(result.tag).toBe('error'); + expect(result.val.code).toBe(-32601); + }); + }); +}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/vitest.config.js b/packages/ftl-cli/examples/javascript-test/handler/vitest.config.js new file mode 100644 index 00000000..c7175ddc --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/handler/vitest.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.js'], + coverage: { + reporter: ['text', 'json', 'html'], + exclude: ['node_modules/', 'src/generated/'] + } + } +}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit b/packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/spin.toml b/packages/ftl-cli/examples/javascript-test/spin.toml new file mode 100644 index 00000000..13aefde0 --- /dev/null +++ b/packages/ftl-cli/examples/javascript-test/spin.toml @@ -0,0 +1,19 @@ +spin_manifest_version = 2 + +[application] +name = "javascript-test" +version = "0.1.0" +authors = ["bowlofarugula "] +description = "An MCP tool written in JavaScript" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/.gitignore b/packages/ftl-cli/examples/test-templates/rust-test/.gitignore new file mode 100644 index 00000000..b7cb55af --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/.gitignore @@ -0,0 +1,5 @@ +target/ +Cargo.lock +.ftl/ +*.wasm +.spin/ \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/Makefile b/packages/ftl-cli/examples/test-templates/rust-test/Makefile new file mode 100644 index 00000000..57307302 --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/Makefile @@ -0,0 +1,16 @@ +.PHONY: build test clean + +build: + cd handler && cargo build --release --target wasm32-wasip1 + +test: + cd handler && cargo test + +clean: + cd handler && cargo clean + +serve: + ftl serve + +deploy: + ftl deploy \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/README.md b/packages/ftl-cli/examples/test-templates/rust-test/README.md new file mode 100644 index 00000000..275889e8 --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/README.md @@ -0,0 +1,95 @@ +# rust-test + +An MCP tool written in Rust + +## Structure + +This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. + +- `handler/` - The Rust implementation of your MCP handler +- `ftl.toml` - FTL configuration file +- `spin.toml` - Spin application manifest + +## Development + +### Prerequisites + +- Rust with `wasm32-wasip1` target +- FTL CLI + +### Building + +```bash +ftl build +# or +make build +``` + +### Testing + +```bash +ftl test +# or +make test +``` + +### Running Locally + +```bash +ftl serve +# or +make serve +``` + +The tool will be available at `http://localhost:3000/mcp` + +### Example Usage + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "rust_test", + "arguments": { + "input": "Hello, world!" + } + }, + "id": 1 + }' +``` + +## Deployment + +```bash +ftl deploy +# or +make deploy +``` + +## Implementing Your Tool + +Edit `handler/src/lib.rs` to implement your tool's functionality: + +1. Modify `list_tools()` to define your tools +2. Implement the tool logic in `call_tool()` +3. Optionally implement resources and prompts + +## Configuration + +### Memory Allocation + +The WebAssembly memory allocator size can be adjusted in `handler/src/lib.rs`: + +```rust +const ARENA_SIZE: usize = 1 * 1024 * 1024; // Default: 1MB +``` + +### Runtime Configuration + +Edit `ftl.toml` to configure: +- Allowed external hosts +- Build optimization flags +- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/ftl.toml b/packages/ftl-cli/examples/test-templates/rust-test/ftl.toml new file mode 100644 index 00000000..cf7a4e64 --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/ftl.toml @@ -0,0 +1,23 @@ +[tool] +name = "rust-test" +version = "0.1.0" +description = "An MCP tool written in Rust" + +[build] +# Build commands are run from the handler directory +commands = [ + "cargo build --release --target wasm32-wasip1" +] + +[optimization] +# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test +flags = [ + "-O4", + "-Oz", +] + +[runtime] +# List of external hosts this tool is allowed to make HTTP requests to. +# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). +# Leave empty to deny all external requests. +allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml b/packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml new file mode 100644 index 00000000..8d49b2da --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rust_test" +version = "0.1.0" +edition = "2021" + +[dependencies] +wit-bindgen = "0.16.0" +serde_json = "1.0" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +panic = "abort" +strip = "symbols" + +[profile.dev] +opt-level = 1 +debug = true + +[workspace] \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs b/packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs new file mode 100644 index 00000000..0525b3ba --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs @@ -0,0 +1,110 @@ +use serde_json::{json, Value}; + +wit_bindgen::generate!({ + world: "mcp-handler", + path: "./wit", + exports: { + "component:mcp/handler": Component + } +}); + +use exports::component::mcp::handler::{ + Guest, Tool, ToolResult, ResourceInfo, ResourceContents, Prompt, PromptMessage, Error as McpError +}; + +struct Component; + +impl Guest for Component { + fn list_tools() -> Vec { + vec![ + Tool { + name: "rust_test".to_string(), + description: "An MCP tool written in Rust".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Input to process" + } + }, + "required": ["input"] + }).to_string(), + }, + // Add more tools here + ] + } + + fn call_tool(name: String, arguments: String) -> ToolResult { + let args: Value = match serde_json::from_str(&arguments) { + Ok(v) => v, + Err(e) => return ToolResult::Error(McpError { + code: -32602, + message: format!("Invalid JSON arguments: {}", e), + data: None, + }), + }; + + match name.as_str() { + "rust_test" => { + let input = args["input"].as_str().unwrap_or("No input provided"); + // TODO: Implement your tool logic here + ToolResult::Text(format!("Processed: {}", input)) + } + _ => ToolResult::Error(McpError { + code: -32601, + message: format!("Unknown tool: {}", name), + data: None, + }), + } + } + + fn list_resources() -> Vec { + vec![ + // Add resources here + // Example: + // ResourceInfo { + // uri: "example://resource".to_string(), + // name: "Example Resource".to_string(), + // description: Some("An example resource".to_string()), + // mime_type: Some("text/plain".to_string()), + // } + ] + } + + fn read_resource(uri: String) -> Result { + // Implement resource reading logic here + Err(McpError { + code: -32601, + message: format!("Resource not found: {}", uri), + data: None, + }) + } + + fn list_prompts() -> Vec { + vec![ + // Add prompts here + // Example: + // Prompt { + // name: "greeting".to_string(), + // description: Some("Generate a greeting".to_string()), + // arguments: vec![ + // PromptArgument { + // name: "name".to_string(), + // description: Some("Name to greet".to_string()), + // required: true, + // } + // ], + // } + ] + } + + fn get_prompt(name: String, _arguments: String) -> Result, McpError> { + // Implement prompt generation logic here + Err(McpError { + code: -32601, + message: format!("Prompt not found: {}", name), + data: None, + }) + } +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit b/packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/spin.toml b/packages/ftl-cli/examples/test-templates/rust-test/spin.toml new file mode 100644 index 00000000..655c0e93 --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/spin.toml @@ -0,0 +1,19 @@ +spin_manifest_version = 2 + +[application] +name = "rust-test" +version = "0.1.0" +authors = ["bowlofarugula "] +description = "An MCP tool written in Rust" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler/target/wasm32-wasip1/release/rust_test.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json b/packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json new file mode 100644 index 00000000..2f5dbf3a --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json @@ -0,0 +1,9 @@ +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "rust_test", + "arguments": {"input":"Hello from Rust!"} + }, + "id": 3 +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/test-call.json b/packages/ftl-cli/examples/test-templates/rust-test/test-call.json new file mode 100644 index 00000000..4979d6a4 --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/test-call.json @@ -0,0 +1,9 @@ +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "rust_test", + "arguments": "{\"input\":\"Hello from Rust!\"}" + }, + "id": 2 +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json b/packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json new file mode 100644 index 00000000..fd3152d6 --- /dev/null +++ b/packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json @@ -0,0 +1,11 @@ +{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "rust_test", + "arguments": { + "input": "Hello from Rust!" + } + }, + "id": 4 +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/.gitignore b/packages/ftl-cli/examples/typescript-test/.gitignore new file mode 100644 index 00000000..1a7f6419 --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.ftl/ +*.wasm +.spin/ +handler/src/generated/ +handler.wasm \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/Makefile b/packages/ftl-cli/examples/typescript-test/Makefile new file mode 100644 index 00000000..d58450ec --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/Makefile @@ -0,0 +1,20 @@ +.PHONY: build test clean install + +install: + cd handler && npm install + +build: install + cd handler && npm run build + +test: install + cd handler && npm test + +clean: + cd handler && npm run clean + cd handler && rm -rf node_modules + +serve: + ftl serve + +deploy: + ftl deploy \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/README.md b/packages/ftl-cli/examples/typescript-test/README.md new file mode 100644 index 00000000..d4527dc7 --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/README.md @@ -0,0 +1,89 @@ +# typescript-test + +An MCP tool written in TypeScript + +## Structure + +This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. + +- `handler/` - The TypeScript implementation of your MCP handler +- `ftl.toml` - FTL configuration file +- `spin.toml` - Spin application manifest + +## Development + +### Prerequisites + +- Node.js >= 20.0.0 +- FTL CLI + +### Building + +```bash +ftl build +# or +make build +``` + +### Testing + +```bash +ftl test +# or +make test +``` + +### Running Locally + +```bash +ftl serve +# or +make serve +``` + +The tool will be available at `http://localhost:3000/mcp` + +### Example Usage + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "typescript_test", + "arguments": { + "input": "Hello, world!" + } + }, + "id": 1 + }' +``` + +## Deployment + +```bash +ftl deploy +# or +make deploy +``` + +## Implementing Your Tool + +Edit `handler/src/index.ts` to implement your tool's functionality: + +1. Modify `listTools()` to define your tools +2. Implement the tool logic in `callTool()` +3. Optionally implement resources and prompts + +## Type Safety + +This template uses `jco` to generate TypeScript types from the WIT interface definition. The types are generated in `handler/src/generated/` when you run `npm run build`. + +## Configuration + +Edit `ftl.toml` to configure: +- Allowed external hosts +- Build optimization flags +- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/ftl.toml b/packages/ftl-cli/examples/typescript-test/ftl.toml new file mode 100644 index 00000000..c15688c3 --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/ftl.toml @@ -0,0 +1,24 @@ +[tool] +name = "typescript-test" +version = "0.1.0" +description = "An MCP tool written in TypeScript" + +[build] +# Build commands are run from the handler directory +commands = [ + "cd handler && npm install", + "cd handler && npm run build" +] + +[optimization] +# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test +flags = [ + "-O4", + "-Oz", +] + +[runtime] +# List of external hosts this tool is allowed to make HTTP requests to. +# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). +# Leave empty to deny all external requests. +allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/package-lock.json b/packages/ftl-cli/examples/typescript-test/handler/package-lock.json new file mode 100644 index 00000000..b60b1d8d --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/handler/package-lock.json @@ -0,0 +1,3711 @@ +{ + "name": "typescript-test-handler", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "typescript-test-handler", + "version": "0.1.0", + "devDependencies": { + "@bytecodealliance/jco": "^1.8.1", + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@bytecodealliance/componentize-js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/componentize-js/-/componentize-js-0.17.0.tgz", + "integrity": "sha512-FDgO5UPipfjyq5OghSB4JW313LkQJK3Sl647WH1jvIuYAyCq1j+bMt+Q66c3UF6IVs6PneNTGfGSjYgzID/k0w==", + "dev": true, + "workspaces": [ + "." + ], + "dependencies": { + "@bytecodealliance/jco": "^1.9.1", + "@bytecodealliance/weval": "^0.3.3", + "@bytecodealliance/wizer": "^7.0.5", + "es-module-lexer": "^1.6.0" + } + }, + "node_modules/@bytecodealliance/jco": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.11.3.tgz", + "integrity": "sha512-Ynv0GDj9iAp+cS03UGg+ss4uo/Hx5e4FyjcFDBfkRt9qJsqbL2AnPqgO/ORNlSXg9zu9PTNC0uGgE7/Vklcn/w==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)", + "dependencies": { + "@bytecodealliance/componentize-js": "^0.17.0", + "@bytecodealliance/preview2-shim": "^0.17.2", + "binaryen": "^123.0.0", + "chalk-template": "^1", + "commander": "^12", + "mkdirp": "^3", + "ora": "^8", + "terser": "^5" + }, + "bin": { + "jco": "src/jco.js" + } + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.2.tgz", + "integrity": "sha512-mNm/lblgES8UkVle8rGImXOz4TtL3eU3inHay/7TVchkKrb/lgcVvTK0+VAw8p5zQ0rgQsXm1j5dOlAAd+MeoA==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@bytecodealliance/weval": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@bytecodealliance/weval/-/weval-0.3.4.tgz", + "integrity": "sha512-+GCKtZXhPj7qyDl5pR0F3PTEk8tWINV8dv1tUbKMjMXvqjHIkvzalkWo5ZL2kCKwh8Bwn8rWpSmyvRC/Nlu9nQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@napi-rs/lzma": "^1.1.2", + "decompress": "^4.2.1", + "decompress-tar": "^4.1.1", + "decompress-unzip": "^4.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@bytecodealliance/wizer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-7.0.5.tgz", + "integrity": "sha512-xIbLzKxmUNaPwDWorcGtdxh1mcgDiXI8fe9KiDaSICKfCl9VtUKVyXIc3ix+VpwFczBbdhek+TlMiiCf+9lpOQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wizer": "wizer.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@bytecodealliance/wizer-darwin-arm64": "7.0.5", + "@bytecodealliance/wizer-darwin-x64": "7.0.5", + "@bytecodealliance/wizer-linux-arm64": "7.0.5", + "@bytecodealliance/wizer-linux-s390x": "7.0.5", + "@bytecodealliance/wizer-linux-x64": "7.0.5", + "@bytecodealliance/wizer-win32-x64": "7.0.5" + } + }, + "node_modules/@bytecodealliance/wizer-darwin-arm64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-7.0.5.tgz", + "integrity": "sha512-Tp0SgVQR568SVPvSfyWDT00yL4ry/w9FS2qy8ZwaP0EauYyjFSZojj6mESX6x9fpYkEnQdprgfdvhw5h1hTwCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "wizer-darwin-arm64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-darwin-x64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-7.0.5.tgz", + "integrity": "sha512-HYmG5Q9SpQJnqR7kimb5J3VAh6E62b30GLG/E+6doS/UwNhSpSmYjaggVfuJvgFDbUxsnD1l36qZny0xMwxikA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "wizer-darwin-x64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-arm64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-7.0.5.tgz", + "integrity": "sha512-01qqaiIWrYXPt2bjrfiluSSOmUL/PMjPtJlYa/XqZgK75g3RVn3sRkSflwoCXtXMRbHdb03qNrJ9w81+F17kvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-arm64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-s390x": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-7.0.5.tgz", + "integrity": "sha512-smGfD4eJou81g6yDlV7MCRoKgKlqd4SQL00pHxQGrNfUPnfYKhZ4z80N9J9T2B++uo2FM14BFilsRrV5UevKlA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-s390x": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-linux-x64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-7.0.5.tgz", + "integrity": "sha512-lxMb25jLd6n+hhjPhlqRBnBdGRumKkcEavqJ3p4OAtjr6pEPdbSfSVmYDt9LnvtqmqQSnUCtFRRr5J2BmQ3SkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "wizer-linux-x64": "wizer" + } + }, + "node_modules/@bytecodealliance/wizer-win32-x64": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-7.0.5.tgz", + "integrity": "sha512-eUY9a82HR20qIfyEffWdJZj7k4GH2wGaZpr70dinDy8Q648LeQayL0Z6FW5nApoezjy+CIBj0Mv+rHUASV9Jzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "wizer-win32-x64": "wizer" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.4.3.tgz", + "integrity": "sha512-uBjLLoUM9ll03jL/bP7XjyPg0vTU0vQ35N1vVqQHbzlK/fVZyuF2B1p/A6kqPsFFhaoBKgO6oaxsuerv091RtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/lzma-android-arm-eabi": "1.4.3", + "@napi-rs/lzma-android-arm64": "1.4.3", + "@napi-rs/lzma-darwin-arm64": "1.4.3", + "@napi-rs/lzma-darwin-x64": "1.4.3", + "@napi-rs/lzma-freebsd-x64": "1.4.3", + "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.3", + "@napi-rs/lzma-linux-arm64-gnu": "1.4.3", + "@napi-rs/lzma-linux-arm64-musl": "1.4.3", + "@napi-rs/lzma-linux-ppc64-gnu": "1.4.3", + "@napi-rs/lzma-linux-riscv64-gnu": "1.4.3", + "@napi-rs/lzma-linux-s390x-gnu": "1.4.3", + "@napi-rs/lzma-linux-x64-gnu": "1.4.3", + "@napi-rs/lzma-linux-x64-musl": "1.4.3", + "@napi-rs/lzma-wasm32-wasi": "1.4.3", + "@napi-rs/lzma-win32-arm64-msvc": "1.4.3", + "@napi-rs/lzma-win32-ia32-msvc": "1.4.3", + "@napi-rs/lzma-win32-x64-msvc": "1.4.3" + } + }, + "node_modules/@napi-rs/lzma-android-arm-eabi": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.3.tgz", + "integrity": "sha512-XpjRUZ/EbWtVbMvW+ucon5Ykz7PjMoX65mIlUdAiVnaPGykzFAUrl8dl6Br5bfqnhQQfDjjUIgTAwWl3G++n1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-android-arm64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.3.tgz", + "integrity": "sha512-Bve6BF/4pnlO6HotIgRWgmUT3rbbW/QH471RF/GBA29GfEeUOPEdfQWC7tlzrLYsVFNX2KCWKd+XlxQNz9sRaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-arm64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.3.tgz", + "integrity": "sha512-UxTb56kL6pSVTsZ1ShibnqLSwJZLTWtPU5TNYuyIjVNQYAIG8JQ5Yxz35azjwBCK7AjD8pBdpWLYUSyJRGAVAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-darwin-x64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.3.tgz", + "integrity": "sha512-ps6HiwGKS1P4ottyV2/hVboZ0ugdM1Z1qO9YFpcuKweORfxAkxwJ6S8jOt7G27LQiWiiQHVwsUCODTHDFhOUPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-freebsd-x64": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.3.tgz", + "integrity": "sha512-W49h41U3+vLnbthbPzvJX1fQtTG+1jyUlfB+wX3oxILvIur06PjJRdMXrFtOZpWkFsihK9gO2DRkQYQJIIgTZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.3.tgz", + "integrity": "sha512-11PNPiMGuwwxIxd9yPZY3Ek6RFGFRFQb/AtMStJIwlmJ6sM/djEknClLJVbVXbC/nqm7htVZEr+qmYgoDy0fAw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.3.tgz", + "integrity": "sha512-XzlxZjSXTcrWFHbvvv2xbV5+bSV5IJqCJ8CCksc7xV3uWEAso9yBPJ8VSRD3GPc7ZoBDRqJmgCb/HQzHpLBekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-musl": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.3.tgz", + "integrity": "sha512-k4fWiI4Pm61Esj8hnm7NWIbpZueTtP2jlJqmMhTqJyjqW3NUxbTHjSErZOZKIFRF1B3if4v5Tyzo7JL2X+BaSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.3.tgz", + "integrity": "sha512-tTIfk+TYZYbFySxaCMuzp4Zz1T3I6OYVYNAm+IrCSkZDLmUKUzBK3+Su+mT+PjcTNsAiHBa5NVjARXC7b7jmgQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.3.tgz", + "integrity": "sha512-HPyLYOYhkN7QYaWiKWhSnsLmx/l0pqgiiyaYeycgxCm9dwL8ummFWxveZqYjqdbUUvG7Mgi1jqgRe+55MVdyZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-s390x-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.3.tgz", + "integrity": "sha512-YkcV+RSZZIMM3D5sPZqvo2Q7/tHXBhgJWBi+6ceo46pTlqgn/nH+pVz+CzsDmLWz5hqNSXyv5IAhOcg2CH6rAg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.3.tgz", + "integrity": "sha512-ep6PLjN1+g4P12Hc7sLRmVpXXaHX22ykqxnOzjXUoj1KTph5XgM4+fUCyE5dsYI+lB4/tXqFuf9ZeFgHk5f00A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-musl": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.3.tgz", + "integrity": "sha512-QkCO6rVw0Z7eY0ziVc4aCFplbOTMpt0UBLPXWxsPd2lXtkAlRChzqaHOxdcL/HoLmBsqdCxmG0EZuHuAP/vKZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.3.tgz", + "integrity": "sha512-+rMamB0xaeDyVt4OP4cV888cnmso+m78iUebNhGcrL/WXIziwql50KQrmj7PBdBCza/W7XEcraZT8pO8gSDGcg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.10" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/lzma-win32-arm64-msvc": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.3.tgz", + "integrity": "sha512-6gQ+R6ztw11hswdsEu0jsOOXXnJPwhOA1yHRjqfuFemhf6esMd8l9b0uh3BfLBNe7qumtrH4KLrHu8yC9pSY3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-ia32-msvc": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.3.tgz", + "integrity": "sha512-+AJeJQoGE+QtZKlwM4VzDkfLmUa+6DsGOO5zdbIPlRCB6PEstRCXxp8lkMiQBNgk9f/IO0UEkRcJSZ+Hhqd8zw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/lzma-win32-x64-msvc": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.3.tgz", + "integrity": "sha512-66dFCX9ACpVUyTTom89nxhllc88yJyjxGFHO0M2olFcrSJArulfbE9kNIATgh04NDAe/l8VsDhnAxWuvJY1GuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz", + "integrity": "sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binaryen": { + "version": "123.0.0", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-123.0.0.tgz", + "integrity": "sha512-/hls/a309aZCc0itqP6uhoR+5DsKSlJVfB8Opd2BY9Ndghs84IScTunlyidyF4r2Xe3lQttnfBNIDjaNpj6mTw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "wasm-as": "bin/wasm-as", + "wasm-ctor-eval": "bin/wasm-ctor-eval", + "wasm-dis": "bin/wasm-dis", + "wasm-merge": "bin/wasm-merge", + "wasm-metadce": "bin/wasm-metadce", + "wasm-opt": "bin/wasm-opt", + "wasm-reduce": "bin/wasm-reduce", + "wasm-shell": "bin/wasm-shell", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/ftl-cli/examples/typescript-test/handler/package.json b/packages/ftl-cli/examples/typescript-test/handler/package.json new file mode 100644 index 00000000..a9487bfa --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/handler/package.json @@ -0,0 +1,24 @@ +{ + "name": "typescript-test-handler", + "version": "0.1.0", + "type": "module", + "description": "An MCP tool written in TypeScript", + "main": "dist/index.js", + "scripts": { + "build": "npm run build:types && npm run build:js && npm run build:component", + "build:types": "jco types wit/mcp.wit -o src/generated", + "build:js": "tsc", + "build:component": "jco componentize dist/index.js --wit wit/mcp.wit --world-name mcp-handler --out ../handler.wasm --disable all", + "test": "vitest", + "clean": "rm -rf dist generated ../handler.wasm" + }, + "devDependencies": { + "@bytecodealliance/jco": "^1.8.1", + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=20.0.0" + } +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/src/index.ts b/packages/ftl-cli/examples/typescript-test/handler/src/index.ts new file mode 100644 index 00000000..b6a2e377 --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/handler/src/index.ts @@ -0,0 +1,82 @@ +export const handler = { + listTools() { + return [{ + name: 'typescript_test', + description: 'An MCP tool written in TypeScript', + inputSchema: JSON.stringify({ + type: 'object', + properties: { + input: { + type: 'string', + description: 'Input to process' + } + }, + required: ['input'] + }) + }]; + }, + + callTool(name: string, argumentsStr: string) { + let args: any; + try { + args = JSON.parse(argumentsStr); + } catch (e) { + return { + tag: 'error', + val: { + code: -32602, + message: `Invalid JSON arguments: ${e}`, + data: undefined + } + }; + } + + switch (name) { + case 'typescript_test': { + const input = args.input || 'No input provided'; + // TODO: Implement your tool logic here + return { + tag: 'text', + val: `Processed: ${input}` + }; + } + default: + return { + tag: 'error', + val: { + code: -32601, + message: `Unknown tool: ${name}`, + data: undefined + } + }; + } + }, + + listResources() { + return []; + }, + + readResource(uri: string) { + // According to WIT, this returns ResourceContents, not a Result + // So we need to return a valid ResourceContents or throw + throw { + code: -32601, + message: `Resource not found: ${uri}`, + data: undefined + }; + }, + + listPrompts() { + return []; + }, + + getPrompt(name: string, argumentsStr: string) { + // According to WIT, this returns Array, not a Result + // So we need to return an array or throw + throw { + code: -32601, + message: `Prompt not found: ${name}`, + data: undefined + }; + } +}; \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts b/packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts new file mode 100644 index 00000000..452208b4 --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import { Handler } from '../src/index'; + +describe('Handler', () => { + describe('listTools', () => { + it('should return tool metadata', () => { + const tools = Handler.listTools(); + expect(tools).toHaveLength(1); + expect(tools[0].name).toBe('typescript_test'); + expect(tools[0].description).toBe('An MCP tool written in TypeScript'); + }); + }); + + describe('callTool', () => { + it('should process input correctly', () => { + const args = JSON.stringify({ input: 'test input' }); + const result = Handler.callTool('typescript_test', args); + + expect(result.tag).toBe('text'); + if (result.tag === 'text') { + expect(result.val).toContain('test input'); + } + }); + + it('should handle invalid JSON', () => { + const result = Handler.callTool('typescript_test', 'invalid json'); + + expect(result.tag).toBe('error'); + if (result.tag === 'error') { + expect(result.val.code).toBe(-32602); + } + }); + + it('should handle unknown tool', () => { + const args = JSON.stringify({ input: 'test' }); + const result = Handler.callTool('unknown_tool', args); + + expect(result.tag).toBe('error'); + if (result.tag === 'error') { + expect(result.val.code).toBe(-32601); + } + }); + }); +}); \ No newline at end of file diff --git a/packages/ftl-sdk-ts/tsconfig.json b/packages/ftl-cli/examples/typescript-test/handler/tsconfig.json similarity index 57% rename from packages/ftl-sdk-ts/tsconfig.json rename to packages/ftl-cli/examples/typescript-test/handler/tsconfig.json index 56b40dc0..090ee7c3 100644 --- a/packages/ftl-sdk-ts/tsconfig.json +++ b/packages/ftl-cli/examples/typescript-test/handler/tsconfig.json @@ -1,21 +1,26 @@ { "compilerOptions": { - "target": "ES2020", - "module": "ES2020", - "lib": ["ES2020", "WebWorker"], + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", "outDir": "./dist", "rootDir": "./src", - "declaration": true, - "declarationMap": true, - "sourceMap": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noEmit": false, + "isolatedModules": true, "allowSyntheticDefaultImports": true, - "resolveJsonModule": true + "types": ["node", "vitest/globals"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "tests"] + "exclude": ["node_modules", "dist", "test"] } \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts b/packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts new file mode 100644 index 00000000..3942f585 --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.ts'], + coverage: { + reporter: ['text', 'json', 'html'], + exclude: ['node_modules/', 'dist/', 'src/generated/'] + } + } +}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit b/packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/spin.toml b/packages/ftl-cli/examples/typescript-test/spin.toml new file mode 100644 index 00000000..97135bfa --- /dev/null +++ b/packages/ftl-cli/examples/typescript-test/spin.toml @@ -0,0 +1,19 @@ +spin_manifest_version = 2 + +[application] +name = "typescript-test" +version = "0.1.0" +authors = ["bowlofarugula "] +description = "An MCP tool written in TypeScript" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/commands/build.rs b/packages/ftl-cli/src/commands/build.rs index 4249ebab..aec49048 100644 --- a/packages/ftl-cli/src/commands/build.rs +++ b/packages/ftl-cli/src/commands/build.rs @@ -9,10 +9,9 @@ use crate::{ build_utils::{format_size, get_file_size, optimize_wasm}, manifest_utils::load_manifest_and_name, spin_installer::check_and_install_spin, - tool_paths::{self, ensure_ftl_dir, get_profile_dir, get_spin_toml_path}, + tool_paths::{self, ensure_ftl_dir, get_spin_toml_path}, }, language::{Language, get_language_support}, - spin_generator::SpinConfig, }; pub async fn execute(name: Option, profile: Option) -> Result<()> { @@ -61,27 +60,11 @@ async fn build_tool(tool_path: &str, profile: Option, quiet: bool) -> Re ensure_ftl_dir(tool_path)?; check_and_install_spin().await?; - // Generate spin.toml in .ftl directory - match manifest.tool.language { - Language::Rust => { - // Generate spin.toml with build configuration for Rust - let profile_dir = get_profile_dir(&build_profile); - let wasm_filename = format!("{}.wasm", tool_name.replace('-', "_")); - let relative_wasm_path = PathBuf::from("..") - .join("target") - .join("wasm32-wasip1") - .join(profile_dir) - .join(&wasm_filename); - - let spin_config = SpinConfig::from_tool(&manifest, &relative_wasm_path)?; - let spin_path = get_spin_toml_path(tool_path); - spin_config.save(&spin_path)?; - } - Language::JavaScript | Language::TypeScript => { - // For JavaScript/TypeScript, spin.toml is already in .ftl directory - // (moved during project creation) We don't need to - // generate or copy it - } + // Copy spin.toml to .ftl directory if it doesn't exist + let spin_toml_src = PathBuf::from(tool_path).join("spin.toml"); + let spin_toml_dest = get_spin_toml_path(tool_path); + if spin_toml_src.exists() && !spin_toml_dest.exists() { + std::fs::copy(&spin_toml_src, &spin_toml_dest)?; } // Create progress bar (only if not quiet) @@ -106,33 +89,59 @@ async fn build_tool(tool_path: &str, profile: Option, quiet: bool) -> Re // Validate language environment language_support.validate_environment()?; - // Run the language-specific build - language_support.build(&manifest, std::path::Path::new(tool_path))?; + // Run build commands from ftl.toml + if let Some(commands) = &manifest.build.commands { + for command in commands { + let parts: Vec<&str> = command.split_whitespace().collect(); + if parts.is_empty() { + continue; + } + + let output = std::process::Command::new(parts[0]) + .args(&parts[1..]) + .current_dir(tool_path) + .output()?; + + if !output.status.success() { + anyhow::bail!( + "Build command failed: {}\n{}", + command, + String::from_utf8_lossy(&output.stderr) + ); + } + } + } else { + // Fallback to language-specific build + language_support.build(&manifest, std::path::Path::new(tool_path))?; + } // Step 2: Verify WASM was built pb.set_message("Verifying build output..."); pb.inc(1); - let wasm_path = tool_paths::get_wasm_path_for_language( - tool_path, - &tool_name, - &build_profile, - manifest.tool.language, - ); + // Check for handler.wasm first (new structure) + let wasm_path = PathBuf::from(tool_path).join("handler.wasm"); + let wasm_path = if wasm_path.exists() { + wasm_path + } else { + // Fallback to old structure + tool_paths::get_wasm_path_for_language( + tool_path, + &tool_name, + &build_profile, + manifest.tool.language, + ) + }; if !wasm_path.exists() { anyhow::bail!("WASM binary not found at: {}", wasm_path.display()); } - // For JavaScript/TypeScript, also copy the WASM to .ftl/dist directory for - // deployment - if matches!( - manifest.tool.language, - Language::JavaScript | Language::TypeScript - ) { - let ftl_dist_dir = PathBuf::from(tool_path).join(".ftl").join("dist"); - std::fs::create_dir_all(&ftl_dist_dir)?; - let dest_wasm = ftl_dist_dir.join(format!("{tool_name}.wasm")); + // Copy WASM to .ftl/dist directory for deployment + let ftl_dist_dir = PathBuf::from(tool_path).join(".ftl").join("dist"); + std::fs::create_dir_all(&ftl_dist_dir)?; + let dest_wasm = ftl_dist_dir.join("handler.wasm"); + if wasm_path != dest_wasm { std::fs::copy(&wasm_path, &dest_wasm)?; } diff --git a/packages/ftl-cli/src/commands/new.rs b/packages/ftl-cli/src/commands/new.rs index d97e8e71..552f2e19 100644 --- a/packages/ftl-cli/src/commands/new.rs +++ b/packages/ftl-cli/src/commands/new.rs @@ -1,12 +1,13 @@ use std::path::PathBuf; +use std::process::Command; -use anyhow::Result; +use anyhow::{Context, Result}; use console::style; use dialoguer::{Input, Select, theme::ColorfulTheme}; use crate::{ - language::{Language, get_language_support}, - templates, + language::Language, + common::spin_installer::check_and_install_spin, }; pub async fn execute( @@ -67,24 +68,68 @@ pub async fn execute( anyhow::bail!("Directory '{name}' already exists"); } - // Create tool using language-specific support - let language_support = get_language_support(selected_language); + // Get spin path + let spin_path = tokio::runtime::Handle::try_current() + .ok() + .and_then(|handle| { + tokio::task::block_in_place(|| handle.block_on(check_and_install_spin()).ok()) + }) + .unwrap_or_else(|| { + // If no runtime exists, create one + let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); + rt.block_on(check_and_install_spin()) + .expect("Failed to install Spin") + }); + + // Use spin new with the appropriate template + let template_id = match selected_language { + Language::Rust => "ftl-rust", + Language::TypeScript => "ftl-typescript", + Language::JavaScript => "ftl-javascript", + }; - // Use templates for Rust (existing), or language-specific for others - match selected_language { - Language::Rust => { - templates::create_tool(&name, &description, &target_dir)?; - } - Language::JavaScript | Language::TypeScript => { - language_support.new_project(&name, &description, "default", &target_dir)?; - } + let template_path = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|p| p.to_path_buf())) + .unwrap_or_else(|| PathBuf::from(".")) + .join("templates") + .join(template_id); + + // If template doesn't exist in binary dir, use the source templates + let template_path = if !template_path.exists() { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("src/templates") + .join(template_id) + } else { + template_path + }; + + let output = Command::new(&spin_path) + .args([ + "new", + "-t", + template_path.to_str().unwrap(), + "-o", + target_dir.to_str().unwrap(), + "--accept-defaults", + &name, + ]) + .env("project-description", &description) + .output() + .context("Failed to run spin new")?; + + if !output.status.success() { + anyhow::bail!( + "Failed to create project with spin new:\n{}", + String::from_utf8_lossy(&output.stderr) + ); } // Success message based on language let main_file = match selected_language { - Language::Rust => "src/lib.rs", - Language::JavaScript => "src/index.js", - Language::TypeScript => "src/index.ts", + Language::Rust => "handler/src/lib.rs", + Language::JavaScript => "handler/src/index.js", + Language::TypeScript => "handler/src/index.ts", }; println!( diff --git a/packages/ftl-cli/src/commands/toolkit.rs b/packages/ftl-cli/src/commands/toolkit.rs index a384008b..b7de7228 100644 --- a/packages/ftl-cli/src/commands/toolkit.rs +++ b/packages/ftl-cli/src/commands/toolkit.rs @@ -424,9 +424,6 @@ fn generate_gateway_code(toolkit_dir: &Path, manifest: &ToolkitManifest) -> Resu let gateway_dir = toolkit_dir.join("gateway"); std::fs::create_dir_all(&gateway_dir)?; - // Get the SDK version from compile-time constant - let sdk_version = env!("FTL_SDK_RS_VERSION"); - // Generate Cargo.toml let cargo_toml = format!( r#"[package] @@ -435,7 +432,7 @@ version = "0.1.0" edition = "2021" [dependencies] -ftl-sdk-rs = {{ version = "^{}" }} +# TODO: Add gateway dependencies spin-sdk = "3.1.1" serde = {{ version = "1.0", features = ["derive"] }} serde_json = "1.0" @@ -444,7 +441,7 @@ serde_json = "1.0" crate-type = ["cdylib"] [workspace]"#, - manifest.toolkit.name, sdk_version + manifest.toolkit.name ); std::fs::write(gateway_dir.join("Cargo.toml"), cargo_toml)?; diff --git a/packages/ftl-cli/src/commands/validate.rs b/packages/ftl-cli/src/commands/validate.rs index a16e8e6c..5f02853c 100644 --- a/packages/ftl-cli/src/commands/validate.rs +++ b/packages/ftl-cli/src/commands/validate.rs @@ -1,6 +1,6 @@ use std::{path::Path, process::Command}; -use anyhow::{Context, Result}; +use anyhow::Result; use tracing::info; use crate::manifest::ToolManifest; @@ -47,84 +47,83 @@ pub async fn execute(name: String) -> Result<()> { } } - // Check Cargo.toml - let cargo_path = tool_dir.join("Cargo.toml"); - if !cargo_path.exists() { - errors.push("Missing Cargo.toml file".to_string()); - } else { - // Validate Cargo.toml - match std::fs::read_to_string(&cargo_path) { - Ok(content) => { - let cargo_toml: toml::Value = - content.parse().context("Failed to parse Cargo.toml")?; - - // Check for required dependencies - if let Some(deps) = cargo_toml.get("dependencies") { - if deps.get("ftl-sdk-rs").is_none() { - errors.push("Missing ftl-sdk-rs dependency in Cargo.toml".to_string()); - } - } else { - errors.push("No dependencies section in Cargo.toml".to_string()); - } + // Check spin.toml + let spin_path = tool_dir.join("spin.toml"); + if !spin_path.exists() { + errors.push("Missing spin.toml file".to_string()); + } - // Check crate type - if let Some(lib) = cargo_toml.get("lib") { - if let Some(crate_types) = lib.get("crate-type") { - let types = crate_types - .as_array() - .and_then(|arr| arr.first()) - .and_then(|v| v.as_str()); - - if types != Some("cdylib") { - errors.push( - "Library crate-type must be [\"cdylib\"] for WebAssembly" - .to_string(), - ); - } - } else { - errors.push("Missing crate-type in [lib] section".to_string()); - } - } else { - errors.push("Missing [lib] section in Cargo.toml".to_string()); - } - } - Err(e) => { - errors.push(format!("Failed to read Cargo.toml: {e}")); - } + // Check handler directory structure + let handler_path = tool_dir.join("handler"); + if !handler_path.exists() { + errors.push("Missing handler directory".to_string()); + } else { + // Check for handler Cargo.toml or package.json + let cargo_path = handler_path.join("Cargo.toml"); + let package_path = handler_path.join("package.json"); + + if !cargo_path.exists() && !package_path.exists() { + errors.push("Missing Cargo.toml or package.json in handler directory".to_string()); + } + + // Check for WIT file + let wit_path = handler_path.join("wit/mcp.wit"); + if !wit_path.exists() { + errors.push("Missing wit/mcp.wit file in handler directory".to_string()); } } - // Check source files - let lib_path = tool_dir.join("src/lib.rs"); - if !lib_path.exists() { - errors.push("Missing src/lib.rs file".to_string()); + // Check handler source files + let handler_src = handler_path.join("src"); + if !handler_src.exists() { + errors.push("Missing src directory in handler".to_string()); } else { - // Basic validation of lib.rs content - match std::fs::read_to_string(&lib_path) { - Ok(content) => { - if !content.contains("impl Tool for") { - warnings.push("No Tool trait implementation found in src/lib.rs".to_string()); + // Check for main source file (lib.rs, index.ts, or index.js) + let lib_rs = handler_src.join("lib.rs"); + let index_ts = handler_src.join("index.ts"); + let index_js = handler_src.join("index.js"); + + if !lib_rs.exists() && !index_ts.exists() && !index_js.exists() { + errors.push("Missing main source file (lib.rs, index.ts, or index.js) in handler/src".to_string()); + } else if lib_rs.exists() { + // Validate Rust handler + match std::fs::read_to_string(&lib_rs) { + Ok(content) => { + if !content.contains("impl Guest for") { + warnings.push("No Guest trait implementation found in handler/src/lib.rs".to_string()); + } + if !content.contains("wit_bindgen::generate!") { + errors.push("Missing wit_bindgen::generate! macro in handler/src/lib.rs".to_string()); + } } - - if !content.contains("ftl_mcp_server!") { - errors - .push("Missing ftl_mcp_server! macro invocation in src/lib.rs".to_string()); + Err(e) => { + errors.push(format!("Failed to read handler/src/lib.rs: {e}")); } } - Err(e) => { - errors.push(format!("Failed to read src/lib.rs: {e}")); - } } } - // Try to run cargo check - println!("\n📦 Running cargo check..."); - let check_result = Command::new("cargo") - .current_dir(tool_dir) - .arg("check") - .arg("--target") - .arg("wasm32-wasip1") - .output(); + // Try to run build check based on handler type + println!("\n📦 Running build check..."); + let cargo_path = handler_path.join("Cargo.toml"); + let package_path = handler_path.join("package.json"); + + let check_result = if cargo_path.exists() { + Command::new("cargo") + .current_dir(&handler_path) + .arg("check") + .arg("--target") + .arg("wasm32-wasip1") + .output() + } else if package_path.exists() { + Command::new("npm") + .current_dir(&handler_path) + .arg("install") + .arg("--dry-run") + .output() + } else { + Err(std::io::Error::new(std::io::ErrorKind::NotFound, "No build file found")) + }; match check_result { Ok(output) => { @@ -132,7 +131,7 @@ pub async fn execute(name: String) -> Result<()> { let stderr = String::from_utf8_lossy(&output.stderr); errors.push(format!("Cargo check failed:\n{stderr}")); } else { - println!("✅ Cargo check passed"); + println!("✅ Build check passed"); } } Err(e) => { diff --git a/packages/ftl-cli/src/language/javascript.rs b/packages/ftl-cli/src/language/javascript.rs index ec1b810a..7990e8b1 100644 --- a/packages/ftl-cli/src/language/javascript.rs +++ b/packages/ftl-cli/src/language/javascript.rs @@ -1,4 +1,4 @@ -use std::{fs, path::Path, process::Command}; +use std::{path::Path, process::Command}; use anyhow::{Context, Result}; @@ -15,137 +15,18 @@ impl JavaScriptSupport { Self } - fn render_template(&self, template_str: &str, name: &str, description: &str) -> Result { - use handlebars::Handlebars; - use serde_json::json; - - let handlebars = Handlebars::new(); - - // Convert name to PascalCase for class name - let tool_name_class = name - .split(&['-', '_'][..]) - .map(|word| { - let mut chars = word.chars(); - match chars.next() { - None => String::new(), - Some(first) => first.to_uppercase().collect::() + chars.as_str(), - } - }) - .collect::(); - - // Get the SDK version from compile-time constant - let sdk_version = env!("FTL_SDK_TS_VERSION"); - - let data = json!({ - "name": name, - "description": description, - "tool_name_class": tool_name_class, - "sdk_version": sdk_version, - }); - - handlebars - .render_template(template_str, &data) - .map_err(|e| anyhow::anyhow!("Template rendering failed: {e}")) - } } impl LanguageSupport for JavaScriptSupport { fn new_project( &self, - name: &str, - description: &str, + _name: &str, + _description: &str, _template: &str, - path: &Path, + _path: &Path, ) -> Result<()> { - // Get spin path using blocking runtime - let spin_path = tokio::runtime::Handle::try_current() - .ok() - .and_then(|handle| { - tokio::task::block_in_place(|| handle.block_on(check_and_install_spin()).ok()) - }) - .unwrap_or_else(|| { - // If no runtime exists, create one - let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); - rt.block_on(check_and_install_spin()) - .expect("Failed to install Spin") - }); - - // Use spin new to create the project - let output = Command::new(&spin_path) - .args([ - "new", - "-t", - "http-js", - "-o", - path.to_str().unwrap(), - "--accept-defaults", - name, - ]) - .output() - .context("Failed to run spin new")?; - - if !output.status.success() { - anyhow::bail!( - "Failed to create JavaScript project with spin new:\n{}", - String::from_utf8_lossy(&output.stderr) - ); - } - - // Move spin.toml to .ftl directory - let spin_toml_src = path.join("spin.toml"); - let ftl_dir = path.join(".ftl"); - fs::create_dir_all(&ftl_dir)?; - let spin_toml_dest = ftl_dir.join("spin.toml"); - - if spin_toml_src.exists() { - fs::rename(&spin_toml_src, &spin_toml_dest) - .context("Failed to move spin.toml to .ftl directory")?; - } - - // Overlay FTL-specific files - - // 1. Add ftl.toml - let ftl_toml = self.render_template( - include_str!("../templates/javascript/ftl.toml.hbs"), - name, - description, - )?; - fs::write(path.join("ftl.toml"), ftl_toml)?; - - // 2. Replace src/index.js with MCP implementation - let index_js = self.render_template( - include_str!("../templates/javascript/index.js.hbs"), - name, - description, - )?; - fs::write(path.join("src/index.js"), index_js)?; - - // 3. Update package.json to include @ftl/sdk-js - let package_json = self.render_template( - include_str!("../templates/javascript/package.json.hbs"), - name, - description, - )?; - fs::write(path.join("package.json"), package_json)?; - - // 4. Replace webpack.config.js - let webpack_config = include_str!("../templates/javascript/webpack.config.js"); - fs::write(path.join("webpack.config.js"), webpack_config)?; - - // 5. Create test directory and test file - fs::create_dir_all(path.join("test"))?; - let test_js = self.render_template( - include_str!("../templates/javascript/tool.test.js.hbs"), - name, - description, - )?; - fs::write(path.join("test/tool.test.js"), test_js)?; - - // 6. Add vitest.config.js - let vitest_config = include_str!("../templates/javascript/vitest.config.js"); - fs::write(path.join("vitest.config.js"), vitest_config)?; - - Ok(()) + // This is now handled by spin templates in the new command + anyhow::bail!("Direct project creation is deprecated. Use 'ftl new' command instead.") } fn build(&self, _manifest: &Manifest, path: &Path) -> Result<()> { diff --git a/packages/ftl-cli/src/language/rust.rs b/packages/ftl-cli/src/language/rust.rs index 9a24b176..b57a9190 100644 --- a/packages/ftl-cli/src/language/rust.rs +++ b/packages/ftl-cli/src/language/rust.rs @@ -2,7 +2,7 @@ use std::{path::Path, process::Command}; use anyhow::{Context, Result}; -use crate::{language::LanguageSupport, manifest::Manifest, templates}; +use crate::{language::LanguageSupport, manifest::Manifest}; pub struct RustSupport; @@ -15,14 +15,13 @@ impl RustSupport { impl LanguageSupport for RustSupport { fn new_project( &self, - name: &str, - description: &str, + _name: &str, + _description: &str, _template: &str, - path: &Path, + _path: &Path, ) -> Result<()> { - // Use existing template generation logic - templates::create_tool(name, description, path)?; - Ok(()) + // This is now handled by spin templates in the new command + anyhow::bail!("Direct project creation is deprecated. Use 'ftl new' command instead.") } fn build(&self, manifest: &Manifest, path: &Path) -> Result<()> { diff --git a/packages/ftl-cli/src/language/typescript.rs b/packages/ftl-cli/src/language/typescript.rs index 91c7eb95..94279f9f 100644 --- a/packages/ftl-cli/src/language/typescript.rs +++ b/packages/ftl-cli/src/language/typescript.rs @@ -1,4 +1,4 @@ -use std::{fs, path::Path, process::Command}; +use std::{path::Path, process::Command}; use anyhow::{Context, Result}; @@ -15,141 +15,18 @@ impl TypeScriptSupport { Self } - fn render_template(&self, template_str: &str, name: &str, description: &str) -> Result { - use handlebars::Handlebars; - use serde_json::json; - - let handlebars = Handlebars::new(); - - // Convert name to PascalCase for class name - let tool_name_class = name - .split(&['-', '_'][..]) - .map(|word| { - let mut chars = word.chars(); - match chars.next() { - None => String::new(), - Some(first) => first.to_uppercase().collect::() + chars.as_str(), - } - }) - .collect::(); - - // Get the SDK version from compile-time constant - let sdk_version = env!("FTL_SDK_TS_VERSION"); - - let data = json!({ - "name": name, - "description": description, - "tool_name_class": tool_name_class, - "sdk_version": sdk_version, - }); - - handlebars - .render_template(template_str, &data) - .map_err(|e| anyhow::anyhow!("Template rendering failed: {e}")) - } } impl LanguageSupport for TypeScriptSupport { fn new_project( &self, - name: &str, - description: &str, + _name: &str, + _description: &str, _template: &str, - path: &Path, + _path: &Path, ) -> Result<()> { - // Get spin path using blocking runtime - let spin_path = tokio::runtime::Handle::try_current() - .ok() - .and_then(|handle| { - tokio::task::block_in_place(|| handle.block_on(check_and_install_spin()).ok()) - }) - .unwrap_or_else(|| { - // If no runtime exists, create one - let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); - rt.block_on(check_and_install_spin()) - .expect("Failed to install Spin") - }); - - // Use spin new to create the project with TypeScript template - let output = Command::new(&spin_path) - .args([ - "new", - "-t", - "http-ts", - "-o", - path.to_str().unwrap(), - "--accept-defaults", - name, - ]) - .output() - .context("Failed to run spin new")?; - - if !output.status.success() { - anyhow::bail!( - "Failed to create TypeScript project with spin new:\n{}", - String::from_utf8_lossy(&output.stderr) - ); - } - - // Move spin.toml to .ftl directory - let spin_toml_src = path.join("spin.toml"); - let ftl_dir = path.join(".ftl"); - fs::create_dir_all(&ftl_dir)?; - let spin_toml_dest = ftl_dir.join("spin.toml"); - - if spin_toml_src.exists() { - fs::rename(&spin_toml_src, &spin_toml_dest) - .context("Failed to move spin.toml to .ftl directory")?; - } - - // Overlay FTL-specific files - - // 1. Add ftl.toml - let ftl_toml = self.render_template( - include_str!("../templates/typescript/ftl.toml.hbs"), - name, - description, - )?; - fs::write(path.join("ftl.toml"), ftl_toml)?; - - // 2. Replace src/index.ts with MCP implementation - let index_ts = self.render_template( - include_str!("../templates/typescript/index.ts.hbs"), - name, - description, - )?; - fs::write(path.join("src/index.ts"), index_ts)?; - - // 3. Update package.json to include @ftl/sdk-js and TypeScript dependencies - let package_json = self.render_template( - include_str!("../templates/typescript/package.json.hbs"), - name, - description, - )?; - fs::write(path.join("package.json"), package_json)?; - - // 4. Replace webpack.config.js with TypeScript version - let webpack_config = include_str!("../templates/typescript/webpack.config.js"); - fs::write(path.join("webpack.config.js"), webpack_config)?; - - // 5. Add tsconfig.json - let tsconfig = include_str!("../templates/typescript/tsconfig.json"); - fs::write(path.join("tsconfig.json"), tsconfig)?; - - // 6. Create test directory and test file - fs::create_dir_all(path.join("test"))?; - let test_ts = self.render_template( - include_str!("../templates/typescript/tool.test.ts.hbs"), - name, - description, - )?; - fs::write(path.join("test/tool.test.ts"), test_ts)?; - - // 7. Add vitest.config.ts - let vitest_config = include_str!("../templates/typescript/vitest.config.ts"); - fs::write(path.join("vitest.config.ts"), vitest_config)?; - - Ok(()) + // This is now handled by spin templates in the new command + anyhow::bail!("Direct project creation is deprecated. Use 'ftl new' command instead.") } fn build(&self, _manifest: &Manifest, path: &Path) -> Result<()> { diff --git a/packages/ftl-cli/src/main.rs b/packages/ftl-cli/src/main.rs index 12b97126..722f73a2 100644 --- a/packages/ftl-cli/src/main.rs +++ b/packages/ftl-cli/src/main.rs @@ -9,7 +9,6 @@ mod common; mod language; mod manifest; mod spin_generator; -mod templates; #[derive(Parser)] #[command(name = "ftl")] diff --git a/packages/ftl-cli/src/manifest.rs b/packages/ftl-cli/src/manifest.rs index 9b7c805e..5f67f8d3 100644 --- a/packages/ftl-cli/src/manifest.rs +++ b/packages/ftl-cli/src/manifest.rs @@ -34,6 +34,8 @@ pub struct BuildConfig { pub profile: String, #[serde(default)] pub features: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub commands: Option>, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] diff --git a/packages/ftl-cli/src/templates.rs b/packages/ftl-cli/src/templates.rs deleted file mode 100644 index 3c28f45a..00000000 --- a/packages/ftl-cli/src/templates.rs +++ /dev/null @@ -1,345 +0,0 @@ -use std::path::Path; - -use anyhow::Result; -use handlebars::Handlebars; -use serde_json::json; - -pub fn create_tool(name: &str, description: &str, target_dir: &Path) -> Result<()> { - // Create directory structure - std::fs::create_dir_all(target_dir)?; - std::fs::create_dir_all(target_dir.join("src"))?; - - // Convert tool name to struct name (PascalCase) - let struct_name = name - .split('-') - .map(|part| { - let mut chars = part.chars(); - match chars.next() { - None => String::new(), - Some(first) => first.to_uppercase().collect::() + chars.as_str(), - } - }) - .collect::(); - - let handlebars = Handlebars::new(); - let data = json!({ - "tool_name": name, - "struct_name": struct_name, - "description": description, - "version": "0.0.1", - }); - - // Create ftl.toml - let ftl_toml_template = r#"[tool] -name = "{{tool_name}}" -version = "{{version}}" -description = "{{description}}" - -[build] -profile = "release" -features = [] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] -"#; - - let ftl_toml = handlebars.render_template(ftl_toml_template, &data)?; - std::fs::write(target_dir.join("ftl.toml"), ftl_toml)?; - - // Get the SDK version from compile-time constant - let sdk_version = env!("FTL_SDK_RS_VERSION"); - let ftl_sdk_dep = format!("{{ version = \"^{sdk_version}\" }}"); - - // Create Cargo.toml - let cargo_toml_template = format!( - r#"[package] -name = "{{{{tool_name}}}}" -version = "{{{{version}}}}" -edition = "2024" - -[dependencies] -ftl-sdk-rs = {ftl_sdk_dep} -talc = {{ version = "4.4.3", default-features = false }} -serde = {{ version = "1.0", features = ["derive"] }} -serde_json = "1.0" -anyhow = "1.0" -spin-sdk = "3.1.1" - -[dev-dependencies] - -[lib] -crate-type = ["cdylib"] - -[profile.release] -opt-level = 3 -lto = "fat" -codegen-units = 1 -panic = "abort" -strip = "symbols" - -[profile.dev] -opt-level = 1 -debug = true - -[workspace] -"# - ); - - let cargo_toml = handlebars.render_template(&cargo_toml_template, &data)?; - std::fs::write(target_dir.join("Cargo.toml"), cargo_toml)?; - - // Create src/lib.rs - let lib_rs_template = r#"use ftl_sdk_rs::prelude::*; -use serde_json::json; - -// --- Global Memory Allocator --- -// FTL tools are compiled to WebAssembly, which requires a global memory -// allocator to be defined. The `talc` allocator is used here with a -// statically-allocated arena. -// -// The size of this arena is a critical trade-off: -// - Larger Arena: Supports more memory-intensive tools. -// - Smaller Arena: Results in a smaller .wasm binary size, which can improve -// cold start times. -// -// You can adjust the `ARENA_SIZE` constant below to fit your tool's specific -// memory requirements. -#[cfg(target_family = "wasm")] -#[global_allocator] -static ALLOC: talc::Talck = { - use talc::*; - // Choose an arena size based on your tool's needs. - // - 64 * 1024 (64KB): For minimal tools (e.g., simple text processing). - // - 1 * 1024 * 1024 (1MB): A good default for most tools. - // - 4 * 1024 * 1024 (4MB): For data-intensive tools (e.g., image processing). - const ARENA_SIZE: usize = 1 * 1024 * 1024; // Default: 1MB - static mut ARENA: [u8; ARENA_SIZE] = [0; ARENA_SIZE]; - Talc::new(unsafe { - ClaimOnOom::new(Span::from_base_size( - std::ptr::addr_of_mut!(ARENA).cast(), - ARENA_SIZE, - )) - }) - .lock() -}; - -#[derive(Clone)] -pub struct {{struct_name}}; - -impl Tool for {{struct_name}} { - fn name(&self) -> &'static str { - "{{tool_name}}" - } - - fn description(&self) -> &'static str { - "{{description}}" - } - - fn input_schema(&self) -> serde_json::Value { - json!({ - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "Input to process" - } - }, - "required": ["input"] - }) - } - - fn call(&self, args: &serde_json::Value) -> Result { - let input = args["input"] - .as_str() - .ok_or_else(|| ToolError::InvalidArguments("input is required".to_string()))?; - - // TODO: Implement your tool logic here - let result = format!("Processed: {input}"); - - Ok(ToolResult::text(result)) - } -} - -ftl_sdk_rs::ftl_mcp_server!({{struct_name}}); - -#[cfg(test)] -mod tests; -"#; - - let lib_rs = handlebars.render_template(lib_rs_template, &data)?; - std::fs::write(target_dir.join("src").join("lib.rs"), lib_rs)?; - - // Create src/tests.rs - let tests_rs_template = r#"use ftl_sdk_rs::prelude::*; -use serde_json::json; - -use super::{{struct_name}}; - -#[test] -fn test_tool_metadata() { - let tool = {{struct_name}}; - assert_eq!(tool.name(), "{{tool_name}}"); - assert_eq!(tool.description(), "{{description}}"); -} - -#[test] -fn test_tool_call() { - let tool = {{struct_name}}; - let args = json!({ - "input": "test input" - }); - - let result = tool.call(&args).unwrap(); - // Result is a text block - assert!(!result.content.is_empty()); - let text = &result.content[0].text; - assert!(text.contains("test input")); -} -"#; - - let tests_rs = handlebars.render_template(tests_rs_template, &data)?; - std::fs::write(target_dir.join("src").join("tests.rs"), tests_rs)?; - - // Create README.md - let readme_template = r#"# {{tool_name}} - -{{description}} - -## Usage - -This tool is designed to be used with the Model Context Protocol (MCP). - -### Development - -```bash -# Serve locally for testing -ftl serve {{tool_name}} - -# Build the tool -ftl build {{tool_name}} - -# Run tests (requires spin test setup) -ftl test {{tool_name}} -``` - -### Input Schema - -```json -{ - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "Input to process" - } - }, - "required": ["input"] -} -``` - -### Example - -```bash -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "{{tool_name}}", - "arguments": { - "input": "Hello, world!" - } - }, - "id": 1 - }' -``` - -## Configuration - -Edit `ftl.toml` to configure build and runtime settings. - -## License - -Apache-2.0 -"#; - - let readme = handlebars.render_template(readme_template, &data)?; - std::fs::write(target_dir.join("README.md"), readme)?; - - // Create .gitignore - let gitignore = r#"target/ -Cargo.lock -.ftl/ -*.wasm -"#; - std::fs::write(target_dir.join(".gitignore"), gitignore)?; - - // Create rustfmt.toml - let rustfmt_toml = r#"# Rust formatting configuration -edition = "2024" -max_width = 100 -hard_tabs = false -tab_spaces = 4 -newline_style = "Unix" -use_small_heuristics = "Default" -reorder_imports = true -reorder_modules = true -remove_nested_parens = true -use_field_init_shorthand = true -use_try_shorthand = true -format_code_in_doc_comments = true -normalize_comments = true -normalize_doc_attributes = true -format_strings = true -format_macro_matchers = true -format_macro_bodies = true -empty_item_single_line = true -struct_lit_single_line = true -fn_single_line = false -where_single_line = false -imports_indent = "Block" -imports_layout = "Mixed" -imports_granularity = "Crate" -group_imports = "StdExternalCrate" -reorder_impl_items = false -type_punctuation_density = "Wide" -space_before_colon = false -space_after_colon = true -spaces_around_ranges = false -binop_separator = "Front" -combine_control_expr = true -overflow_delimited_expr = false -struct_field_align_threshold = 0 -enum_discrim_align_threshold = 0 -match_arm_blocks = true -match_arm_leading_pipes = "Never" -force_multiline_blocks = false -fn_params_layout = "Tall" -brace_style = "SameLineWhere" -control_brace_style = "AlwaysSameLine" -trailing_semicolon = true -trailing_comma = "Vertical" -match_block_trailing_comma = false -blank_lines_upper_bound = 1 -blank_lines_lower_bound = 0 -merge_derives = true -wrap_comments = true -comment_width = 80 -format_generated_files = false -skip_children = false -"#; - std::fs::write(target_dir.join("rustfmt.toml"), rustfmt_toml)?; - - Ok(()) -} diff --git a/packages/ftl-cli/src/templates/javascript/content/.gitignore b/packages/ftl-cli/src/templates/javascript/content/.gitignore new file mode 100644 index 00000000..a418fe81 --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.ftl/ +*.wasm +.spin/ +handler/src/generated/ +handler.wasm \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/Makefile b/packages/ftl-cli/src/templates/javascript/content/Makefile new file mode 100644 index 00000000..0aed9fb3 --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/Makefile @@ -0,0 +1,25 @@ +.PHONY: build test clean install registry-push + +build: install + cd handler && npm run build + +install: + cd handler && npm install + +test: install + cd handler && npm test + +clean: + cd handler && npm run clean + cd handler && rm -rf node_modules + +registry-push: build + @echo "Pushing to registry..." + @VERSION=$$(cd handler && node -p "require('./package.json').version") && \ + USERNAME=$$(git config user.name | tr '[:upper:]' '[:lower:]' | tr ' ' '-') && \ + PROJECT={{project-name | snake_case}} && \ + echo "Pushing ghcr.io/$$USERNAME/$$PROJECT:$$VERSION" && \ + wkg oci push ghcr.io/$$USERNAME/$$PROJECT:$$VERSION handler/dist/handler.wasm + +up: + spin up diff --git a/packages/ftl-cli/src/templates/javascript/content/README.md b/packages/ftl-cli/src/templates/javascript/content/README.md new file mode 100644 index 00000000..198ebd8a --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/README.md @@ -0,0 +1,85 @@ +# {{project-name | kebab_case}} + +{{project-description}} + +## Structure + +This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. + +- `handler/` - The JavaScript implementation of your MCP handler +- `ftl.toml` - FTL configuration file +- `spin.toml` - Spin application manifest + +## Development + +### Prerequisites + +- Node.js >= 20.0.0 +- FTL CLI + +### Building + +```bash +ftl build +# or +make build +``` + +### Testing + +```bash +ftl test +# or +make test +``` + +### Running Locally + +```bash +ftl serve +# or +make serve +``` + +The tool will be available at `http://localhost:3000/mcp` + +### Example Usage + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "{{project-name | snake_case}}", + "arguments": { + "input": "Hello, world!" + } + }, + "id": 1 + }' +``` + +## Deployment + +```bash +ftl deploy +# or +make deploy +``` + +## Implementing Your Tool + +Edit `handler/src/index.js` to implement your tool's functionality: + +1. Modify `listTools()` to define your tools +2. Implement the tool logic in `callTool()` +3. Optionally implement resources and prompts + +## Configuration + +Edit `ftl.toml` to configure: +- Allowed external hosts +- Build optimization flags +- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/ftl.toml b/packages/ftl-cli/src/templates/javascript/content/ftl.toml new file mode 100644 index 00000000..cccf19e9 --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/ftl.toml @@ -0,0 +1,24 @@ +[tool] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +description = "{{project-description}}" + +[build] +# Build commands are run from the handler directory +commands = [ + "cd handler && npm install", + "cd handler && npm run build" +] + +[optimization] +# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test +flags = [ + "-O4", + "-Oz", +] + +[runtime] +# List of external hosts this tool is allowed to make HTTP requests to. +# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). +# Leave empty to deny all external requests. +allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/package.json b/packages/ftl-cli/src/templates/javascript/content/handler/package.json new file mode 100644 index 00000000..04e1ab2b --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/handler/package.json @@ -0,0 +1,21 @@ +{ + "name": "{{project-name | kebab_case}}-handler", + "version": "0.1.0", + "type": "module", + "description": "{{project-description}}", + "main": "src/index.js", + "scripts": { + "build": "npm run build:types && npm run build:component", + "build:types": "jco types wit/mcp.wit -o dist/generated", + "build:component": "jco componentize src/index.js --wit wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", + "test": "vitest", + "clean": "rm -rf dist" + }, + "devDependencies": { + "@bytecodealliance/jco": "^1.8.1", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=20.0.0" + } +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js b/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js new file mode 100644 index 00000000..976e4fab --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js @@ -0,0 +1,104 @@ +// Export the handler implementation for componentize-js +export const handler = { + listTools() { + return [{ + name: '{{project-name | snake_case}}', + description: '{{project-description}}', + inputSchema: JSON.stringify({ + type: 'object', + properties: { + input: { + type: 'string', + description: 'Input to process' + } + }, + required: ['input'] + }) + }]; + }, + + callTool(name, argumentsStr) { + let args; + try { + args = JSON.parse(argumentsStr); + } catch (e) { + return { + tag: 'error', + val: { + code: -32602, + message: `Invalid JSON arguments: ${e}`, + data: undefined + } + }; + } + + switch (name) { + case '{{project-name | snake_case}}': { + const input = args.input || 'No input provided'; + // TODO: Implement your tool logic here + return { + tag: 'text', + val: `Processed: ${input}` + }; + } + default: + return { + tag: 'error', + val: { + code: -32601, + message: `Unknown tool: ${name}`, + data: undefined + } + }; + } + }, + + listResources() { + return [ + // Add resources here + // Example: + // { + // uri: 'example://resource', + // name: 'Example Resource', + // description: 'An example resource', + // mimeType: 'text/plain' + // } + ]; + }, + + readResource(uri) { + // According to WIT, this returns ResourceContents, not a Result + // So we need to return a valid ResourceContents or throw + throw { + code: -32601, + message: `Resource not found: ${uri}`, + data: undefined + }; + }, + + listPrompts() { + return [ + // Add prompts here + // Example: + // { + // name: 'greeting', + // description: 'Generate a greeting', + // arguments: [{ + // name: 'name', + // description: 'Name to greet', + // required: true + // }] + // } + ]; + }, + + getPrompt(name, argumentsStr) { + // According to WIT, this returns Array, not a Result + // So we need to return an array or throw + throw { + code: -32601, + message: `Prompt not found: ${name}`, + data: undefined + }; + } +}; \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js b/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js new file mode 100644 index 00000000..5a15fb1e --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { Handler } from '../src/index.js'; + +describe('Handler', () => { + describe('listTools', () => { + it('should return tool metadata', () => { + const tools = Handler.listTools(); + expect(tools).toHaveLength(1); + expect(tools[0].name).toBe('{{project-name | snake_case}}'); + expect(tools[0].description).toBe('{{project-description}}'); + }); + }); + + describe('callTool', () => { + it('should process input correctly', () => { + const args = JSON.stringify({ input: 'test input' }); + const result = Handler.callTool('{{project-name | snake_case}}', args); + + expect(result.tag).toBe('text'); + expect(result.val).toContain('test input'); + }); + + it('should handle invalid JSON', () => { + const result = Handler.callTool('{{project-name | snake_case}}', 'invalid json'); + + expect(result.tag).toBe('error'); + expect(result.val.code).toBe(-32602); + }); + + it('should handle unknown tool', () => { + const args = JSON.stringify({ input: 'test' }); + const result = Handler.callTool('unknown_tool', args); + + expect(result.tag).toBe('error'); + expect(result.val.code).toBe(-32601); + }); + }); +}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/vitest.config.js b/packages/ftl-cli/src/templates/javascript/content/handler/vitest.config.js new file mode 100644 index 00000000..c7175ddc --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/handler/vitest.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.js'], + coverage: { + reporter: ['text', 'json', 'html'], + exclude: ['node_modules/', 'src/generated/'] + } + } +}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/javascript/content/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/spin.toml b/packages/ftl-cli/src/templates/javascript/content/spin.toml new file mode 100644 index 00000000..e79d06ab --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/spin.toml @@ -0,0 +1,20 @@ +spin_manifest_version = 2 + +[application] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +authors = ["{{authors}}"] +description = "{{project-description}}" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } +build.command = "make build" + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler/dist/handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/ftl.toml.hbs b/packages/ftl-cli/src/templates/javascript/ftl.toml.hbs deleted file mode 100644 index 03072689..00000000 --- a/packages/ftl-cli/src/templates/javascript/ftl.toml.hbs +++ /dev/null @@ -1,11 +0,0 @@ -[tool] -name = "{{name}}" -version = "0.1.0" -description = "{{description}}" -language = "javascript" - -[build] -profile = "release" - -[runtime] -allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/index.js.hbs b/packages/ftl-cli/src/templates/javascript/index.js.hbs deleted file mode 100644 index 3d6c66ee..00000000 --- a/packages/ftl-cli/src/templates/javascript/index.js.hbs +++ /dev/null @@ -1,48 +0,0 @@ -import { ftlTool, Tool, ToolResult, ToolError } from '@fastertools/ftl-sdk-ts'; - -/** - * {{description}} - */ -class {{tool_name_class}} extends Tool { - get name() { - return '{{name}}'; - } - - get description() { - return '{{description}}'; - } - - get inputSchema() { - return { - type: 'object', - properties: { - input: { - type: 'string', - description: 'Input text to process' - } - }, - required: ['input'] - }; - } - - execute(args) { - const { input } = args; - - if (!input) { - throw ToolError.invalidArguments('Input is required'); - } - - // TODO: Implement your tool logic here - const result = `Processed: ${input}`; - - return ToolResult.text(result); - } -} - -// Export the class for testing -export { {{tool_name_class}} }; - -// Register the tool with FTL (only when not in test environment) -if (typeof process === 'undefined' || process.env.NODE_ENV !== 'test') { - ftlTool(new {{tool_name_class}}()); -} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt new file mode 100644 index 00000000..89a39094 --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt @@ -0,0 +1,12 @@ +[[trigger.http]] +route = "{{route}}" +component = "mcp-gateway-{{project-name | kebab_case}}" + +[component.mcp-gateway-{{project-name | kebab_case}}] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } +build.command = "cd {{project-name | kebab_case}} && make build" + +[component.mcp-gateway-{{project-name | kebab_case}}.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "{{project-name | kebab_case}}/handler/dist/handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/metadata/spin-template.toml b/packages/ftl-cli/src/templates/javascript/metadata/spin-template.toml new file mode 100644 index 00000000..8e9e3261 --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/metadata/spin-template.toml @@ -0,0 +1,13 @@ +manifest_version = "1" +id = "ftl-javascript" +description = "FTL JavaScript MCP tool" +tags = ["mcp", "http", "javascript", "ftl"] + +[parameters] +project-description = { type = "string", prompt = "Description", default = "An MCP tool written in JavaScript" } +route = { type = "string", prompt = "HTTP route", default = "/mcp" } + +[add_component] +skip_files = ["spin.toml", "README.md", ".gitignore", "ftl.toml"] +[add_component.snippets] +component = "component.txt" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/package.json.hbs b/packages/ftl-cli/src/templates/javascript/package.json.hbs deleted file mode 100644 index 92a3461b..00000000 --- a/packages/ftl-cli/src/templates/javascript/package.json.hbs +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "{{name}}", - "version": "0.1.0", - "description": "{{description}}", - "main": "src/index.js", - "type": "module", - "scripts": { - "build": "webpack && mkdirp dist && j2w -i build/bundle.js -o dist/{{name}}.wasm", - "dev": "webpack --mode=development --watch", - "test": "vitest run", - "test:watch": "vitest watch" - }, - "dependencies": { - "@fastertools/ftl-sdk-ts": "^{{sdk_version}}", - "@spinframework/build-tools": "^1.0.1", - "@spinframework/wasi-http-proxy": "^1.0.0" - }, - "devDependencies": { - "webpack": "^5.74.0", - "webpack-cli": "^4.10.0", - "vitest": "^2.0.4", - "mkdirp": "^3.0.1" - } -} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/tool.test.js.hbs b/packages/ftl-cli/src/templates/javascript/tool.test.js.hbs deleted file mode 100644 index 4cfbff09..00000000 --- a/packages/ftl-cli/src/templates/javascript/tool.test.js.hbs +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { {{tool_name_class}} } from '../src/index.js'; - -describe('{{tool_name_class}}', () => { - let tool; - - beforeEach(() => { - tool = new {{tool_name_class}}(); - }); - - it('should have correct metadata', () => { - expect(tool.name).toBe('{{name}}'); - expect(tool.description).toBe('{{description}}'); - }); - - it('should define input schema', () => { - const schema = tool.inputSchema; - expect(schema.type).toBe('object'); - expect(schema.properties.input).toBeDefined(); - expect(schema.required).toContain('input'); - }); - - it('should process valid input', () => { - const result = tool.execute({ input: 'test' }); - expect(result.content).toHaveLength(1); - expect(result.content[0].type).toBe('text'); - expect(result.content[0].text).toContain('test'); - }); - - it('should throw error for missing input', () => { - expect(() => tool.execute({})).toThrow('Input is required'); - }); -}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/vitest.config.js b/packages/ftl-cli/src/templates/javascript/vitest.config.js deleted file mode 100644 index bdfa621c..00000000 --- a/packages/ftl-cli/src/templates/javascript/vitest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - env: { - NODE_ENV: 'test' - } - }, -}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/webpack.config.js b/packages/ftl-cli/src/templates/javascript/webpack.config.js deleted file mode 100644 index af3a4ad0..00000000 --- a/packages/ftl-cli/src/templates/javascript/webpack.config.js +++ /dev/null @@ -1,32 +0,0 @@ -import path from 'path'; -import SpinSdkPlugin from "@spinframework/build-tools/plugins/webpack/index.js"; - -const config = async () => { - let SpinPlugin = await SpinSdkPlugin.init() - return { - mode: 'production', - stats: 'errors-only', - entry: './src/index.js', - experiments: { - outputModule: true, - }, - resolve: { - extensions: ['.js'], - }, - output: { - path: path.resolve(process.cwd(), './build'), - filename: 'bundle.js', - module: true, - library: { - type: "module", - } - }, - plugins: [ - SpinPlugin - ], - optimization: { - minimize: false - }, - }; -} -export default config \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/.gitignore b/packages/ftl-cli/src/templates/mcp-server/content/.gitignore new file mode 100644 index 00000000..78242103 --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/.gitignore @@ -0,0 +1,21 @@ +# Rust build artifacts +target/ +Cargo.lock + +# Spin artifacts +.spin/ + +# Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Environment files +.env +.env.local \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/Makefile b/packages/ftl-cli/src/templates/mcp-server/content/Makefile new file mode 100644 index 00000000..3888b50b --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/Makefile @@ -0,0 +1,37 @@ +.PHONY: build +build: + cd handler && cargo component build --release + +.PHONY: run +run: build + spin up + +.PHONY: test +test: + @echo "Testing MCP server..." + @echo "1. List tools:" + @curl -s -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | jq . + @echo "" + @echo "2. Call example tool:" + @curl -s -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "example_tool", "arguments": "{\"message\": \"Hello from test!\"}"}, "id": 2}' | jq . + +.PHONY: deploy +deploy: build + spin cloud deploy + +.PHONY: clean +clean: + cd handler && cargo clean + +.PHONY: help +help: + @echo "Available commands:" + @echo " make build - Build the handler component" + @echo " make run - Build and run the MCP server" + @echo " make test - Test the MCP server (requires server running)" + @echo " make deploy - Deploy to Spin Cloud" + @echo " make clean - Clean build artifacts" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/README.md b/packages/ftl-cli/src/templates/mcp-server/content/README.md new file mode 100644 index 00000000..0a086b7e --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/README.md @@ -0,0 +1,109 @@ +# {{project-name}} + +{{project-description}} + +## Quick Start + +1. Build the handler component: + ```bash + cd handler + cargo component build --release + cd .. + ``` + +2. Run the MCP server: + ```bash + spin up + ``` + +Your MCP server is now running at http://localhost:3000/mcp + +## Testing + +Test your server with curl: + +```bash +# List tools +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' + +# Call a tool +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "example_tool", + "arguments": "{\"message\": \"Hello, MCP!\"}" + }, + "id": 2 + }' +``` + +## Development + +The MCP handler is in the `handler/` directory. To add new functionality: + +1. **Add Tools**: Edit `handler/src/lib.rs` and update the `list_tools()` and `call_tool()` functions +2. **Add Resources**: Implement `list_resources()` and `read_resource()` +3. **Add Prompts**: Implement `list_prompts()` and `get_prompt()` + +After making changes, rebuild the handler: + +```bash +cd handler +cargo component build --release +cd .. +``` + +## Deployment + +Deploy to Spin Cloud: + +```bash +spin cloud deploy +``` + +## MCP Client Configuration + +### Claude Desktop + +Add to your Claude Desktop configuration: + +```json +{ + "mcpServers": { + "{{project-name | kebab_case}}": { + "url": "http://127.0.0.1:3000/mcp", + "transport": "http" + } + } +} +``` + +### Cursor + +Add to your Cursor settings: + +```json +{ + "mcp": { + "servers": { + "{{project-name | kebab_case}}": { + "url": "http://127.0.0.1:3000/mcp", + "transport": "http" + } + } + } +} +``` + +## Architecture + +This MCP server uses: +- A pre-built gateway component ({{gateway-package}}) that handles the MCP protocol +- Your custom handler component that implements the business logic + +The gateway is published to {{registry}} and handles all HTTP and JSON-RPC protocol details, while your handler focuses on implementing tools, resources, and prompts. \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml b/packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml new file mode 100644 index 00000000..8961df79 --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "{{project-name | snake_case}}" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "0.16.0" +serde_json = "1.0" + +[package.metadata.component] +package = "component:{{project-name | kebab_case}}" + +[workspace] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs b/packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs new file mode 100644 index 00000000..e8737226 --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs @@ -0,0 +1,109 @@ +use serde_json::{json, Value}; + +wit_bindgen::generate!({ + world: "mcp-handler", + path: "./wit", + exports: { + "component:mcp/handler": Component + } +}); + +use exports::component::mcp::handler::{ + Guest, Tool, ToolResult, ResourceInfo, ResourceContents, Prompt, PromptMessage, Error as McpError +}; + +struct Component; + +impl Guest for Component { + fn list_tools() -> Vec { + vec![ + Tool { + name: "example_tool".to_string(), + description: "An example tool that echoes input".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo" + } + }, + "required": ["message"] + }).to_string(), + }, + // Add more tools here + ] + } + + fn call_tool(name: String, arguments: String) -> ToolResult { + let args: Value = match serde_json::from_str(&arguments) { + Ok(v) => v, + Err(e) => return ToolResult::Error(McpError { + code: -32602, + message: format!("Invalid JSON arguments: {}", e), + data: None, + }), + }; + + match name.as_str() { + "example_tool" => { + let message = args["message"].as_str().unwrap_or("No message provided"); + ToolResult::Text(format!("Echo: {}", message)) + } + _ => ToolResult::Error(McpError { + code: -32601, + message: format!("Unknown tool: {}", name), + data: None, + }), + } + } + + fn list_resources() -> Vec { + vec![ + // Add resources here + // Example: + // ResourceInfo { + // uri: "example://resource".to_string(), + // name: "Example Resource".to_string(), + // description: Some("An example resource".to_string()), + // mime_type: Some("text/plain".to_string()), + // } + ] + } + + fn read_resource(uri: String) -> Result { + // Implement resource reading logic here + Err(McpError { + code: -32601, + message: format!("Resource not found: {}", uri), + data: None, + }) + } + + fn list_prompts() -> Vec { + vec![ + // Add prompts here + // Example: + // Prompt { + // name: "greeting".to_string(), + // description: Some("Generate a greeting".to_string()), + // arguments: vec![ + // PromptArgument { + // name: "name".to_string(), + // description: Some("Name to greet".to_string()), + // required: true, + // } + // ], + // } + ] + } + + fn get_prompt(name: String, _arguments: String) -> Result, McpError> { + // Implement prompt generation logic here + Err(McpError { + code: -32601, + message: format!("Prompt not found: {}", name), + data: None, + }) + } +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/mcp-server/content/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/spin.toml b/packages/ftl-cli/src/templates/mcp-server/content/spin.toml new file mode 100644 index 00000000..468a5a7a --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/content/spin.toml @@ -0,0 +1,19 @@ +spin_manifest_version = 2 + +[application] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +authors = ["{{authors}}"] +description = "{{project-description}}" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "{{registry}}", package = "{{gateway-package}}", version = "{{gateway-version}}" } + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler/target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt new file mode 100644 index 00000000..b1f8d03a --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt @@ -0,0 +1,11 @@ +[[trigger.http]] +route = "{{route}}" +component = "mcp-gateway-{{project-name | kebab_case}}" + +[component.mcp-gateway-{{project-name | kebab_case}}] +# Using the published gateway component +source = { registry = "{{registry}}", package = "{{gateway-package}}", version = "{{gateway-version}}" } + +[component.mcp-gateway-{{project-name | kebab_case}}.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "{{project-name | kebab_case}}-handler/target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml b/packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml new file mode 100644 index 00000000..89ab556c --- /dev/null +++ b/packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml @@ -0,0 +1,16 @@ +manifest_version = "1" +id = "mcp-server" +description = "Complete MCP server with handler and gateway" +tags = ["mcp", "http", "rust"] + +[parameters] +project-description = { type = "string", prompt = "Description", default = "An MCP server with custom tools" } +route = { type = "string", prompt = "HTTP route", default = "/mcp" } +gateway-version = { type = "string", prompt = "MCP gateway version", default = "0.1.0" } +registry = { type = "string", prompt = "Registry for gateway component (e.g. ghcr.io)", default = "ghcr.io" } +gateway-package = { type = "string", prompt = "Gateway package name", default = "bowlofarugula:mcp-gateway" } + +[add_component] +skip_files = ["spin.toml", "README.md", "Makefile", ".gitignore"] +[add_component.snippets] +component = "component.txt" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/.gitignore b/packages/ftl-cli/src/templates/rust/content/.gitignore new file mode 100644 index 00000000..b7cb55af --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/.gitignore @@ -0,0 +1,5 @@ +target/ +Cargo.lock +.ftl/ +*.wasm +.spin/ \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/Makefile b/packages/ftl-cli/src/templates/rust/content/Makefile new file mode 100644 index 00000000..58d60285 --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/Makefile @@ -0,0 +1,21 @@ +.PHONY: build test clean registry-push + +build: + cd handler && cargo component build --release --target wasm32-wasip1 + +test: + cd handler && cargo test + +clean: + cd handler && cargo clean + +registry-push: build + @echo "Pushing to registry..." + @VERSION=$$(cd handler && cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') && \ + USERNAME=$$(git config user.name | tr '[:upper:]' '[:lower:]' | tr ' ' '-') && \ + PROJECT={{project-name | snake_case}} && \ + echo "Pushing ghcr.io/$$USERNAME/$$PROJECT:$$VERSION" && \ + wkg oci push ghcr.io/$$USERNAME/$$PROJECT:$$VERSION handler/target/wasm32-wasip1/release/$$PROJECT.wasm + +up: + spin up \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/README.md b/packages/ftl-cli/src/templates/rust/content/README.md new file mode 100644 index 00000000..aa6c5dd1 --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/README.md @@ -0,0 +1,95 @@ +# {{project-name | kebab_case}} + +{{project-description}} + +## Structure + +This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. + +- `handler/` - The Rust implementation of your MCP handler +- `ftl.toml` - FTL configuration file +- `spin.toml` - Spin application manifest + +## Development + +### Prerequisites + +- Rust with `wasm32-wasip1` target +- FTL CLI + +### Building + +```bash +ftl build +# or +make build +``` + +### Testing + +```bash +ftl test +# or +make test +``` + +### Running Locally + +```bash +ftl serve +# or +make serve +``` + +The tool will be available at `http://localhost:3000/mcp` + +### Example Usage + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "{{project-name | snake_case}}", + "arguments": { + "input": "Hello, world!" + } + }, + "id": 1 + }' +``` + +## Deployment + +```bash +ftl deploy +# or +make deploy +``` + +## Implementing Your Tool + +Edit `handler/src/lib.rs` to implement your tool's functionality: + +1. Modify `list_tools()` to define your tools +2. Implement the tool logic in `call_tool()` +3. Optionally implement resources and prompts + +## Configuration + +### Memory Allocation + +The WebAssembly memory allocator size can be adjusted in `handler/src/lib.rs`: + +```rust +const ARENA_SIZE: usize = 1 * 1024 * 1024; // Default: 1MB +``` + +### Runtime Configuration + +Edit `ftl.toml` to configure: +- Allowed external hosts +- Build optimization flags +- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/ftl.toml b/packages/ftl-cli/src/templates/rust/content/ftl.toml new file mode 100644 index 00000000..3b4e9c9c --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/ftl.toml @@ -0,0 +1,23 @@ +[tool] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +description = "{{project-description}}" + +[build] +# Build commands are run from the handler directory +commands = [ + "cargo build --release --target wasm32-wasip1" +] + +[optimization] +# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test +flags = [ + "-O4", + "-Oz", +] + +[runtime] +# List of external hosts this tool is allowed to make HTTP requests to. +# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). +# Leave empty to deny all external requests. +allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml b/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml new file mode 100644 index 00000000..207e9ac8 --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "{{project-name | snake_case}}" +version = "0.1.0" +edition = "2021" + +[dependencies] +wit-bindgen = "0.16.0" +serde_json = "1.0" + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "component:{{project-name | kebab_case}}" + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +panic = "abort" +strip = "symbols" + +[profile.dev] +opt-level = 1 +debug = true + +[workspace] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs b/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs new file mode 100644 index 00000000..567a54ad --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs @@ -0,0 +1,110 @@ +use serde_json::{json, Value}; + +wit_bindgen::generate!({ + world: "mcp-handler", + path: "./wit", + exports: { + "component:mcp/handler": Component + } +}); + +use exports::component::mcp::handler::{ + Guest, Tool, ToolResult, ResourceInfo, ResourceContents, Prompt, PromptMessage, Error as McpError +}; + +struct Component; + +impl Guest for Component { + fn list_tools() -> Vec { + vec![ + Tool { + name: "{{project-name | snake_case}}".to_string(), + description: "{{project-description}}".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "Input to process" + } + }, + "required": ["input"] + }).to_string(), + }, + // Add more tools here + ] + } + + fn call_tool(name: String, arguments: String) -> ToolResult { + let args: Value = match serde_json::from_str(&arguments) { + Ok(v) => v, + Err(e) => return ToolResult::Error(McpError { + code: -32602, + message: format!("Invalid JSON arguments: {}", e), + data: None, + }), + }; + + match name.as_str() { + "{{project-name | snake_case}}" => { + let input = args["input"].as_str().unwrap_or("No input provided"); + // TODO: Implement your tool logic here + ToolResult::Text(format!("Processed: {}", input)) + } + _ => ToolResult::Error(McpError { + code: -32601, + message: format!("Unknown tool: {}", name), + data: None, + }), + } + } + + fn list_resources() -> Vec { + vec![ + // Add resources here + // Example: + // ResourceInfo { + // uri: "example://resource".to_string(), + // name: "Example Resource".to_string(), + // description: Some("An example resource".to_string()), + // mime_type: Some("text/plain".to_string()), + // } + ] + } + + fn read_resource(uri: String) -> Result { + // Implement resource reading logic here + Err(McpError { + code: -32601, + message: format!("Resource not found: {}", uri), + data: None, + }) + } + + fn list_prompts() -> Vec { + vec![ + // Add prompts here + // Example: + // Prompt { + // name: "greeting".to_string(), + // description: Some("Generate a greeting".to_string()), + // arguments: vec![ + // PromptArgument { + // name: "name".to_string(), + // description: Some("Name to greet".to_string()), + // required: true, + // } + // ], + // } + ] + } + + fn get_prompt(name: String, _arguments: String) -> Result, McpError> { + // Implement prompt generation logic here + Err(McpError { + code: -32601, + message: format!("Prompt not found: {}", name), + data: None, + }) + } +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/rust/content/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/spin.toml b/packages/ftl-cli/src/templates/rust/content/spin.toml new file mode 100644 index 00000000..9caba145 --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/spin.toml @@ -0,0 +1,20 @@ +spin_manifest_version = 2 + +[application] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +authors = ["{{authors}}"] +description = "{{project-description}}" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } +build.command = "make build" + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler/target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt new file mode 100644 index 00000000..aff6d2e2 --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt @@ -0,0 +1,12 @@ +[[trigger.http]] +route = "{{route}}" +component = "mcp-gateway-{{project-name | kebab_case}}" + +[component.mcp-gateway-{{project-name | kebab_case}}] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } +build.command = "cd {{project-name | kebab_case}} && make build" + +[component.mcp-gateway-{{project-name | kebab_case}}.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "{{project-name | kebab_case}}/handler/target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/metadata/spin-template.toml b/packages/ftl-cli/src/templates/rust/metadata/spin-template.toml new file mode 100644 index 00000000..7a411353 --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/metadata/spin-template.toml @@ -0,0 +1,13 @@ +manifest_version = "1" +id = "ftl-rust" +description = "FTL Rust MCP tool" +tags = ["mcp", "http", "rust", "ftl"] + +[parameters] +project-description = { type = "string", prompt = "Description", default = "An MCP tool written in Rust" } +route = { type = "string", prompt = "HTTP route", default = "/mcp" } + +[add_component] +skip_files = ["spin.toml", "README.md", ".gitignore", "ftl.toml"] +[add_component.snippets] +component = "component.txt" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/.gitignore b/packages/ftl-cli/src/templates/typescript/content/.gitignore new file mode 100644 index 00000000..1a7f6419 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.ftl/ +*.wasm +.spin/ +handler/src/generated/ +handler.wasm \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/Makefile b/packages/ftl-cli/src/templates/typescript/content/Makefile new file mode 100644 index 00000000..0aed9fb3 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/Makefile @@ -0,0 +1,25 @@ +.PHONY: build test clean install registry-push + +build: install + cd handler && npm run build + +install: + cd handler && npm install + +test: install + cd handler && npm test + +clean: + cd handler && npm run clean + cd handler && rm -rf node_modules + +registry-push: build + @echo "Pushing to registry..." + @VERSION=$$(cd handler && node -p "require('./package.json').version") && \ + USERNAME=$$(git config user.name | tr '[:upper:]' '[:lower:]' | tr ' ' '-') && \ + PROJECT={{project-name | snake_case}} && \ + echo "Pushing ghcr.io/$$USERNAME/$$PROJECT:$$VERSION" && \ + wkg oci push ghcr.io/$$USERNAME/$$PROJECT:$$VERSION handler/dist/handler.wasm + +up: + spin up diff --git a/packages/ftl-cli/src/templates/typescript/content/README.md b/packages/ftl-cli/src/templates/typescript/content/README.md new file mode 100644 index 00000000..b56c045b --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/README.md @@ -0,0 +1,89 @@ +# {{project-name | kebab_case}} + +{{project-description}} + +## Structure + +This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. + +- `handler/` - The TypeScript implementation of your MCP handler +- `ftl.toml` - FTL configuration file +- `spin.toml` - Spin application manifest + +## Development + +### Prerequisites + +- Node.js >= 20.0.0 +- FTL CLI + +### Building + +```bash +ftl build +# or +make build +``` + +### Testing + +```bash +ftl test +# or +make test +``` + +### Running Locally + +```bash +ftl serve +# or +make serve +``` + +The tool will be available at `http://localhost:3000/mcp` + +### Example Usage + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "{{project-name | snake_case}}", + "arguments": { + "input": "Hello, world!" + } + }, + "id": 1 + }' +``` + +## Deployment + +```bash +ftl deploy +# or +make deploy +``` + +## Implementing Your Tool + +Edit `handler/src/index.ts` to implement your tool's functionality: + +1. Modify `listTools()` to define your tools +2. Implement the tool logic in `callTool()` +3. Optionally implement resources and prompts + +## Type Safety + +This template uses `jco` to generate TypeScript types from the WIT interface definition. The types are generated in `handler/src/generated/` when you run `npm run build`. + +## Configuration + +Edit `ftl.toml` to configure: +- Allowed external hosts +- Build optimization flags +- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/ftl.toml b/packages/ftl-cli/src/templates/typescript/content/ftl.toml new file mode 100644 index 00000000..cccf19e9 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/ftl.toml @@ -0,0 +1,24 @@ +[tool] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +description = "{{project-description}}" + +[build] +# Build commands are run from the handler directory +commands = [ + "cd handler && npm install", + "cd handler && npm run build" +] + +[optimization] +# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test +flags = [ + "-O4", + "-Oz", +] + +[runtime] +# List of external hosts this tool is allowed to make HTTP requests to. +# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). +# Leave empty to deny all external requests. +allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/package.json b/packages/ftl-cli/src/templates/typescript/content/handler/package.json new file mode 100644 index 00000000..6a31f32b --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/package.json @@ -0,0 +1,24 @@ +{ + "name": "{{project-name | kebab_case}}-handler", + "version": "0.1.0", + "type": "module", + "description": "{{project-description}}", + "main": "dist/index.js", + "scripts": { + "build": "npm run build:types && npm run build:js && npm run build:component", + "build:types": "jco types wit/mcp.wit -o dist/generated", + "build:js": "tsc", + "build:component": "jco componentize dist/index.js --wit wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", + "test": "vitest", + "clean": "rm -rf dist" + }, + "devDependencies": { + "@bytecodealliance/jco": "^1.8.1", + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "vitest": "^1.0.0" + }, + "engines": { + "node": ">=20.0.0" + } +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts b/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts new file mode 100644 index 00000000..30f85907 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts @@ -0,0 +1,83 @@ +// Export the handler implementation for componentize-js +export const handler = { + listTools() { + return [{ + name: '{{project-name | snake_case}}', + description: '{{project-description}}', + inputSchema: JSON.stringify({ + type: 'object', + properties: { + input: { + type: 'string', + description: 'Input to process' + } + }, + required: ['input'] + }) + }]; + }, + + callTool(name: string, argumentsStr: string) { + let args: any; + try { + args = JSON.parse(argumentsStr); + } catch (e) { + return { + tag: 'error', + val: { + code: -32602, + message: `Invalid JSON arguments: ${e}`, + data: undefined + } + }; + } + + switch (name) { + case '{{project-name | snake_case}}': { + const input = args.input || 'No input provided'; + // TODO: Implement your tool logic here + return { + tag: 'text', + val: `Processed: ${input}` + }; + } + default: + return { + tag: 'error', + val: { + code: -32601, + message: `Unknown tool: ${name}`, + data: undefined + } + }; + } + }, + + listResources() { + return []; + }, + + readResource(uri: string) { + // According to WIT, this returns ResourceContents, not a Result + // So we need to return a valid ResourceContents or throw + throw { + code: -32601, + message: `Resource not found: ${uri}`, + data: undefined + }; + }, + + listPrompts() { + return []; + }, + + getPrompt(name: string, argumentsStr: string) { + // According to WIT, this returns Array, not a Result + // So we need to return an array or throw + throw { + code: -32601, + message: `Prompt not found: ${name}`, + data: undefined + }; + } +}; \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts b/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts new file mode 100644 index 00000000..c9b1b765 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import { Handler } from '../src/index'; + +describe('Handler', () => { + describe('listTools', () => { + it('should return tool metadata', () => { + const tools = Handler.listTools(); + expect(tools).toHaveLength(1); + expect(tools[0].name).toBe('{{project-name | snake_case}}'); + expect(tools[0].description).toBe('{{project-description}}'); + }); + }); + + describe('callTool', () => { + it('should process input correctly', () => { + const args = JSON.stringify({ input: 'test input' }); + const result = Handler.callTool('{{project-name | snake_case}}', args); + + expect(result.tag).toBe('text'); + if (result.tag === 'text') { + expect(result.val).toContain('test input'); + } + }); + + it('should handle invalid JSON', () => { + const result = Handler.callTool('{{project-name | snake_case}}', 'invalid json'); + + expect(result.tag).toBe('error'); + if (result.tag === 'error') { + expect(result.val.code).toBe(-32602); + } + }); + + it('should handle unknown tool', () => { + const args = JSON.stringify({ input: 'test' }); + const result = Handler.callTool('unknown_tool', args); + + expect(result.tag).toBe('error'); + if (result.tag === 'error') { + expect(result.val.code).toBe(-32601); + } + }); + }); +}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json b/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json new file mode 100644 index 00000000..090ee7c3 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noEmit": false, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "types": ["node", "vitest/globals"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test"] +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/vitest.config.ts b/packages/ftl-cli/src/templates/typescript/content/handler/vitest.config.ts new file mode 100644 index 00000000..3942f585 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.ts'], + coverage: { + reporter: ['text', 'json', 'html'], + exclude: ['node_modules/', 'dist/', 'src/generated/'] + } + } +}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit new file mode 100644 index 00000000..cfaf6acd --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit @@ -0,0 +1,82 @@ +package component:mcp@0.1.0; + +/// MCP handler interface +interface handler { + /// Tool definition + record tool { + name: string, + description: string, + input-schema: string, // JSON Schema as string + } + + /// Resource definition + record resource-info { + uri: string, + name: string, + description: option, + mime-type: option, + } + + /// Resource contents + record resource-contents { + uri: string, + mime-type: option, + text: option, + blob: option>, + } + + /// Prompt definition + record prompt { + name: string, + description: option, + arguments: list, + } + + record prompt-argument { + name: string, + description: option, + required: bool, + } + + /// Prompt message + record prompt-message { + role: string, + content: string, + } + + /// Error type + record error { + code: s32, + message: string, + data: option, + } + + /// Tool call result + variant tool-result { + text(string), + error(error), + } + + /// List available tools + list-tools: func() -> list; + + /// Call a tool + call-tool: func(name: string, arguments: string) -> tool-result; + + /// List available resources + list-resources: func() -> list; + + /// Read a resource + read-resource: func(uri: string) -> result; + + /// List available prompts + list-prompts: func() -> list; + + /// Get a prompt + get-prompt: func(name: string, arguments: string) -> result, error>; +} + +// World for MCP handler components (libraries) +world mcp-handler { + export handler; +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/spin.toml b/packages/ftl-cli/src/templates/typescript/content/spin.toml new file mode 100644 index 00000000..e79d06ab --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/spin.toml @@ -0,0 +1,20 @@ +spin_manifest_version = 2 + +[application] +name = "{{project-name | kebab_case}}" +version = "0.1.0" +authors = ["{{authors}}"] +description = "{{project-description}}" + +[[trigger.http]] +route = "/mcp" +component = "mcp-gateway" + +[component.mcp-gateway] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } +build.command = "make build" + +[component.mcp-gateway.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "./handler/dist/handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/ftl.toml.hbs b/packages/ftl-cli/src/templates/typescript/ftl.toml.hbs deleted file mode 100644 index 8eccd2d9..00000000 --- a/packages/ftl-cli/src/templates/typescript/ftl.toml.hbs +++ /dev/null @@ -1,11 +0,0 @@ -[tool] -name = "{{name}}" -version = "0.1.0" -description = "{{description}}" -language = "typescript" - -[build] -profile = "release" - -[runtime] -allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/index.ts.hbs b/packages/ftl-cli/src/templates/typescript/index.ts.hbs deleted file mode 100644 index 9fac00ad..00000000 --- a/packages/ftl-cli/src/templates/typescript/index.ts.hbs +++ /dev/null @@ -1,48 +0,0 @@ -import { ftlTool, Tool, ToolResult, ToolError } from '@fastertools/ftl-sdk-ts'; - -/** - * {{description}} - */ -class {{tool_name_class}} extends Tool { - get name(): string { - return '{{name}}'; - } - - get description(): string { - return '{{description}}'; - } - - get inputSchema() { - return { - type: 'object', - properties: { - input: { - type: 'string', - description: 'Input text to process' - } - }, - required: ['input'] - }; - } - - execute(args: { input: string }): ToolResult { - const { input } = args; - - if (!input) { - throw ToolError.invalidArguments('Input is required'); - } - - // TODO: Implement your tool logic here - const result = `Processed: ${input}`; - - return ToolResult.text(result); - } -} - -// Export the class for testing -export { {{tool_name_class}} }; - -// Register the tool with FTL (only when not in test environment) -if (typeof process === 'undefined' || process.env.NODE_ENV !== 'test') { - ftlTool(new {{tool_name_class}}()); -} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt new file mode 100644 index 00000000..89a39094 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt @@ -0,0 +1,12 @@ +[[trigger.http]] +route = "{{route}}" +component = "mcp-gateway-{{project-name | kebab_case}}" + +[component.mcp-gateway-{{project-name | kebab_case}}] +# Using the published gateway component +source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } +build.command = "cd {{project-name | kebab_case}} && make build" + +[component.mcp-gateway-{{project-name | kebab_case}}.dependencies] +# Link to the local handler component +"component:mcp/handler@0.1.0" = { path = "{{project-name | kebab_case}}/handler/dist/handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/metadata/spin-template.toml b/packages/ftl-cli/src/templates/typescript/metadata/spin-template.toml new file mode 100644 index 00000000..7397ca80 --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/metadata/spin-template.toml @@ -0,0 +1,13 @@ +manifest_version = "1" +id = "ftl-typescript" +description = "FTL TypeScript MCP tool" +tags = ["mcp", "http", "typescript", "ftl"] + +[parameters] +project-description = { type = "string", prompt = "Description", default = "An MCP tool written in TypeScript" } +route = { type = "string", prompt = "HTTP route", default = "/mcp" } + +[add_component] +skip_files = ["spin.toml", "README.md", ".gitignore", "ftl.toml"] +[add_component.snippets] +component = "component.txt" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/package.json.hbs b/packages/ftl-cli/src/templates/typescript/package.json.hbs deleted file mode 100644 index 81c08d17..00000000 --- a/packages/ftl-cli/src/templates/typescript/package.json.hbs +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "{{name}}", - "version": "0.1.0", - "description": "{{description}}", - "main": "src/index.ts", - "type": "module", - "scripts": { - "build": "webpack && mkdirp dist && j2w -i build/bundle.js -o dist/{{name}}.wasm", - "dev": "webpack --mode=development --watch", - "test": "vitest run", - "test:watch": "vitest watch" - }, - "dependencies": { - "@fastertools/ftl-sdk-ts": "^{{sdk_version}}", - "@spinframework/build-tools": "^1.0.1", - "@spinframework/wasi-http-proxy": "^1.0.0" - }, - "devDependencies": { - "typescript": "^4.8.4", - "ts-loader": "^9.4.1", - "webpack": "^5.74.0", - "webpack-cli": "^4.10.0", - "vitest": "^2.0.4", - "mkdirp": "^3.0.1", - "@types/node": "^20.0.0" - } -} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/tool.test.ts.hbs b/packages/ftl-cli/src/templates/typescript/tool.test.ts.hbs deleted file mode 100644 index 46afd67b..00000000 --- a/packages/ftl-cli/src/templates/typescript/tool.test.ts.hbs +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; -import { {{tool_name_class}} } from '../src/index'; - -describe('{{tool_name_class}}', () => { - let tool: {{tool_name_class}}; - - beforeEach(() => { - tool = new {{tool_name_class}}(); - }); - - it('should have correct metadata', () => { - expect(tool.name).toBe('{{name}}'); - expect(tool.description).toBe('{{description}}'); - }); - - it('should define input schema', () => { - const schema = tool.inputSchema; - expect(schema.type).toBe('object'); - expect(schema.properties.input).toBeDefined(); - expect(schema.required).toContain('input'); - }); - - it('should process valid input', () => { - const result = tool.execute({ input: 'test' }); - expect(result.content).toHaveLength(1); - expect(result.content[0].type).toBe('text'); - expect(result.content[0].text).toContain('test'); - }); - - it('should throw error for missing input', () => { - expect(() => tool.execute({} as any)).toThrow('Input is required'); - }); -}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/tsconfig.json b/packages/ftl-cli/src/templates/typescript/tsconfig.json deleted file mode 100644 index 5ba9a995..00000000 --- a/packages/ftl-cli/src/templates/typescript/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "ES6", - "lib": ["ES2020", "WebWorker"], - "outDir": "./build", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", - "types": ["node"] - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "test"] -} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/vitest.config.ts b/packages/ftl-cli/src/templates/typescript/vitest.config.ts deleted file mode 100644 index bdfa621c..00000000 --- a/packages/ftl-cli/src/templates/typescript/vitest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - env: { - NODE_ENV: 'test' - } - }, -}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/webpack.config.js b/packages/ftl-cli/src/templates/typescript/webpack.config.js deleted file mode 100644 index 05493af7..00000000 --- a/packages/ftl-cli/src/templates/typescript/webpack.config.js +++ /dev/null @@ -1,41 +0,0 @@ -import path from 'path'; -import SpinSdkPlugin from "@spinframework/build-tools/plugins/webpack/index.js"; - -const config = async () => { - let SpinPlugin = await SpinSdkPlugin.init() - return { - mode: 'production', - stats: 'errors-only', - entry: './src/index.ts', - experiments: { - outputModule: true, - }, - module: { - rules: [ - { - test: /\.ts$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: ['.ts', '.js'], - }, - output: { - path: path.resolve(process.cwd(), './build'), - filename: 'bundle.js', - module: true, - library: { - type: "module", - } - }, - plugins: [ - SpinPlugin - ], - optimization: { - minimize: false - }, - }; -} -export default config \ No newline at end of file diff --git a/packages/ftl-sdk-rs/Cargo.toml b/packages/ftl-sdk-rs/Cargo.toml deleted file mode 100644 index b5e4a1c4..00000000 --- a/packages/ftl-sdk-rs/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "ftl-sdk-rs" -version.workspace = true -authors.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -description = "Core library for FTL tools - lightweight MCP server for WebAssembly" -readme = "README.md" -homepage.workspace = true -documentation.workspace = true -keywords = ["wasm", "mcp", "tools", "webassembly", "spin"] -categories = ["wasm", "web-programming", "development-tools"] - -[dependencies] -serde.workspace = true -serde_json.workspace = true -thiserror.workspace = true -spin-sdk = "3.1.1" -talc = { version = "4.4", default-features = false, features = ["lock_api"] } - -[dev-dependencies] -tokio = { version = "1", features = ["macros", "rt", "sync"] } \ No newline at end of file diff --git a/packages/ftl-sdk-rs/README.md b/packages/ftl-sdk-rs/README.md deleted file mode 100644 index 532890d3..00000000 --- a/packages/ftl-sdk-rs/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# FTL SDK - -This crate provides the core building blocks for creating FTL tools. It is the open-source foundation of the FTL platform, designed for performance, safety, and efficiency. - -## Purpose - -`ftl-sdk-rs` is the library that developers will use to implement the logic of their tools. It provides the necessary abstractions to integrate with the FTL runtime and the Model Context Protocol (MCP) without needing to know the low-level details. - -## Key Components - -- **`Tool` Trait:** The central abstraction for all FTL tools. It defines the interface that the FTL runtime uses to execute a tool. -- **`ftl_mcp_server!` Macro:** A macro that generates the necessary boilerplate to expose a `Tool` implementation as a WebAssembly component that can be served over MCP. -- **`ftl_mcp_gateway!` Macro:** A macro for creating gateway components that aggregate multiple tools into a single MCP endpoint. -- **Gateway Module:** Provides `McpGateway`, `GatewayConfig`, and related types for building MCP gateways. -- **`ToolResult` and `ToolError`:** Standardized types for returning success and error states from a tool. -- **Prelude:** The `ftl_sdk_rs::prelude` module re-exports the most commonly used items for convenience. - -## Usage - -### Creating a Tool - -To create a new tool, you will typically add `ftl-sdk-rs` as a dependency in your `Cargo.toml` and implement the `Tool` trait. - -```rust -use ftl_sdk_rs::prelude::*; - -#[derive(Clone)] -struct MyTool; - -impl Tool for MyTool { - // ... implementation ... -} - -ftl_sdk_rs::ftl_mcp_server!(MyTool); -``` - -### Creating a Gateway - -To create a gateway that aggregates multiple tools: - -```rust -use ftl_sdk_rs::{ftl_mcp_gateway, gateway::{GatewayConfig, ToolEndpoint}, mcp::ServerInfo}; - -fn create_gateway_config() -> GatewayConfig { - GatewayConfig { - tools: vec![ - ToolEndpoint { - name: "tool1".to_string(), - route: "/tool1".to_string(), - description: None, - }, - // ... more tools - ], - server_info: ServerInfo { - name: "my-gateway".to_string(), - version: "1.0.0".to_string(), - }, - base_url: "".to_string(), - } -} - -ftl_mcp_gateway!(create_gateway_config()); -``` - -For more detailed information on developing tools, please see the main project [documentation](../docs/developing-tools.md). diff --git a/packages/ftl-sdk-rs/src/gateway.rs b/packages/ftl-sdk-rs/src/gateway.rs deleted file mode 100644 index 114bf3ca..00000000 --- a/packages/ftl-sdk-rs/src/gateway.rs +++ /dev/null @@ -1,326 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use spin_sdk::http::Method; - -use crate::{ - mcp::{ - CallToolRequest, ErrorCode, InitializeRequest, InitializeResponse, ListToolsRequest, - ListToolsResponse, McpProtocolVersion, ServerInfo, - }, - types::{JsonRpcRequest, JsonRpcResponse}, -}; - -/// Configuration for a tool endpoint in the gateway -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolEndpoint { - /// Name of the tool - pub name: String, - /// HTTP route to the tool's MCP endpoint - pub route: String, - /// Optional description override - pub description: Option, -} - -/// Configuration for the MCP gateway -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GatewayConfig { - /// List of tool endpoints to proxy - pub tools: Vec, - /// Gateway server info - pub server_info: ServerInfo, - /// Base URL for internal tool requests (e.g., "http://localhost:3000") - pub base_url: String, -} - -/// MCP Gateway that proxies requests to multiple tool servers -pub struct McpGateway { - config: GatewayConfig, -} - -impl McpGateway { - /// Create a new MCP gateway - pub fn new(config: GatewayConfig) -> Self { - Self { config } - } - - /// Handle MCP protocol requests - pub async fn handle_request(&mut self, request: JsonRpcRequest) -> JsonRpcResponse { - match request.method.as_str() { - "initialize" => self.handle_initialize(request), - "tools/list" => self.handle_list_tools(request).await, - "tools/call" => self.handle_call_tool(request).await, - _ => JsonRpcResponse::error( - request.id, - ErrorCode::METHOD_NOT_FOUND.0, - &format!("Method {} not found", request.method), - ), - } - } - - /// Handle initialize request - fn handle_initialize(&self, request: JsonRpcRequest) -> JsonRpcResponse { - let params: InitializeRequest = - match serde_json::from_value(request.params.unwrap_or(serde_json::json!({}))) { - Ok(p) => p, - Err(e) => { - return JsonRpcResponse::error( - request.id, - ErrorCode::INVALID_PARAMS.0, - &format!("Invalid initialize parameters: {e}"), - ); - } - }; - - // Check protocol version compatibility - if params.protocol_version != McpProtocolVersion::V1 { - return JsonRpcResponse::error( - request.id, - ErrorCode::INVALID_REQUEST.0, - &format!( - "Unsupported protocol version: {:?}", - params.protocol_version - ), - ); - } - - let response = InitializeResponse { - protocol_version: McpProtocolVersion::V1, - capabilities: serde_json::json!({ - "tools": {} - }), - server_info: self.config.server_info.clone(), - }; - - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: Some(serde_json::to_value(response).unwrap()), - error: None, - id: request.id.unwrap_or(Value::Null), - } - } - - /// Handle tools/list request - async fn handle_list_tools(&mut self, request: JsonRpcRequest) -> JsonRpcResponse { - let _params: ListToolsRequest = - match serde_json::from_value(request.params.unwrap_or(serde_json::json!({}))) { - Ok(p) => p, - Err(e) => { - return JsonRpcResponse::error( - request.id, - ErrorCode::INVALID_PARAMS.0, - &format!("Invalid list tools parameters: {e}"), - ); - } - }; - - // Always fetch fresh tool list from all endpoints - let mut all_tools = Vec::new(); - - for endpoint in &self.config.tools.clone() { - // Use Spin's service chaining for internal requests - let component_id = endpoint.route.trim_start_matches('/'); - let tool_url = format!("http://{component_id}.spin.internal/mcp"); - - // Create a tools/list request - let list_request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - id: Some(serde_json::Value::Number(1.into())), - method: "tools/list".to_string(), - params: Some(serde_json::to_value(ListToolsRequest {}).unwrap()), - }; - - // Send request to tool endpoint - match self.forward_request(&tool_url, list_request).await { - Ok(response) => { - if let Some(result) = response.result { - if let Ok(list_response) = - serde_json::from_value::(result) - { - for mut tool in list_response.tools { - // Override description if configured - if let Some(desc) = &endpoint.description { - tool.description = Some(desc.clone()); - } - all_tools.push(tool); - } - } - } - } - Err(e) => { - // Log error but continue with other tools - let name = &endpoint.name; - eprintln!("Failed to fetch tools from {name}: {e}"); - } - } - } - - let response = ListToolsResponse { tools: all_tools }; - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: Some(serde_json::to_value(response).unwrap()), - error: None, - id: request.id.unwrap_or(Value::Null), - } - } - - /// Handle tools/call request - async fn handle_call_tool(&self, request: JsonRpcRequest) -> JsonRpcResponse { - let params: CallToolRequest = - match serde_json::from_value(request.params.unwrap_or(serde_json::json!({}))) { - Ok(p) => p, - Err(e) => { - return JsonRpcResponse::error( - request.id, - ErrorCode::INVALID_PARAMS.0, - &format!("Invalid call tool parameters: {e}"), - ); - } - }; - - // Find the endpoint for this tool - let endpoint = match self.config.tools.iter().find(|t| t.name == params.name) { - Some(e) => e, - None => { - return JsonRpcResponse::error( - request.id, - ErrorCode::INVALID_REQUEST.0, - &format!("Tool '{}' not found", params.name), - ); - } - }; - - // Forward the request to the tool's MCP endpoint using Spin's service chaining - // Extract component name from route (e.g., "/echo-tool" -> "echo-tool") - let component_id = endpoint.route.trim_start_matches('/'); - let tool_url = format!("http://{component_id}.spin.internal/mcp"); - let forwarded_request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - id: request.id.clone(), - method: "tools/call".to_string(), - params: Some(serde_json::to_value(params).unwrap()), - }; - - match self.forward_request(&tool_url, forwarded_request).await { - Ok(response) => response, - Err(e) => JsonRpcResponse::error( - request.id, - ErrorCode::INTERNAL_ERROR.0, - &format!("Failed to call tool: {e}"), - ), - } - } - - /// Forward a request to a tool endpoint - async fn forward_request( - &self, - url: &str, - request: JsonRpcRequest, - ) -> Result> { - // Serialize request body - let body = serde_json::to_vec(&request)?; - - // Create and send outgoing request using Spin SDK - let req = spin_sdk::http::Request::builder() - .method(Method::Post) - .uri(url) - .header("Content-Type", "application/json") - .body(body) - .build(); - - // Send the request and await the response - let resp: spin_sdk::http::Response = spin_sdk::http::send(req) - .await - .map_err(|e| format!("HTTP request failed: {e}"))?; - - // Parse response body - let response_body: JsonRpcResponse = serde_json::from_slice(resp.body()) - .map_err(|e| format!("Failed to parse response: {e}"))?; - Ok(response_body) - } -} - -/// Handler for a single gateway instance -pub struct GatewayHandler { - gateway: std::sync::Arc>, -} - -impl GatewayHandler { - pub fn new(config: GatewayConfig) -> Self { - Self { - gateway: std::sync::Arc::new(std::sync::Mutex::new(McpGateway::new(config))), - } - } - - #[allow(clippy::await_holding_lock)] - pub async fn handle_request(&self, req: spin_sdk::http::Request) -> spin_sdk::http::Response { - use spin_sdk::http::Response as SpinResponse; - - // Handle CORS preflight - if req.method() == &spin_sdk::http::Method::Options { - return SpinResponse::builder() - .status(200) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .build(); - } - - // Only accept POST requests - if req.method() != &spin_sdk::http::Method::Post { - return SpinResponse::builder() - .status(405) - .header("Allow", "POST, OPTIONS") - .body("Method not allowed") - .build(); - } - - // Parse request body - let body = req.body(); - let request: JsonRpcRequest = match serde_json::from_slice(body) { - Ok(r) => r, - Err(e) => { - let error_response = JsonRpcResponse::error( - None, - ErrorCode::PARSE_ERROR.0, - &format!("Invalid JSON-RPC request: {e}"), - ); - return SpinResponse::builder() - .status(200) - .header("Content-Type", "application/json") - .header("Access-Control-Allow-Origin", "*") - .body(serde_json::to_vec(&error_response).unwrap()) - .build(); - } - }; - - // Handle request asynchronously - // In WASM/Spin environment, this is safe as we're single-threaded - let response = { - let mut gateway = self.gateway.lock().unwrap(); - gateway.handle_request(request).await - }; - - SpinResponse::builder() - .status(200) - .header("Content-Type", "application/json") - .header("Access-Control-Allow-Origin", "*") - .body(serde_json::to_vec(&response).unwrap()) - .build() - } -} - -/// Macro to create a Spin component for the MCP gateway -#[macro_export] -macro_rules! ftl_mcp_gateway { - ($config:expr) => { - static GATEWAY_HANDLER: std::sync::OnceLock<$crate::gateway::GatewayHandler> = - std::sync::OnceLock::new(); - - #[spin_sdk::http_component] - async fn handle_request(req: spin_sdk::http::Request) -> spin_sdk::http::Response { - let handler = - GATEWAY_HANDLER.get_or_init(|| $crate::gateway::GatewayHandler::new($config)); - handler.handle_request(req).await - } - }; -} diff --git a/packages/ftl-sdk-rs/src/gateway_test.rs b/packages/ftl-sdk-rs/src/gateway_test.rs deleted file mode 100644 index b9a73849..00000000 --- a/packages/ftl-sdk-rs/src/gateway_test.rs +++ /dev/null @@ -1,136 +0,0 @@ -#[cfg(test)] -mod tests { - use serde_json::json; - - use crate::{ - gateway::{GatewayConfig, McpGateway, ToolEndpoint}, - mcp::ServerInfo, - types::JsonRpcRequest, - }; - - #[tokio::test] - async fn test_gateway_initialize() { - let config = GatewayConfig { - tools: vec![], - server_info: ServerInfo { - name: "test-gateway".to_string(), - version: "1.0.0".to_string(), - }, - base_url: "http://localhost".to_string(), - }; - - let mut gateway = McpGateway::new(config); - - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - id: Some(json!(1)), - method: "initialize".to_string(), - params: Some(json!({ - "protocolVersion": "2025-03-26", - "clientInfo": { - "name": "test-client", - "version": "1.0.0" - } - })), - }; - - let response = gateway.handle_request(request).await; - - assert!(response.result.is_some()); - assert!(response.error.is_none()); - - let result = response.result.unwrap(); - assert_eq!(result["protocolVersion"], "2025-03-26"); - assert_eq!(result["serverInfo"]["name"], "test-gateway"); - } - - #[tokio::test] - async fn test_gateway_list_tools_empty() { - let config = GatewayConfig { - tools: vec![], - server_info: ServerInfo { - name: "test-gateway".to_string(), - version: "1.0.0".to_string(), - }, - base_url: "http://localhost".to_string(), - }; - - let mut gateway = McpGateway::new(config); - - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - id: Some(json!(1)), - method: "tools/list".to_string(), - params: Some(json!({})), - }; - - let response = gateway.handle_request(request).await; - - assert!(response.result.is_some()); - let result = response.result.unwrap(); - assert_eq!(result["tools"], json!([])); - } - - #[tokio::test] - async fn test_gateway_invalid_method() { - let config = GatewayConfig { - tools: vec![], - server_info: ServerInfo { - name: "test-gateway".to_string(), - version: "1.0.0".to_string(), - }, - base_url: "http://localhost".to_string(), - }; - - let mut gateway = McpGateway::new(config); - - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - id: Some(json!(1)), - method: "invalid/method".to_string(), - params: Some(json!({})), - }; - - let response = gateway.handle_request(request).await; - - assert!(response.error.is_some()); - assert!(response.result.is_none()); - - let error = response.error.unwrap(); - assert_eq!(error.code, -32601); // MethodNotFound - } - - #[tokio::test] - async fn test_gateway_tool_not_found() { - let config = GatewayConfig { - tools: vec![ToolEndpoint { - name: "tool1".to_string(), - route: "/tool1".to_string(), - description: None, - }], - server_info: ServerInfo { - name: "test-gateway".to_string(), - version: "1.0.0".to_string(), - }, - base_url: "http://localhost".to_string(), - }; - - let mut gateway = McpGateway::new(config); - - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - id: Some(json!(1)), - method: "tools/call".to_string(), - params: Some(json!({ - "name": "nonexistent", - "arguments": {} - })), - }; - - let response = gateway.handle_request(request).await; - - assert!(response.error.is_some()); - let error = response.error.unwrap(); - assert!(error.message.contains("not found")); - } -} diff --git a/packages/ftl-sdk-rs/src/lib.rs b/packages/ftl-sdk-rs/src/lib.rs deleted file mode 100644 index 6d8f0a4c..00000000 --- a/packages/ftl-sdk-rs/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! # FTL Core -//! -//! Core MCP server implementation for building AI agent tools. - -pub mod gateway; -pub mod mcp; -pub mod server; -pub mod spin; -pub mod tool; -pub mod types; - -#[cfg(test)] -mod gateway_test; - -// Re-export commonly used items -pub use gateway::{GatewayConfig, GatewayHandler, McpGateway, ToolEndpoint}; -pub use server::McpServer; -pub use tool::Tool; -pub use types::{JsonRpcRequest, JsonRpcResponse, ToolError, ToolResult}; - -/// Convenient re-exports for tool implementations -pub mod prelude { - pub use serde_json::{self, json}; - - pub use crate::{ - ftl_mcp_server, - tool::Tool, - types::{ToolError, ToolResult}, - }; -} diff --git a/packages/ftl-sdk-rs/src/mcp.rs b/packages/ftl-sdk-rs/src/mcp.rs deleted file mode 100644 index 53b56671..00000000 --- a/packages/ftl-sdk-rs/src/mcp.rs +++ /dev/null @@ -1,167 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -/// MCP protocol version -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum McpProtocolVersion { - #[serde(rename = "2025-03-26")] - V1, -} - -/// JSON-RPC request -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Request { - pub jsonrpc: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - pub method: String, - pub params: Value, -} - -/// JSON-RPC response -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Response { - pub jsonrpc: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, -} - -/// JSON-RPC error -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Error { - pub code: ErrorCode, - pub message: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -/// Error codes -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -#[serde(transparent)] -pub struct ErrorCode(pub i32); - -impl ErrorCode { - pub const PARSE_ERROR: ErrorCode = ErrorCode(-32700); - pub const INVALID_REQUEST: ErrorCode = ErrorCode(-32600); - pub const METHOD_NOT_FOUND: ErrorCode = ErrorCode(-32601); - pub const INVALID_PARAMS: ErrorCode = ErrorCode(-32602); - pub const INTERNAL_ERROR: ErrorCode = ErrorCode(-32603); -} - -/// Initialize request -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InitializeRequest { - #[serde(rename = "protocolVersion")] - pub protocol_version: McpProtocolVersion, - #[serde(rename = "clientInfo")] - pub client_info: ClientInfo, -} - -/// Initialize response -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InitializeResponse { - #[serde(rename = "protocolVersion")] - pub protocol_version: McpProtocolVersion, - pub capabilities: Value, - #[serde(rename = "serverInfo")] - pub server_info: ServerInfo, -} - -/// Client info -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ClientInfo { - pub name: String, - pub version: String, -} - -/// Server info -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ServerInfo { - pub name: String, - pub version: String, -} - -/// List tools request -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ListToolsRequest {} - -/// List tools response -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ListToolsResponse { - pub tools: Vec, -} - -/// Tool definition -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Tool { - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - #[serde(rename = "inputSchema")] - pub input_schema: Value, -} - -/// Call tool request -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CallToolRequest { - pub name: String, - pub arguments: Value, -} - -/// Call tool response -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CallToolResponse { - pub content: Vec, -} - -/// Content item -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Content { - #[serde(rename = "type")] - pub content_type: String, - pub text: String, -} - -impl Response { - /// Create a success response - pub fn success(id: Value, result: Value) -> Self { - Self { - jsonrpc: "2.0".to_string(), - id: Some(id), - result: Some(result), - error: None, - } - } - - /// Create an error response - pub fn error(id: Value, code: ErrorCode, message: String, data: Option) -> Self { - Self { - jsonrpc: "2.0".to_string(), - id: Some(id), - result: None, - error: Some(Error { - code, - message, - data, - }), - } - } - - /// Create an error response without an ID (for parse errors) - pub fn error_without_id(code: ErrorCode, message: String, data: Option) -> Self { - Self { - jsonrpc: "2.0".to_string(), - id: None, - result: None, - error: Some(Error { - code, - message, - data, - }), - } - } -} diff --git a/packages/ftl-sdk-rs/src/server.rs b/packages/ftl-sdk-rs/src/server.rs deleted file mode 100644 index 4634a480..00000000 --- a/packages/ftl-sdk-rs/src/server.rs +++ /dev/null @@ -1,263 +0,0 @@ -use serde_json::{Value, json}; - -use crate::{ - tool::{Tool, ToolInfo}, - types::{JsonRpcError, JsonRpcRequest, JsonRpcResponse, McpError, ToolError}, -}; - -pub struct McpServer { - tool: T, -} - -impl McpServer { - pub fn new(tool: T) -> Self { - Self { tool } - } - - pub fn handle_request(&self, request: JsonRpcRequest) -> JsonRpcResponse { - let request_id = request.id.clone().unwrap_or(serde_json::Value::Null); - match self.process_request(request) { - Ok(result) => JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: Some(result), - error: None, - id: request_id, - }, - Err(e) => JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: None, - error: Some(self.error_to_jsonrpc(e)), - id: request_id, - }, - } - } - - fn process_request(&self, request: JsonRpcRequest) -> Result { - match request.method.as_str() { - "initialize" => self.handle_initialize(request.params), - "tools/list" => self.handle_tools_list(), - "tools/call" => self.handle_tools_call(request.params), - method => Err(McpError::MethodNotFound(method.to_string())), - } - } - - fn handle_initialize(&self, _params: Option) -> Result { - Ok(json!({ - "protocolVersion": "2025-03-26", - "serverInfo": { - "name": self.tool.server_name(), - "version": self.tool.server_version() - }, - "capabilities": self.tool.capabilities() - })) - } - - fn handle_tools_list(&self) -> Result { - let tool_info = ToolInfo::from(&self.tool); - Ok(json!({ - "tools": [{ - "name": tool_info.name, - "description": tool_info.description, - "inputSchema": tool_info.input_schema - }] - })) - } - - fn handle_tools_call(&self, params: Option) -> Result { - let params = params - .ok_or_else(|| McpError::InvalidRequest("Missing params for tools/call".to_string()))?; - - let tool_name = params["name"] - .as_str() - .ok_or_else(|| McpError::InvalidRequest("Missing tool name".to_string()))?; - - if tool_name != self.tool.name() { - return Err(McpError::ToolError(ToolError::InvalidArguments(format!( - "Unknown tool: {tool_name}" - )))); - } - - let arguments = params.get("arguments").cloned().unwrap_or(json!({})); - - match self.tool.call(&arguments) { - Ok(result) => Ok(json!(result)), - Err(e) => Err(McpError::ToolError(e)), - } - } - - fn error_to_jsonrpc(&self, error: McpError) -> JsonRpcError { - match error { - McpError::InvalidRequest(msg) => JsonRpcError::invalid_request(msg), - McpError::MethodNotFound(method) => JsonRpcError::method_not_found(method), - McpError::ToolError(e) => JsonRpcError::internal_error(e.to_string()), - McpError::JsonError(e) => JsonRpcError::internal_error(e.to_string()), - } - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - use crate::{ - tool::Tool, - types::{ToolError, ToolResult}, - }; - - #[derive(Clone)] - struct TestTool; - - impl Tool for TestTool { - fn name(&self) -> &'static str { - "test_tool" - } - - fn description(&self) -> &'static str { - "A test tool" - } - - fn input_schema(&self) -> Value { - json!({ - "type": "object", - "properties": { - "input": {"type": "string"} - }, - "required": ["input"] - }) - } - - fn call(&self, args: &Value) -> Result { - if let Some(input) = args.get("input").and_then(|v| v.as_str()) { - Ok(ToolResult::text(format!("Processed: {input}"))) - } else { - Err(ToolError::InvalidArguments("Missing input".to_string())) - } - } - } - - #[test] - fn test_initialize() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "initialize".to_string(), - params: None, - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_none()); - - let result = response.result.unwrap(); - assert_eq!(result["protocolVersion"], "2025-03-26"); - assert_eq!(result["serverInfo"]["name"], "ftl-test_tool"); - } - - #[test] - fn test_tools_list() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "tools/list".to_string(), - params: None, - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_none()); - - let result = response.result.unwrap(); - let tools = result["tools"].as_array().unwrap(); - assert_eq!(tools.len(), 1); - assert_eq!(tools[0]["name"], "test_tool"); - assert_eq!(tools[0]["description"], "A test tool"); - } - - #[test] - fn test_tools_call_success() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "tools/call".to_string(), - params: Some(json!({ - "name": "test_tool", - "arguments": { - "input": "hello" - } - })), - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_none()); - - let result = response.result.unwrap(); - assert_eq!(result["content"][0]["text"], "Processed: hello"); - } - - #[test] - fn test_tools_call_missing_argument() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "tools/call".to_string(), - params: Some(json!({ - "name": "test_tool", - "arguments": {} - })), - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_some()); - assert_eq!(response.error.unwrap().code, -32603); - } - - #[test] - fn test_unknown_method() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "unknown/method".to_string(), - params: None, - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_some()); - assert_eq!(response.error.unwrap().code, -32601); - } - - #[test] - fn test_missing_params() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "tools/call".to_string(), - params: None, - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_some()); - assert_eq!(response.error.unwrap().code, -32600); - } - - #[test] - fn test_wrong_tool_name() { - let server = McpServer::new(TestTool); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "tools/call".to_string(), - params: Some(json!({ - "name": "wrong_tool", - "arguments": {"input": "test"} - })), - id: Some(json!(1)), - }; - - let response = server.handle_request(request); - assert!(response.error.is_some()); - } -} diff --git a/packages/ftl-sdk-rs/src/spin.rs b/packages/ftl-sdk-rs/src/spin.rs deleted file mode 100644 index d57dfcee..00000000 --- a/packages/ftl-sdk-rs/src/spin.rs +++ /dev/null @@ -1,205 +0,0 @@ -use spin_sdk::http::{Method, Request, Response}; - -use crate::{server::McpServer, tool::Tool, types::JsonRpcRequest}; - -/// Create a handler function for serving a single FTL SDK tool as an MCP -/// server over HTTP -/// -/// This function handles all HTTP/CORS details and delegates MCP protocol -/// handling to the McpServer. -pub fn create_handler( - tool: T, -) -> impl Fn(Request) -> Result + Clone { - move |req: Request| -> Result { - let server = McpServer::new(tool.clone()); - handle_mcp_request(server, req) - } -} - -fn handle_mcp_request(server: McpServer, req: Request) -> Result { - // Handle CORS preflight requests - if *req.method() == Method::Options { - return Ok(Response::builder() - .status(200) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body("") - .build()); - } - - // Only handle POST requests for JSON-RPC - if *req.method() != Method::Post { - return Ok(Response::builder() - .status(405) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body("Method not allowed") - .build()); - } - - // Parse the JSON-RPC request - match read_request_body(&req) { - Ok(body_str) => match serde_json::from_str::(&body_str) { - Ok(json_req) => { - let response_data = server.handle_request(json_req); - let response_json = serde_json::to_string(&response_data) - .map_err(|e| format!("Failed to serialize response: {e}"))?; - - Ok(Response::builder() - .status(200) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body(response_json) - .build()) - } - Err(e) => { - eprintln!("Failed to parse JSON-RPC request: {e}"); - let error_response = - crate::types::JsonRpcResponse::error(None, -32700, "Parse error"); - - let response_json = serde_json::to_string(&error_response) - .map_err(|e| format!("Failed to serialize error response: {e}"))?; - Ok(Response::builder() - .status(400) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body(response_json) - .build()) - } - }, - Err(e) => { - eprintln!("Failed to read request body: {e}"); - let error_response = - crate::types::JsonRpcResponse::error(None, -32700, "Failed to read request body"); - - let response_json = serde_json::to_string(&error_response) - .map_err(|e| format!("Failed to serialize error response: {e}"))?; - Ok(Response::builder() - .status(400) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body(response_json) - .build()) - } - } -} - -fn read_request_body(req: &Request) -> Result { - String::from_utf8(req.body().to_vec()) - .map_err(|e| format!("Failed to parse request body as UTF-8: {e}")) -} - -/// Macro to create the main entry point for a tool server -/// -/// Example: -/// ```rust -/// use ftl_sdk_rs::prelude::*; -/// -/// #[derive(Clone)] -/// struct MyTool; -/// -/// impl Tool for MyTool { -/// fn name(&self) -> &'static str { -/// "my-tool" -/// } -/// fn description(&self) -> &'static str { -/// "Example tool" -/// } -/// fn input_schema(&self) -> serde_json::Value { -/// serde_json::json!({}) -/// } -/// fn call(&self, _args: &serde_json::Value) -> Result { -/// Ok(ToolResult::text("Hello".to_string())) -/// } -/// } -/// -/// // Then use the macro: -/// // ftl_mcp_server!(MyTool); -/// ``` -#[macro_export] -macro_rules! ftl_mcp_server { - ($tool:expr) => { - #[spin_sdk::http_component] - fn handle_mcp_request(req: spin_sdk::http::Request) -> spin_sdk::http::Response { - let server = $crate::McpServer::new($tool); - - // Helper function to create error responses - let create_error_response = |status: u16, message: &str| { - spin_sdk::http::Response::builder() - .status(status) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body(message) - .build() - }; - - match req.method() { - &spin_sdk::http::Method::Options => spin_sdk::http::Response::builder() - .status(200) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body("") - .build(), - &spin_sdk::http::Method::Post => { - let body_str = match String::from_utf8(req.body().to_vec()) { - Ok(s) => s, - Err(_) => { - return create_error_response(400, "Invalid UTF-8 in request body"); - } - }; - - match serde_json::from_str::<$crate::JsonRpcRequest>(&body_str) { - Ok(json_req) => { - let response_data = server.handle_request(json_req); - match serde_json::to_string(&response_data) { - Ok(response_json) => spin_sdk::http::Response::builder() - .status(200) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body(response_json) - .build(), - Err(_) => { - create_error_response(500, "Failed to serialize response") - } - } - } - Err(_) => { - let error_response = - $crate::JsonRpcResponse::error(None, -32700, "Parse error"); - match serde_json::to_string(&error_response) { - Ok(response_json) => spin_sdk::http::Response::builder() - .status(400) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "POST, OPTIONS") - .header("Access-Control-Allow-Headers", "Content-Type") - .header("Content-Type", "application/json") - .body(response_json) - .build(), - Err(_) => { - create_error_response(500, "Failed to serialize error response") - } - } - } - } - } - _ => create_error_response(405, "Method not allowed"), - } - } - }; -} diff --git a/packages/ftl-sdk-rs/src/tool.rs b/packages/ftl-sdk-rs/src/tool.rs deleted file mode 100644 index c869840f..00000000 --- a/packages/ftl-sdk-rs/src/tool.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde_json::Value; - -use crate::types::{ToolError, ToolResult}; - -/// Trait that all FTL Core tools must implement -/// -/// This trait defines a single computational tool optimized for WebAssembly -/// performance. FTL Core follows the "1 tool per server" architecture for -/// maximum efficiency. -pub trait Tool: Clone { - /// The name of the tool (used in MCP tool calls) - fn name(&self) -> &'static str; - - /// Human-readable description of what the tool does - fn description(&self) -> &'static str; - - /// JSON schema for the tool's input parameters - /// - /// Return a JSON object describing the expected input structure. - /// Example: - /// ```json - /// { - /// "type": "object", - /// "properties": { - /// "text": { - /// "type": "string", - /// "description": "The text to process" - /// } - /// }, - /// "required": ["text"] - /// } - /// ``` - fn input_schema(&self) -> Value; - - /// Execute the tool with the provided arguments - /// - /// This is where your tool's core logic goes. The arguments are validated - /// against your input schema before this method is called. - fn call(&self, arguments: &Value) -> Result; - - /// Optional: Custom server name (defaults to tool name) - fn server_name(&self) -> String { - let name = self.name(); - format!("ftl-{name}") - } - - /// Optional: Server version (defaults to "0.0.1") - fn server_version(&self) -> &'static str { - "0.0.1" - } - - /// Optional: Additional server capabilities - fn capabilities(&self) -> Value { - serde_json::json!({ - "tools": {} - }) - } -} - -/// Information about a tool for MCP tool listing -#[derive(Debug)] -pub struct ToolInfo { - pub name: String, - pub description: String, - pub input_schema: Value, -} - -impl From<&T> for ToolInfo { - fn from(tool: &T) -> Self { - Self { - name: tool.name().to_string(), - description: tool.description().to_string(), - input_schema: tool.input_schema(), - } - } -} diff --git a/packages/ftl-sdk-rs/src/types.rs b/packages/ftl-sdk-rs/src/types.rs deleted file mode 100644 index df5dd525..00000000 --- a/packages/ftl-sdk-rs/src/types.rs +++ /dev/null @@ -1,215 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use thiserror::Error; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JsonRpcRequest { - pub jsonrpc: String, - pub method: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub params: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JsonRpcResponse { - pub jsonrpc: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - pub id: Value, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JsonRpcError { - pub code: i32, - pub message: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -#[derive(Debug, Error)] -pub enum McpError { - #[error("Invalid request: {0}")] - InvalidRequest(String), - - #[error("Method not found: {0}")] - MethodNotFound(String), - - #[error("Tool error: {0}")] - ToolError(#[from] ToolError), - - #[error("JSON error: {0}")] - JsonError(#[from] serde_json::Error), -} - -#[derive(Debug, Error)] -pub enum ToolError { - #[error("Invalid arguments: {0}")] - InvalidArguments(String), - - #[error("Execution error: {0}")] - ExecutionError(String), - - #[error("Network error: {0}")] - NetworkError(String), - - #[error("Timeout")] - Timeout, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolResult { - #[serde(rename = "type")] - pub result_type: String, - pub content: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TextContent { - #[serde(rename = "type")] - pub content_type: String, - pub text: String, -} - -impl ToolResult { - pub fn text(content: String) -> Self { - Self { - result_type: "content".to_string(), - content: vec![TextContent { - content_type: "text".to_string(), - text: content, - }], - } - } -} - -impl JsonRpcError { - pub fn invalid_request(message: String) -> Self { - Self { - code: -32600, - message, - data: None, - } - } - - pub fn method_not_found(method: String) -> Self { - Self { - code: -32601, - message: format!("Method not found: {method}"), - data: None, - } - } - - pub fn internal_error(message: String) -> Self { - Self { - code: -32603, - message, - data: None, - } - } -} - -impl JsonRpcResponse { - pub fn error(id: Option, code: i32, message: &str) -> Self { - Self { - jsonrpc: "2.0".to_string(), - result: None, - error: Some(JsonRpcError { - code, - message: message.to_string(), - data: None, - }), - id: id.unwrap_or(serde_json::Value::Null), - } - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_jsonrpc_request_serialization() { - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "test".to_string(), - params: Some(json!({"key": "value"})), - id: Some(json!(1)), - }; - - let serialized = serde_json::to_string(&request).unwrap(); - let deserialized: JsonRpcRequest = serde_json::from_str(&serialized).unwrap(); - - assert_eq!(deserialized.jsonrpc, "2.0"); - assert_eq!(deserialized.method, "test"); - assert_eq!(deserialized.params, Some(json!({"key": "value"}))); - assert_eq!(deserialized.id, Some(json!(1))); - } - - #[test] - fn test_jsonrpc_response_with_result() { - let response = JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: Some(json!({"status": "ok"})), - error: None, - id: json!(1), - }; - - let serialized = serde_json::to_string(&response).unwrap(); - assert!(!serialized.contains("\"error\"")); - assert!(serialized.contains("\"result\"")); - } - - #[test] - fn test_jsonrpc_response_with_error() { - let response = JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: None, - error: Some(JsonRpcError::method_not_found("unknown".to_string())), - id: json!(1), - }; - - let serialized = serde_json::to_string(&response).unwrap(); - assert!(serialized.contains("\"error\"")); - assert!(!serialized.contains("\"result\"")); - } - - #[test] - fn test_tool_result_creation() { - let result = ToolResult::text("Hello, world!".to_string()); - - assert_eq!(result.result_type, "content"); - assert_eq!(result.content.len(), 1); - assert_eq!(result.content[0].content_type, "text"); - assert_eq!(result.content[0].text, "Hello, world!"); - } - - #[test] - fn test_jsonrpc_error_codes() { - let invalid_request = JsonRpcError::invalid_request("bad request".to_string()); - assert_eq!(invalid_request.code, -32600); - - let method_not_found = JsonRpcError::method_not_found("unknown".to_string()); - assert_eq!(method_not_found.code, -32601); - - let internal_error = JsonRpcError::internal_error("server error".to_string()); - assert_eq!(internal_error.code, -32603); - } - - #[test] - fn test_tool_error_display() { - let error = ToolError::InvalidArguments("missing field".to_string()); - assert_eq!(error.to_string(), "Invalid arguments: missing field"); - - let error = ToolError::ExecutionError("failed to process".to_string()); - assert_eq!(error.to_string(), "Execution error: failed to process"); - - let error = ToolError::Timeout; - assert_eq!(error.to_string(), "Timeout"); - } -} diff --git a/packages/ftl-sdk-ts/README.md b/packages/ftl-sdk-ts/README.md deleted file mode 100644 index 7c91cd4d..00000000 --- a/packages/ftl-sdk-ts/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# @fastertools/ftl-sdk-js - -JavaScript SDK for building FTL MCP (Model Context Protocol) tools. - -## Installation - -```bash -npm install @fastertools/ftl-sdk-js -``` - -## Usage - -```javascript -import { createHandler } from '@fastertools/ftl-sdk-js'; - -const handler = createHandler({ - name: 'my-tool', - version: '1.0.0', - description: 'My awesome FTL tool', - tools: { - myTool: { - description: 'Does something useful', - parameters: { - input: { type: 'string', required: true } - }, - execute: async ({ input }) => { - return `Processed: ${input}`; - } - } - } -}); - -export default handler; -``` - -## Documentation - -For full documentation, visit [https://github.com/anthropics/ftl-cli](https://github.com/anthropics/ftl-cli) - -## License - -Apache-2.0 \ No newline at end of file diff --git a/packages/ftl-sdk-ts/package-lock.json b/packages/ftl-sdk-ts/package-lock.json deleted file mode 100644 index 749e33ce..00000000 --- a/packages/ftl-sdk-ts/package-lock.json +++ /dev/null @@ -1,3041 +0,0 @@ -{ - "name": "@fastertools/ftl-sdk-ts", - "version": "0.0.14", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@fastertools/ftl-sdk-ts", - "version": "0.0.14", - "license": "Apache-2.0", - "dependencies": { - "itty-router": "^5.0.18" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.0.0", - "typescript": "^5.0.0", - "vitest": "^2.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", - "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", - "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", - "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", - "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", - "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", - "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", - "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", - "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", - "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", - "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", - "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", - "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", - "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", - "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", - "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", - "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", - "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", - "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", - "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", - "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.2.tgz", - "integrity": "sha512-9pLGGwdzOUBDYi0GNjM97FIA+f92fqSke6joWeBjWXllfNxZBs7qeMF7tvtOIsbY45xkWkxrdwUfUf3MnQa9gA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/itty-router": { - "version": "5.0.18", - "resolved": "https://registry.npmjs.org/itty-router/-/itty-router-5.0.18.tgz", - "integrity": "sha512-mK3ReOt4ARAGy0V0J7uHmArG2USN2x0zprZ+u+YgmeRjXTDbaowDy3kPcsmQY6tH+uHhDgpWit9Vqmv/4rTXwA==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loupe": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", - "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", - "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.1", - "@rollup/rollup-android-arm64": "4.44.1", - "@rollup/rollup-darwin-arm64": "4.44.1", - "@rollup/rollup-darwin-x64": "4.44.1", - "@rollup/rollup-freebsd-arm64": "4.44.1", - "@rollup/rollup-freebsd-x64": "4.44.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", - "@rollup/rollup-linux-arm-musleabihf": "4.44.1", - "@rollup/rollup-linux-arm64-gnu": "4.44.1", - "@rollup/rollup-linux-arm64-musl": "4.44.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-musl": "4.44.1", - "@rollup/rollup-linux-s390x-gnu": "4.44.1", - "@rollup/rollup-linux-x64-gnu": "4.44.1", - "@rollup/rollup-linux-x64-musl": "4.44.1", - "@rollup/rollup-win32-arm64-msvc": "4.44.1", - "@rollup/rollup-win32-ia32-msvc": "4.44.1", - "@rollup/rollup-win32-x64-msvc": "4.44.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", - "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", - "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.9", - "@vitest/mocker": "2.1.9", - "@vitest/pretty-format": "^2.1.9", - "@vitest/runner": "2.1.9", - "@vitest/snapshot": "2.1.9", - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0", - "vite-node": "2.1.9", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.9", - "@vitest/ui": "2.1.9", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/packages/ftl-sdk-ts/package.json b/packages/ftl-sdk-ts/package.json deleted file mode 100644 index 25cc96ae..00000000 --- a/packages/ftl-sdk-ts/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@fastertools/ftl-sdk-ts", - "version": "0.0.18", - "description": "TypeScript SDK for building FTL MCP tools", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "type": "module", - "scripts": { - "build": "tsc", - "test": "vitest run", - "watch": "vitest watch", - "lint": "eslint src/**/*.ts", - "prepublishOnly": "npm run build" - }, - "files": [ - "dist", - "src" - ], - "keywords": [ - "ftl", - "mcp", - "model-context-protocol", - "webassembly", - "spin", - "typescript" - ], - "author": "FTL Contributors", - "license": "Apache-2.0", - "dependencies": { - "itty-router": "^5.0.18" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", - "eslint": "^8.0.0", - "typescript": "^5.0.0", - "vitest": "^2.0.4" - }, - "engines": { - "node": ">=18.0.0" - }, - "publishConfig": { - "access": "public" - } -} \ No newline at end of file diff --git a/packages/ftl-sdk-ts/src/index.ts b/packages/ftl-sdk-ts/src/index.ts deleted file mode 100644 index 439d1a06..00000000 --- a/packages/ftl-sdk-ts/src/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { AutoRouter } from 'itty-router'; -import { McpServer } from './mcp-server.js'; -import { Tool } from './tool.js'; -import { ToolResult, ToolError } from './types.js'; - -// Define FetchEvent type for WebWorker environment -interface FetchEvent extends Event { - request: Request; - respondWith(response: Response | Promise): void; -} - -/** - * Register an FTL tool and set up the fetch event listener - * @param tool - The tool implementation - */ -export function ftlTool(tool: Tool): void { - const server = new McpServer(tool); - const router = AutoRouter(); - - // MCP endpoint - router.post('/mcp', async (request: Request) => { - try { - // In Spin, we use async/await with request.text() - const bodyText = await request.text(); - const jsonRpcRequest = JSON.parse(bodyText); - - // Handle the request - const response = server.handleRequest(jsonRpcRequest); - - return new Response(JSON.stringify(response), { - headers: { 'Content-Type': 'application/json' } - }); - } catch (error: any) { - return new Response(JSON.stringify({ - jsonrpc: '2.0', - error: { - code: -32700, - message: 'Parse error', - data: error.message - }, - id: null - }), { - status: 400, - headers: { 'Content-Type': 'application/json' } - }); - } - }); - - // Health check endpoint - router.get('/health', () => new Response('OK')); - - // Info endpoint - router.get('/', () => new Response(JSON.stringify({ - name: tool.name, - version: tool.version || '0.1.0', - description: tool.description, - mcp_endpoint: '/mcp' - }), { - headers: { 'Content-Type': 'application/json' } - })); - - // 404 handler - router.all('*', () => new Response('Not Found', { status: 404 })); - - // Register fetch event listener - addEventListener('fetch', (event: Event) => { - const fetchEvent = event as FetchEvent; - fetchEvent.respondWith(router.fetch(fetchEvent.request)); - }); -} - -// Re-export everything -export { Tool, ToolResult, ToolError, McpServer }; - -// Default export for convenience -export default { - ftlTool, - Tool, - ToolResult, - ToolError, - McpServer -}; \ No newline at end of file diff --git a/packages/ftl-sdk-ts/src/mcp-server.ts b/packages/ftl-sdk-ts/src/mcp-server.ts deleted file mode 100644 index abea193b..00000000 --- a/packages/ftl-sdk-ts/src/mcp-server.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Tool } from './tool.js'; -import { ToolError, JsonRpcRequest, JsonRpcResponse, JsonRpcError } from './types.js'; - -/** - * MCP initialization parameters - */ -interface InitializeParams { - protocolVersion?: string; - capabilities?: Record; - clientInfo?: { - name: string; - version?: string; - }; -} - -/** - * MCP tools/call parameters - */ -interface ToolsCallParams { - name: string; - arguments?: any; -} - -/** - * MCP Server implementation - */ -export class McpServer { - private tool: Tool; - - constructor(tool: Tool) { - this.tool = tool; - } - - /** - * Handle a JSON-RPC request - */ - handleRequest(request: JsonRpcRequest): JsonRpcResponse { - const requestId = request.id ?? null; - - try { - const result = this.processRequest(request); - - return { - jsonrpc: '2.0', - result, - id: requestId - }; - } catch (error: any) { - return { - jsonrpc: '2.0', - error: this.errorToJsonRpc(error), - id: requestId - }; - } - } - - /** - * Process a request and return the result - */ - private processRequest(request: JsonRpcRequest): any { - switch (request.method) { - case 'initialize': - return this.handleInitialize(request.params as InitializeParams); - - case 'initialized': - return { status: 'ok' }; - - case 'tools/list': - return this.handleToolsList(); - - case 'tools/call': - return this.handleToolsCall(request.params as ToolsCallParams); - - default: - throw new McpError(`Method not found: ${request.method}`, -32601); - } - } - - /** - * Handle initialize request - */ - private handleInitialize(params?: InitializeParams): any { - return { - protocolVersion: '2025-03-26', - serverInfo: { - name: this.tool.serverName, - version: this.tool.serverVersion - }, - capabilities: this.tool.capabilities - }; - } - - /** - * Handle tools/list request - */ - private handleToolsList(): any { - return { - tools: [{ - name: this.tool.name, - description: this.tool.description, - inputSchema: this.tool.inputSchema - }] - }; - } - - /** - * Handle tools/call request - */ - private handleToolsCall(params: ToolsCallParams): any { - if (!params || typeof params !== 'object') { - throw new McpError('Invalid params', -32602); - } - - const { name, arguments: args } = params; - - if (name !== this.tool.name) { - throw new McpError(`Unknown tool: ${name}`, -32602); - } - - try { - // Execute the tool (synchronously) - const result = this.tool.execute(args || {}); - - // Return the result content - return result; - } catch (error: any) { - if (error instanceof ToolError) { - throw new McpError(error.message, -32603, { - code: error.code, - details: error.details - }); - } - throw new McpError(`Tool execution failed: ${error.message}`, -32603); - } - } - - /** - * Convert an error to JSON-RPC format - */ - private errorToJsonRpc(error: any): JsonRpcError { - if (error instanceof McpError) { - return { - code: error.code, - message: error.message, - data: error.data - }; - } - - return { - code: -32603, - message: 'Internal error', - data: error.message - }; - } -} - -/** - * MCP-specific error - */ -class McpError extends Error { - code: number; - data?: any; - - constructor(message: string, code: number, data?: any) { - super(message); - this.name = 'McpError'; - this.code = code; - this.data = data; - } -} \ No newline at end of file diff --git a/packages/ftl-sdk-ts/src/tool.ts b/packages/ftl-sdk-ts/src/tool.ts deleted file mode 100644 index e4370c63..00000000 --- a/packages/ftl-sdk-ts/src/tool.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { ToolResult, ToolError } from './types.js'; - -/** - * JSON Schema type definition - */ -export interface JsonSchema { - type: string; - properties?: Record; - required?: string[]; - [key: string]: any; -} - -/** - * Server capabilities - */ -export interface ServerCapabilities { - tools?: Record; - [key: string]: any; -} - -/** - * Base class for FTL tools - */ -export abstract class Tool { - /** - * The name of the tool (used in MCP tool calls) - */ - abstract get name(): string; - - /** - * Human-readable description of what the tool does - */ - abstract get description(): string; - - /** - * JSON schema for the tool's input parameters - */ - get inputSchema(): JsonSchema { - return { - type: 'object', - properties: {}, - required: [] - }; - } - - /** - * Execute the tool with the provided arguments - * @param args - The arguments object - * @returns The tool result - * @throws ToolError if the tool execution fails - */ - abstract execute(args: any): ToolResult | Promise; - - /** - * Optional: Server name (defaults to tool name) - */ - get serverName(): string { - return `ftl-${this.name}`; - } - - /** - * Optional: Server version - */ - get serverVersion(): string { - return '0.0.1'; - } - - /** - * Optional: Additional server capabilities - */ - get capabilities(): ServerCapabilities { - return { - tools: {} - }; - } - - /** - * Optional: Tool version (for display purposes) - */ - get version(): string | undefined { - return undefined; - } -} \ No newline at end of file diff --git a/packages/ftl-sdk-ts/src/types.ts b/packages/ftl-sdk-ts/src/types.ts deleted file mode 100644 index 18e7db33..00000000 --- a/packages/ftl-sdk-ts/src/types.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Content block type for tool results - */ -export interface ContentBlock { - type: string; - text?: string; - data?: any; -} - -/** - * Result returned by a tool execution - */ -export class ToolResult { - content: ContentBlock[]; - - constructor(content: ContentBlock[]) { - this.content = content; - } - - /** - * Create a text result - */ - static text(text: string): ToolResult { - return new ToolResult([{ type: 'text', text }]); - } - - /** - * Create a JSON result - */ - static json(data: any): ToolResult { - return new ToolResult([{ - type: 'text', - text: JSON.stringify(data, null, 2) - }]); - } - - /** - * Create a multi-part result - */ - static multi(parts: ContentBlock[]): ToolResult { - return new ToolResult(parts); - } -} - -/** - * Error thrown by tool execution - */ -export class ToolError extends Error { - code: string; - details: any; - - constructor(message: string, code: string = 'TOOL_ERROR', details: any = null) { - super(message); - this.name = 'ToolError'; - this.code = code; - this.details = details; - } - - /** - * Create an invalid arguments error - */ - static invalidArguments(message: string): ToolError { - return new ToolError(message, 'INVALID_ARGUMENTS'); - } - - /** - * Create an execution error - */ - static executionError(message: string, details?: any): ToolError { - return new ToolError(message, 'EXECUTION_ERROR', details); - } -} - -/** - * JSON-RPC request - */ -export interface JsonRpcRequest { - jsonrpc: string; - method: string; - params?: any; - id?: string | number | null; -} - -/** - * JSON-RPC response - */ -export interface JsonRpcResponse { - jsonrpc: string; - result?: any; - error?: JsonRpcError; - id: string | number | null; -} - -/** - * JSON-RPC error - */ -export interface JsonRpcError { - code: number; - message: string; - data?: any; -} \ No newline at end of file diff --git a/packages/ftl-sdk-ts/tests/tool.test.ts b/packages/ftl-sdk-ts/tests/tool.test.ts deleted file mode 100644 index 035e16dd..00000000 --- a/packages/ftl-sdk-ts/tests/tool.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Tool, ToolResult } from '../dist/index.js'; - -describe('Tool', () => { - it('should have abstract methods that must be implemented', () => { - // Since Tool is abstract, we need to create a concrete implementation - class MyTool extends Tool { - get name(): string { - return 'my-tool'; - } - get description(): string { - return 'My tool description'; - } - execute(args: any): ToolResult { - return ToolResult.text('test'); - } - } - - const tool = new MyTool(); - expect(tool.name).toBe('my-tool'); - expect(tool.description).toBe('My tool description'); - }); - - it('should return a default input schema', () => { - class MyTool extends Tool { - get name(): string { - return 'my-tool'; - } - get description(): string { - return 'My tool description'; - } - execute(args: any): ToolResult { - return ToolResult.text('test'); - } - } - const tool = new MyTool(); - expect(tool.inputSchema).toEqual({ - type: 'object', - properties: {}, - required: [] - }); - }); - - it('should return a server name based on the tool name', () => { - class MyTool extends Tool { - get name(): string { - return 'my-tool'; - } - get description(): string { - return 'My tool description'; - } - execute(args: any): ToolResult { - return ToolResult.text('test'); - } - } - const tool = new MyTool(); - expect(tool.serverName).toBe('ftl-my-tool'); - }); - - it('should return a default server version', () => { - class MyTool extends Tool { - get name(): string { - return 'my-tool'; - } - get description(): string { - return 'My tool description'; - } - execute(args: any): ToolResult { - return ToolResult.text('test'); - } - } - const tool = new MyTool(); - expect(tool.serverVersion).toBe('0.0.1'); - }); - - it('should return default capabilities', () => { - class MyTool extends Tool { - get name(): string { - return 'my-tool'; - } - get description(): string { - return 'My tool description'; - } - execute(args: any): ToolResult { - return ToolResult.text('test'); - } - } - const tool = new MyTool(); - expect(tool.capabilities).toEqual({ - tools: {} - }); - }); - - it('should allow custom input schema', () => { - class MyTool extends Tool { - get name(): string { - return 'my-tool'; - } - get description(): string { - return 'My tool description'; - } - get inputSchema() { - return { - type: 'object', - properties: { - input: { type: 'string' } - }, - required: ['input'] - }; - } - execute(args: any): ToolResult { - return ToolResult.text('test'); - } - } - const tool = new MyTool(); - expect(tool.inputSchema).toEqual({ - type: 'object', - properties: { - input: { type: 'string' } - }, - required: ['input'] - }); - }); -}); \ No newline at end of file diff --git a/packages/ftl-sdk-ts/tests/types.test.ts b/packages/ftl-sdk-ts/tests/types.test.ts deleted file mode 100644 index c9b33dab..00000000 --- a/packages/ftl-sdk-ts/tests/types.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { ToolResult, ToolError } from '../dist/index.js'; - -describe('ToolResult', () => { - it('should create a text result', () => { - const result = ToolResult.text('Hello, world!'); - expect(result.content).toEqual([ - { type: 'text', text: 'Hello, world!' } - ]); - }); - - it('should create a JSON result', () => { - const data = { foo: 'bar', count: 42 }; - const result = ToolResult.json(data); - expect(result.content).toEqual([ - { type: 'text', text: JSON.stringify(data, null, 2) } - ]); - }); - - it('should create a multi-part result', () => { - const parts = [ - { type: 'text', text: 'Part 1' }, - { type: 'text', text: 'Part 2' }, - { type: 'data', data: { key: 'value' } } - ]; - const result = ToolResult.multi(parts); - expect(result.content).toEqual(parts); - }); -}); - -describe('ToolError', () => { - it('should create a basic error', () => { - const error = new ToolError('Something went wrong'); - expect(error.message).toBe('Something went wrong'); - expect(error.code).toBe('TOOL_ERROR'); - expect(error.details).toBeNull(); - expect(error.name).toBe('ToolError'); - }); - - it('should create an error with custom code and details', () => { - const details = { field: 'input', reason: 'too long' }; - const error = new ToolError('Validation failed', 'VALIDATION_ERROR', details); - expect(error.message).toBe('Validation failed'); - expect(error.code).toBe('VALIDATION_ERROR'); - expect(error.details).toEqual(details); - }); - - it('should create an invalid arguments error', () => { - const error = ToolError.invalidArguments('Missing required field: input'); - expect(error.message).toBe('Missing required field: input'); - expect(error.code).toBe('INVALID_ARGUMENTS'); - expect(error.details).toBeNull(); - }); - - it('should create an execution error', () => { - const error = ToolError.executionError('API call failed'); - expect(error.message).toBe('API call failed'); - expect(error.code).toBe('EXECUTION_ERROR'); - expect(error.details).toBeNull(); - }); - - it('should create an execution error with details', () => { - const details = { statusCode: 500, response: 'Internal Server Error' }; - const error = ToolError.executionError('API call failed', details); - expect(error.message).toBe('API call failed'); - expect(error.code).toBe('EXECUTION_ERROR'); - expect(error.details).toEqual(details); - }); -}); \ No newline at end of file diff --git a/packages/ftl-sdk-ts/vitest.config.js b/packages/ftl-sdk-ts/vitest.config.js deleted file mode 100644 index 7382f40e..00000000 --- a/packages/ftl-sdk-ts/vitest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - }, -}); From 8ebb26cc9f66217227ddf1511d83b064c2f703b7 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sat, 5 Jul 2025 23:11:19 -0700 Subject: [PATCH 02/21] feat: Working with SDKs --- Cargo.toml | 2 +- .../content/handler/{wit => .wit}/mcp.wit | 0 .../javascript/content/handler/package.json | 11 +- .../content/handler/src/features.js | 56 ++++ .../javascript/content/handler/src/index.js | 111 +------ .../templates/mcp-server/content/.gitignore | 21 -- .../src/templates/mcp-server/content/Makefile | 37 --- .../templates/mcp-server/content/README.md | 109 ------ .../mcp-server/content/handler/Cargo.toml | 16 - .../mcp-server/content/handler/src/lib.rs | 109 ------ .../templates/mcp-server/content/spin.toml | 19 -- .../metadata/snippets/component.txt | 11 - .../mcp-server/metadata/spin-template.toml | 16 - .../wit => rust/content/handler/.wit}/mcp.wit | 0 .../templates/rust/content/handler/Cargo.toml | 2 + .../rust/content/handler/src/features.rs | 67 ++++ .../templates/rust/content/handler/src/lib.rs | 114 +------ .../content/handler/.wit}/mcp.wit | 0 .../typescript/content/handler/package.json | 11 +- .../content/handler/src/features.ts | 56 ++++ .../typescript/content/handler/src/index.ts | 90 +---- .../typescript/content/handler/tsconfig.json | 2 +- .../typescript/content/handler/wit/mcp.wit | 82 ----- packages/ftl-sdk-rust/Cargo.toml | 13 + packages/ftl-sdk-rust/src/lib.rs | 309 ++++++++++++++++++ packages/ftl-sdk-typescript/package-lock.json | 50 +++ packages/ftl-sdk-typescript/package.json | 29 ++ packages/ftl-sdk-typescript/src/index.ts | 192 +++++++++++ packages/ftl-sdk-typescript/tsconfig.json | 19 ++ 29 files changed, 834 insertions(+), 720 deletions(-) rename packages/ftl-cli/src/templates/javascript/content/handler/{wit => .wit}/mcp.wit (100%) create mode 100644 packages/ftl-cli/src/templates/javascript/content/handler/src/features.js delete mode 100644 packages/ftl-cli/src/templates/mcp-server/content/.gitignore delete mode 100644 packages/ftl-cli/src/templates/mcp-server/content/Makefile delete mode 100644 packages/ftl-cli/src/templates/mcp-server/content/README.md delete mode 100644 packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml delete mode 100644 packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs delete mode 100644 packages/ftl-cli/src/templates/mcp-server/content/spin.toml delete mode 100644 packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt delete mode 100644 packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml rename packages/ftl-cli/src/templates/{mcp-server/content/handler/wit => rust/content/handler/.wit}/mcp.wit (100%) create mode 100644 packages/ftl-cli/src/templates/rust/content/handler/src/features.rs rename packages/ftl-cli/src/templates/{rust/content/handler/wit => typescript/content/handler/.wit}/mcp.wit (100%) create mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/src/features.ts delete mode 100644 packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit create mode 100644 packages/ftl-sdk-rust/Cargo.toml create mode 100644 packages/ftl-sdk-rust/src/lib.rs create mode 100644 packages/ftl-sdk-typescript/package-lock.json create mode 100644 packages/ftl-sdk-typescript/package.json create mode 100644 packages/ftl-sdk-typescript/src/index.ts create mode 100644 packages/ftl-sdk-typescript/tsconfig.json diff --git a/Cargo.toml b/Cargo.toml index bb873ecc..16289ed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = ["packages/ftl-cli"] -exclude = ["demo_hash", "toolkit-*", "packages/ftl-cli/src/templates/**"] +exclude = ["demo_hash", "toolkit-*", "packages/ftl-cli/src/templates/**", "packages/ftl-sdk-rust"] resolver = "2" # Don't build CLI for wasm target diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/javascript/content/handler/.wit/mcp.wit similarity index 100% rename from packages/ftl-cli/src/templates/javascript/content/handler/wit/mcp.wit rename to packages/ftl-cli/src/templates/javascript/content/handler/.wit/mcp.wit diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/package.json b/packages/ftl-cli/src/templates/javascript/content/handler/package.json index 04e1ab2b..0570f375 100644 --- a/packages/ftl-cli/src/templates/javascript/content/handler/package.json +++ b/packages/ftl-cli/src/templates/javascript/content/handler/package.json @@ -5,14 +5,19 @@ "description": "{{project-description}}", "main": "src/index.js", "scripts": { - "build": "npm run build:types && npm run build:component", - "build:types": "jco types wit/mcp.wit -o dist/generated", - "build:component": "jco componentize src/index.js --wit wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", + "build": "npm run build:types && npm run build:bundle && npm run build:component", + "build:types": "jco types ./.wit/mcp.wit -o dist/generated", + "build:bundle": "esbuild src/index.js --bundle --format=esm --platform=node --outfile=dist/index.js", + "build:component": "jco componentize dist/index.js --wit ./.wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", "test": "vitest", "clean": "rm -rf dist" }, + "dependencies": { + "@fastertools/ftl-sdk": "^0.1.0" + }, "devDependencies": { "@bytecodealliance/jco": "^1.8.1", + "esbuild": "^0.19.0", "vitest": "^1.0.0" }, "engines": { diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/src/features.js b/packages/ftl-cli/src/templates/javascript/content/handler/src/features.js new file mode 100644 index 00000000..f78769fb --- /dev/null +++ b/packages/ftl-cli/src/templates/javascript/content/handler/src/features.js @@ -0,0 +1,56 @@ +import { createTool, createResource, createPrompt } from '@fastertools/ftl-sdk'; + +// Define your tools +export const tools = [ + createTool({ + name: 'echo', + description: 'Echo a message back to the user', + inputSchema: { + type: 'object', + properties: { + message: { + type: 'string', + description: 'Message to echo back' + } + }, + required: ['message'] + }, + execute: async (args) => { + const message = args.message || 'Hello, world!'; + return `Echo: ${message}`; + } + }), + // Add more tools here +]; + +// Define your resources +export const resources = [ + // Example: + // createResource({ + // uri: 'file:///example.txt', + // name: 'Example File', + // description: 'An example text file', + // mimeType: 'text/plain', + // read: async () => { + // return 'File contents here'; + // } + // }), +]; + +// Define your prompts +export const prompts = [ + // Example: + // createPrompt({ + // name: 'greeting', + // description: 'Generate a greeting message', + // arguments: [ + // { name: 'name', description: 'Name to greet', required: true } + // ], + // resolve: async (args) => { + // return [ + // { role: 'user', content: `Please greet ${args.name}` }, + // { role: 'assistant', content: `Hello, ${args.name}! How can I help you today?` } + // ]; + // } + // }), +]; \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js b/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js index 976e4fab..0503d93d 100644 --- a/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js +++ b/packages/ftl-cli/src/templates/javascript/content/handler/src/index.js @@ -1,104 +1,9 @@ -// Export the handler implementation for componentize-js -export const handler = { - listTools() { - return [{ - name: '{{project-name | snake_case}}', - description: '{{project-description}}', - inputSchema: JSON.stringify({ - type: 'object', - properties: { - input: { - type: 'string', - description: 'Input to process' - } - }, - required: ['input'] - }) - }]; - }, - - callTool(name, argumentsStr) { - let args; - try { - args = JSON.parse(argumentsStr); - } catch (e) { - return { - tag: 'error', - val: { - code: -32602, - message: `Invalid JSON arguments: ${e}`, - data: undefined - } - }; - } - - switch (name) { - case '{{project-name | snake_case}}': { - const input = args.input || 'No input provided'; - // TODO: Implement your tool logic here - return { - tag: 'text', - val: `Processed: ${input}` - }; - } - default: - return { - tag: 'error', - val: { - code: -32601, - message: `Unknown tool: ${name}`, - data: undefined - } - }; - } - }, - - listResources() { - return [ - // Add resources here - // Example: - // { - // uri: 'example://resource', - // name: 'Example Resource', - // description: 'An example resource', - // mimeType: 'text/plain' - // } - ]; - }, +import { createHandler } from '@fastertools/ftl-sdk'; +import { tools, resources, prompts } from './features.js'; - readResource(uri) { - // According to WIT, this returns ResourceContents, not a Result - // So we need to return a valid ResourceContents or throw - throw { - code: -32601, - message: `Resource not found: ${uri}`, - data: undefined - }; - }, - - listPrompts() { - return [ - // Add prompts here - // Example: - // { - // name: 'greeting', - // description: 'Generate a greeting', - // arguments: [{ - // name: 'name', - // description: 'Name to greet', - // required: true - // }] - // } - ]; - }, - - getPrompt(name, argumentsStr) { - // According to WIT, this returns Array, not a Result - // So we need to return an array or throw - throw { - code: -32601, - message: `Prompt not found: ${name}`, - data: undefined - }; - } -}; \ No newline at end of file +// Export the handler implementation for componentize-js +export const handler = createHandler({ + tools, + resources, + prompts +}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/.gitignore b/packages/ftl-cli/src/templates/mcp-server/content/.gitignore deleted file mode 100644 index 78242103..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/content/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# Rust build artifacts -target/ -Cargo.lock - -# Spin artifacts -.spin/ - -# Editor files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS files -.DS_Store -Thumbs.db - -# Environment files -.env -.env.local \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/Makefile b/packages/ftl-cli/src/templates/mcp-server/content/Makefile deleted file mode 100644 index 3888b50b..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/content/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -.PHONY: build -build: - cd handler && cargo component build --release - -.PHONY: run -run: build - spin up - -.PHONY: test -test: - @echo "Testing MCP server..." - @echo "1. List tools:" - @curl -s -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | jq . - @echo "" - @echo "2. Call example tool:" - @curl -s -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "example_tool", "arguments": "{\"message\": \"Hello from test!\"}"}, "id": 2}' | jq . - -.PHONY: deploy -deploy: build - spin cloud deploy - -.PHONY: clean -clean: - cd handler && cargo clean - -.PHONY: help -help: - @echo "Available commands:" - @echo " make build - Build the handler component" - @echo " make run - Build and run the MCP server" - @echo " make test - Test the MCP server (requires server running)" - @echo " make deploy - Deploy to Spin Cloud" - @echo " make clean - Clean build artifacts" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/README.md b/packages/ftl-cli/src/templates/mcp-server/content/README.md deleted file mode 100644 index 0a086b7e..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/content/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# {{project-name}} - -{{project-description}} - -## Quick Start - -1. Build the handler component: - ```bash - cd handler - cargo component build --release - cd .. - ``` - -2. Run the MCP server: - ```bash - spin up - ``` - -Your MCP server is now running at http://localhost:3000/mcp - -## Testing - -Test your server with curl: - -```bash -# List tools -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' - -# Call a tool -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "example_tool", - "arguments": "{\"message\": \"Hello, MCP!\"}" - }, - "id": 2 - }' -``` - -## Development - -The MCP handler is in the `handler/` directory. To add new functionality: - -1. **Add Tools**: Edit `handler/src/lib.rs` and update the `list_tools()` and `call_tool()` functions -2. **Add Resources**: Implement `list_resources()` and `read_resource()` -3. **Add Prompts**: Implement `list_prompts()` and `get_prompt()` - -After making changes, rebuild the handler: - -```bash -cd handler -cargo component build --release -cd .. -``` - -## Deployment - -Deploy to Spin Cloud: - -```bash -spin cloud deploy -``` - -## MCP Client Configuration - -### Claude Desktop - -Add to your Claude Desktop configuration: - -```json -{ - "mcpServers": { - "{{project-name | kebab_case}}": { - "url": "http://127.0.0.1:3000/mcp", - "transport": "http" - } - } -} -``` - -### Cursor - -Add to your Cursor settings: - -```json -{ - "mcp": { - "servers": { - "{{project-name | kebab_case}}": { - "url": "http://127.0.0.1:3000/mcp", - "transport": "http" - } - } - } -} -``` - -## Architecture - -This MCP server uses: -- A pre-built gateway component ({{gateway-package}}) that handles the MCP protocol -- Your custom handler component that implements the business logic - -The gateway is published to {{registry}} and handles all HTTP and JSON-RPC protocol details, while your handler focuses on implementing tools, resources, and prompts. \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml b/packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml deleted file mode 100644 index 8961df79..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/content/handler/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "{{project-name | snake_case}}" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -wit-bindgen = "0.16.0" -serde_json = "1.0" - -[package.metadata.component] -package = "component:{{project-name | kebab_case}}" - -[workspace] \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs b/packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs deleted file mode 100644 index e8737226..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/content/handler/src/lib.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde_json::{json, Value}; - -wit_bindgen::generate!({ - world: "mcp-handler", - path: "./wit", - exports: { - "component:mcp/handler": Component - } -}); - -use exports::component::mcp::handler::{ - Guest, Tool, ToolResult, ResourceInfo, ResourceContents, Prompt, PromptMessage, Error as McpError -}; - -struct Component; - -impl Guest for Component { - fn list_tools() -> Vec { - vec![ - Tool { - name: "example_tool".to_string(), - description: "An example tool that echoes input".to_string(), - input_schema: json!({ - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Message to echo" - } - }, - "required": ["message"] - }).to_string(), - }, - // Add more tools here - ] - } - - fn call_tool(name: String, arguments: String) -> ToolResult { - let args: Value = match serde_json::from_str(&arguments) { - Ok(v) => v, - Err(e) => return ToolResult::Error(McpError { - code: -32602, - message: format!("Invalid JSON arguments: {}", e), - data: None, - }), - }; - - match name.as_str() { - "example_tool" => { - let message = args["message"].as_str().unwrap_or("No message provided"); - ToolResult::Text(format!("Echo: {}", message)) - } - _ => ToolResult::Error(McpError { - code: -32601, - message: format!("Unknown tool: {}", name), - data: None, - }), - } - } - - fn list_resources() -> Vec { - vec![ - // Add resources here - // Example: - // ResourceInfo { - // uri: "example://resource".to_string(), - // name: "Example Resource".to_string(), - // description: Some("An example resource".to_string()), - // mime_type: Some("text/plain".to_string()), - // } - ] - } - - fn read_resource(uri: String) -> Result { - // Implement resource reading logic here - Err(McpError { - code: -32601, - message: format!("Resource not found: {}", uri), - data: None, - }) - } - - fn list_prompts() -> Vec { - vec![ - // Add prompts here - // Example: - // Prompt { - // name: "greeting".to_string(), - // description: Some("Generate a greeting".to_string()), - // arguments: vec![ - // PromptArgument { - // name: "name".to_string(), - // description: Some("Name to greet".to_string()), - // required: true, - // } - // ], - // } - ] - } - - fn get_prompt(name: String, _arguments: String) -> Result, McpError> { - // Implement prompt generation logic here - Err(McpError { - code: -32601, - message: format!("Prompt not found: {}", name), - data: None, - }) - } -} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/spin.toml b/packages/ftl-cli/src/templates/mcp-server/content/spin.toml deleted file mode 100644 index 468a5a7a..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/content/spin.toml +++ /dev/null @@ -1,19 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "{{project-name | kebab_case}}" -version = "0.1.0" -authors = ["{{authors}}"] -description = "{{project-description}}" - -[[trigger.http]] -route = "/mcp" -component = "mcp-gateway" - -[component.mcp-gateway] -# Using the published gateway component -source = { registry = "{{registry}}", package = "{{gateway-package}}", version = "{{gateway-version}}" } - -[component.mcp-gateway.dependencies] -# Link to the local handler component -"component:mcp/handler@0.1.0" = { path = "./handler/target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt deleted file mode 100644 index b1f8d03a..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/metadata/snippets/component.txt +++ /dev/null @@ -1,11 +0,0 @@ -[[trigger.http]] -route = "{{route}}" -component = "mcp-gateway-{{project-name | kebab_case}}" - -[component.mcp-gateway-{{project-name | kebab_case}}] -# Using the published gateway component -source = { registry = "{{registry}}", package = "{{gateway-package}}", version = "{{gateway-version}}" } - -[component.mcp-gateway-{{project-name | kebab_case}}.dependencies] -# Link to the local handler component -"component:mcp/handler@0.1.0" = { path = "{{project-name | kebab_case}}-handler/target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml b/packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml deleted file mode 100644 index 89ab556c..00000000 --- a/packages/ftl-cli/src/templates/mcp-server/metadata/spin-template.toml +++ /dev/null @@ -1,16 +0,0 @@ -manifest_version = "1" -id = "mcp-server" -description = "Complete MCP server with handler and gateway" -tags = ["mcp", "http", "rust"] - -[parameters] -project-description = { type = "string", prompt = "Description", default = "An MCP server with custom tools" } -route = { type = "string", prompt = "HTTP route", default = "/mcp" } -gateway-version = { type = "string", prompt = "MCP gateway version", default = "0.1.0" } -registry = { type = "string", prompt = "Registry for gateway component (e.g. ghcr.io)", default = "ghcr.io" } -gateway-package = { type = "string", prompt = "Gateway package name", default = "bowlofarugula:mcp-gateway" } - -[add_component] -skip_files = ["spin.toml", "README.md", "Makefile", ".gitignore"] -[add_component.snippets] -component = "component.txt" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/mcp-server/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/rust/content/handler/.wit/mcp.wit similarity index 100% rename from packages/ftl-cli/src/templates/mcp-server/content/handler/wit/mcp.wit rename to packages/ftl-cli/src/templates/rust/content/handler/.wit/mcp.wit diff --git a/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml b/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml index 207e9ac8..2849fdb7 100644 --- a/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml +++ b/packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml @@ -5,7 +5,9 @@ edition = "2021" [dependencies] wit-bindgen = "0.16.0" +serde = "1.0" serde_json = "1.0" +ftl-sdk = "0.2.0" [lib] crate-type = ["cdylib"] diff --git a/packages/ftl-cli/src/templates/rust/content/handler/src/features.rs b/packages/ftl-cli/src/templates/rust/content/handler/src/features.rs new file mode 100644 index 00000000..71efe0cc --- /dev/null +++ b/packages/ftl-cli/src/templates/rust/content/handler/src/features.rs @@ -0,0 +1,67 @@ +use ftl_sdk::{create_tool, create_resource, create_prompt, json, Tool, Resource, Prompt, PromptMessage, PromptRole}; + +// Define your tools +pub fn tools() -> Vec { + vec![ + create_tool( + "echo", + "Echo a message back to the user", + json!({ + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to echo back" + } + }, + "required": ["message"] + }), + |args| { + let message = args["message"].as_str().unwrap_or("Hello, world!"); + Ok(format!("Echo: {}", message)) + } + ), + // Add more tools here + ] +} + +// Define your resources +pub fn resources() -> Vec { + vec![ + // Example: + // create_resource( + // "file:///example.txt", + // "Example File", + // || { + // Ok("File contents here".to_string()) + // } + // ) + // .with_description("An example text file") + // .with_mime_type("text/plain"), + ] +} + +// Define your prompts +pub fn prompts() -> Vec { + vec![ + // Example: + // create_prompt( + // "greeting", + // |args| { + // let name = args["name"].as_str().unwrap_or("User"); + // Ok(vec![ + // PromptMessage { + // role: PromptRole::User, + // content: format!("Please greet {}", name), + // }, + // PromptMessage { + // role: PromptRole::Assistant, + // content: format!("Hello, {}! How can I help you today?", name), + // }, + // ]) + // } + // ) + // .with_description("Generate a greeting message") + // .with_argument("name", "Name to greet", true), + ] +} \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs b/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs index 567a54ad..90197585 100644 --- a/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs +++ b/packages/ftl-cli/src/templates/rust/content/handler/src/lib.rs @@ -1,110 +1,10 @@ -use serde_json::{json, Value}; +mod features; -wit_bindgen::generate!({ - world: "mcp-handler", - path: "./wit", - exports: { - "component:mcp/handler": Component - } -}); +use features::{tools, resources, prompts}; -use exports::component::mcp::handler::{ - Guest, Tool, ToolResult, ResourceInfo, ResourceContents, Prompt, PromptMessage, Error as McpError -}; - -struct Component; - -impl Guest for Component { - fn list_tools() -> Vec { - vec![ - Tool { - name: "{{project-name | snake_case}}".to_string(), - description: "{{project-description}}".to_string(), - input_schema: json!({ - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "Input to process" - } - }, - "required": ["input"] - }).to_string(), - }, - // Add more tools here - ] - } - - fn call_tool(name: String, arguments: String) -> ToolResult { - let args: Value = match serde_json::from_str(&arguments) { - Ok(v) => v, - Err(e) => return ToolResult::Error(McpError { - code: -32602, - message: format!("Invalid JSON arguments: {}", e), - data: None, - }), - }; - - match name.as_str() { - "{{project-name | snake_case}}" => { - let input = args["input"].as_str().unwrap_or("No input provided"); - // TODO: Implement your tool logic here - ToolResult::Text(format!("Processed: {}", input)) - } - _ => ToolResult::Error(McpError { - code: -32601, - message: format!("Unknown tool: {}", name), - data: None, - }), - } - } - - fn list_resources() -> Vec { - vec![ - // Add resources here - // Example: - // ResourceInfo { - // uri: "example://resource".to_string(), - // name: "Example Resource".to_string(), - // description: Some("An example resource".to_string()), - // mime_type: Some("text/plain".to_string()), - // } - ] - } - - fn read_resource(uri: String) -> Result { - // Implement resource reading logic here - Err(McpError { - code: -32601, - message: format!("Resource not found: {}", uri), - data: None, - }) - } - - fn list_prompts() -> Vec { - vec![ - // Add prompts here - // Example: - // Prompt { - // name: "greeting".to_string(), - // description: Some("Generate a greeting".to_string()), - // arguments: vec![ - // PromptArgument { - // name: "name".to_string(), - // description: Some("Name to greet".to_string()), - // required: true, - // } - // ], - // } - ] - } - - fn get_prompt(name: String, _arguments: String) -> Result, McpError> { - // Implement prompt generation logic here - Err(McpError { - code: -32601, - message: format!("Prompt not found: {}", name), - data: None, - }) - } +// Create the MCP handler using the SDK macro +ftl_sdk::create_handler! { + tools: tools, + resources: resources, + prompts: prompts } \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/typescript/content/handler/.wit/mcp.wit similarity index 100% rename from packages/ftl-cli/src/templates/rust/content/handler/wit/mcp.wit rename to packages/ftl-cli/src/templates/typescript/content/handler/.wit/mcp.wit diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/package.json b/packages/ftl-cli/src/templates/typescript/content/handler/package.json index 6a31f32b..a0122893 100644 --- a/packages/ftl-cli/src/templates/typescript/content/handler/package.json +++ b/packages/ftl-cli/src/templates/typescript/content/handler/package.json @@ -5,16 +5,21 @@ "description": "{{project-description}}", "main": "dist/index.js", "scripts": { - "build": "npm run build:types && npm run build:js && npm run build:component", - "build:types": "jco types wit/mcp.wit -o dist/generated", + "build": "npm run build:types && npm run build:js && npm run build:bundle && npm run build:component", + "build:types": "jco types .wit/mcp.wit -o dist/generated", "build:js": "tsc", - "build:component": "jco componentize dist/index.js --wit wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", + "build:bundle": "esbuild dist/index.js --bundle --format=esm --platform=node --outfile=dist/bundled.js", + "build:component": "jco componentize dist/bundled.js --wit ./.wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", "test": "vitest", "clean": "rm -rf dist" }, + "dependencies": { + "@fastertools/ftl-sdk": "^0.1.0" + }, "devDependencies": { "@bytecodealliance/jco": "^1.8.1", "@types/node": "^20.0.0", + "esbuild": "^0.19.0", "typescript": "^5.0.0", "vitest": "^1.0.0" }, diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/src/features.ts b/packages/ftl-cli/src/templates/typescript/content/handler/src/features.ts new file mode 100644 index 00000000..f78769fb --- /dev/null +++ b/packages/ftl-cli/src/templates/typescript/content/handler/src/features.ts @@ -0,0 +1,56 @@ +import { createTool, createResource, createPrompt } from '@fastertools/ftl-sdk'; + +// Define your tools +export const tools = [ + createTool({ + name: 'echo', + description: 'Echo a message back to the user', + inputSchema: { + type: 'object', + properties: { + message: { + type: 'string', + description: 'Message to echo back' + } + }, + required: ['message'] + }, + execute: async (args) => { + const message = args.message || 'Hello, world!'; + return `Echo: ${message}`; + } + }), + // Add more tools here +]; + +// Define your resources +export const resources = [ + // Example: + // createResource({ + // uri: 'file:///example.txt', + // name: 'Example File', + // description: 'An example text file', + // mimeType: 'text/plain', + // read: async () => { + // return 'File contents here'; + // } + // }), +]; + +// Define your prompts +export const prompts = [ + // Example: + // createPrompt({ + // name: 'greeting', + // description: 'Generate a greeting message', + // arguments: [ + // { name: 'name', description: 'Name to greet', required: true } + // ], + // resolve: async (args) => { + // return [ + // { role: 'user', content: `Please greet ${args.name}` }, + // { role: 'assistant', content: `Hello, ${args.name}! How can I help you today?` } + // ]; + // } + // }), +]; \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts b/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts index 30f85907..0503d93d 100644 --- a/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts +++ b/packages/ftl-cli/src/templates/typescript/content/handler/src/index.ts @@ -1,83 +1,9 @@ -// Export the handler implementation for componentize-js -export const handler = { - listTools() { - return [{ - name: '{{project-name | snake_case}}', - description: '{{project-description}}', - inputSchema: JSON.stringify({ - type: 'object', - properties: { - input: { - type: 'string', - description: 'Input to process' - } - }, - required: ['input'] - }) - }]; - }, - - callTool(name: string, argumentsStr: string) { - let args: any; - try { - args = JSON.parse(argumentsStr); - } catch (e) { - return { - tag: 'error', - val: { - code: -32602, - message: `Invalid JSON arguments: ${e}`, - data: undefined - } - }; - } - - switch (name) { - case '{{project-name | snake_case}}': { - const input = args.input || 'No input provided'; - // TODO: Implement your tool logic here - return { - tag: 'text', - val: `Processed: ${input}` - }; - } - default: - return { - tag: 'error', - val: { - code: -32601, - message: `Unknown tool: ${name}`, - data: undefined - } - }; - } - }, - - listResources() { - return []; - }, +import { createHandler } from '@fastertools/ftl-sdk'; +import { tools, resources, prompts } from './features.js'; - readResource(uri: string) { - // According to WIT, this returns ResourceContents, not a Result - // So we need to return a valid ResourceContents or throw - throw { - code: -32601, - message: `Resource not found: ${uri}`, - data: undefined - }; - }, - - listPrompts() { - return []; - }, - - getPrompt(name: string, argumentsStr: string) { - // According to WIT, this returns Array, not a Result - // So we need to return an array or throw - throw { - code: -32601, - message: `Prompt not found: ${name}`, - data: undefined - }; - } -}; \ No newline at end of file +// Export the handler implementation for componentize-js +export const handler = createHandler({ + tools, + resources, + prompts +}); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json b/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json index 090ee7c3..09876edb 100644 --- a/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json +++ b/packages/ftl-cli/src/templates/typescript/content/handler/tsconfig.json @@ -19,7 +19,7 @@ "noEmit": false, "isolatedModules": true, "allowSyntheticDefaultImports": true, - "types": ["node", "vitest/globals"] + "types": [] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "test"] diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit b/packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit deleted file mode 100644 index cfaf6acd..00000000 --- a/packages/ftl-cli/src/templates/typescript/content/handler/wit/mcp.wit +++ /dev/null @@ -1,82 +0,0 @@ -package component:mcp@0.1.0; - -/// MCP handler interface -interface handler { - /// Tool definition - record tool { - name: string, - description: string, - input-schema: string, // JSON Schema as string - } - - /// Resource definition - record resource-info { - uri: string, - name: string, - description: option, - mime-type: option, - } - - /// Resource contents - record resource-contents { - uri: string, - mime-type: option, - text: option, - blob: option>, - } - - /// Prompt definition - record prompt { - name: string, - description: option, - arguments: list, - } - - record prompt-argument { - name: string, - description: option, - required: bool, - } - - /// Prompt message - record prompt-message { - role: string, - content: string, - } - - /// Error type - record error { - code: s32, - message: string, - data: option, - } - - /// Tool call result - variant tool-result { - text(string), - error(error), - } - - /// List available tools - list-tools: func() -> list; - - /// Call a tool - call-tool: func(name: string, arguments: string) -> tool-result; - - /// List available resources - list-resources: func() -> list; - - /// Read a resource - read-resource: func(uri: string) -> result; - - /// List available prompts - list-prompts: func() -> list; - - /// Get a prompt - get-prompt: func(name: string, arguments: string) -> result, error>; -} - -// World for MCP handler components (libraries) -world mcp-handler { - export handler; -} \ No newline at end of file diff --git a/packages/ftl-sdk-rust/Cargo.toml b/packages/ftl-sdk-rust/Cargo.toml new file mode 100644 index 00000000..ceae1f99 --- /dev/null +++ b/packages/ftl-sdk-rust/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ftl-sdk" +version = "0.2.0" +edition = "2021" +description = "FTL SDK for building MCP tools" +license = "MIT" +repository = "https://github.com/fastertools/ftl-cli" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[dev-dependencies] \ No newline at end of file diff --git a/packages/ftl-sdk-rust/src/lib.rs b/packages/ftl-sdk-rust/src/lib.rs new file mode 100644 index 00000000..e81b6a1b --- /dev/null +++ b/packages/ftl-sdk-rust/src/lib.rs @@ -0,0 +1,309 @@ +use serde_json::Value; + +// MCP Feature Types + +pub struct Tool { + pub name: String, + pub description: String, + pub input_schema: Value, + pub execute: fn(Value) -> Result, +} + +pub struct Resource { + pub uri: String, + pub name: String, + pub description: Option, + pub mime_type: Option, + pub read: fn() -> Result, +} + +impl Resource { + pub fn with_description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + pub fn with_mime_type(mut self, mime_type: impl Into) -> Self { + self.mime_type = Some(mime_type.into()); + self + } +} + +pub struct Prompt { + pub name: String, + pub description: Option, + pub arguments: Option>, + pub resolve: fn(Value) -> Result, String>, +} + +impl Prompt { + pub fn with_description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + pub fn with_argument(mut self, name: impl Into, description: impl Into, required: bool) -> Self { + let arg = PromptArgument { + name: name.into(), + description: Some(description.into()), + required: Some(required), + }; + if let Some(ref mut args) = self.arguments { + args.push(arg); + } else { + self.arguments = Some(vec![arg]); + } + self + } +} + +#[derive(Clone)] +pub struct PromptArgument { + pub name: String, + pub description: Option, + pub required: Option, +} + +#[derive(Clone)] +pub struct PromptMessage { + pub role: PromptRole, + pub content: String, +} + +#[derive(Clone)] +pub enum PromptRole { + User, + Assistant, +} + +// Builder functions + +pub fn create_tool( + name: impl Into, + description: impl Into, + input_schema: Value, + execute: fn(Value) -> Result, +) -> Tool { + Tool { + name: name.into(), + description: description.into(), + input_schema, + execute, + } +} + +pub fn create_resource( + uri: impl Into, + name: impl Into, + read: fn() -> Result, +) -> Resource { + Resource { + uri: uri.into(), + name: name.into(), + description: None, + mime_type: None, + read, + } +} + +pub fn create_prompt( + name: impl Into, + resolve: fn(Value) -> Result, String>, +) -> Prompt { + Prompt { + name: name.into(), + description: None, + arguments: None, + resolve, + } +} + +// Re-export for convenience +pub use serde_json::json; + +// Handler creation macro +#[macro_export] +macro_rules! create_handler { + ( + tools: $tools:expr, + resources: $resources:expr, + prompts: $prompts:expr + ) => { + wit_bindgen::generate!({ + world: "mcp-handler", + path: ".wit", + exports: { + "component:mcp/handler": McpComponent + } + }); + + use exports::component::mcp::handler::*; + + struct McpComponent; + + impl Guest for McpComponent { + fn list_tools() -> Vec { + $tools() + .into_iter() + .map(|tool| exports::component::mcp::handler::Tool { + name: tool.name, + description: tool.description, + input_schema: tool.input_schema.to_string(), + }) + .collect() + } + + fn call_tool(name: String, arguments: String) -> ToolResult { + let args = match serde_json::from_str::(&arguments) { + Ok(v) => v, + Err(e) => return ToolResult::Error(Error { + code: -32602, + message: format!("Invalid JSON arguments: {}", e), + data: None, + }), + }; + + for tool in $tools() { + if tool.name == name { + match (tool.execute)(args) { + Ok(result) => return ToolResult::Text(result), + Err(e) => return ToolResult::Error(Error { + code: -32603, + message: e, + data: None, + }), + } + } + } + + ToolResult::Error(Error { + code: -32601, + message: format!("Unknown tool: {}", name), + data: None, + }) + } + + fn list_resources() -> Vec { + $resources() + .into_iter() + .map(|resource| ResourceInfo { + uri: resource.uri, + name: resource.name, + description: resource.description, + mime_type: resource.mime_type, + }) + .collect() + } + + fn read_resource(uri: String) -> Result { + for resource in $resources() { + if resource.uri == uri { + match (resource.read)() { + Ok(contents) => return Ok(ResourceContents { + uri: resource.uri.clone(), + mime_type: resource.mime_type, + text: Some(contents), + blob: None, + }), + Err(e) => return Err(Error { + code: -32603, + message: e, + data: None, + }), + } + } + } + + Err(Error { + code: -32601, + message: format!("Resource not found: {}", uri), + data: None, + }) + } + + fn list_prompts() -> Vec { + $prompts() + .into_iter() + .map(|prompt| Prompt { + name: prompt.name, + description: prompt.description, + arguments: prompt.arguments.map(|args| { + args.into_iter() + .map(|arg| PromptArgument { + name: arg.name, + description: arg.description, + required: arg.required.unwrap_or(false), + }) + .collect() + }).unwrap_or_default(), + }) + .collect() + } + + fn get_prompt(name: String, arguments: String) -> Result, Error> { + let args = if arguments.is_empty() { + serde_json::Value::Object(serde_json::Map::new()) + } else { + match serde_json::from_str::(&arguments) { + Ok(v) => v, + Err(e) => return Err(Error { + code: -32602, + message: format!("Invalid JSON arguments: {}", e), + data: None, + }), + } + }; + + for prompt in $prompts() { + if prompt.name == name { + match (prompt.resolve)(args) { + Ok(messages) => return Ok(messages.into_iter() + .map(|msg| PromptMessage { + role: match msg.role { + $crate::PromptRole::User => "user".to_string(), + $crate::PromptRole::Assistant => "assistant".to_string(), + }, + content: msg.content, + }) + .collect()), + Err(e) => return Err(Error { + code: -32603, + message: e, + data: None, + }), + } + } + } + + Err(Error { + code: -32601, + message: format!("Prompt not found: {}", name), + data: None, + }) + } + } + }; +} + +// Macros for easier creation + +#[macro_export] +macro_rules! tool { + ($name:expr, $desc:expr, $schema:expr, $execute:expr) => { + $crate::create_tool($name, $desc, $schema, $execute) + }; +} + +#[macro_export] +macro_rules! resource { + ($uri:expr, $name:expr, $read:expr) => { + $crate::create_resource($uri, $name, $read) + }; +} + +#[macro_export] +macro_rules! prompt { + ($name:expr, $resolve:expr) => { + $crate::create_prompt($name, $resolve) + }; +} \ No newline at end of file diff --git a/packages/ftl-sdk-typescript/package-lock.json b/packages/ftl-sdk-typescript/package-lock.json new file mode 100644 index 00000000..47d13125 --- /dev/null +++ b/packages/ftl-sdk-typescript/package-lock.json @@ -0,0 +1,50 @@ +{ + "name": "@fastertools/ftl-sdk", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@fastertools/ftl-sdk", + "version": "0.1.0", + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz", + "integrity": "sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/packages/ftl-sdk-typescript/package.json b/packages/ftl-sdk-typescript/package.json new file mode 100644 index 00000000..9b14551a --- /dev/null +++ b/packages/ftl-sdk-typescript/package.json @@ -0,0 +1,29 @@ +{ + "name": "@fastertools/ftl-sdk", + "version": "0.1.0", + "type": "module", + "description": "FTL SDK for building MCP tools", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "clean": "rm -rf dist", + "prepublishOnly": "npm run build" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=20.0.0" + } +} \ No newline at end of file diff --git a/packages/ftl-sdk-typescript/src/index.ts b/packages/ftl-sdk-typescript/src/index.ts new file mode 100644 index 00000000..c105c2c2 --- /dev/null +++ b/packages/ftl-sdk-typescript/src/index.ts @@ -0,0 +1,192 @@ +// MCP Feature Types + +export interface Tool { + name: string; + description: string; + inputSchema: object; + execute: (args: TArgs) => string | Promise; +} + +export interface Resource { + uri: string; + name: string; + description?: string; + mimeType?: string; + read: () => string | Promise; +} + +export interface Prompt { + name: string; + description?: string; + arguments?: Array<{ + name: string; + description?: string; + required?: boolean; + }>; + resolve: (args: TArgs) => PromptMessage[] | Promise; +} + +export interface PromptMessage { + role: 'user' | 'assistant'; + content: string; +} + +// Factory functions + +export function createTool(config: Tool): Tool { + return config; +} + +export function createResource(config: Resource): Resource { + return config; +} + +export function createPrompt(config: Prompt): Prompt { + return config; +} + +// Handler creation + +export interface McpFeatures { + tools?: Tool[]; + resources?: Resource[]; + prompts?: Prompt[]; +} + +export function createHandler(features: McpFeatures) { + const tools = features.tools || []; + const resources = features.resources || []; + const prompts = features.prompts || []; + + return { + listTools() { + return tools.map(tool => ({ + name: tool.name, + description: tool.description, + inputSchema: JSON.stringify(tool.inputSchema) + })); + }, + + async callTool(name: string, argumentsStr: string) { + let args: any; + try { + args = JSON.parse(argumentsStr); + } catch (e) { + return { + tag: 'error', + val: { + code: -32602, + message: `Invalid JSON arguments: ${e}`, + data: undefined + } + }; + } + + const tool = tools.find(t => t.name === name); + if (!tool) { + return { + tag: 'error', + val: { + code: -32601, + message: `Unknown tool: ${name}`, + data: undefined + } + }; + } + + try { + const result = await tool.execute(args); + return { + tag: 'text', + val: result + }; + } catch (e: any) { + return { + tag: 'error', + val: { + code: -32603, + message: e.message || String(e), + data: undefined + } + }; + } + }, + + listResources() { + return resources.map(resource => ({ + uri: resource.uri, + name: resource.name, + description: resource.description, + mimeType: resource.mimeType + })); + }, + + async readResource(uri: string) { + const resource = resources.find(r => r.uri === uri); + if (!resource) { + throw { + code: -32601, + message: `Resource not found: ${uri}`, + data: undefined + }; + } + + try { + const contents = await resource.read(); + return { + contents, + mimeType: resource.mimeType + }; + } catch (e: any) { + throw { + code: -32603, + message: e.message || String(e), + data: undefined + }; + } + }, + + listPrompts() { + return prompts.map(prompt => ({ + name: prompt.name, + description: prompt.description, + arguments: prompt.arguments + })); + }, + + async getPrompt(name: string, argumentsStr: string) { + const prompt = prompts.find(p => p.name === name); + if (!prompt) { + throw { + code: -32601, + message: `Prompt not found: ${name}`, + data: undefined + }; + } + + let args: any = {}; + if (argumentsStr) { + try { + args = JSON.parse(argumentsStr); + } catch (e) { + throw { + code: -32602, + message: `Invalid JSON arguments: ${e}`, + data: undefined + }; + } + } + + try { + const messages = await prompt.resolve(args); + return messages; + } catch (e: any) { + throw { + code: -32603, + message: e.message || String(e), + data: undefined + }; + } + } + }; +} \ No newline at end of file diff --git a/packages/ftl-sdk-typescript/tsconfig.json b/packages/ftl-sdk-typescript/tsconfig.json new file mode 100644 index 00000000..dac62bf3 --- /dev/null +++ b/packages/ftl-sdk-typescript/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022"], + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file From 17232a931d0275cfc460dc933530a65389b28af4 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 01:57:09 -0700 Subject: [PATCH 03/21] feat: wip --- .gitignore | 4 +- ARCHITECTURE_RECOMMENDATIONS.md | 140 + CHANGELOG.md | 26 + README.md | 377 +- docs/cli-reference.md | 259 +- docs/getting-started.md | 179 +- docs/manifest.md | 51 +- packages/ftl-cli/Cargo.toml | 2 +- packages/ftl-cli/README.md | 160 +- packages/ftl-cli/build.rs | 2 +- .../examples/javascript-test/.gitignore | 6 - .../ftl-cli/examples/javascript-test/Makefile | 20 - .../examples/javascript-test/README.md | 85 - .../ftl-cli/examples/javascript-test/ftl.toml | 24 - .../javascript-test/handler/package-lock.json | 3678 ---------------- .../javascript-test/handler/package.json | 21 - .../javascript-test/handler/src/index.js | 104 - .../handler/test/handler.test.js | 38 - .../javascript-test/handler/vitest.config.js | 13 - .../javascript-test/handler/wit/mcp.wit | 82 - .../examples/javascript-test/spin.toml | 19 - .../test-templates/rust-test/.gitignore | 5 - .../test-templates/rust-test/Makefile | 16 - .../test-templates/rust-test/README.md | 95 - .../test-templates/rust-test/ftl.toml | 23 - .../rust-test/handler/Cargo.toml | 24 - .../rust-test/handler/src/lib.rs | 110 - .../rust-test/handler/wit/mcp.wit | 82 - .../test-templates/rust-test/spin.toml | 19 - .../rust-test/test-call-object.json | 9 - .../test-templates/rust-test/test-call.json | 9 - .../rust-test/test-mcp-spec.json | 11 - .../examples/typescript-test/.gitignore | 7 - .../ftl-cli/examples/typescript-test/Makefile | 20 - .../examples/typescript-test/README.md | 89 - .../ftl-cli/examples/typescript-test/ftl.toml | 24 - .../typescript-test/handler/package-lock.json | 3711 ----------------- .../typescript-test/handler/package.json | 24 - .../typescript-test/handler/src/index.ts | 82 - .../handler/test/handler.test.ts | 44 - .../typescript-test/handler/tsconfig.json | 26 - .../typescript-test/handler/vitest.config.ts | 13 - .../typescript-test/handler/wit/mcp.wit | 82 - .../examples/typescript-test/spin.toml | 19 - packages/ftl-cli/src/commands/add.rs | 232 ++ packages/ftl-cli/src/commands/build.rs | 304 +- packages/ftl-cli/src/commands/delete.rs | 80 - packages/ftl-cli/src/commands/deploy.rs | 130 +- packages/ftl-cli/src/commands/export.rs | 190 - packages/ftl-cli/src/commands/init.rs | 112 + packages/ftl-cli/src/commands/link.rs | 77 - packages/ftl-cli/src/commands/list.rs | 45 - packages/ftl-cli/src/commands/login.rs | 84 - packages/ftl-cli/src/commands/logs.rs | 57 - packages/ftl-cli/src/commands/mod.rs | 21 +- packages/ftl-cli/src/commands/new.rs | 155 - packages/ftl-cli/src/commands/publish.rs | 157 + packages/ftl-cli/src/commands/registry.rs | 67 + packages/ftl-cli/src/commands/serve.rs | 219 - packages/ftl-cli/src/commands/setup.rs | 184 + packages/ftl-cli/src/commands/size.rs | 519 --- packages/ftl-cli/src/commands/spin.rs | 151 - packages/ftl-cli/src/commands/status.rs | 51 - packages/ftl-cli/src/commands/test.rs | 140 +- packages/ftl-cli/src/commands/toolkit.rs | 494 --- packages/ftl-cli/src/commands/unlink.rs | 54 - packages/ftl-cli/src/commands/up.rs | 62 + packages/ftl-cli/src/commands/validate.rs | 194 - packages/ftl-cli/src/commands/watch.rs | 101 - packages/ftl-cli/src/common/build_utils.rs | 60 - packages/ftl-cli/src/common/config.rs | 69 - packages/ftl-cli/src/common/deploy_utils.rs | 16 - packages/ftl-cli/src/common/manifest_utils.rs | 29 - packages/ftl-cli/src/common/mod.rs | 7 - packages/ftl-cli/src/common/spin_installer.rs | 19 + packages/ftl-cli/src/common/spin_utils.rs | 187 - packages/ftl-cli/src/common/tool_paths.rs | 114 - packages/ftl-cli/src/common/watch_utils.rs | 140 - packages/ftl-cli/src/language.rs | 74 +- packages/ftl-cli/src/language/javascript.rs | 122 - packages/ftl-cli/src/language/rust.rs | 121 - packages/ftl-cli/src/language/typescript.rs | 124 - packages/ftl-cli/src/main.rs | 330 +- packages/ftl-cli/src/manifest.rs | 161 +- packages/ftl-cli/src/spin_generator.rs | 211 - .../src/templates/javascript/content/ftl.toml | 24 +- .../javascript/content/handler/package.json | 2 +- .../src/templates/rust/content/ftl.toml | 23 +- .../src/templates/typescript/content/ftl.toml | 24 +- .../typescript/content/handler/package.json | 2 +- packages/ftl-sdk-rust/Cargo.toml | 2 +- packages/ftl-sdk-typescript/package.json | 2 +- 92 files changed, 2063 insertions(+), 13514 deletions(-) create mode 100644 ARCHITECTURE_RECOMMENDATIONS.md delete mode 100644 packages/ftl-cli/examples/javascript-test/.gitignore delete mode 100644 packages/ftl-cli/examples/javascript-test/Makefile delete mode 100644 packages/ftl-cli/examples/javascript-test/README.md delete mode 100644 packages/ftl-cli/examples/javascript-test/ftl.toml delete mode 100644 packages/ftl-cli/examples/javascript-test/handler/package-lock.json delete mode 100644 packages/ftl-cli/examples/javascript-test/handler/package.json delete mode 100644 packages/ftl-cli/examples/javascript-test/handler/src/index.js delete mode 100644 packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js delete mode 100644 packages/ftl-cli/examples/javascript-test/handler/vitest.config.js delete mode 100644 packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit delete mode 100644 packages/ftl-cli/examples/javascript-test/spin.toml delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/.gitignore delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/Makefile delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/README.md delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/ftl.toml delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/spin.toml delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/test-call.json delete mode 100644 packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json delete mode 100644 packages/ftl-cli/examples/typescript-test/.gitignore delete mode 100644 packages/ftl-cli/examples/typescript-test/Makefile delete mode 100644 packages/ftl-cli/examples/typescript-test/README.md delete mode 100644 packages/ftl-cli/examples/typescript-test/ftl.toml delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/package-lock.json delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/package.json delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/src/index.ts delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/tsconfig.json delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts delete mode 100644 packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit delete mode 100644 packages/ftl-cli/examples/typescript-test/spin.toml create mode 100644 packages/ftl-cli/src/commands/add.rs delete mode 100644 packages/ftl-cli/src/commands/delete.rs delete mode 100644 packages/ftl-cli/src/commands/export.rs create mode 100644 packages/ftl-cli/src/commands/init.rs delete mode 100644 packages/ftl-cli/src/commands/link.rs delete mode 100644 packages/ftl-cli/src/commands/list.rs delete mode 100644 packages/ftl-cli/src/commands/login.rs delete mode 100644 packages/ftl-cli/src/commands/logs.rs delete mode 100644 packages/ftl-cli/src/commands/new.rs create mode 100644 packages/ftl-cli/src/commands/publish.rs create mode 100644 packages/ftl-cli/src/commands/registry.rs delete mode 100644 packages/ftl-cli/src/commands/serve.rs create mode 100644 packages/ftl-cli/src/commands/setup.rs delete mode 100644 packages/ftl-cli/src/commands/size.rs delete mode 100644 packages/ftl-cli/src/commands/spin.rs delete mode 100644 packages/ftl-cli/src/commands/status.rs delete mode 100644 packages/ftl-cli/src/commands/toolkit.rs delete mode 100644 packages/ftl-cli/src/commands/unlink.rs create mode 100644 packages/ftl-cli/src/commands/up.rs delete mode 100644 packages/ftl-cli/src/commands/validate.rs delete mode 100644 packages/ftl-cli/src/commands/watch.rs delete mode 100644 packages/ftl-cli/src/common/build_utils.rs delete mode 100644 packages/ftl-cli/src/common/config.rs delete mode 100644 packages/ftl-cli/src/common/deploy_utils.rs delete mode 100644 packages/ftl-cli/src/common/manifest_utils.rs delete mode 100644 packages/ftl-cli/src/common/spin_utils.rs delete mode 100644 packages/ftl-cli/src/common/tool_paths.rs delete mode 100644 packages/ftl-cli/src/common/watch_utils.rs delete mode 100644 packages/ftl-cli/src/language/javascript.rs delete mode 100644 packages/ftl-cli/src/language/rust.rs delete mode 100644 packages/ftl-cli/src/language/typescript.rs delete mode 100644 packages/ftl-cli/src/spin_generator.rs diff --git a/.gitignore b/.gitignore index e38ca868..7403516c 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,6 @@ flamegraph.svg # Release artifacts dist/ -release/ \ No newline at end of file +release/ + +.claude/ diff --git a/ARCHITECTURE_RECOMMENDATIONS.md b/ARCHITECTURE_RECOMMENDATIONS.md new file mode 100644 index 00000000..4e6d0f58 --- /dev/null +++ b/ARCHITECTURE_RECOMMENDATIONS.md @@ -0,0 +1,140 @@ +# FTL CLI Architecture Recommendations + +## Executive Summary + +After analyzing the new template system and existing CLI implementation, I recommend a significant architectural shift to align FTL with the new component-based MCP server model. The key change is moving from managing full Spin applications to managing individual MCP components that can be composed into Spin applications. + +## Current State Analysis + +### New Template System +- Templates now generate **standalone WebAssembly components** implementing the MCP protocol +- Each component is designed to be published to a registry (e.g., `ghcr.io/username/component:version`) +- Components use a pre-published `mcp-gateway` component for HTTP->MCP translation +- Templates include Makefiles with `registry-push` targets for publishing + +### SDK Architecture +- Three SDKs (Rust, TypeScript, JavaScript) provide consistent APIs for building MCP handlers +- SDKs handle tool registration, resource management, and prompt handling +- Clean separation between MCP protocol implementation and user code + +### Existing CLI Issues +1. **Tool-centric model** - Commands like `ftl new`, `ftl build`, `ftl deploy` assume managing complete tools +2. **Toolkit abstraction** - The toolkit concept doesn't align with Spin's component composition model +3. **Custom spin.toml generation** - Overly complex for the new component model +4. **Missing component lifecycle** - No support for publishing/versioning components + +## Recommended Architecture + +### 1. Embrace Component-First Design + +**Remove the "tool" abstraction entirely.** Instead, work with: +- **MCP Components**: Individual WebAssembly components implementing MCP features +- **Spin Projects**: Compositions of multiple MCP components + +### 2. New CLI Command Structure + +```bash +# Component Development +ftl init # Create new MCP component project +ftl dev # Run component in development mode +ftl test # Test the component +ftl build # Build the component +ftl publish # Publish to registry + +# Component & Project Management +ftl init # Create empty Spin project (wraps spin new -t http-empty) +ftl add # Add component to current project +ftl up # Serve the project locally +ftl deploy # Deploy to FTL + +# Component Management +ftl registry list # List available components +ftl registry search # Search for components +ftl registry info # Show component details +``` + +### 3. Simplified Workflow + +**For single component development:** +```bash +ftl init my-weather-tool --lang typescript +cd my-weather-tool +ftl dev # Starts at localhost:3000/mcp +ftl publish # Publishes to registry +``` + +**For multi-component projects:** +```bash +ftl init my-assistant +cd my-assistant +ftl add github-tool --language typescript +ftl add weather-tool --language typescript +ftl add my-custom-tool --language rust +ftl up # All components available +ftl deploy +``` + +### 4. Key Implementation Changes + +#### Remove Complexity +1. **Delete toolkit functionality** - It's redundant with Spin's component model +2. **Simplify manifest handling** - Use ftl.toml only for component metadata +3. **Remove custom spin.toml generation** - Let Spin handle this via templates + +#### Add Component Features +1. **Registry integration** - First-class support for publishing/consuming components +2. **Version management** - Handle component versioning properly +3. **Dependency resolution** - Ensure compatible component versions + +#### Improve Developer Experience +1. **Template installation** - Auto-install templates on first use +2. **Component scaffolding** - Quick generators for common patterns +3. **Testing utilities** - Built-in MCP protocol testing + +### 5. Migration Path + +1. **Phase 1**: Add new commands alongside existing ones +2. **Phase 2**: Deprecate old commands with migration guides +3. **Phase 3**: Remove deprecated commands in next major version + +### 6. Technical Details + +#### Component Publishing +- Use `wkg` (WebAssembly package manager) for registry operations +- Support multiple registries (GitHub Container Registry, Docker Hub, etc.) +- Include metadata in published components (description, version, MCP features) + +#### Local Development +- Use Spin's built-in development server +- Provide MCP-specific development tools (request inspection, mock clients) +- Hot reload support where possible + +#### App Composition +- Leverage Spin's component composition features +- Support both local and registry components +- Handle component dependency conflicts + +## Benefits + +1. **Simplicity** - Aligns with Spin's model instead of fighting it +2. **Modularity** - True component reusability across projects +3. **Ecosystem** - Enables component marketplace/discovery +4. **Standards** - Follows WebAssembly component model standards +5. **Future-proof** - Ready for WASI Preview 2 and component model evolution + +## Risks and Mitigations + +1. **Breaking changes** - Mitigate with careful migration path and tooling +2. **Learning curve** - Provide excellent documentation and examples +3. **Registry dependency** - Support local-only workflows as fallback + +## Next Steps + +1. Validate approach with key stakeholders +2. Create proof-of-concept for new command structure +3. Design component registry integration +4. Plan migration strategy for existing users + +## Conclusion + +This architecture positions FTL as a best-in-class tool for building and deploying MCP servers on WebAssembly. By embracing Spin's component model rather than abstracting over it, we can provide a simpler, more powerful developer experience while enabling a rich ecosystem of reusable MCP components. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index aac3c84b..2d6ca526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Support for Spin template options in `ftl init` and `ftl setup templates` commands + - `--git`: Use a Git repository as the template source + - `--branch`: Specify Git branch (requires `--git`) + - `--dir`: Use a local directory as the template source + - `--tar`: Use a tarball as the template source + +### Fixed +- Routes specified in `ftl add` now correctly appear in spin.toml (using --value instead of env vars) +- HTTP routes now automatically end with `/mcp` and use kebab-case +- `ftl build` now shows verbose build output like `ftl up --build` does + +### Changed +- Removed `ftl project` subcommand entirely - all commands are now at root level + - `ftl project init` → `ftl init` (already existed) + - `ftl project serve` → `ftl up` (already existed) + - `ftl project deploy` → `ftl deploy` (newly added) + - Removed `ftl project add` (use `ftl add` instead) +- Restructured component creation workflow + - `ftl init` now creates a project (http-empty container) instead of a single component + - Added new `ftl add` command for adding components to projects + - Removed `--language` option from `ftl init` (moved to `ftl add`) + - This ensures consistent workflow for single and multi-component projects +- Simplified ftl.toml to only contain component metadata + - Removed unused `[build]`, `[optimization]`, and `[runtime]` sections + - Build configuration is handled by Makefiles and language-specific tools + - Runtime configuration belongs in spin.toml - Initial release of FTL CLI framework - Core CLI commands: `new`, `build`, `serve`, `deploy` - Tool management commands: `test`, `watch`, `validate`, `size` diff --git a/README.md b/README.md index 6125ab01..86f94318 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ # `ftl` -Fast tools for AI agents +Build and deploy Model Context Protocol (MCP) servers on WebAssembly -[![CI](https://github.com/fastertools/ftl-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/fastertools/core/actions/workflows/ci.yml) +[![CI](https://github.com/fastertools/ftl-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/fastertools/ftl-cli/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) [![Rust](https://img.shields.io/badge/rust-1.87+-orange.svg)](https://www.rust-lang.org) [![WebAssembly](https://img.shields.io/badge/WebAssembly-compatible-purple.svg)](https://webassembly.org/) @@ -13,322 +13,193 @@ Fast tools for AI agents -FTL is a platform for developing and deploying fast, edge-hosted [Model Context Protocol](https://modelcontextprotocol.io/introduction) tools for AI agents. +FTL is a developer platform for building and deploying [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers as WebAssembly components that run on the [Fermyon Spin](https://www.fermyon.com/spin) platform. -This repository contains the `ftl` command-line interface, which is the primary entry point. - -## Getting Started - -### Installation +## Quick Start ```bash +# Install FTL cargo install ftl-cli -``` -### Create a New Tool +# Create a new project +ftl init my-assistant +cd my-assistant -
-🦀 Rust +# Add a component +ftl add weather-tool --language typescript -```bash -ftl new my-tool --language rust +# Build and run locally +ftl build +ftl up + +# Publish to registry +ftl publish ``` -This creates a new directory with: -- `ftl.toml` - Tool manifest -- `spin.toml` - Spin application configuration -- `handler/` - Tool implementation directory - - `Cargo.toml` - Rust dependencies - - `src/lib.rs` - Tool implementation - - `wit/mcp.wit` - WebAssembly interface definition +## Key Features -```rust -use serde_json::{json, Value}; - -wit_bindgen::generate!({ - world: "mcp-handler", - path: "./wit", - exports: { - "component:mcp/handler": Component - } -}); +- **Component-First Architecture**: Build MCP servers as reusable WebAssembly components +- **Multi-Language Support**: Write components in Rust, TypeScript, or JavaScript +- **Registry Publishing**: Share components via OCI registries (GitHub, Docker Hub) +- **Project Composition**: Combine multiple MCP components into a single Spin project +- **Edge Deployment**: Deploy to FTL or self-host with Spin -use exports::component::mcp::handler::{Guest, Tool, ToolResult, Error as McpError}; - -struct Component; - -impl Guest for Component { - fn list_tools() -> Vec { - vec![Tool { - name: "my_tool".to_string(), - description: "My tool description".to_string(), - input_schema: json!({ - "type": "object", - "properties": { - "input": { "type": "string" } - }, - "required": ["input"] - }).to_string(), - }] - } - - fn call_tool(name: String, arguments: String) -> ToolResult { - let args: Value = serde_json::from_str(&arguments).unwrap(); - let input = args["input"].as_str().unwrap_or("No input"); - ToolResult::Text(format!("Processed: {}", input)) - } - - // ... other required methods -} -``` -
+## Creating MCP Projects -
-🔷 TypeScript +### TypeScript Example ```bash -ftl new my-tool --language typescript +# Create project and add TypeScript component +ftl init my-project +cd my-project +ftl add my-tool --language typescript ``` -This creates a new directory with: -- `ftl.toml` - Tool manifest -- `spin.toml` - Spin application configuration -- `handler/` - Tool implementation directory - - `package.json` - Node dependencies - - `tsconfig.json` - TypeScript configuration - - `src/index.ts` - Tool implementation - - `wit/mcp.wit` - WebAssembly interface definition - ```typescript -import { handler } from '../generated/mcp'; - -const { Tool, ToolResult } = handler; - -export const Handler = { - listTools(): handler.Tool[] { - return [{ - name: 'my-tool', - description: 'My tool description', - inputSchema: JSON.stringify({ - type: 'object', - properties: { - input: { type: 'string' } - }, - required: ['input'] - }) - }]; - }, - - callTool(name: string, argumentsStr: string): handler.ToolResult { - const args = JSON.parse(argumentsStr); - const input = args.input || 'No input'; - return { - tag: 'text', - val: `Processed: ${input}` - }; - } - - // ... other required methods -}; +// my-tool/src/index.ts +import { createHandler } from '@fastertools/ftl-sdk'; +import { tools, resources, prompts } from './features.js'; + +export const handler = createHandler({ + tools, // Your MCP tools + resources, // Your MCP resources + prompts // Your MCP prompts +}); ``` -
- - - -
-🟨 JavaScript +### Rust Example ```bash -ftl new my-tool --language javascript +# Create project and add Rust component +ftl init my-project +cd my-project +ftl add my-tool --language rust ``` -This creates a new directory with: -- `ftl.toml` - Tool manifest -- `spin.toml` - Spin application configuration -- `handler/` - Tool implementation directory - - `package.json` - Node dependencies - - `src/index.js` - Tool implementation - - `wit/mcp.wit` - WebAssembly interface definition - -```javascript -import { handler } from '../generated/mcp.js'; - -const { Tool, ToolResult } = handler; - -export const Handler = { - listTools() { - return [{ - name: 'my-tool', - description: 'My tool description', - inputSchema: JSON.stringify({ - type: 'object', - properties: { - input: { type: 'string' } - }, - required: ['input'] - }) - }]; - }, - - callTool(name, argumentsStr) { - const args = JSON.parse(argumentsStr); - const input = args.input || 'No input'; - return { - tag: 'text', - val: `Processed: ${input}` - }; - } - - // ... other required methods -}; +```rust +// my-tool/src/lib.rs +use ftl_sdk::*; + +create_handler!( + tools: get_tools, + resources: get_resources, + prompts: get_prompts +); + +fn get_tools() -> Vec { + vec![ + tool!("my_tool", "Tool description", schema, execute_tool) + ] +} ``` -
- -### Serve Locally +## Component Lifecycle +### 1. Development ```bash -ftl serve +ftl build # Build all components (from project root) +ftl test # Run tests +ftl up --port 3000 # Run locally ``` -This will start a local development server with hot reloading. You can test your tool by sending it a JSON-RPC request: - +### 2. Publishing ```bash -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"my-tool","arguments":{"input":"test"}},"id":1}' -``` - -Each FTL tool is a complete MCP server that exposes a single tool. When you deploy an individual tool, you're deploying a standalone MCP server. Toolkits (described below) bundle multiple tools together with a gateway that acts as a unified MCP server over multiple tools. - -### Deploy to FTL Edge +# Publish to GitHub Container Registry +ftl publish --tag v1.0.0 -```bash -ftl deploy +# Publish to Docker Hub +ftl publish --registry docker.io --tag latest ``` -This will deploy your tool to FTL Edge, where it can be called by your AI agents. - -## Toolkits - -FTL supports bundling multiple tools together as a toolkit, providing a powerful way to create comprehensive agent capabilities. Toolkits leverage the WebAssembly component model to enable secure, high-performance composition of tools. - -### Architecture - -Each FTL tool is a self-contained WebAssembly component that implements its own MCP server exposing a single tool. Toolkits take this further by: - -- **Component Composition**: Multiple WebAssembly components (tools) are bundled together using the component model. -- **Automatic Gateway**: FTL generates a gateway component that acts as a logical MCP server over each tool in the toolkit. The hop between the gateway component and the tool component happens fast, in memory. -- **Language Agnostic**: Each tool can be written in a different language (Rust, JavaScript, etc.), allowing you to mix languages within a single toolkit / MCP server. -- **Fast Tool Chaining**: Tools within a toolkit can be chained directly via instant in-memory calls. -- **Local Development**: Toolkits work seamlessly both locally and when deployed to the edge. +### 3. Composition +```bash +# Create a project composed of multiple components +ftl init my-assistant +cd my-assistant -### How It Works +# Add components +ftl add weather-tool --language typescript +ftl add github-tool --language rust +ftl add my-custom-tool --language javascript -```mermaid -graph TD - Client[AI agent] -->|tools/call| Gateway - Gateway["Gateway component (/mcp)"] - Gateway -->|/rs-tool/mcp| Rust["Rust tool server"] - Gateway -->|/js-tool/mcp| JS["JavaScript tool server"] - Gateway -->|/ts-tool/mcp| TS["TypeScript tool server"] +# Run the composed project +ftl up --build ``` -The gateway component: -- Exposes a Streamable HTTP /mcp endpoint that exposes an MCP server over all individual tool components -- Routes `tools/call` requests to the appropriate tool component -- Maintains protocol compatibility across all tools -- The request is passed from the gateway component to the tool in memory without leaving the host process. This is fast. - -### Create a Toolkit - +### 4. Deployment ```bash -# Build individual tools (can be different languages) -ftl new rust-analyzer # Rust tool -ftl new js-formatter # JavaScript tool -ftl new ts-linter # TypeScript tool -ftl new data-processor # Another Rust tool - -# Bundle them as a toolkit -ftl toolkit build --name dev-toolkit rust-analyzer js-formatter ts-linter data-processor -``` - -### Serve a Toolkit Locally +# Deploy to FTL +ftl deploy -```bash -ftl toolkit serve dev-toolkit +# Or use Spin directly +spin deploy ``` -This starts a local server with: -- `/mcp` - Unified endpoint that aggregates all tools -- `/rust-analyzer/mcp` - Direct access to individual tool -- `/js-formatter/mcp` - Direct access to individual tool -- `/ts-linter/mcp` - Direct access to individual tool -- `/data-processor/mcp` - Direct access to individual tool +## Architecture -### Deploy a Toolkit +FTL leverages the WebAssembly component model and Spin platform: -```bash -ftl toolkit deploy dev-toolkit +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ MCP Client │────▶│ Spin Project │────▶│ MCP Component │ +│ (AI Agent) │ │ (HTTP Router) │ │ (WASM Module) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + ├────▶ Component 1 (Rust) + ├────▶ Component 2 (TypeScript) + └────▶ Component 3 (JavaScript) ``` -### Benefits +Each component: +- Is a standalone WebAssembly module +- Implements the MCP protocol +- Can be developed and tested independently +- Can be composed with other components +- Runs in a secure sandbox -- **Single Integration Point**: AI agents connect to one MCP endpoint to access all tools -- **Mixed Language Support**: Combine Rust tools for performance-critical operations with JavaScript/TypeScript tools for rapid development -- **Component Isolation**: Each tool runs in its own sandboxed WebAssembly module -- **Local-First Development**: Test complete toolkits locally before deployment -- **Dynamic Composition**: Add or remove tools without changing agent configurations +## Prerequisites + +- **Rust toolchain** (for FTL CLI) +- **Language toolchains**: + - Rust: cargo with wasm32-wasip1 target + - TypeScript/JavaScript: Node.js 20+ +- **wkg** for publishing ([install](https://github.com/bytecodealliance/wasm-pkg-tools)) +- **Spin** (auto-installed by FTL) ## Documentation -For more detailed documentation, please see the [docs](./docs/introduction.md) directory in this repository. +- [Getting Started Guide](./docs/introduction.md) +- [Component Development](./docs/components.md) +- [Publishing Components](./docs/publishing.md) +- [Project Composition](./docs/composition.md) +- [API Reference](./docs/api.md) ## Development ### Running CI Checks Locally -This project uses [just](https://github.com/casey/just) for task automation. Install it with: +This project uses [just](https://github.com/casey/just) for task automation: ```bash -cargo install-just -# or install all dev tools: -cargo install-dev-tools -``` +# Install just +cargo install just -Then you can run: - -```bash -# Run all CI checks (same as CI) +# Run all CI checks just ci -# Individual checks -just fmt-check # Check formatting -just lint # Run clippy with CI settings -just test-all # Run all tests - -# Development helpers -just fix # Fix formatting and clippy warnings -just fix-fmt # Auto-fix formatting only -just fix-clippy # Auto-fix clippy warnings only - -# Quick checks -just dev # Format and lint (quick check) -just pre-push # Fix and test before pushing - -# Other tasks -just build-wasm # Build the SDK for WASM target -just spin-install # Install FTL-managed Spin +# Development workflow +just dev # Format and lint +just test-all # Run all tests +just pre-push # Full check before pushing -# See all available commands +# See all commands just --list ``` ## Contributing -Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for more information. +Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for details. ## License -This project is licensed under the Apache-2.0 License. +Apache-2.0 \ No newline at end of file diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 3c4ff5af..356cf6c9 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -1,140 +1,267 @@ # CLI Reference -The `ftl` command-line interface is the primary entry point for developers using the FTL platform. It provides a number of commands for creating, testing, and deploying tools. +The `ftl` command-line interface provides commands for creating, building, testing, and deploying MCP components. ## Global Options -- `-v, --verbose`: Increase logging verbosity. +- `-v, --verbose`: Increase logging verbosity (can be used multiple times) +- `--help`: Show help information +- `--version`: Show version information -## `ftl new` +## Project & Component Commands -Create a new tool from a template. +### `ftl init` + +Create a new MCP project for composing components. ```bash -ftl new [OPTIONS] +ftl init [name] [OPTIONS] ``` -### Arguments +**Arguments:** +- `[name]`: Project name (optional, will prompt if not provided) + +**Options:** +- `--here`: Initialize in current directory -- ``: The name of the tool. +**Examples:** +```bash +# Create a new project +ftl init my-assistant -### Options +# Initialize in current directory +ftl init my-project --here -- `-d, --description `: The description of the tool. +# Interactive mode +ftl init +``` -## `ftl build` +### `ftl add` -Build a tool. +Add a new MCP component to the current project. ```bash -ftl build [name] [OPTIONS] +ftl add [name] [OPTIONS] ``` -### Arguments +**Arguments:** +- `[name]`: Component name (optional, will prompt if not provided) + +**Options:** +- `-l, --language `: Language to use (`rust`, `typescript`, `javascript`) +- `-d, --description `: Component description +- `-r, --route `: HTTP route for the component (default: `/[name]`) +- `--git `: Use a Git repository as the template source +- `--branch `: Git branch to use (requires `--git`) +- `--dir `: Use a local directory as the template source +- `--tar `: Use a tarball as the template source + +**Examples:** +```bash +# Add a component with specified language +ftl add weather-api --language typescript --description "Weather data for AI agents" + +# Add a component with custom route +ftl add calculator --language rust --route /calc + +# Using a custom Git template +ftl add my-component --git https://github.com/user/template --branch main + +# Interactive mode +ftl add +``` + +### `ftl build` + +Build the component or project in the current directory. -- `[name]`: The name of the tool to build (defaults to the current directory). +```bash +ftl build [OPTIONS] +``` + +**Options:** +- `-r, --release`: Build in release mode +- `-p, --path `: Path to component or project directory (default: current) + +**Examples:** +```bash +# Build entire project (from project root with spin.toml) +ftl build + +# Build specific component +cd math-tools +ftl build --release + +# Build specific component from project root +ftl build --path math-tools +``` + +### `ftl up` + +Run the component locally for development. + +```bash +ftl up [OPTIONS] +``` -### Options +**Options:** +- `--build`: Build before running +- `-p, --port `: Port to serve on (default: 3000) +- `--path `: Path to component directory -- `-p, --profile `: The build profile to use (`dev`, `release`, or `tiny`). -- `-s, --serve`: Start a local development server after the build completes. +**Example:** +```bash +ftl up --build --port 8080 +``` -## `ftl serve` +### `ftl test` -Serve a tool locally. +Run component tests. ```bash -ftl serve [name] [OPTIONS] +ftl test [OPTIONS] ``` -### Arguments +**Options:** +- `-p, --path `: Path to component directory -- `[name]`: The name of the tool to serve (defaults to the current directory). +**Example:** +```bash +ftl test +``` -### Options +### `ftl publish` -- `-p, --port `: The port to serve on (defaults to 3000). -- `-b, --build`: Build the tool before serving. +Publish component to an OCI registry. -## `ftl test` +```bash +ftl publish [OPTIONS] +``` -Run tests for a tool. +**Options:** +- `-r, --registry `: Registry URL (default: ghcr.io) +- `-t, --tag `: Version tag to publish +- `--path `: Path to component directory +**Example:** ```bash -ftl test [name] +ftl publish --tag v1.0.0 +ftl publish --registry docker.io --tag latest ``` -### Arguments -- `[name]`: The name of the tool to test (defaults to the current directory). -## `ftl deploy` +### `ftl deploy` -Deploy a tool to the FTL Edge. +Deploy the project to FTL. ```bash -ftl deploy [name] +ftl deploy [OPTIONS] ``` -### Arguments +**Options:** +- `-e, --environment `: Target environment -- `[name]`: The name of the tool to deploy (defaults to the current directory). +**Example:** +```bash +ftl deploy --environment production +``` + +## Configuration Commands -## `ftl toolkit` +### `ftl setup templates` -Manage toolkits (collections of tools). +Install or update FTL component templates. -### `ftl toolkit build` +```bash +ftl setup templates [OPTIONS] +``` -Build a toolkit from multiple tools. This command: -- Builds each specified tool in release mode -- Bundles all tool WebAssembly modules together -- Automatically generates a gateway component that provides a unified MCP endpoint -- Creates a deployable toolkit directory with all components +**Options:** +- `--force`: Force reinstall even if already installed +- `--git `: Install templates from a Git repository +- `--branch `: Git branch to use (requires `--git`) +- `--dir `: Install templates from a local directory +- `--tar `: Install templates from a tarball +**Examples:** ```bash -ftl toolkit build --name +# Install default FTL templates +ftl setup templates + +# Install templates from a Git repository +ftl setup templates --git https://github.com/user/ftl-templates --branch main + +# Install templates from a local directory +ftl setup templates --dir ./my-templates + +# Install templates from a tarball +ftl setup templates --tar ./templates.tar.gz + +# Force reinstall templates +ftl setup templates --force ``` -#### Options +### `ftl setup info` -- `--name `: The name of the toolkit. +Show FTL configuration and status. -#### Arguments +```bash +ftl setup info +``` -- ``: The tools to include in the toolkit. Each tool must exist as a directory in the current working directory. +Displays: +- FTL CLI version +- Spin installation status +- Template installation status +- wkg availability -### `ftl toolkit serve` +## Registry Commands -Serve a toolkit locally. This starts a development server with: -- `/mcp` - Unified MCP endpoint that aggregates all tools in the toolkit -- `//mcp` - Individual endpoints for each tool (e.g., `/tool1/mcp`, `/tool2/mcp`) +### `ftl registry list` -The gateway endpoint supports all standard MCP operations: -- `initialize` - Initialize the connection -- `tools/list` - List all available tools across the toolkit -- `tools/call` - Call any tool in the toolkit +List available components (coming soon). ```bash -ftl toolkit serve [OPTIONS] +ftl registry list [OPTIONS] ``` -#### Arguments +**Options:** +- `-r, --registry `: Registry to list from + +### `ftl registry search` -- ``: The name of the toolkit directory. +Search for components (coming soon). -#### Options +```bash +ftl registry search [OPTIONS] +``` + +**Arguments:** +- ``: Search query -- `-p, --port `: The port to serve on (defaults to 3000). +**Options:** +- `-r, --registry `: Registry to search in -### `ftl toolkit deploy` +### `ftl registry info` -Deploy a toolkit to the FTL Edge. +Show component details (coming soon). ```bash -ftl toolkit deploy +ftl registry info ``` -#### Arguments +**Arguments:** +- ``: Component name or URL + +## Environment Variables + +- `FTL_AUTO_INSTALL`: Set to `true` to auto-install Spin without prompting +- `RUST_LOG`: Control logging verbosity (e.g., `info`, `debug`, `trace`) + +## Exit Codes -- ``: The name of the toolkit. +- `0`: Success +- `1`: General error +- `2`: Invalid arguments +- `127`: Command not found \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 1299677b..db831fec 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,87 +1,176 @@ # Getting Started -This guide will walk you through the process of creating, building, and deploying your first FTL tool. +This guide will walk you through creating, building, and deploying your first MCP component with FTL. ## Prerequisites -- [Rust](https://www.rust-lang.org/tools/install) -- [Docker](https://docs.docker.com/get-docker/) (for the local development server) +- [Rust](https://www.rust-lang.org/tools/install) (for the FTL CLI) +- Language-specific requirements: + - **Rust**: cargo with wasm32-wasip1 target + - **TypeScript/JavaScript**: Node.js 20+ +- [wkg](https://github.com/bytecodealliance/wasm-pkg-tools) (for publishing) ## 1. Install the FTL CLI -The FTL command-line interface is the primary entry point for developers using the FTL platform. You can install it using `cargo`: +```bash +cargo install ftl-cli +``` + +## 2. Create a New Project + +Start by creating a new MCP project: ```bash -cargo install ftl +ftl init my-assistant +cd my-assistant ``` -## 2. Create a New Tool +This creates an empty Spin project ready for adding components. -The `ftl new` command will create a new directory with a simple `ftl.toml` manifest, a `Cargo.toml` file, and a `src/lib.rs` file with a boilerplate tool implementation. +## 3. Add Your First Component + +Now add a component to your project: ```bash -ftl new my-tool --description "A new tool for my agent" -cd my-tool +ftl add weather-tool --language typescript --description "Weather information for AI agents" ``` -## 3. Implement Your Tool +This creates: +- `weather-tool/` - Component directory +- `weather-tool/ftl.toml` - Component configuration +- `weather-tool/Makefile` - Build automation +- `weather-tool/src/` - Component source code +- Updates `spin.toml` to include the component -Open `src/lib.rs` in your favorite editor and implement the `ftl_sdk_rs::Tool` trait. This trait defines the name, description, input schema, and `call` method for your tool. +## 4. Implement Your Component -```rust -use ftl_sdk_rs::prelude::*; - -#[derive(Clone)] -struct MyTool; - -impl Tool for MyTool { - fn name(&self) -> &'static str { "my-tool" } - fn description(&self) -> &'static str { "My tool description" } - - fn input_schema(&self) -> serde_json::Value { - json!({ - "type": "object", - "properties": { - "input": {"type": "string"} +Edit the component implementation in `weather-tool/src/`: + +### TypeScript Example + +```typescript +// weather-tool/src/features.ts +import { createTool } from '@fastertools/ftl-sdk'; + +export const tools = [ + createTool({ + name: 'get_weather', + description: 'Get current weather for a location', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string', description: 'City name' } }, - "required": ["input"] - }) - } - - fn call(&self, args: &serde_json::Value) -> Result { - let input = args["input"].as_str() - .ok_or(ToolError::InvalidArguments("input required".into()))?; - - Ok(ToolResult::text(format!("Processed: {}", input))) - } + required: ['location'] + }, + async execute(args) { + return `The weather in ${args.location} is sunny and 72°F`; + } + }) +]; + +export const resources = []; +export const prompts = []; +``` + +### Rust Example + +```rust +// weather-tool/src/features.rs +use ftl_sdk::*; + +pub fn get_tools() -> Vec { + vec![ + create_tool( + "get_weather", + "Get current weather for a location", + json!({ + "type": "object", + "properties": { + "location": { "type": "string", "description": "City name" } + }, + "required": ["location"] + }), + |args| { + let location = args["location"].as_str().unwrap_or("unknown"); + Ok(format!("The weather in {} is sunny and 72°F", location)) + } + ) + ] } +``` + +## 5. Build Your Components -ftl_sdk_rs::ftl_mcp_server!(MyTool); +```bash +ftl build ``` -## 4. Serve Locally +This compiles all components in your project into optimized WebAssembly modules. + +## 6. Test Locally -The `ftl serve` command will start a local development server with hot reloading. This allows you to test your tool without having to deploy it to the FTL Edge. +Run your project locally for testing: ```bash -ftl serve +ftl up ``` -You can test your tool by sending it a JSON-RPC request: +Test it with a curl request: ```bash curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"my-tool","arguments":{"input":"test"}},"id":1}' + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": {"location": "San Francisco"} + }, + "id": 1 + }' +``` + +## 7. Publish Your Components + +Publish components to a container registry: + +```bash +# Publish to GitHub Container Registry (default) +ftl publish --tag v1.0.0 + +# Or publish to Docker Hub +ftl publish --registry docker.io --tag latest +``` + +Your components are now available at: +- `ghcr.io/[username]/weather-tool:v1.0.0` + +## 8. Add More Components + +Add additional components to your project: + +```bash +# Add more components +ftl add news-tool --language typescript +ftl add calculator --language rust + +# Run the project with all components +ftl up --build ``` -## 5. Deploy to FTL Edge +## 9. Deploy to Production -The `ftl deploy` command will deploy your tool to the FTL Edge, where it can be called by your AI agents. +Deploy your project to FTL: ```bash ftl deploy ``` -You will be prompted to log in to your FTL account. Once you are logged in, your tool will be deployed and you will be given a URL that you can use to call it. +## Next Steps +- Read the [Component Development Guide](./developing-tools.md) +- Learn about [Publishing Components](./publishing.md) +- Explore [Project Composition](./composition.md) +- Check the [CLI Reference](./cli-reference.md) \ No newline at end of file diff --git a/docs/manifest.md b/docs/manifest.md index 0ca644d6..f2d7796b 100644 --- a/docs/manifest.md +++ b/docs/manifest.md @@ -1,49 +1,30 @@ # The ftl.toml Manifest -The `ftl.toml` file is the manifest for your FTL tool. It contains metadata about your tool, as well as configuration for the build process and the runtime environment. +The `ftl.toml` file is the manifest for your MCP component. It contains metadata about your component. ## Example ```toml -[tool] -name = "my-tool" -version = "0.0.1" -description = "A new tool for my agent" - -[build] -profile = "release" -features = [] - -[optimization] -flags = ["-O4"] - -[runtime] -allowed_hosts = [] +[component] +name = "my-component" +version = "0.1.0" +description = "An MCP component for AI agents" ``` -## `[tool]` - -The `[tool]` section contains metadata about your tool. - -- `name`: The name of your tool. This must be unique within your FTL account. -- `version`: The version of your tool. This should follow the [SemVer](https://semver.org/) specification. -- `description`: A short description of your tool. - -## `[build]` - -The `[build]` section contains configuration for the build process. - -- `profile`: The build profile to use. This can be `dev`, `release`, or `tiny`. -- `features`: A list of features to enable when building your tool. +## `[component]` -## `[optimization]` +The `[component]` section contains metadata about your component. -The `[optimization]` section contains configuration for the `wasm-opt` tool, which is used to optimize the size and performance of your tool's WebAssembly binary. +- `name`: The name of your component. This should be lowercase with hyphens (e.g., `my-component`). +- `version`: The version of your component. This should follow the [SemVer](https://semver.org/) specification. +- `description`: A short description of what your component does. -- `flags`: A list of flags to pass to `wasm-opt`. +## Build Configuration -## `[runtime]` +Build configuration is handled through: +- `Makefile` - For custom build commands +- Language-specific build tools (cargo, npm, etc.) -The `[runtime]` section contains configuration for the runtime environment in which your tool will be executed. +## Runtime Configuration -- `allowed_hosts`: A list of hosts that your tool is allowed to make HTTP requests to. If this list is empty, your tool will not be able to make any external HTTP requests. +Runtime configuration such as allowed hosts is configured in `spin.toml` rather than `ftl.toml`. \ No newline at end of file diff --git a/packages/ftl-cli/Cargo.toml b/packages/ftl-cli/Cargo.toml index 71e68d52..6d609f72 100644 --- a/packages/ftl-cli/Cargo.toml +++ b/packages/ftl-cli/Cargo.toml @@ -5,7 +5,7 @@ authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "CLI for building and deploying WebAssembly-based MCP tools" +description = "Build and deploy Model Context Protocol (MCP) servers on WebAssembly" readme = "README.md" homepage.workspace = true documentation.workspace = true diff --git a/packages/ftl-cli/README.md b/packages/ftl-cli/README.md index 06a41725..c3984b9f 100644 --- a/packages/ftl-cli/README.md +++ b/packages/ftl-cli/README.md @@ -1,15 +1,157 @@ # FTL CLI -This crate contains the command-line interface for the FTL framework. It provides a complete developer experience for the entire lifecycle of creating, testing, and deploying WebAssembly-based tools for AI agents. +Build and deploy Model Context Protocol (MCP) servers on WebAssembly. -## Features +FTL provides a streamlined developer experience for creating, testing, and deploying MCP components that run on the Fermyon Spin platform. -- **Tool Scaffolding:** `ftl new` creates a new tool from a template. -- **Building:** `ftl build` compiles your Rust code into an optimized WebAssembly component. -- **Local Development:** `ftl serve` starts a local server with hot reloading for rapid development and testing. -- **Deployment:** `ftl deploy` pushes your tool to the FTL Edge platform. -- **Toolkit Management:** `ftl toolkit` commands allow you to bundle multiple tools into a single deployable unit. +## Installation -## Usage +```bash +cargo install ftl-cli +``` -The `ftl` CLI is the primary entry point for developers using the FTL platform. For installation and usage instructions, please see the main project [README.md](../README.md) and the [documentation](../docs). +Or download pre-built binaries from the [releases page](https://github.com/fastertools/ftl-cli/releases). + +## Quick Start + +```bash +# Create a new MCP project +ftl init my-assistant +cd my-assistant + +# Add a component +ftl add weather-tool --language typescript + +# Build the components +ftl build + +# Run locally +ftl up + +# Publish to registry +ftl publish +``` + +## Commands + +### Project & Component Management + +#### `ftl init [name]` +Create a new MCP project for composing components. + +Options: +- `--here` - Initialize in current directory + +#### `ftl add [name]` +Add a new MCP component to the current project. + +Options: +- `--language ` - Language to use (rust, typescript, javascript) +- `--description ` - Component description +- `--route ` - HTTP route for the component + +#### `ftl build` +Build the component in the current directory. + +Options: +- `--release` - Build in release mode +- `--path ` - Path to component directory + +#### `ftl up` +Run the component locally for development. + +Options: +- `--build` - Build before running +- `--port ` - Port to serve on (default: 3000) + +#### `ftl test` +Run component tests. + +Options: +- `--path ` - Path to component directory + +#### `ftl publish` +Publish component to an OCI registry. + +Options: +- `--registry ` - Registry URL (default: ghcr.io) +- `--tag ` - Version tag to publish + +#### `ftl deploy` +Deploy the project to FTL. + +Options: +- `-e, --environment ` - Target environment + +### Configuration + +#### `ftl setup templates` +Install or update FTL component templates. + +Options: +- `--force` - Force reinstall + +#### `ftl setup info` +Show FTL configuration and status. + +### Registry Operations + +#### `ftl registry list` +List available components (coming soon). + +#### `ftl registry search ` +Search for components (coming soon). + +#### `ftl registry info ` +Show component details (coming soon). + +## Project Structure + +FTL projects follow a standard structure: + +``` +my-project/ +├── spin.toml # Spin project configuration +├── weather-tool/ # Component directory +│ ├── ftl.toml # Component manifest +│ ├── Makefile # Build automation +│ └── src/ # Component source code +│ ├── index.ts # Main entry point +│ └── features.ts # MCP tools/resources +└── calculator/ # Another component + ├── ftl.toml + ├── Makefile + └── src/ + └── lib.rs +``` + +## Publishing Components + +Components are published as OCI artifacts to container registries: + +```bash +# Publish to GitHub Container Registry (default) +ftl publish + +# Publish with specific version +ftl publish --tag v1.0.0 + +# Publish to Docker Hub +ftl publish --registry docker.io +``` + +Published components can be referenced as: +- `ghcr.io/username/component-name:version` +- `docker.io/username/component-name:version` + +## Prerequisites + +- **Spin**: Automatically installed by FTL if not present +- **wkg**: Required for publishing ([install](https://github.com/bytecodealliance/wasm-pkg-tools)) +- **Language toolchains**: + - Rust: cargo with wasm32-wasi target + - TypeScript/JavaScript: Node.js 20+ + +## License + +Apache-2.0 \ No newline at end of file diff --git a/packages/ftl-cli/build.rs b/packages/ftl-cli/build.rs index 829e6dd4..cbf5da8b 100644 --- a/packages/ftl-cli/build.rs +++ b/packages/ftl-cli/build.rs @@ -1,4 +1,4 @@ fn main() { // Empty build script for now // Can be used in the future for other build-time operations -} \ No newline at end of file +} diff --git a/packages/ftl-cli/examples/javascript-test/.gitignore b/packages/ftl-cli/examples/javascript-test/.gitignore deleted file mode 100644 index a418fe81..00000000 --- a/packages/ftl-cli/examples/javascript-test/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -.ftl/ -*.wasm -.spin/ -handler/src/generated/ -handler.wasm \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/Makefile b/packages/ftl-cli/examples/javascript-test/Makefile deleted file mode 100644 index d58450ec..00000000 --- a/packages/ftl-cli/examples/javascript-test/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -.PHONY: build test clean install - -install: - cd handler && npm install - -build: install - cd handler && npm run build - -test: install - cd handler && npm test - -clean: - cd handler && npm run clean - cd handler && rm -rf node_modules - -serve: - ftl serve - -deploy: - ftl deploy \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/README.md b/packages/ftl-cli/examples/javascript-test/README.md deleted file mode 100644 index 722306a1..00000000 --- a/packages/ftl-cli/examples/javascript-test/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# javascript-test - -An MCP tool written in JavaScript - -## Structure - -This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. - -- `handler/` - The JavaScript implementation of your MCP handler -- `ftl.toml` - FTL configuration file -- `spin.toml` - Spin application manifest - -## Development - -### Prerequisites - -- Node.js >= 20.0.0 -- FTL CLI - -### Building - -```bash -ftl build -# or -make build -``` - -### Testing - -```bash -ftl test -# or -make test -``` - -### Running Locally - -```bash -ftl serve -# or -make serve -``` - -The tool will be available at `http://localhost:3000/mcp` - -### Example Usage - -```bash -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "javascript_test", - "arguments": { - "input": "Hello, world!" - } - }, - "id": 1 - }' -``` - -## Deployment - -```bash -ftl deploy -# or -make deploy -``` - -## Implementing Your Tool - -Edit `handler/src/index.js` to implement your tool's functionality: - -1. Modify `listTools()` to define your tools -2. Implement the tool logic in `callTool()` -3. Optionally implement resources and prompts - -## Configuration - -Edit `ftl.toml` to configure: -- Allowed external hosts -- Build optimization flags -- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/ftl.toml b/packages/ftl-cli/examples/javascript-test/ftl.toml deleted file mode 100644 index 6ee30115..00000000 --- a/packages/ftl-cli/examples/javascript-test/ftl.toml +++ /dev/null @@ -1,24 +0,0 @@ -[tool] -name = "javascript-test" -version = "0.1.0" -description = "An MCP tool written in JavaScript" - -[build] -# Build commands are run from the handler directory -commands = [ - "cd handler && npm install", - "cd handler && npm run build" -] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/package-lock.json b/packages/ftl-cli/examples/javascript-test/handler/package-lock.json deleted file mode 100644 index b7a519d1..00000000 --- a/packages/ftl-cli/examples/javascript-test/handler/package-lock.json +++ /dev/null @@ -1,3678 +0,0 @@ -{ - "name": "javascript-test-handler", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "javascript-test-handler", - "version": "0.1.0", - "devDependencies": { - "@bytecodealliance/jco": "^1.8.1", - "vitest": "^1.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@bytecodealliance/componentize-js": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/componentize-js/-/componentize-js-0.17.0.tgz", - "integrity": "sha512-FDgO5UPipfjyq5OghSB4JW313LkQJK3Sl647WH1jvIuYAyCq1j+bMt+Q66c3UF6IVs6PneNTGfGSjYgzID/k0w==", - "dev": true, - "workspaces": [ - "." - ], - "dependencies": { - "@bytecodealliance/jco": "^1.9.1", - "@bytecodealliance/weval": "^0.3.3", - "@bytecodealliance/wizer": "^7.0.5", - "es-module-lexer": "^1.6.0" - } - }, - "node_modules/@bytecodealliance/jco": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.11.3.tgz", - "integrity": "sha512-Ynv0GDj9iAp+cS03UGg+ss4uo/Hx5e4FyjcFDBfkRt9qJsqbL2AnPqgO/ORNlSXg9zu9PTNC0uGgE7/Vklcn/w==", - "dev": true, - "license": "(Apache-2.0 WITH LLVM-exception)", - "dependencies": { - "@bytecodealliance/componentize-js": "^0.17.0", - "@bytecodealliance/preview2-shim": "^0.17.2", - "binaryen": "^123.0.0", - "chalk-template": "^1", - "commander": "^12", - "mkdirp": "^3", - "ora": "^8", - "terser": "^5" - }, - "bin": { - "jco": "src/jco.js" - } - }, - "node_modules/@bytecodealliance/preview2-shim": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.2.tgz", - "integrity": "sha512-mNm/lblgES8UkVle8rGImXOz4TtL3eU3inHay/7TVchkKrb/lgcVvTK0+VAw8p5zQ0rgQsXm1j5dOlAAd+MeoA==", - "dev": true, - "license": "(Apache-2.0 WITH LLVM-exception)" - }, - "node_modules/@bytecodealliance/weval": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@bytecodealliance/weval/-/weval-0.3.4.tgz", - "integrity": "sha512-+GCKtZXhPj7qyDl5pR0F3PTEk8tWINV8dv1tUbKMjMXvqjHIkvzalkWo5ZL2kCKwh8Bwn8rWpSmyvRC/Nlu9nQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@napi-rs/lzma": "^1.1.2", - "decompress": "^4.2.1", - "decompress-tar": "^4.1.1", - "decompress-unzip": "^4.0.1" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@bytecodealliance/wizer": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-7.0.5.tgz", - "integrity": "sha512-xIbLzKxmUNaPwDWorcGtdxh1mcgDiXI8fe9KiDaSICKfCl9VtUKVyXIc3ix+VpwFczBbdhek+TlMiiCf+9lpOQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "wizer": "wizer.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@bytecodealliance/wizer-darwin-arm64": "7.0.5", - "@bytecodealliance/wizer-darwin-x64": "7.0.5", - "@bytecodealliance/wizer-linux-arm64": "7.0.5", - "@bytecodealliance/wizer-linux-s390x": "7.0.5", - "@bytecodealliance/wizer-linux-x64": "7.0.5", - "@bytecodealliance/wizer-win32-x64": "7.0.5" - } - }, - "node_modules/@bytecodealliance/wizer-darwin-arm64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-7.0.5.tgz", - "integrity": "sha512-Tp0SgVQR568SVPvSfyWDT00yL4ry/w9FS2qy8ZwaP0EauYyjFSZojj6mESX6x9fpYkEnQdprgfdvhw5h1hTwCQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "wizer-darwin-arm64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-darwin-x64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-7.0.5.tgz", - "integrity": "sha512-HYmG5Q9SpQJnqR7kimb5J3VAh6E62b30GLG/E+6doS/UwNhSpSmYjaggVfuJvgFDbUxsnD1l36qZny0xMwxikA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "wizer-darwin-x64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-linux-arm64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-7.0.5.tgz", - "integrity": "sha512-01qqaiIWrYXPt2bjrfiluSSOmUL/PMjPtJlYa/XqZgK75g3RVn3sRkSflwoCXtXMRbHdb03qNrJ9w81+F17kvA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-arm64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-linux-s390x": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-7.0.5.tgz", - "integrity": "sha512-smGfD4eJou81g6yDlV7MCRoKgKlqd4SQL00pHxQGrNfUPnfYKhZ4z80N9J9T2B++uo2FM14BFilsRrV5UevKlA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-s390x": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-linux-x64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-7.0.5.tgz", - "integrity": "sha512-lxMb25jLd6n+hhjPhlqRBnBdGRumKkcEavqJ3p4OAtjr6pEPdbSfSVmYDt9LnvtqmqQSnUCtFRRr5J2BmQ3SkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-x64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-win32-x64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-7.0.5.tgz", - "integrity": "sha512-eUY9a82HR20qIfyEffWdJZj7k4GH2wGaZpr70dinDy8Q648LeQayL0Z6FW5nApoezjy+CIBj0Mv+rHUASV9Jzw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "wizer-win32-x64": "wizer" - } - }, - "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", - "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/lzma": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.4.3.tgz", - "integrity": "sha512-uBjLLoUM9ll03jL/bP7XjyPg0vTU0vQ35N1vVqQHbzlK/fVZyuF2B1p/A6kqPsFFhaoBKgO6oaxsuerv091RtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/lzma-android-arm-eabi": "1.4.3", - "@napi-rs/lzma-android-arm64": "1.4.3", - "@napi-rs/lzma-darwin-arm64": "1.4.3", - "@napi-rs/lzma-darwin-x64": "1.4.3", - "@napi-rs/lzma-freebsd-x64": "1.4.3", - "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.3", - "@napi-rs/lzma-linux-arm64-gnu": "1.4.3", - "@napi-rs/lzma-linux-arm64-musl": "1.4.3", - "@napi-rs/lzma-linux-ppc64-gnu": "1.4.3", - "@napi-rs/lzma-linux-riscv64-gnu": "1.4.3", - "@napi-rs/lzma-linux-s390x-gnu": "1.4.3", - "@napi-rs/lzma-linux-x64-gnu": "1.4.3", - "@napi-rs/lzma-linux-x64-musl": "1.4.3", - "@napi-rs/lzma-wasm32-wasi": "1.4.3", - "@napi-rs/lzma-win32-arm64-msvc": "1.4.3", - "@napi-rs/lzma-win32-ia32-msvc": "1.4.3", - "@napi-rs/lzma-win32-x64-msvc": "1.4.3" - } - }, - "node_modules/@napi-rs/lzma-android-arm-eabi": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.3.tgz", - "integrity": "sha512-XpjRUZ/EbWtVbMvW+ucon5Ykz7PjMoX65mIlUdAiVnaPGykzFAUrl8dl6Br5bfqnhQQfDjjUIgTAwWl3G++n1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-android-arm64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.3.tgz", - "integrity": "sha512-Bve6BF/4pnlO6HotIgRWgmUT3rbbW/QH471RF/GBA29GfEeUOPEdfQWC7tlzrLYsVFNX2KCWKd+XlxQNz9sRaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-darwin-arm64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.3.tgz", - "integrity": "sha512-UxTb56kL6pSVTsZ1ShibnqLSwJZLTWtPU5TNYuyIjVNQYAIG8JQ5Yxz35azjwBCK7AjD8pBdpWLYUSyJRGAVAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-darwin-x64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.3.tgz", - "integrity": "sha512-ps6HiwGKS1P4ottyV2/hVboZ0ugdM1Z1qO9YFpcuKweORfxAkxwJ6S8jOt7G27LQiWiiQHVwsUCODTHDFhOUPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-freebsd-x64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.3.tgz", - "integrity": "sha512-W49h41U3+vLnbthbPzvJX1fQtTG+1jyUlfB+wX3oxILvIur06PjJRdMXrFtOZpWkFsihK9gO2DRkQYQJIIgTZw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.3.tgz", - "integrity": "sha512-11PNPiMGuwwxIxd9yPZY3Ek6RFGFRFQb/AtMStJIwlmJ6sM/djEknClLJVbVXbC/nqm7htVZEr+qmYgoDy0fAw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.3.tgz", - "integrity": "sha512-XzlxZjSXTcrWFHbvvv2xbV5+bSV5IJqCJ8CCksc7xV3uWEAso9yBPJ8VSRD3GPc7ZoBDRqJmgCb/HQzHpLBekw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm64-musl": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.3.tgz", - "integrity": "sha512-k4fWiI4Pm61Esj8hnm7NWIbpZueTtP2jlJqmMhTqJyjqW3NUxbTHjSErZOZKIFRF1B3if4v5Tyzo7JL2X+BaSQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.3.tgz", - "integrity": "sha512-tTIfk+TYZYbFySxaCMuzp4Zz1T3I6OYVYNAm+IrCSkZDLmUKUzBK3+Su+mT+PjcTNsAiHBa5NVjARXC7b7jmgQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.3.tgz", - "integrity": "sha512-HPyLYOYhkN7QYaWiKWhSnsLmx/l0pqgiiyaYeycgxCm9dwL8ummFWxveZqYjqdbUUvG7Mgi1jqgRe+55MVdyZQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-s390x-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.3.tgz", - "integrity": "sha512-YkcV+RSZZIMM3D5sPZqvo2Q7/tHXBhgJWBi+6ceo46pTlqgn/nH+pVz+CzsDmLWz5hqNSXyv5IAhOcg2CH6rAg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.3.tgz", - "integrity": "sha512-ep6PLjN1+g4P12Hc7sLRmVpXXaHX22ykqxnOzjXUoj1KTph5XgM4+fUCyE5dsYI+lB4/tXqFuf9ZeFgHk5f00A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-musl": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.3.tgz", - "integrity": "sha512-QkCO6rVw0Z7eY0ziVc4aCFplbOTMpt0UBLPXWxsPd2lXtkAlRChzqaHOxdcL/HoLmBsqdCxmG0EZuHuAP/vKZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-wasm32-wasi": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.3.tgz", - "integrity": "sha512-+rMamB0xaeDyVt4OP4cV888cnmso+m78iUebNhGcrL/WXIziwql50KQrmj7PBdBCza/W7XEcraZT8pO8gSDGcg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.10" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@napi-rs/lzma-win32-arm64-msvc": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.3.tgz", - "integrity": "sha512-6gQ+R6ztw11hswdsEu0jsOOXXnJPwhOA1yHRjqfuFemhf6esMd8l9b0uh3BfLBNe7qumtrH4KLrHu8yC9pSY3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-win32-ia32-msvc": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.3.tgz", - "integrity": "sha512-+AJeJQoGE+QtZKlwM4VzDkfLmUa+6DsGOO5zdbIPlRCB6PEstRCXxp8lkMiQBNgk9f/IO0UEkRcJSZ+Hhqd8zw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-win32-x64-msvc": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.3.tgz", - "integrity": "sha512-66dFCX9ACpVUyTTom89nxhllc88yJyjxGFHO0M2olFcrSJArulfbE9kNIATgh04NDAe/l8VsDhnAxWuvJY1GuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", - "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", - "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", - "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", - "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", - "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", - "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", - "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", - "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", - "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", - "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", - "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", - "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", - "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", - "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", - "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", - "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", - "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", - "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", - "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", - "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", - "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^2.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/binaryen": { - "version": "123.0.0", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-123.0.0.tgz", - "integrity": "sha512-/hls/a309aZCc0itqP6uhoR+5DsKSlJVfB8Opd2BY9Ndghs84IScTunlyidyF4r2Xe3lQttnfBNIDjaNpj6mTw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "wasm-as": "bin/wasm-as", - "wasm-ctor-eval": "bin/wasm-ctor-eval", - "wasm-dis": "bin/wasm-dis", - "wasm-merge": "bin/wasm-merge", - "wasm-metadce": "bin/wasm-metadce", - "wasm-opt": "bin/wasm-opt", - "wasm-reduce": "bin/wasm-reduce", - "wasm-shell": "bin/wasm-shell", - "wasm2js": "bin/wasm2js" - } - }, - "node_modules/bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", - "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", - "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tar": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tarbz2": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tarbz2/node_modules/file-type": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-targz": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-unzip": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", - "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-unzip/node_modules/file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-natural-number": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rollup": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", - "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.1", - "@rollup/rollup-android-arm64": "4.44.1", - "@rollup/rollup-darwin-arm64": "4.44.1", - "@rollup/rollup-darwin-x64": "4.44.1", - "@rollup/rollup-freebsd-arm64": "4.44.1", - "@rollup/rollup-freebsd-x64": "4.44.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", - "@rollup/rollup-linux-arm-musleabihf": "4.44.1", - "@rollup/rollup-linux-arm64-gnu": "4.44.1", - "@rollup/rollup-linux-arm64-musl": "4.44.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-musl": "4.44.1", - "@rollup/rollup-linux-s390x-gnu": "4.44.1", - "@rollup/rollup-linux-x64-gnu": "4.44.1", - "@rollup/rollup-linux-x64-musl": "4.44.1", - "@rollup/rollup-win32-arm64-msvc": "4.44.1", - "@rollup/rollup-win32-ia32-msvc": "4.44.1", - "@rollup/rollup-win32-x64-msvc": "4.44.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/seek-bzip": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", - "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^2.8.1" - }, - "bin": { - "seek-bunzip": "bin/seek-bunzip", - "seek-table": "bin/seek-bzip-table" - } - }, - "node_modules/seek-bzip/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-natural-number": "^4.0.1" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-buffer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", - "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-buffer/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/packages/ftl-cli/examples/javascript-test/handler/package.json b/packages/ftl-cli/examples/javascript-test/handler/package.json deleted file mode 100644 index 51749bf9..00000000 --- a/packages/ftl-cli/examples/javascript-test/handler/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "javascript-test-handler", - "version": "0.1.0", - "type": "module", - "description": "An MCP tool written in JavaScript", - "main": "src/index.js", - "scripts": { - "build": "npm run build:types && npm run build:component", - "build:types": "jco types wit/mcp.wit -o src/generated", - "build:component": "jco componentize src/index.js --wit wit/mcp.wit --world-name mcp-handler --out ../handler.wasm --disable all", - "test": "vitest", - "clean": "rm -rf generated ../handler.wasm" - }, - "devDependencies": { - "@bytecodealliance/jco": "^1.8.1", - "vitest": "^1.0.0" - }, - "engines": { - "node": ">=20.0.0" - } -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/src/index.js b/packages/ftl-cli/examples/javascript-test/handler/src/index.js deleted file mode 100644 index 5c605294..00000000 --- a/packages/ftl-cli/examples/javascript-test/handler/src/index.js +++ /dev/null @@ -1,104 +0,0 @@ -// Export the handler implementation for componentize-js -export const handler = { - listTools() { - return [{ - name: 'javascript_test', - description: 'An MCP tool written in JavaScript', - inputSchema: JSON.stringify({ - type: 'object', - properties: { - input: { - type: 'string', - description: 'Input to process' - } - }, - required: ['input'] - }) - }]; - }, - - callTool(name, argumentsStr) { - let args; - try { - args = JSON.parse(argumentsStr); - } catch (e) { - return { - tag: 'error', - val: { - code: -32602, - message: `Invalid JSON arguments: ${e}`, - data: undefined - } - }; - } - - switch (name) { - case 'javascript_test': { - const input = args.input || 'No input provided'; - // TODO: Implement your tool logic here - return { - tag: 'text', - val: `Processed: ${input}` - }; - } - default: - return { - tag: 'error', - val: { - code: -32601, - message: `Unknown tool: ${name}`, - data: undefined - } - }; - } - }, - - listResources() { - return [ - // Add resources here - // Example: - // { - // uri: 'example://resource', - // name: 'Example Resource', - // description: 'An example resource', - // mimeType: 'text/plain' - // } - ]; - }, - - readResource(uri) { - // According to WIT, this returns ResourceContents, not a Result - // So we need to return a valid ResourceContents or throw - throw { - code: -32601, - message: `Resource not found: ${uri}`, - data: undefined - }; - }, - - listPrompts() { - return [ - // Add prompts here - // Example: - // { - // name: 'greeting', - // description: 'Generate a greeting', - // arguments: [{ - // name: 'name', - // description: 'Name to greet', - // required: true - // }] - // } - ]; - }, - - getPrompt(name, argumentsStr) { - // According to WIT, this returns Array, not a Result - // So we need to return an array or throw - throw { - code: -32601, - message: `Prompt not found: ${name}`, - data: undefined - }; - } -}; \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js b/packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js deleted file mode 100644 index fe8ce82b..00000000 --- a/packages/ftl-cli/examples/javascript-test/handler/test/handler.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Handler } from '../src/index.js'; - -describe('Handler', () => { - describe('listTools', () => { - it('should return tool metadata', () => { - const tools = Handler.listTools(); - expect(tools).toHaveLength(1); - expect(tools[0].name).toBe('javascript_test'); - expect(tools[0].description).toBe('An MCP tool written in JavaScript'); - }); - }); - - describe('callTool', () => { - it('should process input correctly', () => { - const args = JSON.stringify({ input: 'test input' }); - const result = Handler.callTool('javascript_test', args); - - expect(result.tag).toBe('text'); - expect(result.val).toContain('test input'); - }); - - it('should handle invalid JSON', () => { - const result = Handler.callTool('javascript_test', 'invalid json'); - - expect(result.tag).toBe('error'); - expect(result.val.code).toBe(-32602); - }); - - it('should handle unknown tool', () => { - const args = JSON.stringify({ input: 'test' }); - const result = Handler.callTool('unknown_tool', args); - - expect(result.tag).toBe('error'); - expect(result.val.code).toBe(-32601); - }); - }); -}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/vitest.config.js b/packages/ftl-cli/examples/javascript-test/handler/vitest.config.js deleted file mode 100644 index c7175ddc..00000000 --- a/packages/ftl-cli/examples/javascript-test/handler/vitest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.js'], - coverage: { - reporter: ['text', 'json', 'html'], - exclude: ['node_modules/', 'src/generated/'] - } - } -}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit b/packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit deleted file mode 100644 index cfaf6acd..00000000 --- a/packages/ftl-cli/examples/javascript-test/handler/wit/mcp.wit +++ /dev/null @@ -1,82 +0,0 @@ -package component:mcp@0.1.0; - -/// MCP handler interface -interface handler { - /// Tool definition - record tool { - name: string, - description: string, - input-schema: string, // JSON Schema as string - } - - /// Resource definition - record resource-info { - uri: string, - name: string, - description: option, - mime-type: option, - } - - /// Resource contents - record resource-contents { - uri: string, - mime-type: option, - text: option, - blob: option>, - } - - /// Prompt definition - record prompt { - name: string, - description: option, - arguments: list, - } - - record prompt-argument { - name: string, - description: option, - required: bool, - } - - /// Prompt message - record prompt-message { - role: string, - content: string, - } - - /// Error type - record error { - code: s32, - message: string, - data: option, - } - - /// Tool call result - variant tool-result { - text(string), - error(error), - } - - /// List available tools - list-tools: func() -> list; - - /// Call a tool - call-tool: func(name: string, arguments: string) -> tool-result; - - /// List available resources - list-resources: func() -> list; - - /// Read a resource - read-resource: func(uri: string) -> result; - - /// List available prompts - list-prompts: func() -> list; - - /// Get a prompt - get-prompt: func(name: string, arguments: string) -> result, error>; -} - -// World for MCP handler components (libraries) -world mcp-handler { - export handler; -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/javascript-test/spin.toml b/packages/ftl-cli/examples/javascript-test/spin.toml deleted file mode 100644 index 13aefde0..00000000 --- a/packages/ftl-cli/examples/javascript-test/spin.toml +++ /dev/null @@ -1,19 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "javascript-test" -version = "0.1.0" -authors = ["bowlofarugula "] -description = "An MCP tool written in JavaScript" - -[[trigger.http]] -route = "/mcp" -component = "mcp-gateway" - -[component.mcp-gateway] -# Using the published gateway component -source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } - -[component.mcp-gateway.dependencies] -# Link to the local handler component -"component:mcp/handler@0.1.0" = { path = "./handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/.gitignore b/packages/ftl-cli/examples/test-templates/rust-test/.gitignore deleted file mode 100644 index b7cb55af..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -target/ -Cargo.lock -.ftl/ -*.wasm -.spin/ \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/Makefile b/packages/ftl-cli/examples/test-templates/rust-test/Makefile deleted file mode 100644 index 57307302..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -.PHONY: build test clean - -build: - cd handler && cargo build --release --target wasm32-wasip1 - -test: - cd handler && cargo test - -clean: - cd handler && cargo clean - -serve: - ftl serve - -deploy: - ftl deploy \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/README.md b/packages/ftl-cli/examples/test-templates/rust-test/README.md deleted file mode 100644 index 275889e8..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# rust-test - -An MCP tool written in Rust - -## Structure - -This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. - -- `handler/` - The Rust implementation of your MCP handler -- `ftl.toml` - FTL configuration file -- `spin.toml` - Spin application manifest - -## Development - -### Prerequisites - -- Rust with `wasm32-wasip1` target -- FTL CLI - -### Building - -```bash -ftl build -# or -make build -``` - -### Testing - -```bash -ftl test -# or -make test -``` - -### Running Locally - -```bash -ftl serve -# or -make serve -``` - -The tool will be available at `http://localhost:3000/mcp` - -### Example Usage - -```bash -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "rust_test", - "arguments": { - "input": "Hello, world!" - } - }, - "id": 1 - }' -``` - -## Deployment - -```bash -ftl deploy -# or -make deploy -``` - -## Implementing Your Tool - -Edit `handler/src/lib.rs` to implement your tool's functionality: - -1. Modify `list_tools()` to define your tools -2. Implement the tool logic in `call_tool()` -3. Optionally implement resources and prompts - -## Configuration - -### Memory Allocation - -The WebAssembly memory allocator size can be adjusted in `handler/src/lib.rs`: - -```rust -const ARENA_SIZE: usize = 1 * 1024 * 1024; // Default: 1MB -``` - -### Runtime Configuration - -Edit `ftl.toml` to configure: -- Allowed external hosts -- Build optimization flags -- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/ftl.toml b/packages/ftl-cli/examples/test-templates/rust-test/ftl.toml deleted file mode 100644 index cf7a4e64..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/ftl.toml +++ /dev/null @@ -1,23 +0,0 @@ -[tool] -name = "rust-test" -version = "0.1.0" -description = "An MCP tool written in Rust" - -[build] -# Build commands are run from the handler directory -commands = [ - "cargo build --release --target wasm32-wasip1" -] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml b/packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml deleted file mode 100644 index 8d49b2da..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/handler/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "rust_test" -version = "0.1.0" -edition = "2021" - -[dependencies] -wit-bindgen = "0.16.0" -serde_json = "1.0" - -[lib] -crate-type = ["cdylib"] - -[profile.release] -opt-level = 3 -lto = "fat" -codegen-units = 1 -panic = "abort" -strip = "symbols" - -[profile.dev] -opt-level = 1 -debug = true - -[workspace] \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs b/packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs deleted file mode 100644 index 0525b3ba..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/handler/src/lib.rs +++ /dev/null @@ -1,110 +0,0 @@ -use serde_json::{json, Value}; - -wit_bindgen::generate!({ - world: "mcp-handler", - path: "./wit", - exports: { - "component:mcp/handler": Component - } -}); - -use exports::component::mcp::handler::{ - Guest, Tool, ToolResult, ResourceInfo, ResourceContents, Prompt, PromptMessage, Error as McpError -}; - -struct Component; - -impl Guest for Component { - fn list_tools() -> Vec { - vec![ - Tool { - name: "rust_test".to_string(), - description: "An MCP tool written in Rust".to_string(), - input_schema: json!({ - "type": "object", - "properties": { - "input": { - "type": "string", - "description": "Input to process" - } - }, - "required": ["input"] - }).to_string(), - }, - // Add more tools here - ] - } - - fn call_tool(name: String, arguments: String) -> ToolResult { - let args: Value = match serde_json::from_str(&arguments) { - Ok(v) => v, - Err(e) => return ToolResult::Error(McpError { - code: -32602, - message: format!("Invalid JSON arguments: {}", e), - data: None, - }), - }; - - match name.as_str() { - "rust_test" => { - let input = args["input"].as_str().unwrap_or("No input provided"); - // TODO: Implement your tool logic here - ToolResult::Text(format!("Processed: {}", input)) - } - _ => ToolResult::Error(McpError { - code: -32601, - message: format!("Unknown tool: {}", name), - data: None, - }), - } - } - - fn list_resources() -> Vec { - vec![ - // Add resources here - // Example: - // ResourceInfo { - // uri: "example://resource".to_string(), - // name: "Example Resource".to_string(), - // description: Some("An example resource".to_string()), - // mime_type: Some("text/plain".to_string()), - // } - ] - } - - fn read_resource(uri: String) -> Result { - // Implement resource reading logic here - Err(McpError { - code: -32601, - message: format!("Resource not found: {}", uri), - data: None, - }) - } - - fn list_prompts() -> Vec { - vec![ - // Add prompts here - // Example: - // Prompt { - // name: "greeting".to_string(), - // description: Some("Generate a greeting".to_string()), - // arguments: vec![ - // PromptArgument { - // name: "name".to_string(), - // description: Some("Name to greet".to_string()), - // required: true, - // } - // ], - // } - ] - } - - fn get_prompt(name: String, _arguments: String) -> Result, McpError> { - // Implement prompt generation logic here - Err(McpError { - code: -32601, - message: format!("Prompt not found: {}", name), - data: None, - }) - } -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit b/packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit deleted file mode 100644 index cfaf6acd..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/handler/wit/mcp.wit +++ /dev/null @@ -1,82 +0,0 @@ -package component:mcp@0.1.0; - -/// MCP handler interface -interface handler { - /// Tool definition - record tool { - name: string, - description: string, - input-schema: string, // JSON Schema as string - } - - /// Resource definition - record resource-info { - uri: string, - name: string, - description: option, - mime-type: option, - } - - /// Resource contents - record resource-contents { - uri: string, - mime-type: option, - text: option, - blob: option>, - } - - /// Prompt definition - record prompt { - name: string, - description: option, - arguments: list, - } - - record prompt-argument { - name: string, - description: option, - required: bool, - } - - /// Prompt message - record prompt-message { - role: string, - content: string, - } - - /// Error type - record error { - code: s32, - message: string, - data: option, - } - - /// Tool call result - variant tool-result { - text(string), - error(error), - } - - /// List available tools - list-tools: func() -> list; - - /// Call a tool - call-tool: func(name: string, arguments: string) -> tool-result; - - /// List available resources - list-resources: func() -> list; - - /// Read a resource - read-resource: func(uri: string) -> result; - - /// List available prompts - list-prompts: func() -> list; - - /// Get a prompt - get-prompt: func(name: string, arguments: string) -> result, error>; -} - -// World for MCP handler components (libraries) -world mcp-handler { - export handler; -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/spin.toml b/packages/ftl-cli/examples/test-templates/rust-test/spin.toml deleted file mode 100644 index 655c0e93..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/spin.toml +++ /dev/null @@ -1,19 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "rust-test" -version = "0.1.0" -authors = ["bowlofarugula "] -description = "An MCP tool written in Rust" - -[[trigger.http]] -route = "/mcp" -component = "mcp-gateway" - -[component.mcp-gateway] -# Using the published gateway component -source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } - -[component.mcp-gateway.dependencies] -# Link to the local handler component -"component:mcp/handler@0.1.0" = { path = "./handler/target/wasm32-wasip1/release/rust_test.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json b/packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json deleted file mode 100644 index 2f5dbf3a..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/test-call-object.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "rust_test", - "arguments": {"input":"Hello from Rust!"} - }, - "id": 3 -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/test-call.json b/packages/ftl-cli/examples/test-templates/rust-test/test-call.json deleted file mode 100644 index 4979d6a4..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/test-call.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "rust_test", - "arguments": "{\"input\":\"Hello from Rust!\"}" - }, - "id": 2 -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json b/packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json deleted file mode 100644 index fd3152d6..00000000 --- a/packages/ftl-cli/examples/test-templates/rust-test/test-mcp-spec.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "rust_test", - "arguments": { - "input": "Hello from Rust!" - } - }, - "id": 4 -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/.gitignore b/packages/ftl-cli/examples/typescript-test/.gitignore deleted file mode 100644 index 1a7f6419..00000000 --- a/packages/ftl-cli/examples/typescript-test/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules/ -dist/ -.ftl/ -*.wasm -.spin/ -handler/src/generated/ -handler.wasm \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/Makefile b/packages/ftl-cli/examples/typescript-test/Makefile deleted file mode 100644 index d58450ec..00000000 --- a/packages/ftl-cli/examples/typescript-test/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -.PHONY: build test clean install - -install: - cd handler && npm install - -build: install - cd handler && npm run build - -test: install - cd handler && npm test - -clean: - cd handler && npm run clean - cd handler && rm -rf node_modules - -serve: - ftl serve - -deploy: - ftl deploy \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/README.md b/packages/ftl-cli/examples/typescript-test/README.md deleted file mode 100644 index d4527dc7..00000000 --- a/packages/ftl-cli/examples/typescript-test/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# typescript-test - -An MCP tool written in TypeScript - -## Structure - -This is an FTL tool that implements the Model Context Protocol (MCP) using WebAssembly components. - -- `handler/` - The TypeScript implementation of your MCP handler -- `ftl.toml` - FTL configuration file -- `spin.toml` - Spin application manifest - -## Development - -### Prerequisites - -- Node.js >= 20.0.0 -- FTL CLI - -### Building - -```bash -ftl build -# or -make build -``` - -### Testing - -```bash -ftl test -# or -make test -``` - -### Running Locally - -```bash -ftl serve -# or -make serve -``` - -The tool will be available at `http://localhost:3000/mcp` - -### Example Usage - -```bash -curl -X POST http://localhost:3000/mcp \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "method": "tools/call", - "params": { - "name": "typescript_test", - "arguments": { - "input": "Hello, world!" - } - }, - "id": 1 - }' -``` - -## Deployment - -```bash -ftl deploy -# or -make deploy -``` - -## Implementing Your Tool - -Edit `handler/src/index.ts` to implement your tool's functionality: - -1. Modify `listTools()` to define your tools -2. Implement the tool logic in `callTool()` -3. Optionally implement resources and prompts - -## Type Safety - -This template uses `jco` to generate TypeScript types from the WIT interface definition. The types are generated in `handler/src/generated/` when you run `npm run build`. - -## Configuration - -Edit `ftl.toml` to configure: -- Allowed external hosts -- Build optimization flags -- Other runtime settings \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/ftl.toml b/packages/ftl-cli/examples/typescript-test/ftl.toml deleted file mode 100644 index c15688c3..00000000 --- a/packages/ftl-cli/examples/typescript-test/ftl.toml +++ /dev/null @@ -1,24 +0,0 @@ -[tool] -name = "typescript-test" -version = "0.1.0" -description = "An MCP tool written in TypeScript" - -[build] -# Build commands are run from the handler directory -commands = [ - "cd handler && npm install", - "cd handler && npm run build" -] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/package-lock.json b/packages/ftl-cli/examples/typescript-test/handler/package-lock.json deleted file mode 100644 index b60b1d8d..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/package-lock.json +++ /dev/null @@ -1,3711 +0,0 @@ -{ - "name": "typescript-test-handler", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "typescript-test-handler", - "version": "0.1.0", - "devDependencies": { - "@bytecodealliance/jco": "^1.8.1", - "@types/node": "^20.0.0", - "typescript": "^5.0.0", - "vitest": "^1.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@bytecodealliance/componentize-js": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/componentize-js/-/componentize-js-0.17.0.tgz", - "integrity": "sha512-FDgO5UPipfjyq5OghSB4JW313LkQJK3Sl647WH1jvIuYAyCq1j+bMt+Q66c3UF6IVs6PneNTGfGSjYgzID/k0w==", - "dev": true, - "workspaces": [ - "." - ], - "dependencies": { - "@bytecodealliance/jco": "^1.9.1", - "@bytecodealliance/weval": "^0.3.3", - "@bytecodealliance/wizer": "^7.0.5", - "es-module-lexer": "^1.6.0" - } - }, - "node_modules/@bytecodealliance/jco": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.11.3.tgz", - "integrity": "sha512-Ynv0GDj9iAp+cS03UGg+ss4uo/Hx5e4FyjcFDBfkRt9qJsqbL2AnPqgO/ORNlSXg9zu9PTNC0uGgE7/Vklcn/w==", - "dev": true, - "license": "(Apache-2.0 WITH LLVM-exception)", - "dependencies": { - "@bytecodealliance/componentize-js": "^0.17.0", - "@bytecodealliance/preview2-shim": "^0.17.2", - "binaryen": "^123.0.0", - "chalk-template": "^1", - "commander": "^12", - "mkdirp": "^3", - "ora": "^8", - "terser": "^5" - }, - "bin": { - "jco": "src/jco.js" - } - }, - "node_modules/@bytecodealliance/preview2-shim": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.2.tgz", - "integrity": "sha512-mNm/lblgES8UkVle8rGImXOz4TtL3eU3inHay/7TVchkKrb/lgcVvTK0+VAw8p5zQ0rgQsXm1j5dOlAAd+MeoA==", - "dev": true, - "license": "(Apache-2.0 WITH LLVM-exception)" - }, - "node_modules/@bytecodealliance/weval": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@bytecodealliance/weval/-/weval-0.3.4.tgz", - "integrity": "sha512-+GCKtZXhPj7qyDl5pR0F3PTEk8tWINV8dv1tUbKMjMXvqjHIkvzalkWo5ZL2kCKwh8Bwn8rWpSmyvRC/Nlu9nQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@napi-rs/lzma": "^1.1.2", - "decompress": "^4.2.1", - "decompress-tar": "^4.1.1", - "decompress-unzip": "^4.0.1" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@bytecodealliance/wizer": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-7.0.5.tgz", - "integrity": "sha512-xIbLzKxmUNaPwDWorcGtdxh1mcgDiXI8fe9KiDaSICKfCl9VtUKVyXIc3ix+VpwFczBbdhek+TlMiiCf+9lpOQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "wizer": "wizer.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@bytecodealliance/wizer-darwin-arm64": "7.0.5", - "@bytecodealliance/wizer-darwin-x64": "7.0.5", - "@bytecodealliance/wizer-linux-arm64": "7.0.5", - "@bytecodealliance/wizer-linux-s390x": "7.0.5", - "@bytecodealliance/wizer-linux-x64": "7.0.5", - "@bytecodealliance/wizer-win32-x64": "7.0.5" - } - }, - "node_modules/@bytecodealliance/wizer-darwin-arm64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-7.0.5.tgz", - "integrity": "sha512-Tp0SgVQR568SVPvSfyWDT00yL4ry/w9FS2qy8ZwaP0EauYyjFSZojj6mESX6x9fpYkEnQdprgfdvhw5h1hTwCQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "wizer-darwin-arm64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-darwin-x64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-7.0.5.tgz", - "integrity": "sha512-HYmG5Q9SpQJnqR7kimb5J3VAh6E62b30GLG/E+6doS/UwNhSpSmYjaggVfuJvgFDbUxsnD1l36qZny0xMwxikA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "wizer-darwin-x64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-linux-arm64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-7.0.5.tgz", - "integrity": "sha512-01qqaiIWrYXPt2bjrfiluSSOmUL/PMjPtJlYa/XqZgK75g3RVn3sRkSflwoCXtXMRbHdb03qNrJ9w81+F17kvA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-arm64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-linux-s390x": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-7.0.5.tgz", - "integrity": "sha512-smGfD4eJou81g6yDlV7MCRoKgKlqd4SQL00pHxQGrNfUPnfYKhZ4z80N9J9T2B++uo2FM14BFilsRrV5UevKlA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-s390x": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-linux-x64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-7.0.5.tgz", - "integrity": "sha512-lxMb25jLd6n+hhjPhlqRBnBdGRumKkcEavqJ3p4OAtjr6pEPdbSfSVmYDt9LnvtqmqQSnUCtFRRr5J2BmQ3SkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-x64": "wizer" - } - }, - "node_modules/@bytecodealliance/wizer-win32-x64": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-7.0.5.tgz", - "integrity": "sha512-eUY9a82HR20qIfyEffWdJZj7k4GH2wGaZpr70dinDy8Q648LeQayL0Z6FW5nApoezjy+CIBj0Mv+rHUASV9Jzw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "wizer-win32-x64": "wizer" - } - }, - "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", - "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/lzma": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.4.3.tgz", - "integrity": "sha512-uBjLLoUM9ll03jL/bP7XjyPg0vTU0vQ35N1vVqQHbzlK/fVZyuF2B1p/A6kqPsFFhaoBKgO6oaxsuerv091RtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/lzma-android-arm-eabi": "1.4.3", - "@napi-rs/lzma-android-arm64": "1.4.3", - "@napi-rs/lzma-darwin-arm64": "1.4.3", - "@napi-rs/lzma-darwin-x64": "1.4.3", - "@napi-rs/lzma-freebsd-x64": "1.4.3", - "@napi-rs/lzma-linux-arm-gnueabihf": "1.4.3", - "@napi-rs/lzma-linux-arm64-gnu": "1.4.3", - "@napi-rs/lzma-linux-arm64-musl": "1.4.3", - "@napi-rs/lzma-linux-ppc64-gnu": "1.4.3", - "@napi-rs/lzma-linux-riscv64-gnu": "1.4.3", - "@napi-rs/lzma-linux-s390x-gnu": "1.4.3", - "@napi-rs/lzma-linux-x64-gnu": "1.4.3", - "@napi-rs/lzma-linux-x64-musl": "1.4.3", - "@napi-rs/lzma-wasm32-wasi": "1.4.3", - "@napi-rs/lzma-win32-arm64-msvc": "1.4.3", - "@napi-rs/lzma-win32-ia32-msvc": "1.4.3", - "@napi-rs/lzma-win32-x64-msvc": "1.4.3" - } - }, - "node_modules/@napi-rs/lzma-android-arm-eabi": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.4.3.tgz", - "integrity": "sha512-XpjRUZ/EbWtVbMvW+ucon5Ykz7PjMoX65mIlUdAiVnaPGykzFAUrl8dl6Br5bfqnhQQfDjjUIgTAwWl3G++n1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-android-arm64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.4.3.tgz", - "integrity": "sha512-Bve6BF/4pnlO6HotIgRWgmUT3rbbW/QH471RF/GBA29GfEeUOPEdfQWC7tlzrLYsVFNX2KCWKd+XlxQNz9sRaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-darwin-arm64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.4.3.tgz", - "integrity": "sha512-UxTb56kL6pSVTsZ1ShibnqLSwJZLTWtPU5TNYuyIjVNQYAIG8JQ5Yxz35azjwBCK7AjD8pBdpWLYUSyJRGAVAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-darwin-x64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.4.3.tgz", - "integrity": "sha512-ps6HiwGKS1P4ottyV2/hVboZ0ugdM1Z1qO9YFpcuKweORfxAkxwJ6S8jOt7G27LQiWiiQHVwsUCODTHDFhOUPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-freebsd-x64": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.4.3.tgz", - "integrity": "sha512-W49h41U3+vLnbthbPzvJX1fQtTG+1jyUlfB+wX3oxILvIur06PjJRdMXrFtOZpWkFsihK9gO2DRkQYQJIIgTZw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.4.3.tgz", - "integrity": "sha512-11PNPiMGuwwxIxd9yPZY3Ek6RFGFRFQb/AtMStJIwlmJ6sM/djEknClLJVbVXbC/nqm7htVZEr+qmYgoDy0fAw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.4.3.tgz", - "integrity": "sha512-XzlxZjSXTcrWFHbvvv2xbV5+bSV5IJqCJ8CCksc7xV3uWEAso9yBPJ8VSRD3GPc7ZoBDRqJmgCb/HQzHpLBekw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-arm64-musl": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.4.3.tgz", - "integrity": "sha512-k4fWiI4Pm61Esj8hnm7NWIbpZueTtP2jlJqmMhTqJyjqW3NUxbTHjSErZOZKIFRF1B3if4v5Tyzo7JL2X+BaSQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.4.3.tgz", - "integrity": "sha512-tTIfk+TYZYbFySxaCMuzp4Zz1T3I6OYVYNAm+IrCSkZDLmUKUzBK3+Su+mT+PjcTNsAiHBa5NVjARXC7b7jmgQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.4.3.tgz", - "integrity": "sha512-HPyLYOYhkN7QYaWiKWhSnsLmx/l0pqgiiyaYeycgxCm9dwL8ummFWxveZqYjqdbUUvG7Mgi1jqgRe+55MVdyZQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-s390x-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.4.3.tgz", - "integrity": "sha512-YkcV+RSZZIMM3D5sPZqvo2Q7/tHXBhgJWBi+6ceo46pTlqgn/nH+pVz+CzsDmLWz5hqNSXyv5IAhOcg2CH6rAg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.4.3.tgz", - "integrity": "sha512-ep6PLjN1+g4P12Hc7sLRmVpXXaHX22ykqxnOzjXUoj1KTph5XgM4+fUCyE5dsYI+lB4/tXqFuf9ZeFgHk5f00A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-linux-x64-musl": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.4.3.tgz", - "integrity": "sha512-QkCO6rVw0Z7eY0ziVc4aCFplbOTMpt0UBLPXWxsPd2lXtkAlRChzqaHOxdcL/HoLmBsqdCxmG0EZuHuAP/vKZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-wasm32-wasi": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.4.3.tgz", - "integrity": "sha512-+rMamB0xaeDyVt4OP4cV888cnmso+m78iUebNhGcrL/WXIziwql50KQrmj7PBdBCza/W7XEcraZT8pO8gSDGcg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.10" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@napi-rs/lzma-win32-arm64-msvc": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.4.3.tgz", - "integrity": "sha512-6gQ+R6ztw11hswdsEu0jsOOXXnJPwhOA1yHRjqfuFemhf6esMd8l9b0uh3BfLBNe7qumtrH4KLrHu8yC9pSY3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-win32-ia32-msvc": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.4.3.tgz", - "integrity": "sha512-+AJeJQoGE+QtZKlwM4VzDkfLmUa+6DsGOO5zdbIPlRCB6PEstRCXxp8lkMiQBNgk9f/IO0UEkRcJSZ+Hhqd8zw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/lzma-win32-x64-msvc": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.4.3.tgz", - "integrity": "sha512-66dFCX9ACpVUyTTom89nxhllc88yJyjxGFHO0M2olFcrSJArulfbE9kNIATgh04NDAe/l8VsDhnAxWuvJY1GuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", - "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", - "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", - "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", - "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", - "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", - "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", - "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", - "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", - "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", - "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", - "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", - "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", - "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", - "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", - "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", - "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", - "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", - "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", - "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", - "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", - "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz", - "integrity": "sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^2.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/binaryen": { - "version": "123.0.0", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-123.0.0.tgz", - "integrity": "sha512-/hls/a309aZCc0itqP6uhoR+5DsKSlJVfB8Opd2BY9Ndghs84IScTunlyidyF4r2Xe3lQttnfBNIDjaNpj6mTw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "wasm-as": "bin/wasm-as", - "wasm-ctor-eval": "bin/wasm-ctor-eval", - "wasm-dis": "bin/wasm-dis", - "wasm-merge": "bin/wasm-merge", - "wasm-metadce": "bin/wasm-metadce", - "wasm-opt": "bin/wasm-opt", - "wasm-reduce": "bin/wasm-reduce", - "wasm-shell": "bin/wasm-shell", - "wasm2js": "bin/wasm2js" - } - }, - "node_modules/bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", - "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", - "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tar": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tarbz2": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-tarbz2/node_modules/file-type": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-targz": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-unzip": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", - "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/decompress-unzip/node_modules/file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-natural-number": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rollup": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", - "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.1", - "@rollup/rollup-android-arm64": "4.44.1", - "@rollup/rollup-darwin-arm64": "4.44.1", - "@rollup/rollup-darwin-x64": "4.44.1", - "@rollup/rollup-freebsd-arm64": "4.44.1", - "@rollup/rollup-freebsd-x64": "4.44.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", - "@rollup/rollup-linux-arm-musleabihf": "4.44.1", - "@rollup/rollup-linux-arm64-gnu": "4.44.1", - "@rollup/rollup-linux-arm64-musl": "4.44.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-gnu": "4.44.1", - "@rollup/rollup-linux-riscv64-musl": "4.44.1", - "@rollup/rollup-linux-s390x-gnu": "4.44.1", - "@rollup/rollup-linux-x64-gnu": "4.44.1", - "@rollup/rollup-linux-x64-musl": "4.44.1", - "@rollup/rollup-win32-arm64-msvc": "4.44.1", - "@rollup/rollup-win32-ia32-msvc": "4.44.1", - "@rollup/rollup-win32-x64-msvc": "4.44.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/seek-bzip": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", - "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^2.8.1" - }, - "bin": { - "seek-bunzip": "bin/seek-bunzip", - "seek-table": "bin/seek-bzip-table" - } - }, - "node_modules/seek-bzip/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-natural-number": "^4.0.1" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-buffer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", - "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-buffer/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/packages/ftl-cli/examples/typescript-test/handler/package.json b/packages/ftl-cli/examples/typescript-test/handler/package.json deleted file mode 100644 index a9487bfa..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "typescript-test-handler", - "version": "0.1.0", - "type": "module", - "description": "An MCP tool written in TypeScript", - "main": "dist/index.js", - "scripts": { - "build": "npm run build:types && npm run build:js && npm run build:component", - "build:types": "jco types wit/mcp.wit -o src/generated", - "build:js": "tsc", - "build:component": "jco componentize dist/index.js --wit wit/mcp.wit --world-name mcp-handler --out ../handler.wasm --disable all", - "test": "vitest", - "clean": "rm -rf dist generated ../handler.wasm" - }, - "devDependencies": { - "@bytecodealliance/jco": "^1.8.1", - "@types/node": "^20.0.0", - "typescript": "^5.0.0", - "vitest": "^1.0.0" - }, - "engines": { - "node": ">=20.0.0" - } -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/src/index.ts b/packages/ftl-cli/examples/typescript-test/handler/src/index.ts deleted file mode 100644 index b6a2e377..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/src/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -export const handler = { - listTools() { - return [{ - name: 'typescript_test', - description: 'An MCP tool written in TypeScript', - inputSchema: JSON.stringify({ - type: 'object', - properties: { - input: { - type: 'string', - description: 'Input to process' - } - }, - required: ['input'] - }) - }]; - }, - - callTool(name: string, argumentsStr: string) { - let args: any; - try { - args = JSON.parse(argumentsStr); - } catch (e) { - return { - tag: 'error', - val: { - code: -32602, - message: `Invalid JSON arguments: ${e}`, - data: undefined - } - }; - } - - switch (name) { - case 'typescript_test': { - const input = args.input || 'No input provided'; - // TODO: Implement your tool logic here - return { - tag: 'text', - val: `Processed: ${input}` - }; - } - default: - return { - tag: 'error', - val: { - code: -32601, - message: `Unknown tool: ${name}`, - data: undefined - } - }; - } - }, - - listResources() { - return []; - }, - - readResource(uri: string) { - // According to WIT, this returns ResourceContents, not a Result - // So we need to return a valid ResourceContents or throw - throw { - code: -32601, - message: `Resource not found: ${uri}`, - data: undefined - }; - }, - - listPrompts() { - return []; - }, - - getPrompt(name: string, argumentsStr: string) { - // According to WIT, this returns Array, not a Result - // So we need to return an array or throw - throw { - code: -32601, - message: `Prompt not found: ${name}`, - data: undefined - }; - } -}; \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts b/packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts deleted file mode 100644 index 452208b4..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/test/handler.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Handler } from '../src/index'; - -describe('Handler', () => { - describe('listTools', () => { - it('should return tool metadata', () => { - const tools = Handler.listTools(); - expect(tools).toHaveLength(1); - expect(tools[0].name).toBe('typescript_test'); - expect(tools[0].description).toBe('An MCP tool written in TypeScript'); - }); - }); - - describe('callTool', () => { - it('should process input correctly', () => { - const args = JSON.stringify({ input: 'test input' }); - const result = Handler.callTool('typescript_test', args); - - expect(result.tag).toBe('text'); - if (result.tag === 'text') { - expect(result.val).toContain('test input'); - } - }); - - it('should handle invalid JSON', () => { - const result = Handler.callTool('typescript_test', 'invalid json'); - - expect(result.tag).toBe('error'); - if (result.tag === 'error') { - expect(result.val.code).toBe(-32602); - } - }); - - it('should handle unknown tool', () => { - const args = JSON.stringify({ input: 'test' }); - const result = Handler.callTool('unknown_tool', args); - - expect(result.tag).toBe('error'); - if (result.tag === 'error') { - expect(result.val.code).toBe(-32601); - } - }); - }); -}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/tsconfig.json b/packages/ftl-cli/examples/typescript-test/handler/tsconfig.json deleted file mode 100644 index 090ee7c3..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "lib": ["ES2022"], - "moduleResolution": "node", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "allowJs": true, - "checkJs": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "noEmit": false, - "isolatedModules": true, - "allowSyntheticDefaultImports": true, - "types": ["node", "vitest/globals"] - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test"] -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts b/packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts deleted file mode 100644 index 3942f585..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/vitest.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['test/**/*.test.ts'], - coverage: { - reporter: ['text', 'json', 'html'], - exclude: ['node_modules/', 'dist/', 'src/generated/'] - } - } -}); \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit b/packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit deleted file mode 100644 index cfaf6acd..00000000 --- a/packages/ftl-cli/examples/typescript-test/handler/wit/mcp.wit +++ /dev/null @@ -1,82 +0,0 @@ -package component:mcp@0.1.0; - -/// MCP handler interface -interface handler { - /// Tool definition - record tool { - name: string, - description: string, - input-schema: string, // JSON Schema as string - } - - /// Resource definition - record resource-info { - uri: string, - name: string, - description: option, - mime-type: option, - } - - /// Resource contents - record resource-contents { - uri: string, - mime-type: option, - text: option, - blob: option>, - } - - /// Prompt definition - record prompt { - name: string, - description: option, - arguments: list, - } - - record prompt-argument { - name: string, - description: option, - required: bool, - } - - /// Prompt message - record prompt-message { - role: string, - content: string, - } - - /// Error type - record error { - code: s32, - message: string, - data: option, - } - - /// Tool call result - variant tool-result { - text(string), - error(error), - } - - /// List available tools - list-tools: func() -> list; - - /// Call a tool - call-tool: func(name: string, arguments: string) -> tool-result; - - /// List available resources - list-resources: func() -> list; - - /// Read a resource - read-resource: func(uri: string) -> result; - - /// List available prompts - list-prompts: func() -> list; - - /// Get a prompt - get-prompt: func(name: string, arguments: string) -> result, error>; -} - -// World for MCP handler components (libraries) -world mcp-handler { - export handler; -} \ No newline at end of file diff --git a/packages/ftl-cli/examples/typescript-test/spin.toml b/packages/ftl-cli/examples/typescript-test/spin.toml deleted file mode 100644 index 97135bfa..00000000 --- a/packages/ftl-cli/examples/typescript-test/spin.toml +++ /dev/null @@ -1,19 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "typescript-test" -version = "0.1.0" -authors = ["bowlofarugula "] -description = "An MCP tool written in TypeScript" - -[[trigger.http]] -route = "/mcp" -component = "mcp-gateway" - -[component.mcp-gateway] -# Using the published gateway component -source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } - -[component.mcp-gateway.dependencies] -# Link to the local handler component -"component:mcp/handler@0.1.0" = { path = "./handler.wasm" } \ No newline at end of file diff --git a/packages/ftl-cli/src/commands/add.rs b/packages/ftl-cli/src/commands/add.rs new file mode 100644 index 00000000..00d4d7b7 --- /dev/null +++ b/packages/ftl-cli/src/commands/add.rs @@ -0,0 +1,232 @@ +use std::path::PathBuf; +use std::process::Command; + +use anyhow::{Context, Result}; +use console::style; +use dialoguer::{Input, Select, theme::ColorfulTheme}; + +use crate::{common::spin_installer::check_and_install_spin, language::Language}; + +pub async fn execute( + name: Option, + description: Option, + language: Option, + route: Option, + git: Option, + branch: Option, + dir: Option, + tar: Option, +) -> Result<()> { + // Check if we're in a Spin project directory + if !PathBuf::from("spin.toml").exists() { + anyhow::bail!("No spin.toml found. Not in a Spin project directory? Run 'ftl init' first."); + } + + // Get component name interactively if not provided + let component_name = match name { + Some(n) => n, + None => Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("Component name") + .interact_text()?, + }; + + println!( + "{} Adding component: {}", + style("→").cyan(), + style(&component_name).bold() + ); + + // Validate component name + if !component_name + .chars() + .all(|c| c.is_lowercase() || c == '-' || c.is_numeric()) + { + anyhow::bail!("Component name must be lowercase with hyphens (e.g., my-component)"); + } + + // Don't allow leading or trailing hyphens, or double hyphens + if component_name.starts_with('-') + || component_name.ends_with('-') + || component_name.contains("--") + { + anyhow::bail!("Component name cannot start or end with hyphens, or contain double hyphens"); + } + + // Get description interactively if not provided + let description = match description { + Some(d) => d, + None => Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("Component description") + .interact_text()?, + }; + + // Determine language + let selected_language = match language { + Some(lang_str) => Language::from_str(&lang_str).ok_or_else(|| { + anyhow::anyhow!( + "Invalid language: {lang_str}. Valid options are: rust, javascript, typescript" + ) + })?, + None => { + // Interactive language selection + let languages = vec!["rust", "javascript", "typescript"]; + let selection = Select::with_theme(&ColorfulTheme::default()) + .with_prompt("Select programming language") + .items(&languages) + .default(0) + .interact()?; + + Language::from_str(languages[selection]).unwrap() + } + }; + + // Get route interactively if not provided + let route = match route { + Some(r) => { + // Ensure route ends with /mcp + if r.ends_with("/mcp") { + r + } else if r.ends_with('/') { + format!("{r}mcp") + } else { + format!("{r}/mcp") + } + } + None => { + // Convert component name to kebab-case for the route + let kebab_name = component_name.replace('_', "-").to_lowercase(); + let default_route = format!("/{}/mcp", kebab_name); + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("HTTP route") + .default(default_route) + .interact_text()? + } + }; + + // Get spin path + let spin_path = check_and_install_spin().await?; + + // Use spin add with the appropriate template + let template_id = match selected_language { + Language::Rust => "ftl-rust", + Language::TypeScript => "ftl-typescript", + Language::JavaScript => "ftl-javascript", + }; + + // Check if custom template source is provided + let using_custom_template = git.is_some() || dir.is_some() || tar.is_some(); + + let mut spin_cmd = Command::new(&spin_path); + spin_cmd.args(["add"]); + + // Add template source options + if let Some(git_url) = &git { + spin_cmd.args(["--git", git_url]); + if let Some(branch_name) = &branch { + spin_cmd.args(["--branch", branch_name]); + } + } else if let Some(dir_path) = &dir { + spin_cmd.args(["--dir", dir_path.to_str().unwrap()]); + } else if let Some(tar_path) = &tar { + spin_cmd.args(["--tar", tar_path]); + } else { + // Use default template + spin_cmd.args(["-t", template_id]); + } + + spin_cmd.args([ + "--accept-defaults", + "--value", + &format!("project-description={}", description), + "--value", + &format!("route={}", route), + &component_name, + ]); + + let output = spin_cmd.output().context("Failed to run spin add")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + + // Check if templates need to be installed (only for default templates) + if !using_custom_template + && (stderr.contains("no such template") || stderr.contains("template not found")) + { + println!(); + println!( + "{} FTL templates not found. Installing...", + style("→").yellow() + ); + + // Install templates + let template_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); + + let install_output = Command::new(&spin_path) + .args([ + "templates", + "install", + "--dir", + template_dir.to_str().unwrap(), + "--upgrade", + ]) + .output() + .context("Failed to install templates")?; + + if !install_output.status.success() { + anyhow::bail!( + "Failed to install templates:\n{}", + String::from_utf8_lossy(&install_output.stderr) + ); + } + + println!("{} Templates installed successfully!", style("✓").green()); + println!(); + + // Retry spin add + let retry_output = spin_cmd + .output() + .context("Failed to run spin add after template installation")?; + + if !retry_output.status.success() { + anyhow::bail!( + "Failed to add component:\n{}", + String::from_utf8_lossy(&retry_output.stderr) + ); + } + } else { + anyhow::bail!("Failed to add component:\n{}", stderr); + } + } + + // Success message based on language + let main_file = match selected_language { + Language::Rust => format!("{}/src/lib.rs", component_name), + Language::JavaScript => format!("{}/src/index.js", component_name), + Language::TypeScript => format!("{}/src/index.ts", component_name), + }; + + println!( + r#" +{} {} component added successfully! + +{} Component location: + └── {}/ # Component source code + +{} Edit {} to implement your MCP features + +{} cd {} && make build # Build component + +{} ftl up --build # Build all components and start development server"#, + style("✓").green(), + selected_language, + style("📁").blue(), + component_name, + style("💡").bright(), + style(main_file).cyan(), + style("🔨").bright(), + component_name, + style("🚀").yellow(), + ); + + Ok(()) +} diff --git a/packages/ftl-cli/src/commands/build.rs b/packages/ftl-cli/src/commands/build.rs index aec49048..54b35908 100644 --- a/packages/ftl-cli/src/commands/build.rs +++ b/packages/ftl-cli/src/commands/build.rs @@ -1,191 +1,177 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; -use anyhow::Result; +use anyhow::{Context, Result}; use console::style; -use indicatif::{ProgressBar, ProgressStyle}; - -use crate::{ - common::{ - build_utils::{format_size, get_file_size, optimize_wasm}, - manifest_utils::load_manifest_and_name, - spin_installer::check_and_install_spin, - tool_paths::{self, ensure_ftl_dir, get_spin_toml_path}, - }, - language::{Language, get_language_support}, -}; - -pub async fn execute(name: Option, profile: Option) -> Result<()> { - let tool_path = name.unwrap_or_else(|| ".".to_string()); - build_tool(&tool_path, profile, false).await -} - -pub async fn execute_quiet(tool_path: &str, profile: Option) -> Result<()> { - build_tool(tool_path, profile, true).await -} -pub async fn execute_and_serve(name: Option, profile: Option) -> Result<()> { - let tool_path = name.unwrap_or_else(|| ".".to_string()); +use crate::{common::spin_installer::check_and_install_spin, manifest::ComponentManifest}; - // First build the tool - build_tool(&tool_path, profile, false).await?; +pub async fn execute(path: Option, release: bool) -> Result<()> { + let working_path = path.unwrap_or_else(|| PathBuf::from(".")); - // Then serve it - println!(); - crate::commands::serve::execute(tool_path, 3000, false).await -} - -async fn build_tool(tool_path: &str, profile: Option, quiet: bool) -> Result<()> { - if !quiet { + // Check if we're in a project directory (has spin.toml) or component directory (has ftl.toml) + if working_path.join("spin.toml").exists() { + // Project-level build - use spin build println!( - "{} Building tool: {}", + "{} Building project {}", style("→").cyan(), - style(tool_path).bold() + style(working_path.display()).bold() ); - } - // Validate tool directory exists - crate::common::tool_paths::validate_tool_exists(tool_path)?; - - // Load and validate manifest - let (manifest, tool_name) = load_manifest_and_name(tool_path)?; - manifest.validate()?; + let spin_path = check_and_install_spin().await?; + let mut child = Command::new(&spin_path) + .args(["build"]) + .current_dir(&working_path) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run spin build")?; + + let status = child.wait()?; + if !status.success() { + anyhow::bail!("Build failed"); + } - // Determine build profile - let build_profile = profile.unwrap_or_else(|| manifest.build.profile.clone()); + println!("\n{} Project built successfully!", style("✓").green()); - // Get language support - let language_support = get_language_support(manifest.tool.language); + return Ok(()); + } - // Ensure .ftl directory exists and check spin is installed - ensure_ftl_dir(tool_path)?; - check_and_install_spin().await?; + // Component-level build + println!( + "{} Building component {}", + style("→").cyan(), + style(working_path.display()).bold() + ); - // Copy spin.toml to .ftl directory if it doesn't exist - let spin_toml_src = PathBuf::from(tool_path).join("spin.toml"); - let spin_toml_dest = get_spin_toml_path(tool_path); - if spin_toml_src.exists() && !spin_toml_dest.exists() { - std::fs::copy(&spin_toml_src, &spin_toml_dest)?; + // Validate component directory exists + if !working_path.join("ftl.toml").exists() { + anyhow::bail!("No ftl.toml or spin.toml found. Not in a component or project directory?"); } - // Create progress bar (only if not quiet) - let pb = if quiet { - ProgressBar::hidden() - } else { - let pb = ProgressBar::new(3); - pb.set_style( - ProgressStyle::default_bar() - .template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}") - .unwrap() - .progress_chars("#>-"), - ); - pb - }; + let component_path = working_path; - // Step 1: Run language-specific build - let language = manifest.tool.language; - pb.set_message(format!("Building {language} tool...")); - pb.inc(1); - - // Validate language environment - language_support.validate_environment()?; - - // Run build commands from ftl.toml - if let Some(commands) = &manifest.build.commands { - for command in commands { - let parts: Vec<&str> = command.split_whitespace().collect(); - if parts.is_empty() { - continue; - } - - let output = std::process::Command::new(parts[0]) - .args(&parts[1..]) - .current_dir(tool_path) - .output()?; - - if !output.status.success() { - anyhow::bail!( - "Build command failed: {}\n{}", - command, - String::from_utf8_lossy(&output.stderr) - ); - } - } - } else { - // Fallback to language-specific build - language_support.build(&manifest, std::path::Path::new(tool_path))?; - } + // Load component manifest + let manifest = ComponentManifest::load(&component_path)?; - // Step 2: Verify WASM was built - pb.set_message("Verifying build output..."); - pb.inc(1); + // Run build based on detected build system + println!("{} Running build...", style("▶").green()); + println!(); - // Check for handler.wasm first (new structure) - let wasm_path = PathBuf::from(tool_path).join("handler.wasm"); - let wasm_path = if wasm_path.exists() { - wasm_path + let status = if component_path.join("Makefile").exists() { + // Use make if available + Command::new("make") + .arg("build") + .current_dir(&component_path) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run make build")? + .wait()? + } else if component_path.join("handler/Cargo.toml").exists() { + // Rust component + let profile = if release { "release" } else { "debug" }; + Command::new("cargo") + .args(["component", "build", "--target", "wasm32-wasip1"]) + .arg(format!("--{}", profile)) + .current_dir(component_path.join("handler")) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run cargo build")? + .wait()? + } else if component_path.join("handler/package.json").exists() { + // JavaScript/TypeScript component + // First install dependencies + println!("{} Installing dependencies...", style("→").dim()); + let npm_install_status = Command::new("npm") + .arg("install") + .current_dir(component_path.join("handler")) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run npm install")? + .wait()?; + + if !npm_install_status.success() { + anyhow::bail!("npm install failed"); + } + + // Then build + println!("\n{} Building component...", style("→").dim()); + Command::new("npm") + .args(["run", "build"]) + .current_dir(component_path.join("handler")) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to run npm build")? + .wait()? } else { - // Fallback to old structure - tool_paths::get_wasm_path_for_language( - tool_path, - &tool_name, - &build_profile, - manifest.tool.language, - ) + anyhow::bail!("Unable to determine build system for component"); }; - if !wasm_path.exists() { - anyhow::bail!("WASM binary not found at: {}", wasm_path.display()); + if !status.success() { + anyhow::bail!("Build failed"); } - // Copy WASM to .ftl/dist directory for deployment - let ftl_dist_dir = PathBuf::from(tool_path).join(".ftl").join("dist"); - std::fs::create_dir_all(&ftl_dist_dir)?; - let dest_wasm = ftl_dist_dir.join("handler.wasm"); - if wasm_path != dest_wasm { - std::fs::copy(&wasm_path, &dest_wasm)?; - } + // Verify output exists + let wasm_path = find_wasm_output(&component_path, &manifest)?; + let size = std::fs::metadata(&wasm_path)?.len(); - // Step 3: Run wasm-opt (post-build optimization) - only for Rust - if manifest.tool.language == Language::Rust { - pb.set_message("Optimizing WASM binary..."); - pb.inc(1); - - // Always include flags to match Rust's target features - let mut wasm_opt_flags = vec![ - "--enable-simd".to_string(), - "--enable-bulk-memory".to_string(), - "--enable-mutable-globals".to_string(), - "--enable-sign-ext".to_string(), - "--enable-nontrapping-float-to-int".to_string(), - "--enable-reference-types".to_string(), - ]; - - // Add user-specified flags - if !manifest.optimization.flags.is_empty() { - wasm_opt_flags.extend(manifest.optimization.flags.clone()); - } else { - // Default optimization if none specified - wasm_opt_flags.push("-O2".to_string()); - } + println!("\n{} Component built successfully!", style("✓").green()); + println!(" Output: {}", style(wasm_path.display()).dim()); + println!(" Size: {}", style(format_size(size)).yellow()); - optimize_wasm(&wasm_path, &wasm_opt_flags)?; - } else { - pb.inc(1); + Ok(()) +} + +fn find_wasm_output(component_path: &Path, manifest: &ComponentManifest) -> Result { + // Check common output locations based on language + let possible_paths = vec![ + // Rust + component_path + .join("handler/target/wasm32-wasip1/release") + .join(format!( + "{}.wasm", + manifest.component.name.replace('-', "_") + )), + component_path + .join("handler/target/wasm32-wasip1/debug") + .join(format!( + "{}.wasm", + manifest.component.name.replace('-', "_") + )), + // JavaScript/TypeScript + component_path.join("handler/dist/handler.wasm"), + component_path + .join("handler/dist") + .join(format!("{}.wasm", manifest.component.name)), + ]; + + for path in possible_paths { + if path.exists() { + return Ok(path); + } } - pb.finish_with_message("Build complete!"); + anyhow::bail!("Could not find built WASM file. Build may have failed.") +} - // Display binary size (only if not quiet) - if !quiet { - let size = get_file_size(&wasm_path)?; +fn format_size(bytes: u64) -> String { + const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; + let mut size = bytes as f64; + let mut unit_index = 0; - println!(); - println!("{} Build successful!", style("✓").green()); - println!(" Binary: {}", wasm_path.display()); - let size = format_size(size); - println!(" Size: {size}"); - println!(" Profile: {build_profile}"); + while size >= 1024.0 && unit_index < UNITS.len() - 1 { + size /= 1024.0; + unit_index += 1; } - Ok(()) + format!("{:.2} {}", size, UNITS[unit_index]) } diff --git a/packages/ftl-cli/src/commands/delete.rs b/packages/ftl-cli/src/commands/delete.rs deleted file mode 100644 index ac2c70dc..00000000 --- a/packages/ftl-cli/src/commands/delete.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::{ - io::{self, Write}, - process::Command, -}; - -use anyhow::Result; -use console::style; - -use crate::common::deploy_utils::infer_app_name; - -pub async fn execute(name: Option, yes: bool) -> Result<()> { - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - // Get the app name - either provided or inferred from current directory - let app_name = match name { - Some(n) => n, - None => infer_app_name(".")?, - }; - - // Confirm deletion unless --yes flag is provided - if !yes { - print!( - "{} Are you sure you want to delete '{}'? [y/N] ", - style("?").yellow(), - style(&app_name).bold() - ); - io::stdout().flush()?; - - let mut response = String::new(); - io::stdin().read_line(&mut response)?; - - if !response.trim().eq_ignore_ascii_case("y") { - println!("Deletion cancelled."); - return Ok(()); - } - } - - println!( - "{} Deleting tool/toolkit: {}", - style("→").cyan(), - style(&app_name).bold() - ); - - // Run spin aka app delete with --app-name flag and --no-confirm - let output = Command::new("spin") - .args([ - "aka", - "app", - "delete", - "--app-name", - &app_name, - "--no-confirm", - ]) - .output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("not logged in") || stderr.contains("authentication") { - anyhow::bail!("Not authenticated with FTL Edge. Please run: ftl login"); - } - if stderr.contains("not found") || stderr.contains("does not exist") { - anyhow::bail!( - "Tool/toolkit '{app_name}' not found. Use 'ftl list' to see deployed tools and toolkits." - ); - } - anyhow::bail!("Failed to delete tool/toolkit:\n{stderr}"); - } - - println!( - "{} Tool/toolkit '{app_name}' deleted successfully", - style("✓").green() - ); - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/deploy.rs b/packages/ftl-cli/src/commands/deploy.rs index 1a0932f3..a2b99245 100644 --- a/packages/ftl-cli/src/commands/deploy.rs +++ b/packages/ftl-cli/src/commands/deploy.rs @@ -1,91 +1,75 @@ -use anyhow::Result; +use std::path::PathBuf; +use std::process::Command; + +use anyhow::{Context, Result}; use console::style; -use indicatif::{ProgressBar, ProgressStyle}; -use crate::common::{ - config::FtlConfig, - manifest_utils::load_manifest_and_name, - spin_installer::check_and_install_spin, - spin_utils::{check_akamai_auth, deploy_to_akamai}, - tool_paths::{get_spin_toml_path, validate_tool_exists}, -}; +use crate::common::spin_installer::check_and_install_spin; -pub async fn execute(tool_path: String) -> Result<()> { +pub async fn execute(environment: Option) -> Result<()> { println!( - "{} Deploying tool: {}", + "{} Deploying project{}", style("→").cyan(), - style(&tool_path).bold() + environment + .as_ref() + .map(|e| format!(" to {}", e)) + .unwrap_or_default() ); - // Validate tool exists and load manifest - validate_tool_exists(&tool_path)?; - let (_manifest, tool_name) = load_manifest_and_name(&tool_path)?; - - // Ensure tool is built with production profile - println!("{} Building release version...", style("→").cyan()); - crate::commands::build::execute(Some(tool_path.clone()), Some("release".to_string())).await?; - - // Check if spin.toml exists - let spin_path = get_spin_toml_path(&tool_path); - if !spin_path.exists() { - anyhow::bail!(".ftl/spin.toml not found. This should have been created during build."); + // Check if we're in a Spin project directory + if !PathBuf::from("spin.toml").exists() { + anyhow::bail!("No spin.toml found. Not in a project directory?"); } - // Get spin path and check Akamai authentication + // Get spin path let spin_path = check_and_install_spin().await?; - check_akamai_auth(&spin_path).await?; - // Deploy using spin aka with spinner - let spinner = ProgressBar::new_spinner(); - spinner.set_style( - ProgressStyle::default_spinner() - .template("{spinner:.cyan} {msg}") - .unwrap() - .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), - ); - spinner.set_message("Deploying to FTL Edge..."); - spinner.enable_steady_tick(std::time::Duration::from_millis(80)); + // Build the project first + println!("{} Building project...", style("→").dim()); + let build_output = Command::new(&spin_path) + .args(["build"]) + .output() + .context("Failed to build project")?; - // Load config and generate app name with user prefix - let config = FtlConfig::load().unwrap_or_default(); - let prefix = config.get_app_prefix(); - let app_name = format!("{prefix}{tool_name}"); + if !build_output.status.success() { + anyhow::bail!( + "Build failed:\n{}", + String::from_utf8_lossy(&build_output.stderr) + ); + } - // Deploy with the generated app name - let deployment_result = deploy_to_akamai(&tool_path, Some(&app_name)).await; + // Deploy using spin deploy + println!("{} Deploying to FTL...", style("→").dim()); + let mut deploy_args = vec!["deploy"]; - // Handle deployment result - match deployment_result { - Ok(deployment_info) => { - spinner.finish_and_clear(); - // Ensure URL includes /mcp path - let full_url = if deployment_info.url.ends_with("/mcp") { - deployment_info.url.clone() - } else { - let url = deployment_info.url.trim_end_matches('/'); - format!("{url}/mcp") - }; + if let Some(env) = &environment { + deploy_args.extend(["--environment-name", env]); + } - println!("{} Deployment successful!", style("✓").green()); - println!(" Name: {}", style(&deployment_info.app_name).cyan()); - println!(" URL: {}", style(&full_url).yellow().bold()); - println!(); - println!("Test your tool:"); - println!(" curl -X POST {full_url} \\"); - println!(" -H \"Content-Type: application/json\" \\"); - println!(" -d '{{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":1}}'"); - println!(); - println!("Manage your deployment:"); - let app_name = &deployment_info.app_name; - println!(" ftl status {app_name}"); - println!(" ftl logs {app_name}"); - println!(" ftl delete {app_name}"); - Ok(()) - } - Err(e) => { - spinner.finish_and_clear(); - println!("{} Deployment failed", style("✗").red()); - anyhow::bail!("{e}"); + let deploy_output = Command::new(&spin_path) + .args(&deploy_args) + .output() + .context("Failed to deploy project")?; + + if !deploy_output.status.success() { + let stderr = String::from_utf8_lossy(&deploy_output.stderr); + + if stderr.contains("not logged in") { + anyhow::bail!("Not logged in to FTL. Run 'spin login' first."); } + + anyhow::bail!("Deploy failed:\n{}", stderr); } + + // Parse deployment URL + let output_str = String::from_utf8_lossy(&deploy_output.stdout); + if let Some(url_line) = output_str.lines().find(|l| l.contains("https://")) { + println!(); + println!("{} Project deployed successfully!", style("✓").green()); + println!(" URL: {}", style(url_line.trim()).cyan()); + } else { + println!("{} Project deployed successfully!", style("✓").green()); + } + + Ok(()) } diff --git a/packages/ftl-cli/src/commands/export.rs b/packages/ftl-cli/src/commands/export.rs deleted file mode 100644 index 7dadb40f..00000000 --- a/packages/ftl-cli/src/commands/export.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, - process::Command, -}; - -use anyhow::{Context, Result}; -use console::style; - -use crate::common::{ - manifest_utils::load_manifest_and_name, - tool_paths::{self, get_profile_dir, validate_tool_exists}, -}; - -const WASI_ADAPTER_URL: &str = "https://github.com/bytecodealliance/wasmtime/releases/download/v22.0.0/wasi_snapshot_preview1.reactor.wasm"; - -pub async fn execute( - name: Option, - output: Option, - profile: Option, -) -> Result<()> { - let tool_path = name.unwrap_or_else(|| ".".to_string()); - - println!( - "{} Exporting tool: {}", - style("→").cyan(), - style(&tool_path).bold() - ); - - // Validate tool directory exists - validate_tool_exists(&tool_path)?; - - // Load manifest to get tool name - let (manifest, tool_name) = load_manifest_and_name(&tool_path)?; - - // Determine build profile - let build_profile = profile.unwrap_or_else(|| manifest.build.profile.clone()); - let language = manifest.tool.language; - - // Get the WASM path based on language - let wasm_path = - tool_paths::get_wasm_path_for_language(&tool_path, &tool_name, &build_profile, language); - - if !wasm_path.exists() { - let display = wasm_path.display(); - anyhow::bail!("WASM file not found at {display}. Please run 'ftl build' first."); - } - - // Check if wasm-tools is installed - if which::which("wasm-tools").is_err() { - anyhow::bail!( - "wasm-tools CLI not found. Please install it from: https://github.com/bytecodealliance/wasm-tools" - ); - } - - // Determine output path - let output_path = match output { - Some(path) => path, - None => { - use crate::language::Language; - match language { - Language::Rust => { - let profile_dir = get_profile_dir(&build_profile); - PathBuf::from(&tool_path) - .join("target") - .join("wasm32-wasip1") - .join(profile_dir) - .join(format!("{}.component.wasm", tool_name.replace('-', "_"))) - } - Language::JavaScript | Language::TypeScript => { - // For JavaScript/TypeScript, put the component next to the WASM file in dist - PathBuf::from(&tool_path) - .join("dist") - .join(format!("{tool_name}.component.wasm")) - } - } - } - }; - - // Ensure output directory exists - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent)?; - } - - // Check if the WASM file is already a component - let validate_output = Command::new("wasm-tools") - .args([ - "validate", - wasm_path.to_str().unwrap(), - "--features", - "component-model", - ]) - .output() - .context("Failed to run wasm-tools validate")?; - - let is_already_component = validate_output.status.success(); - - if is_already_component { - // For JavaScript, the WASM is already a component, just copy it - println!( - "{} WASM is already a component, copying...", - style("→").cyan() - ); - fs::copy(&wasm_path, &output_path).context("Failed to copy component")?; - } else { - // For Rust, we need to componentize the module - // Download WASI adapter if not already cached - let adapter_path = get_wasi_adapter_path()?; - if !adapter_path.exists() { - println!("{} Downloading WASI adapter...", style("→").cyan()); - download_wasi_adapter(&adapter_path).await?; - } - - // Run wasm-tools component new - println!("{} Creating WASM component...", style("→").cyan()); - - let output = Command::new("wasm-tools") - .args([ - "component", - "new", - wasm_path.to_str().unwrap(), - "-o", - output_path.to_str().unwrap(), - "--adapt", - adapter_path.to_str().unwrap(), - ]) - .output() - .context("Failed to run wasm-tools")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Failed to create WASM component:\n{stderr}"); - } - } - - let component_size = fs::metadata(&output_path)?.len(); - - println!(); - println!("{} Export successful!", style("✓").green()); - println!(" Component: {}", output_path.display()); - let size = format_file_size(component_size); - println!(" Size: {size}"); - println!(); - println!("You can now serve this component with:"); - println!(" wasmtime serve -Scli {}", output_path.display()); - - Ok(()) -} - -fn get_wasi_adapter_path() -> Result { - let cache_dir = dirs::cache_dir() - .ok_or_else(|| anyhow::anyhow!("Could not find cache directory"))? - .join("ftl"); - - fs::create_dir_all(&cache_dir)?; - - Ok(cache_dir.join("wasi_snapshot_preview1.reactor.wasm")) -} - -async fn download_wasi_adapter(path: &Path) -> Result<()> { - let response = reqwest::get(WASI_ADAPTER_URL) - .await - .context("Failed to download WASI adapter")?; - - if !response.status().is_success() { - anyhow::bail!( - "Failed to download WASI adapter: HTTP {}", - response.status() - ); - } - - let bytes = response.bytes().await?; - fs::write(path, bytes).context("Failed to write WASI adapter")?; - - Ok(()) -} - -fn format_file_size(size: u64) -> String { - const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; - let mut size = size as f64; - let mut unit_index = 0; - - while size >= 1024.0 && unit_index < UNITS.len() - 1 { - size /= 1024.0; - unit_index += 1; - } - - let unit = UNITS[unit_index]; - format!("{size:.2} {unit}") -} diff --git a/packages/ftl-cli/src/commands/init.rs b/packages/ftl-cli/src/commands/init.rs new file mode 100644 index 00000000..d540479e --- /dev/null +++ b/packages/ftl-cli/src/commands/init.rs @@ -0,0 +1,112 @@ +use std::path::PathBuf; +use std::process::Command; + +use anyhow::{Context, Result}; +use console::style; +use dialoguer::{Input, theme::ColorfulTheme}; + +use crate::common::spin_installer::check_and_install_spin; + +pub async fn execute(name: Option, here: bool) -> Result<()> { + // Get project name interactively if not provided + let project_name = match name { + Some(n) => n, + None => Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("Project name") + .interact_text()?, + }; + + println!( + "{} Initializing new MCP project: {}", + style("→").cyan(), + style(&project_name).bold() + ); + + // Validate project name + if !project_name + .chars() + .all(|c| c.is_lowercase() || c == '-' || c.is_numeric()) + { + anyhow::bail!("Project name must be lowercase with hyphens (e.g., my-project)"); + } + + // Don't allow leading or trailing hyphens, or double hyphens + if project_name.starts_with('-') || project_name.ends_with('-') || project_name.contains("--") { + anyhow::bail!("Project name cannot start or end with hyphens, or contain double hyphens"); + } + + // Get spin path + let spin_path = check_and_install_spin().await?; + + // Determine output directory + let output_dir = if here { + ".".to_string() + } else { + project_name.clone() + }; + + // Check if directory exists and is not empty (unless using --here) + if !here && PathBuf::from(&output_dir).exists() { + anyhow::bail!("Directory '{}' already exists", project_name); + } else if here { + let current_dir = std::env::current_dir()?; + if current_dir.read_dir()?.next().is_some() { + anyhow::bail!("Current directory is not empty"); + } + } + + // Use spin new with http-empty template to create the project container + let mut spin_cmd = Command::new(&spin_path); + spin_cmd.args([ + "new", + "-t", + "http-empty", + "-o", + &output_dir, + "--accept-defaults", + ]); + + if !here { + spin_cmd.arg(&project_name); + } + + let output = spin_cmd.output().context("Failed to run spin new")?; + + if !output.status.success() { + anyhow::bail!( + "Failed to create project:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let cd_instruction = if here { + "" + } else { + &format!("cd {} && ", project_name) + }; + + println!( + r#" +{} MCP project initialized! + +{} Structure: + └── spin.toml # Spin project manifest + +{} Next steps: + {}ftl add # Add a component to the project + ftl up --build # Run the project locally + +{} Example: + {}ftl add weather-api --language typescript + {}ftl add calculator --language rust"#, + style("✓").green(), + style("📁").blue(), + style("🚀").yellow(), + cd_instruction, + style("💡").bright(), + cd_instruction, + cd_instruction + ); + + Ok(()) +} diff --git a/packages/ftl-cli/src/commands/link.rs b/packages/ftl-cli/src/commands/link.rs deleted file mode 100644 index 18b6e25e..00000000 --- a/packages/ftl-cli/src/commands/link.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::process::Command; - -use anyhow::Result; -use console::style; - -use crate::common::tool_paths::validate_tool_exists; - -pub async fn execute(name: String, path: Option) -> Result<()> { - let tool_path = path.unwrap_or_else(|| ".".to_string()); - - // Validate tool directory exists - validate_tool_exists(&tool_path)?; - - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - println!( - "{} Linking tool to deployment: {}", - style("→").cyan(), - style(&name).bold() - ); - - // Check if .ftl/spin.toml exists - let spin_toml = std::path::Path::new(&tool_path).join(".ftl/spin.toml"); - if !spin_toml.exists() { - anyhow::bail!(".ftl/spin.toml not found. Please build the tool first with: ftl build"); - } - - // Run spin aka app link with --app-name flag - let output = Command::new("spin") - .args([ - "aka", - "app", - "link", - "--app-name", - &name, - "-f", - ".ftl/spin.toml", - ]) - .current_dir(&tool_path) - .output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("not logged in") || stderr.contains("authentication") { - anyhow::bail!("Not authenticated with FTL Edge. Please run: ftl login"); - } - if stderr.contains("not found") || stderr.contains("does not exist") { - anyhow::bail!( - "Tool/toolkit '{name}' not found in FTL Edge. Use 'ftl list' to see available \ - deployments." - ); - } - if stderr.contains("already linked") { - anyhow::bail!( - "This tool is already linked to a deployment. Use 'ftl unlink' first to unlink it." - ); - } - anyhow::bail!("Failed to link tool:\n{stderr}"); - } - - println!( - "{} Tool successfully linked to '{name}'", - style("✓").green() - ); - println!(); - println!("You can now:"); - println!(" ftl deploy # Deploy updates to the linked tool"); - println!(" ftl logs {name} # View logs"); - println!(" ftl status {name} # Check status"); - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/list.rs b/packages/ftl-cli/src/commands/list.rs deleted file mode 100644 index a9b65c0a..00000000 --- a/packages/ftl-cli/src/commands/list.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::process::Command; - -use anyhow::Result; -use console::style; - -pub async fn execute() -> Result<()> { - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - println!( - "{} Listing deployed tools and toolkits...", - style("→").cyan() - ); - println!(); - - // Run spin aka app list - let output = Command::new("spin").args(["aka", "app", "list"]).output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("not logged in") || stderr.contains("authentication") { - anyhow::bail!("Not authenticated with FTL Edge. Please run: ftl login"); - } - anyhow::bail!("Failed to list tools and toolkits:\n{stderr}"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - - // Check if there are no apps - if stdout.trim().is_empty() || stdout.contains("No apps") { - println!("No tools or toolkits deployed yet."); - println!(); - println!("Deploy your first tool with:"); - println!(" ftl deploy "); - } else { - // Print the output as-is (spin aka app list has nice formatting) - print!("{stdout}"); - } - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/login.rs b/packages/ftl-cli/src/commands/login.rs deleted file mode 100644 index 952c8b07..00000000 --- a/packages/ftl-cli/src/commands/login.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{ - io::{BufRead, BufReader}, - process::{Command, Stdio}, -}; - -use anyhow::Result; -use console::style; - -use crate::common::config::FtlConfig; - -pub async fn execute() -> Result<()> { - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - println!("{} Logging in to FTL Edge...", style("→").cyan()); - println!(); - - // Run spin aka auth login and capture output to parse username - let mut child = Command::new("spin") - .args(["aka", "auth", "login"]) - .stdin(Stdio::inherit()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let stdout = child.stdout.take().expect("Failed to capture stdout"); - let stderr = child.stderr.take().expect("Failed to capture stderr"); - - // Read output line by line, looking for "Welcome, username." - let stdout_reader = BufReader::new(stdout); - let stderr_reader = BufReader::new(stderr); - - // Print stdout and look for username - let stdout_handle = std::thread::spawn(move || { - let mut username = None; - for line in stdout_reader.lines().map_while(Result::ok) { - println!("{line}"); - if line.starts_with("Welcome, ") && line.ends_with('.') { - // Extract username from "Welcome, username." - let user = line - .trim_start_matches("Welcome, ") - .trim_end_matches('.') - .to_string(); - username = Some(user); - } - } - username - }); - - // Print stderr - for line in stderr_reader.lines().map_while(Result::ok) { - eprintln!("{line}"); - } - - let status = child.wait()?; - let captured_username = stdout_handle.join().unwrap(); - - if status.success() { - println!(); - println!("{} Successfully logged in to FTL Edge!", style("✓").green()); - - // Load existing config - let mut config = FtlConfig::load().unwrap_or_default(); - - // Save username if we captured it - if let Some(username) = captured_username { - config.username = Some(username.clone()); - config.save()?; - } - - println!(); - println!("You can now:"); - println!(" • Deploy tools with: ftl deploy"); - println!(" • List your tools with: ftl list"); - } else { - anyhow::bail!("Login failed"); - } - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/logs.rs b/packages/ftl-cli/src/commands/logs.rs deleted file mode 100644 index 48d44568..00000000 --- a/packages/ftl-cli/src/commands/logs.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::process::{Command, Stdio}; - -use anyhow::Result; -use console::style; - -use crate::common::deploy_utils::infer_app_name; - -pub async fn execute(name: Option, _follow: bool, tail: Option) -> Result<()> { - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - // Get the app name - either provided or inferred from current directory - let app_name = match name { - Some(n) => n, - None => infer_app_name(".")?, - }; - - println!( - "{} Fetching logs for: {}", - style("→").cyan(), - style(&app_name).bold() - ); - - let mut args = vec!["aka", "app", "logs", "--app-name", &app_name]; - - // Add tail option if specified - let tail_str; - if let Some(lines) = tail { - tail_str = lines.to_string(); - args.push("--tail"); - args.push(&tail_str); - } - - // Note: spin aka app logs doesn't support --follow yet - - // Run spin aka app logs with inherited stdio for real-time output - let mut child = Command::new("spin") - .args(&args) - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()?; - - // Wait for the command to complete - let status = child.wait()?; - - if !status.success() { - // Error handling is done by spin itself with inherited stderr - std::process::exit(status.code().unwrap_or(1)); - } - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/mod.rs b/packages/ftl-cli/src/commands/mod.rs index 347d8991..c4fa2436 100644 --- a/packages/ftl-cli/src/commands/mod.rs +++ b/packages/ftl-cli/src/commands/mod.rs @@ -1,18 +1,9 @@ +pub mod add; pub mod build; -pub mod delete; pub mod deploy; -pub mod export; -pub mod link; -pub mod list; -pub mod login; -pub mod logs; -pub mod new; -pub mod serve; -pub mod size; -pub mod spin; -pub mod status; +pub mod init; +pub mod publish; +pub mod registry; +pub mod setup; pub mod test; -pub mod toolkit; -pub mod unlink; -pub mod validate; -pub mod watch; +pub mod up; diff --git a/packages/ftl-cli/src/commands/new.rs b/packages/ftl-cli/src/commands/new.rs deleted file mode 100644 index 552f2e19..00000000 --- a/packages/ftl-cli/src/commands/new.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::path::PathBuf; -use std::process::Command; - -use anyhow::{Context, Result}; -use console::style; -use dialoguer::{Input, Select, theme::ColorfulTheme}; - -use crate::{ - language::Language, - common::spin_installer::check_and_install_spin, -}; - -pub async fn execute( - name: String, - description: Option, - language: Option, -) -> Result<()> { - println!( - "{} Creating new tool: {}", - style("→").cyan(), - style(&name).bold() - ); - - // Validate tool name - if !name - .chars() - .all(|c| c.is_lowercase() || c == '-' || c.is_numeric()) - { - anyhow::bail!("Tool name must be lowercase with hyphens (e.g., my-tool)"); - } - - // Don't allow leading or trailing hyphens, or double hyphens - if name.starts_with('-') || name.ends_with('-') || name.contains("--") { - anyhow::bail!("Tool name cannot start or end with hyphens, or contain double hyphens"); - } - - // Get description interactively if not provided - let description = match description { - Some(d) => d, - None => Input::::with_theme(&ColorfulTheme::default()) - .with_prompt("Tool description") - .interact_text()?, - }; - - // Determine language - let selected_language = match language { - Some(lang_str) => Language::from_str(&lang_str).ok_or_else(|| { - anyhow::anyhow!( - "Invalid language: {lang_str}. Valid options are: rust, javascript, typescript" - ) - })?, - None => { - // Interactive language selection - let languages = vec!["rust", "javascript", "typescript"]; - let selection = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Select programming language") - .items(&languages) - .default(0) - .interact()?; - - Language::from_str(languages[selection]).unwrap() - } - }; - - // Determine target directory - let target_dir = PathBuf::from(&name); - if target_dir.exists() { - anyhow::bail!("Directory '{name}' already exists"); - } - - // Get spin path - let spin_path = tokio::runtime::Handle::try_current() - .ok() - .and_then(|handle| { - tokio::task::block_in_place(|| handle.block_on(check_and_install_spin()).ok()) - }) - .unwrap_or_else(|| { - // If no runtime exists, create one - let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); - rt.block_on(check_and_install_spin()) - .expect("Failed to install Spin") - }); - - // Use spin new with the appropriate template - let template_id = match selected_language { - Language::Rust => "ftl-rust", - Language::TypeScript => "ftl-typescript", - Language::JavaScript => "ftl-javascript", - }; - - let template_path = std::env::current_exe() - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - .unwrap_or_else(|| PathBuf::from(".")) - .join("templates") - .join(template_id); - - // If template doesn't exist in binary dir, use the source templates - let template_path = if !template_path.exists() { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("src/templates") - .join(template_id) - } else { - template_path - }; - - let output = Command::new(&spin_path) - .args([ - "new", - "-t", - template_path.to_str().unwrap(), - "-o", - target_dir.to_str().unwrap(), - "--accept-defaults", - &name, - ]) - .env("project-description", &description) - .output() - .context("Failed to run spin new")?; - - if !output.status.success() { - anyhow::bail!( - "Failed to create project with spin new:\n{}", - String::from_utf8_lossy(&output.stderr) - ); - } - - // Success message based on language - let main_file = match selected_language { - Language::Rust => "handler/src/lib.rs", - Language::JavaScript => "handler/src/index.js", - Language::TypeScript => "handler/src/index.ts", - }; - - println!( - r#" -{} {selected_language} tool created successfully! - -Next steps: - 1. cd {name} - 2. ftl build # Build your tool - 3. ftl test # Run the included tests - 4. ftl serve # Start development server - -Then edit {main_file} to implement your tool logic! - -Other commands: - ftl deploy # Deploy to FTL Edge - ftl validate # Validate tool configuration - ftl size # Show binary size details"#, - style("✓").green() - ); - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/publish.rs b/packages/ftl-cli/src/commands/publish.rs new file mode 100644 index 00000000..be0087d4 --- /dev/null +++ b/packages/ftl-cli/src/commands/publish.rs @@ -0,0 +1,157 @@ +use std::path::PathBuf; +use std::process::Command; + +use anyhow::{Context, Result}; +use console::style; + +use crate::manifest::ComponentManifest; + +pub async fn execute( + path: Option, + registry: Option, + tag: Option, +) -> Result<()> { + let component_path = path.unwrap_or_else(|| PathBuf::from(".")); + + println!("{} Publishing component", style("→").cyan()); + + // Validate component directory exists + if !component_path.join("ftl.toml").exists() { + anyhow::bail!("No ftl.toml found. Not in a component directory?"); + } + + // Load component manifest + let manifest = ComponentManifest::load(&component_path)?; + let version = tag.as_ref().unwrap_or(&manifest.component.version); + + // Use make registry-push if Makefile exists + if component_path.join("Makefile").exists() { + println!("{} Using Makefile to publish...", style("→").dim()); + + let output = Command::new("make") + .arg("registry-push") + .current_dir(&component_path) + .output() + .context("Failed to run make registry-push")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + + // Check if wkg is missing + if stderr.contains("wkg: command not found") || stderr.contains("wkg: not found") { + anyhow::bail!( + "The 'wkg' tool is required for publishing. Install it from: https://github.com/bytecodealliance/wasm-pkg-tools" + ); + } + + anyhow::bail!("Publishing failed:\n{}\n{}", stdout, stderr); + } + + // Parse the output to get the published URL + let output_str = String::from_utf8_lossy(&output.stdout); + if let Some(line) = output_str.lines().find(|l| l.contains("Pushing ghcr.io/")) { + println!(); + println!("{} Component published!", style("✓").green()); + println!(" {}", style(line.trim_start_matches("Pushing ")).cyan()); + } else { + println!("{} Component published successfully!", style("✓").green()); + } + } else { + // Manual publish flow + let registry_url = registry.as_deref().unwrap_or("ghcr.io"); + + // Get username from git config + let username_output = Command::new("git") + .args(["config", "user.name"]) + .output() + .context("Failed to get git username")?; + + let username = String::from_utf8_lossy(&username_output.stdout) + .trim() + .to_lowercase() + .replace(' ', "-"); + + if username.is_empty() { + anyhow::bail!("Could not determine username from git config"); + } + + // Build component first + println!("{} Building component...", style("→").dim()); + crate::commands::build::execute(Some(component_path.clone()), true).await?; + + // Find the built WASM file + let wasm_path = find_wasm_file(&component_path, &manifest)?; + + // Construct package URL + let package_name = format!( + "{}/{}/{}:{}", + registry_url, username, manifest.component.name, version + ); + + println!("{} Publishing to {}...", style("→").dim(), package_name); + + // Use wkg to push + let output = Command::new("wkg") + .args(["oci", "push", &package_name, wasm_path.to_str().unwrap()]) + .output() + .context("Failed to run wkg")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + + if stderr.contains("not found") && stderr.contains("wkg") { + anyhow::bail!( + "The 'wkg' tool is required for publishing. Install it from: https://github.com/bytecodealliance/wasm-pkg-tools" + ); + } + + anyhow::bail!("Publishing failed:\n{}", stderr); + } + + println!(); + println!("{} Component published!", style("✓").green()); + println!(" {}", style(&package_name).cyan()); + } + + println!(); + println!("{} Next steps:", style("→").dim()); + println!( + " - Use 'ftl project add {}' to add this component to a project", + manifest.component.name + ); + println!( + " - Share the registry URL: {}", + tag.as_ref() + .map(|t| format!("{}:{}", manifest.component.name, t)) + .unwrap_or_else(|| manifest.component.name.clone()) + ); + + Ok(()) +} + +fn find_wasm_file(component_path: &PathBuf, manifest: &ComponentManifest) -> Result { + // Check common locations + let candidates = vec![ + // Rust + component_path + .join("handler/target/wasm32-wasip1/release") + .join(format!( + "{}.wasm", + manifest.component.name.replace('-', "_") + )), + // JS/TS + component_path.join("handler/dist/handler.wasm"), + component_path + .join("handler/dist") + .join(format!("{}.wasm", manifest.component.name)), + ]; + + for path in candidates { + if path.exists() { + return Ok(path); + } + } + + anyhow::bail!("Could not find built WASM file. Did you run 'ftl build'?") +} diff --git a/packages/ftl-cli/src/commands/registry.rs b/packages/ftl-cli/src/commands/registry.rs new file mode 100644 index 00000000..180d21a9 --- /dev/null +++ b/packages/ftl-cli/src/commands/registry.rs @@ -0,0 +1,67 @@ +use anyhow::Result; +use console::style; + +pub async fn list(registry: Option) -> Result<()> { + let registry_url = registry.as_deref().unwrap_or("ghcr.io"); + + println!( + "{} Listing components from {}", + style("→").cyan(), + style(registry_url).bold() + ); + + println!(); + println!( + "{} Registry listing not yet implemented", + style("!").yellow() + ); + println!(); + println!("For now, you can browse components at:"); + println!(" - GitHub Container Registry: https://github.com/orgs/YOUR_ORG/packages"); + println!(" - Docker Hub: https://hub.docker.com/"); + + Ok(()) +} + +pub async fn search(query: String, registry: Option) -> Result<()> { + let registry_url = registry.as_deref().unwrap_or("ghcr.io"); + + println!( + "{} Searching for '{}' in {}", + style("→").cyan(), + style(&query).bold(), + style(registry_url).dim() + ); + + println!(); + println!( + "{} Registry search not yet implemented", + style("!").yellow() + ); + println!(); + println!("For now, you can search at:"); + println!( + " - GitHub: https://github.com/search?q=mcp+{}&type=registrypackages", + query + ); + + Ok(()) +} + +pub async fn info(component: String) -> Result<()> { + println!( + "{} Getting info for component: {}", + style("→").cyan(), + style(&component).bold() + ); + + println!(); + println!("{} Registry info not yet implemented", style("!").yellow()); + println!(); + println!("Component reference formats:"); + println!(" - ghcr.io/username/component:version"); + println!(" - docker.io/username/component:version"); + println!(" - component-name (searches default registry)"); + + Ok(()) +} diff --git a/packages/ftl-cli/src/commands/serve.rs b/packages/ftl-cli/src/commands/serve.rs deleted file mode 100644 index 618f7e55..00000000 --- a/packages/ftl-cli/src/commands/serve.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::{ - path::PathBuf, - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, - time::Duration, -}; - -use anyhow::Result; -use console::style; -use tokio::{signal, time::sleep}; -use tracing::{debug, warn}; - -use crate::{ - common::{ - manifest_utils::load_manifest_and_name, - spin_installer::check_and_install_spin, - spin_utils::start_spin_server_with_path, - tool_paths::{ - ensure_ftl_dir, get_profile_dir, get_spin_toml_path, get_wasm_path, - validate_tool_exists, - }, - watch_utils::{Debouncer, setup_file_watcher}, - }, - language::Language, - spin_generator, -}; - -pub async fn execute(tool_path: String, port: u16, build_first: bool) -> Result<()> { - println!( - "{} Serving tool: {} on port {}", - style("→").cyan(), - style(&tool_path).bold(), - style(port).yellow() - ); - - // Validate tool exists and load manifest - validate_tool_exists(&tool_path)?; - let (manifest, tool_name) = load_manifest_and_name(&tool_path)?; - - // Build if requested - if build_first { - println!("{} Building tool first...", style("→").cyan()); - crate::commands::build::execute(Some(tool_path.clone()), None).await?; - } - - // Check WASM binary exists and determine spin.toml path - let (_wasm_path, spin_toml_path) = match manifest.tool.language { - Language::Rust => { - let wasm = get_wasm_path(&tool_path, &tool_name, &manifest.build.profile); - if !wasm.exists() { - let display = wasm.display(); - anyhow::bail!( - "WASM binary not found at: {display}. Run 'ftl build {tool_path}' first." - ); - } - - // Ensure .ftl directory and spin.toml exist for Rust - ensure_ftl_dir(&tool_path)?; - let spin_path = get_spin_toml_path(&tool_path); - - if !spin_path.exists() { - // Generate development spin.toml if it doesn't exist - let profile_dir = get_profile_dir(&manifest.build.profile); - let wasm_filename = format!("{}.wasm", tool_name.replace('-', "_")); - let relative_wasm_path = PathBuf::from("..") - .join("target") - .join("wasm32-wasip1") - .join(profile_dir) - .join(&wasm_filename); - - let spin_content = spin_generator::generate_development_config( - &tool_name, - port, - &relative_wasm_path, - )?; - std::fs::write(&spin_path, spin_content)?; - } - - (wasm, spin_path) - } - Language::JavaScript | Language::TypeScript => { - // For JS/TS, use Spin's generated paths - let wasm = PathBuf::from(&tool_path) - .join("dist") - .join(format!("{tool_name}.wasm")); - if !wasm.exists() { - let display = wasm.display(); - anyhow::bail!( - "WASM binary not found at: {display}. Run 'ftl build {tool_path}' first." - ); - } - - // Use spin.toml from .ftl directory - let spin_path = get_spin_toml_path(&tool_path); - if !spin_path.exists() { - anyhow::bail!( - "spin.toml not found in .ftl directory. Run 'ftl build {tool_path}' first." - ); - } - - (wasm, spin_path) - } - }; - - // Check spin is installed and get the path - let spin_path = check_and_install_spin().await?; - - // Set up hot reload - let should_rebuild = Arc::new(AtomicBool::new(false)); - let rebuild_flag = should_rebuild.clone(); - - // Set up file watcher - let (tx, rx) = std::sync::mpsc::channel(); - let _watcher = setup_file_watcher(&tool_path, tx)?; - - let tool_path_clone = tool_path.clone(); - let watcher_task = tokio::task::spawn_blocking(move || { - let mut debouncer = Debouncer::new(Duration::from_millis(500)); - - while let Ok(event) = rx.recv() { - if debouncer.should_trigger() { - // Set rebuild flag - rebuild_flag.store(true, Ordering::Relaxed); - - // Display changed files - for path in &event.paths { - if let Ok(rel_path) = path.strip_prefix(&tool_path_clone) { - let display = rel_path.display(); - println!("\n📝 Changed: {display}"); - } - } - - println!("🔄 Reloading..."); - } - } - }); - - // Start initial server - println!(); - println!( - "{} Starting development server with hot reload...", - style("▶").green() - ); - println!(); - println!(" Tool: {tool_path}"); - println!(" URL: http://localhost:{port}/mcp"); - println!(" Watching for changes in src/"); - println!(); - println!("Press Ctrl+C to stop"); - println!(); - - let mut server = - start_spin_server_with_path(&spin_path, &tool_path, port, Some(&spin_toml_path))?; - - // Main server loop with rebuild handling - let rebuild_check = should_rebuild.clone(); - let mut rebuild_interval = tokio::time::interval(Duration::from_millis(250)); - - loop { - tokio::select! { - _ = signal::ctrl_c() => { - println!(); - println!("{} Stopping server...", style("■").red()); - break; - } - _ = rebuild_interval.tick() => { - if rebuild_check.load(Ordering::Relaxed) { - rebuild_check.store(false, Ordering::Relaxed); - - // Stop current server - if let Err(e) = server.kill() { - warn!("Failed to stop server: {e}"); - } - let _ = server.wait(); - - // Rebuild - match crate::commands::build::execute(Some(tool_path.clone()), None).await { - Ok(_) => { - println!("✅ Build successful, restarting server..."); - - // Small delay to ensure port is released - sleep(Duration::from_millis(100)).await; - - // Restart server - match start_spin_server_with_path(&spin_path, &tool_path, port, Some(&spin_toml_path)) { - Ok(new_server) => { - server = new_server; - } - Err(e) => { - println!("❌ Failed to restart server: {e}"); - println!(" Fix the issue and save to retry"); - } - } - } - Err(e) => { - println!("❌ Build failed: {e}"); - println!(" Fix the error and save to retry"); - - // Restart server anyway (will serve last good build) - if let Ok(new_server) = start_spin_server_with_path(&spin_path, &tool_path, port, Some(&spin_toml_path)) { - server = new_server; - } - } - } - } - } - } - } - - // Cleanup - drop(watcher_task); - if let Err(e) = server.kill() { - debug!("Failed to stop server during cleanup: {e}"); - } - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/setup.rs b/packages/ftl-cli/src/commands/setup.rs new file mode 100644 index 00000000..683743b2 --- /dev/null +++ b/packages/ftl-cli/src/commands/setup.rs @@ -0,0 +1,184 @@ +use std::path::PathBuf; +use std::process::Command; + +use anyhow::{Context, Result}; +use console::style; + +use crate::common::spin_installer::check_and_install_spin; + +pub async fn templates( + force: bool, + git: Option, + branch: Option, + dir: Option, + tar: Option, +) -> Result<()> { + println!("{} Managing FTL templates", style("→").cyan()); + + // Get spin path + let spin_path = check_and_install_spin().await?; + + // Check if templates are already installed + if !force { + let list_output = Command::new(&spin_path) + .args(["templates", "list"]) + .output() + .context("Failed to list templates")?; + + let output_str = String::from_utf8_lossy(&list_output.stdout); + let has_ftl_templates = output_str.contains("ftl-rust") + || output_str.contains("ftl-typescript") + || output_str.contains("ftl-javascript"); + + if has_ftl_templates { + println!("{} FTL templates are already installed", style("✓").green()); + println!(); + println!("Use --force to reinstall/update them"); + return Ok(()); + } + } + + // Build install command based on provided options + let mut install_cmd = Command::new(&spin_path); + install_cmd.args(["templates", "install"]); + + if let Some(git_url) = &git { + println!( + "{} Installing templates from Git: {}", + style("→").dim(), + style(git_url).dim() + ); + install_cmd.args(["--git", git_url]); + if let Some(branch_name) = &branch { + install_cmd.args(["--branch", branch_name]); + } + } else if let Some(dir_path) = &dir { + println!( + "{} Installing templates from directory: {}", + style("→").dim(), + style(dir_path.display()).dim() + ); + install_cmd.args(["--dir", dir_path.to_str().unwrap()]); + } else if let Some(tar_path) = &tar { + println!( + "{} Installing templates from tarball: {}", + style("→").dim(), + style(tar_path).dim() + ); + install_cmd.args(["--tar", tar_path]); + } else { + // Default: install from bundled templates + let template_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); + + println!( + "{} Installing FTL templates from {}", + style("→").dim(), + style(template_dir.display()).dim() + ); + + install_cmd.args(["--dir", template_dir.to_str().unwrap()]); + } + + install_cmd.arg("--upgrade"); + + let install_output = install_cmd + .output() + .context("Failed to install templates")?; + + if !install_output.status.success() { + anyhow::bail!( + "Failed to install templates:\n{}", + String::from_utf8_lossy(&install_output.stderr) + ); + } + + println!("{} Templates installed successfully!", style("✓").green()); + println!(); + + // List installed FTL templates + let list_output = Command::new(&spin_path) + .args(["templates", "list"]) + .output() + .context("Failed to list templates")?; + + let output_str = String::from_utf8_lossy(&list_output.stdout); + println!("Available FTL templates:"); + for line in output_str.lines() { + if line.contains("ftl-") { + println!(" {}", line.trim()); + } + } + + Ok(()) +} + +pub async fn info() -> Result<()> { + println!("{} FTL Configuration", style("→").cyan()); + println!(); + + // Show version + println!("FTL CLI version: {}", env!("CARGO_PKG_VERSION")); + println!(); + + // Check spin installation + match crate::common::spin_installer::get_spin_path() { + Ok(spin_path) => { + println!( + "Spin: {} {}", + style("✓").green(), + style(spin_path.display()).dim() + ); + + // Get spin version + if let Ok(output) = Command::new(&spin_path).arg("--version").output() { + let version = String::from_utf8_lossy(&output.stdout); + println!(" Version: {}", version.trim()); + } + } + Err(_) => { + println!("Spin: {} Not installed", style("✗").red()); + println!(" Run 'ftl setup templates' to install"); + } + } + println!(); + + // Check templates + if let Ok(spin_path) = crate::common::spin_installer::get_spin_path() { + if let Ok(output) = Command::new(&spin_path) + .args(["templates", "list"]) + .output() + { + let output_str = String::from_utf8_lossy(&output.stdout); + let ftl_templates: Vec<&str> = output_str + .lines() + .filter(|line| line.contains("ftl-")) + .collect(); + + if ftl_templates.is_empty() { + println!("FTL Templates: {} Not installed", style("✗").red()); + println!(" Run 'ftl setup templates' to install"); + } else { + println!("FTL Templates: {} Installed", style("✓").green()); + for template in ftl_templates { + println!(" - {}", template.trim()); + } + } + } + } + println!(); + + // Check for wkg + match Command::new("wkg").arg("--version").output() { + Ok(output) => { + let version = String::from_utf8_lossy(&output.stdout); + println!("wkg: {} {}", style("✓").green(), version.trim()); + } + Err(_) => { + println!("wkg: {} Not installed", style("✗").red()); + println!(" Required for 'ftl publish'"); + println!(" Install from: https://github.com/bytecodealliance/wasm-pkg-tools"); + } + } + + Ok(()) +} diff --git a/packages/ftl-cli/src/commands/size.rs b/packages/ftl-cli/src/commands/size.rs deleted file mode 100644 index 9a4276c4..00000000 --- a/packages/ftl-cli/src/commands/size.rs +++ /dev/null @@ -1,519 +0,0 @@ -use std::{collections::HashMap, path::Path, process::Command}; - -use anyhow::{Context, Result}; -use serde_json; - -use crate::{common::tool_paths, manifest::ToolManifest}; - -pub async fn execute(tool_path: String, verbose: bool) -> Result<()> { - let tool_dir = Path::new(&tool_path); - if !tool_dir.exists() { - anyhow::bail!("Tool directory '{tool_path}' not found"); - } - - let manifest_path = tool_dir.join("ftl.toml"); - if !manifest_path.exists() { - anyhow::bail!("No ftl.toml found in '{tool_path}'"); - } - - let manifest = ToolManifest::load(&manifest_path)?; - let tool_name = &manifest.tool.name; - let build_profile = &manifest.build.profile; - let language = manifest.tool.language; - - println!("📊 Size analysis for tool: {tool_name}"); - println!(" Profile: {build_profile}"); - - // Get the WASM path based on the language - let wasm_path = - tool_paths::get_wasm_path_for_language(tool_dir, tool_name, build_profile, language); - - if !wasm_path.exists() { - println!("\n⚠️ WASM binary not found. Building first..."); - crate::commands::build::execute(Some(tool_path.clone()), None).await?; - } - - // Get file metadata - let metadata = std::fs::metadata(&wasm_path).context("Failed to read WASM file metadata")?; - let wasm_size = metadata.len(); - - // Get build time - if let Ok(modified) = metadata.modified() { - if let Ok(elapsed) = std::time::SystemTime::now().duration_since(modified) { - let age = format_duration(elapsed.as_secs()); - println!(" Built: {age}"); - } - } - - println!("\n📦 Binary Sizes:"); - let wasm_size_str = format_size(wasm_size); - println!(" WASM: {wasm_size_str}"); - - // Check for optimized version if using wasm-opt - let opt_wasm_path = wasm_path.with_extension("opt.wasm"); - if opt_wasm_path.exists() { - let opt_size = std::fs::metadata(&opt_wasm_path)?.len(); - let opt_size_str = format_size(opt_size); - let reduction = (wasm_size - opt_size) * 100 / wasm_size; - println!(" Optimized WASM: {opt_size_str} ({reduction}% reduction)"); - } - - // Run wasm-tools if available to get detailed info - if which::which("wasm-tools").is_ok() { - println!("\n🔍 Detailed Analysis:"); - - // Get module info - let output = Command::new("wasm-tools") - .arg("print") - .arg(&wasm_path) - .arg("--print-offsets") - .output(); - - if let Ok(output) = output { - if output.status.success() { - // Count sections - let content = String::from_utf8_lossy(&output.stdout); - let func_count = content.matches("(func ").count(); - let type_count = content.matches("(type ").count(); - let import_count = content.matches("(import ").count(); - let export_count = content.matches("(export ").count(); - - println!(" Functions: {func_count}"); - println!(" Types: {type_count}"); - println!(" Imports: {import_count}"); - println!(" Exports: {export_count}"); - } - } - - // Get section sizes - let output = Command::new("wasm-tools") - .arg("objdump") - .arg(&wasm_path) - .arg("--section-offsets") - .output(); - - if let Ok(output) = output { - if output.status.success() { - let content = String::from_utf8_lossy(&output.stdout); - println!("\n📋 Section Breakdown:"); - - // Parse section info - let mut sections = Vec::new(); - for line in content.lines() { - if line.contains("section") && line.contains("size") { - sections.push(line.trim().to_string()); - } - } - - // Sort sections by size if possible - sections.sort_by(|a, b| { - let size_a = extract_size_from_section(a).unwrap_or(0); - let size_b = extract_size_from_section(b).unwrap_or(0); - size_b.cmp(&size_a) - }); - - for section in sections.iter().take(if verbose { 20 } else { 5 }) { - println!(" {section}"); - } - - if verbose && sections.len() > 20 { - println!(" ... and {} more sections", sections.len() - 20); - } - } - } - - // Verbose mode: Show detailed import analysis - if verbose { - println!("\n📥 Import Analysis (Startup Cost):"); - - let output = Command::new("wasm-tools") - .arg("print") - .arg(&wasm_path) - .output(); - - if let Ok(output) = output { - if output.status.success() { - let content = String::from_utf8_lossy(&output.stdout); - let mut import_counts: HashMap = HashMap::new(); - - for line in content.lines() { - if line.contains("(import \"") { - if let Some(module) = extract_import_module(line) { - *import_counts.entry(module).or_insert(0) += 1; - } - } - } - - let mut imports: Vec<_> = import_counts.into_iter().collect(); - imports.sort_by(|a, b| b.1.cmp(&a.1)); - - for (module, count) in imports { - println!(" {module}: {count} imports"); - } - - println!("\n💡 Import Tips:"); - println!(" - Each import adds startup overhead"); - println!(" - Consider bundling multiple operations into single imports"); - println!(" - Lazy-load optional functionality"); - } - } - - // Show size history if available - let size_history_path = tool_dir.join(".ftl").join("size_history.json"); - if size_history_path.exists() { - println!("\n📈 Size History:"); - if let Ok(history) = std::fs::read_to_string(&size_history_path) { - if let Ok(history_data) = serde_json::from_str::(&history) { - if let Some(entries) = history_data.as_array() { - let current_size_in_history = entries - .last() - .and_then(|e| e["size"].as_u64()) - .unwrap_or(wasm_size); - - // Show current if different from last recorded - if current_size_in_history != wasm_size { - let current_str = format_size(wasm_size); - let diff_str = if wasm_size > current_size_in_history { - let diff = format_size(wasm_size - current_size_in_history); - format!("+{diff}") - } else { - let diff = format_size(current_size_in_history - wasm_size); - format!("-{diff}") - }; - println!(" Current: {current_str} ({diff_str})"); - } - - // Show last 5 entries - for entry in entries.iter().rev().take(5) { - if let Some(size) = entry["size"].as_u64() { - let date_display = - if let Some(timestamp) = entry["timestamp"].as_u64() { - format_timestamp(timestamp) - } else { - entry["date"].as_str().unwrap_or("Unknown").to_string() - }; - - let size_str = format_size(size); - println!(" {date_display}: {size_str}"); - } - } - } - } - } - } - - // Save current size to history - save_size_to_history(tool_dir, wasm_size)?; - } - } else { - println!("\n💡 Tip: Install wasm-tools for detailed binary analysis"); - println!(" cargo install wasm-tools"); - } - - // Size recommendations (only in verbose mode) - if verbose { - println!("\n💡 Optimization Suggestions:"); - - let mut suggestions = Vec::new(); - - if wasm_size > 5_000_000 { - suggestions.push( - "⚠️ Binary is larger than 5MB - optimization strongly recommended".to_string(), - ); - } - - // Check optimization flags - let has_opt_flags = manifest - .optimization - .flags - .iter() - .any(|f| f.contains("-O") || f.contains("--optimize")); - - if !has_opt_flags { - suggestions.push( - "Add wasm-opt optimization to ftl.toml:\n [optimization]\n flags = [\"-O3\", \"--enable-bulk-memory\"]" - .to_string(), - ); - } else { - // Check if they could use more aggressive optimization - let has_o3_or_higher = manifest.optimization.flags.iter().any(|f| { - f.contains("-O3") || f.contains("-O4") || f.contains("-Os") || f.contains("-Oz") - }); - - if !has_o3_or_higher { - let flags = &manifest.optimization.flags; - suggestions.push(format!( - "Consider more aggressive optimization in ftl.toml:\n Current: {flags:?}\n \ - Try: [\"-O3\"] or [\"-Oz\"] for size" - )); - } - } - - // Profile suggestions - match manifest.build.profile.as_str() { - "dev" | "debug" => { - let profile = &manifest.build.profile; - suggestions.push(format!( - "Using '{profile}' profile - switch to 'release' for smaller binaries:\n \ - [build]\n profile = \"release\"" - )); - } - "release" => { - if wasm_size > 1_000_000 { - suggestions.push( - "Consider 'tiny' profile for maximum size reduction:\n [build]\n \ - profile = \"tiny\"" - .to_string(), - ); - } - } - _ => {} - } - - // Cargo.toml suggestions - suggestions.push( - "Review Cargo.toml for optimization opportunities:\n - Remove unused \ - dependencies\n - Use default-features = false where possible\n - Consider \ - lighter alternatives to heavy crates" - .to_string(), - ); - - // Show suggestions - for (i, suggestion) in suggestions.iter().enumerate() { - if i > 0 { - println!(); - } - println!(" {suggestion}"); - } - } // End of verbose mode optimization suggestions - - // Check dependencies size impact - if let Ok(metadata) = cargo_metadata::MetadataCommand::new() - .current_dir(tool_dir) - .exec() - { - // Count direct dependencies vs total - let total_packages = metadata.packages.len(); - let workspace_member = metadata.workspace_members.first(); - - let direct_deps = if let Some(member_id) = workspace_member { - metadata - .packages - .iter() - .find(|p| &p.id == member_id) - .map(|p| p.dependencies.len()) - .unwrap_or(0) - } else { - 0 - }; - - let transitive_deps = total_packages.saturating_sub(1); // Exclude the tool itself - - // In verbose mode, show full dependency analysis - if verbose && transitive_deps > 20 { - println!(); - println!(" 📦 Dependency Analysis:"); - println!(" Direct dependencies: {direct_deps}"); - println!(" Total (including transitive): {transitive_deps}"); - - if transitive_deps > 50 { - println!(" ⚠️ High dependency count may significantly increase binary size"); - println!(); - println!(" To investigate dependencies:"); - println!(" • See which crates bring in the most dependencies:"); - println!(" cargo tree --duplicates"); - println!(" • Find a specific heavy dependency:"); - println!(" cargo tree -i "); - println!(" • See dependency tree sorted by depth:"); - println!(" cargo tree --depth 1"); - } - } - - // Always show heaviest dependencies (in both normal and verbose modes) - if let Some(member_id) = workspace_member { - if let Some(package) = metadata.packages.iter().find(|p| &p.id == member_id) { - let mut dep_weights: Vec<(String, usize)> = Vec::new(); - - for dep in &package.dependencies { - // Count how many packages depend on this dependency - let weight = count_dependency_weight(&metadata, &dep.name); - dep_weights.push((dep.name.clone(), weight)); - } - - // Sort by weight - dep_weights.sort_by(|a, b| b.1.cmp(&a.1)); - - // Show top 5 if any are significant - let significant_deps: Vec<_> = dep_weights - .iter() - .take(5) - .filter(|(_, weight)| *weight > 5) - .collect(); - - if !significant_deps.is_empty() { - println!("\n💡 Heaviest dependencies:"); - for (name, weight) in significant_deps { - println!(" • {name} (brings in ~{weight} crates)"); - } - } - } - } - } - - Ok(()) -} - -fn format_size(bytes: u64) -> String { - const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; - let mut size = bytes as f64; - let mut unit_index = 0; - - while size >= 1024.0 && unit_index < UNITS.len() - 1 { - size /= 1024.0; - unit_index += 1; - } - - if unit_index == 0 { - let size_int = size as u64; - let unit = UNITS[unit_index]; - format!("{size_int} {unit}") - } else { - let unit = UNITS[unit_index]; - format!("{size:.2} {unit}") - } -} - -fn extract_size_from_section(section: &str) -> Option { - // Extract size from section string like "code section: size 123456" - if let Some(size_pos) = section.find("size ") { - let size_str = §ion[size_pos + 5..]; - let size_end = size_str - .find(|c: char| !c.is_numeric()) - .unwrap_or(size_str.len()); - size_str[..size_end].parse().ok() - } else { - None - } -} - -fn extract_import_module(line: &str) -> Option { - // Extract module name from import line like: (import "wasi:io/streams@0.2.0" - // ...) - if let Some(start) = line.find("(import \"") { - let rest = &line[start + 9..]; - if let Some(end) = rest.find('"') { - let full_module = &rest[..end]; - // Extract just the module part before @version - if let Some(at_pos) = full_module.find('@') { - Some(full_module[..at_pos].to_string()) - } else { - Some(full_module.to_string()) - } - } else { - None - } - } else { - None - } -} - -fn format_duration(seconds: u64) -> String { - if seconds < 60 { - "just now".to_string() - } else if seconds < 3600 { - format!("{} minutes ago", seconds / 60) - } else if seconds < 86400 { - format!("{} hours ago", seconds / 3600) - } else { - format!("{} days ago", seconds / 86400) - } -} - -fn format_timestamp(timestamp: u64) -> String { - use std::time::{SystemTime, UNIX_EPOCH}; - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - let diff = now.saturating_sub(timestamp); - - if diff < 60 { - "Just now".to_string() - } else if diff < 3600 { - format!("{} minutes ago", diff / 60) - } else if diff < 86400 { - format!("{} hours ago", diff / 3600) - } else { - format!("{} days ago", diff / 86400) - } -} - -fn count_dependency_weight(metadata: &cargo_metadata::Metadata, dep_name: &str) -> usize { - // Simple heuristic: count packages that have this dependency in their name - // This catches things like "serde" counting "serde_derive", "serde_json", etc. - metadata - .packages - .iter() - .filter(|p| p.name.contains(dep_name) || p.dependencies.iter().any(|d| d.name == dep_name)) - .count() -} - -fn save_size_to_history(tool_dir: &Path, size: u64) -> Result<()> { - use std::time::{SystemTime, UNIX_EPOCH}; - - let ftl_dir = tool_dir.join(".ftl"); - std::fs::create_dir_all(&ftl_dir)?; - - let history_path = ftl_dir.join("size_history.json"); - - // Load existing history - let mut history: Vec = if history_path.exists() { - let content = std::fs::read_to_string(&history_path)?; - serde_json::from_str(&content).unwrap_or_default() - } else { - Vec::new() - }; - - // Get current timestamp - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - // Format date simply - let date = format!("{timestamp}"); - - // Add new entry - history.push(serde_json::json!({ - "date": date, - "size": size, - "timestamp": timestamp, - })); - - // Keep only last 20 entries - if history.len() > 20 { - let skip_count = history.len() - 20; - history = history.into_iter().skip(skip_count).collect(); - } - - // Save updated history - std::fs::write(&history_path, serde_json::to_string_pretty(&history)?)?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_size() { - assert_eq!(format_size(0), "0 B"); - assert_eq!(format_size(1023), "1023 B"); - assert_eq!(format_size(1024), "1.00 KB"); - assert_eq!(format_size(1536), "1.50 KB"); - assert_eq!(format_size(1048576), "1.00 MB"); - assert_eq!(format_size(1073741824), "1.00 GB"); - } -} diff --git a/packages/ftl-cli/src/commands/spin.rs b/packages/ftl-cli/src/commands/spin.rs deleted file mode 100644 index 0a80662c..00000000 --- a/packages/ftl-cli/src/commands/spin.rs +++ /dev/null @@ -1,151 +0,0 @@ -use anyhow::{Context, Result}; -use console::style; -use dialoguer::Confirm; -use std::process::Command; - -use crate::common::spin_installer::{SPIN_REQUIRED_VERSION, check_and_install_spin}; - -pub async fn install() -> Result<()> { - println!( - "{} Installing Spin v{}", - style("→").cyan(), - SPIN_REQUIRED_VERSION - ); - - check_and_install_spin().await?; - - Ok(()) -} - -pub async fn update() -> Result<()> { - println!( - "{} Updating Spin to v{}", - style("→").cyan(), - SPIN_REQUIRED_VERSION - ); - - // For now, update is the same as install since the script handles it - check_and_install_spin().await?; - - Ok(()) -} - -pub async fn remove() -> Result<()> { - println!("{} Removing Spin", style("→").cyan()); - - // Check FTL-managed installation first - let home_dir = dirs::home_dir().context("Could not determine home directory")?; - let ftl_bin_dir = home_dir.join(".ftl").join("bin"); - let ftl_spin_path = ftl_bin_dir.join("spin"); - - if ftl_spin_path.exists() { - let confirm = Confirm::new() - .with_prompt("Remove FTL-managed Spin installation?") - .default(false) - .interact()?; - - if !confirm { - println!("Removal cancelled"); - return Ok(()); - } - - std::fs::remove_file(&ftl_spin_path)?; - println!( - "{} FTL-managed Spin removed successfully", - style("✓").green() - ); - return Ok(()); - } - - // Check if spin is in PATH (system-wide) - if let Ok(spin_path) = which::which("spin") { - println!("Found system-wide Spin at: {}", spin_path.display()); - println!("⚠️ This appears to be a system-wide installation."); - println!("FTL cannot remove system-wide installations."); - - // Try to provide OS-specific instructions - match std::env::consts::OS { - "macos" => { - println!("\nFor macOS, you might have installed it via:"); - println!(" - Homebrew: brew uninstall fermyon-spin"); - println!(" - Or manually via the install script"); - } - "linux" => { - println!("\nFor Linux, check if you installed it via:"); - println!(" - Your package manager (apt, yum, dnf, etc.)"); - println!(" - Or manually via the install script"); - } - _ => {} - } - } else { - println!("Spin is not installed"); - } - - Ok(()) -} - -pub async fn info() -> Result<()> { - println!("{} Spin Installation Info", style("ℹ").blue()); - println!(); - - // Check FTL-managed installation first - let home_dir = dirs::home_dir().context("Could not determine home directory")?; - let ftl_bin_dir = home_dir.join(".ftl").join("bin"); - let ftl_spin_path = ftl_bin_dir.join("spin"); - - let mut found_ftl_managed = false; - if ftl_spin_path.exists() { - found_ftl_managed = true; - println!("FTL-managed Spin:"); - println!(" Path: {}", ftl_spin_path.display()); - - // Get version - let output = Command::new(&ftl_spin_path).arg("--version").output()?; - - if output.status.success() { - let version = String::from_utf8_lossy(&output.stdout); - let version = version.trim(); - println!(" Version: {version}"); - } - println!(); - } - - // Check system-wide installation - if let Ok(system_spin_path) = which::which("spin") { - // Skip if it's the same as FTL-managed - if !found_ftl_managed || system_spin_path != ftl_spin_path { - println!("System-wide Spin:"); - println!(" Path: {}", system_spin_path.display()); - - // Get version - let output = Command::new(&system_spin_path).arg("--version").output()?; - - if output.status.success() { - let version = String::from_utf8_lossy(&output.stdout); - let version = version.trim(); - println!(" Version: {version}"); - } - println!(); - } - } - - println!("FTL requires Spin v{SPIN_REQUIRED_VERSION}"); - println!(); - - if found_ftl_managed { - println!("✓ FTL will use the managed installation in ~/.ftl/bin"); - } else if which::which("spin").is_ok() { - println!("⚠️ System Spin found, but FTL prefers its own managed version"); - println!( - " Run 'ftl spin install' to install Spin v{SPIN_REQUIRED_VERSION} in ~/.ftl/bin" - ); - println!(" This ensures version compatibility and won't affect your system installation"); - } else { - println!("❌ Spin is not installed"); - println!( - " Run 'ftl spin install' to install Spin v{SPIN_REQUIRED_VERSION} in ~/.ftl/bin" - ); - } - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/status.rs b/packages/ftl-cli/src/commands/status.rs deleted file mode 100644 index 4a2751d9..00000000 --- a/packages/ftl-cli/src/commands/status.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::process::Command; - -use anyhow::Result; -use console::style; - -use crate::common::deploy_utils::infer_app_name; - -pub async fn execute(name: Option) -> Result<()> { - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - // Get the app name - either provided or inferred from current directory - let app_name = match name { - Some(n) => n, - None => infer_app_name(".")?, - }; - - println!( - "{} Getting status for: {}", - style("→").cyan(), - style(&app_name).bold() - ); - println!(); - - // Run spin aka app status with --app-name flag - let output = Command::new("spin") - .args(["aka", "app", "status", "--app-name", &app_name]) - .output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("not logged in") || stderr.contains("authentication") { - anyhow::bail!("Not authenticated with FTL Edge. Please run: ftl login"); - } - if stderr.contains("not found") || stderr.contains("does not exist") { - anyhow::bail!( - "Tool/toolkit '{app_name}' not found. Use 'ftl list' to see deployed tools and toolkits." - ); - } - anyhow::bail!("Failed to get tool/toolkit status:\n{stderr}"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - print!("{stdout}"); - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/test.rs b/packages/ftl-cli/src/commands/test.rs index 4f9841e5..344af9fe 100644 --- a/packages/ftl-cli/src/commands/test.rs +++ b/packages/ftl-cli/src/commands/test.rs @@ -1,104 +1,64 @@ -use anyhow::Result; -use tracing::info; +use std::path::PathBuf; +use std::process::Command; -use crate::{ - common::{manifest_utils::validate_and_load_manifest, tool_paths::validate_tool_exists}, - language::get_language_support, -}; +use anyhow::{Context, Result}; +use console::style; -pub async fn execute(name: Option) -> Result<()> { - let tool_path = name.unwrap_or_else(|| ".".to_string()); - test_tool(&tool_path).await -} - -async fn test_tool(tool_path: &str) -> Result<()> { - // Validate tool exists and load manifest - validate_tool_exists(tool_path)?; - let manifest = validate_and_load_manifest(tool_path)?; +pub async fn execute(path: Option) -> Result<()> { + let component_path = path.unwrap_or_else(|| PathBuf::from(".")); - let name = &manifest.tool.name; - info!("Testing tool: {name}"); - println!("🧪 Running tests for '{tool_path}'..."); + println!("{} Running tests", style("→").cyan()); - // Get language support and run tests - let language_support = get_language_support(manifest.tool.language); + // Check if Makefile exists and has test target + if component_path.join("Makefile").exists() { + let output = Command::new("make") + .arg("test") + .current_dir(&component_path) + .output() + .context("Failed to run make test")?; - // Use the language-specific test implementation - match language_support.test(&manifest, std::path::Path::new(tool_path)) { - Ok(_) => { - println!("✅ All tests passed for '{tool_path}'"); - Ok(()) + if !output.status.success() { + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("{}", String::from_utf8_lossy(&output.stderr)); + anyhow::bail!("Tests failed"); } - Err(e) => { - println!("❌ Tests failed for '{tool_path}'"); - // Provide helpful message for missing tests - let error_msg = e.to_string(); - if error_msg.contains("0 tests") || error_msg.contains("could not find") { - println!( - "\n💡 No tests found. Your tool template includes example tests in {}", - match manifest.tool.language { - crate::language::Language::Rust => "src/lib.rs", - crate::language::Language::JavaScript => "src/index.test.js", - crate::language::Language::TypeScript => "src/index.test.ts", - } - ); - println!(" The tests verify basic tool functionality like name and description."); - println!("\nTo add more tests:"); - match manifest.tool.language { - crate::language::Language::Rust => { - println!(" 1. Add #[test] functions to src/lib.rs"); - println!(" 2. Test your tool's logic without needing WASM runtime"); - println!(" 3. Use standard Rust testing patterns"); - } - crate::language::Language::JavaScript => { - println!(" 1. Add test files matching *.test.js or *.spec.js"); - println!(" 2. Use your preferred JavaScript testing framework"); - println!(" 3. Tests run in Node.js, not in the WASM runtime"); - } - crate::language::Language::TypeScript => { - println!(" 1. Add test files matching *.test.ts or *.spec.ts"); - println!(" 2. Use your preferred TypeScript testing framework"); - println!(" 3. Tests run in Node.js, not in the WASM runtime"); - } - } - println!("\nNote: These are unit tests that run natively, not in WASM."); - println!("For WASM runtime testing, consider spin-test (experimental)."); + println!("{}", String::from_utf8_lossy(&output.stdout)); + } else { + // Try to detect test framework + if component_path.join("handler/Cargo.toml").exists() { + // Rust component + let output = Command::new("cargo") + .arg("test") + .current_dir(component_path.join("handler")) + .output() + .context("Failed to run cargo test")?; + + println!("{}", String::from_utf8_lossy(&output.stdout)); + if !output.status.success() { + println!("{}", String::from_utf8_lossy(&output.stderr)); + anyhow::bail!("Tests failed"); } + } else if component_path.join("handler/package.json").exists() { + // JavaScript/TypeScript component + let output = Command::new("npm") + .arg("test") + .current_dir(component_path.join("handler")) + .output() + .context("Failed to run npm test")?; - Err(e) + println!("{}", String::from_utf8_lossy(&output.stdout)); + if !output.status.success() { + println!("{}", String::from_utf8_lossy(&output.stderr)); + anyhow::bail!("Tests failed"); + } + } else { + anyhow::bail!("Could not determine how to run tests for this component"); } } -} - -#[cfg(test)] -mod tests { - use std::fs; - use tempfile::TempDir; + println!(); + println!("{} All tests passed!", style("✓").green()); - use super::*; - - #[tokio::test] - async fn test_missing_tool() { - let result = test_tool("nonexistent_tool").await; - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("not found")); - } - - #[tokio::test] - async fn test_missing_manifest() { - let temp_dir = TempDir::new().unwrap(); - let tool_dir = temp_dir.path().join("test_tool"); - fs::create_dir(&tool_dir).unwrap(); - - let result = test_tool(tool_dir.to_str().unwrap()).await; - assert!(result.is_err()); - assert!( - result - .unwrap_err() - .to_string() - .contains("No ftl.toml found") - ); - } + Ok(()) } diff --git a/packages/ftl-cli/src/commands/toolkit.rs b/packages/ftl-cli/src/commands/toolkit.rs deleted file mode 100644 index b7de7228..00000000 --- a/packages/ftl-cli/src/commands/toolkit.rs +++ /dev/null @@ -1,494 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - process::Command, - sync::Arc, -}; - -use anyhow::{Context, Result}; -use console::style; -use indicatif::{ProgressBar, ProgressStyle}; -use tokio::task::JoinSet; - -use crate::{ - common::config::FtlConfig, - manifest::{ToolkitConfig, ToolkitManifest, ToolkitTool}, - spin_generator::SpinConfig, -}; - -pub async fn build(name: String, tools: Vec) -> Result<()> { - println!( - "{} Building toolkit: {} with {} tools", - style("→").cyan(), - style(&name).bold(), - tools.len() - ); - - if tools.is_empty() { - anyhow::bail!("No tools specified for toolkit"); - } - - // Create toolkit directory - let toolkit_dir = PathBuf::from(&name); - std::fs::create_dir_all(&toolkit_dir)?; - - // First check that all tools exist - for tool_name in &tools { - let tool_dir = PathBuf::from(tool_name); - if !tool_dir.join("ftl.toml").exists() { - anyhow::bail!("Tool '{tool_name}' not found"); - } - } - - // Build all tools in parallel - println!(); - let tools_list = tools.join(", "); - println!("Tools to build: {tools_list}"); - println!(); - - // Create a single progress bar that shows overall progress - let pb = ProgressBar::new(tools.len() as u64); - pb.set_style( - ProgressStyle::default_bar() - .template("{spinner:.cyan} [{bar:30.cyan/blue}] {pos}/{len} {msg}") - .unwrap() - .progress_chars("█▉▊▋▌▍▎▏ ") - .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), - ); - pb.set_message("🔨 Building..."); - pb.enable_steady_tick(std::time::Duration::from_millis(80)); - - let mut tasks = JoinSet::new(); - let completed = Arc::new(std::sync::Mutex::new(Vec::new())); - - for tool_name in tools.clone() { - let toolkit_dir = toolkit_dir.clone(); - let pb = pb.clone(); - let completed = completed.clone(); - let tools_count = tools.len(); - - tasks.spawn(async move { - // Build the tool quietly - let build_result = - crate::commands::build::execute_quiet(&tool_name, Some("release".to_string())) - .await; - - if let Err(e) = build_result { - return Err(anyhow::anyhow!("Failed to build {tool_name}: {e}")); - } - - // Update progress - pb.inc(1); - let mut comp = completed.lock().unwrap(); - comp.push(tool_name.clone()); - let done = comp.len(); - - if done < tools_count { - pb.set_message(format!("completed {tool_name}")); - } - - // Find and copy WASM - check multiple locations - let tool_dir = PathBuf::from(&tool_name); - let wasm_filename = format!("{}.wasm", tool_name.replace('-', "_")); - - // Try Rust path first - let rust_wasm_path = tool_dir - .join("target") - .join("wasm32-wasip1") - .join("release") - .join(&wasm_filename); - - // Try JavaScript path - let js_wasm_filename = format!("{tool_name}.wasm"); - let js_wasm_path = tool_dir.join("dist").join(&js_wasm_filename); - - let (wasm_path, final_wasm_filename) = if rust_wasm_path.exists() { - (rust_wasm_path, wasm_filename) - } else if js_wasm_path.exists() { - (js_wasm_path, js_wasm_filename) - } else { - return Err(anyhow::anyhow!( - "WASM binary not found for tool: {}", - tool_name - )); - }; - - // Copy WASM to toolkit directory - let dest_path = toolkit_dir.join(&final_wasm_filename); - std::fs::copy(&wasm_path, &dest_path)?; - - // Path relative to .ftl/ directory - Ok((tool_name, format!("../{final_wasm_filename}"))) - }); - } - - // Wait for all builds to complete - let mut tool_paths = Vec::new(); - while let Some(result) = tasks.join_next().await { - match result { - Ok(Ok(tool_info)) => tool_paths.push(tool_info), - Ok(Err(e)) => { - pb.abandon(); - return Err(e); - } - Err(e) => { - pb.abandon(); - return Err(anyhow::anyhow!("Task failed: {e}")); - } - } - } - - pb.set_style( - ProgressStyle::default_bar() - .template("{prefix:.green} [{bar:30.green}] {pos}/{len} {msg}") - .unwrap() - .progress_chars("█▉▊▋▌▍▎▏ "), - ); - pb.set_prefix("✓"); - pb.finish_with_message("✓ All tools built successfully"); - - println!(); - println!("{} Building gateway component...", style("→").cyan()); - - // Create toolkit manifest - let tool_count = tools.len(); - let toolkit_manifest = ToolkitManifest { - toolkit: ToolkitConfig { - name: name.clone(), - version: "1.0.0".to_string(), - description: format!("Toolkit containing {tool_count} tools"), - }, - tools: tools - .iter() - .map(|tool_name| ToolkitTool { - name: tool_name.clone(), - route: format!("/{tool_name}"), - }) - .collect(), - gateway: None, // Gateway is opt-in - }; - - // Save toolkit manifest - let manifest_path = toolkit_dir.join("toolkit.toml"); - toolkit_manifest.save(&manifest_path)?; - - // Create .ftl directory in toolkit - let ftl_dir = toolkit_dir.join(".ftl"); - std::fs::create_dir_all(&ftl_dir)?; - - // Generate gateway code - generate_gateway_code(&toolkit_dir, &toolkit_manifest)?; - - // Build the gateway - let gateway_output = Command::new("cargo") - .args([ - "build", - "--target", - "wasm32-wasip1", - "--release", - "--manifest-path", - "gateway/Cargo.toml", - ]) - .current_dir(&toolkit_dir) - .output() - .context("Failed to build gateway")?; - - if !gateway_output.status.success() { - anyhow::bail!( - "Gateway build failed: {}", - String::from_utf8_lossy(&gateway_output.stderr) - ); - } - - // Copy gateway WASM to toolkit directory - let gateway_wasm_path = toolkit_dir - .join("gateway") - .join("target") - .join("wasm32-wasip1") - .join("release") - .join(format!( - "{}_gateway.wasm", - toolkit_manifest.toolkit.name.replace('-', "_") - )); - - let gateway_dest = toolkit_dir.join("gateway.wasm"); - std::fs::copy(&gateway_wasm_path, &gateway_dest).context("Failed to copy gateway WASM")?; - - println!("{} Gateway built successfully", style("✓").green()); - - // Generate spin.toml for toolkit - let spin_config = SpinConfig::from_toolkit(&toolkit_manifest, &tool_paths)?; - let spin_path = ftl_dir.join("spin.toml"); - spin_config.save(&spin_path)?; - - println!(); - println!("{} Toolkit built successfully!", style("✓").green()); - println!(); - println!("Toolkit directory: {}", toolkit_dir.display()); - println!("Tools included:"); - for tool in &tools { - println!(" - {tool}"); - } - println!(); - println!("Next steps:"); - println!(" ftl toolkit serve {name} # Serve locally"); - println!(" ftl toolkit deploy {name} # Deploy to FTL Edge"); - - Ok(()) -} - -pub async fn serve(name: String, port: u16) -> Result<()> { - println!( - "{} Serving toolkit: {} on port {}", - style("→").cyan(), - style(&name).bold(), - style(port).yellow() - ); - - let toolkit_dir = PathBuf::from(&name); - if !toolkit_dir.exists() { - anyhow::bail!( - "Toolkit '{}' not found. Build it first with: ftl toolkit build", - name - ); - } - - // Check if spin.toml exists - let spin_path = toolkit_dir.join(".ftl").join("spin.toml"); - if !spin_path.exists() { - anyhow::bail!(".ftl/spin.toml not found in toolkit directory"); - } - - // Start spin server - println!(); - println!("{} Starting toolkit server...", style("▶").green()); - println!(); - println!(" Toolkit: {name}"); - println!(" URL: http://localhost:{port}"); - - // Load toolkit manifest to show available routes - let manifest_path = toolkit_dir.join("toolkit.toml"); - if let Ok(manifest) = ToolkitManifest::load(&manifest_path) { - println!(" Routes:"); - println!(" - {} (aggregates all tools)", style("/mcp").yellow()); - for tool in &manifest.tools { - let route = &tool.route; - println!(" - {route}/mcp"); - } - } - - println!(); - println!("Press Ctrl+C to stop"); - println!(); - - let mut child = Command::new("spin") - .arg("up") - .arg("--listen") - .arg(format!("127.0.0.1:{port}")) - .arg("--from") - .arg(".ftl/spin.toml") - .current_dir(&toolkit_dir) - .spawn() - .context("Failed to start spin server")?; - - // Wait for Ctrl+C - tokio::signal::ctrl_c().await?; - - println!(); - println!("{} Stopping server...", style("■").red()); - - // Kill the spin process - child.kill().context("Failed to kill spin process")?; - - Ok(()) -} - -pub async fn deploy(name: String) -> Result<()> { - println!( - "{} Deploying toolkit: {}", - style("→").cyan(), - style(&name).bold() - ); - - let toolkit_dir = PathBuf::from(&name); - if !toolkit_dir.exists() { - anyhow::bail!( - "Toolkit '{}' not found. Build it first with: ftl toolkit build", - name - ); - } - - // Deploy using spin aka with spinner - let spinner = ProgressBar::new_spinner(); - spinner.set_style( - ProgressStyle::default_spinner() - .template("{spinner:.cyan} {msg}") - .unwrap() - .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), - ); - spinner.set_message("Deploying to FTL Edge..."); - spinner.enable_steady_tick(std::time::Duration::from_millis(80)); - - // Load config and generate app name with user prefix - let config = FtlConfig::load().unwrap_or_default(); - let app_name = format!("{}{name}", config.get_app_prefix()); - - // Try deploying without --create-name first (for existing apps) - let output = Command::new("spin") - .args(["aka", "deploy", "--from", ".ftl/spin.toml", "--no-confirm"]) - .current_dir(&toolkit_dir) - .output() - .context("Failed to run spin aka deploy")?; - - let output = if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - // If it fails because app doesn't exist, try with --create-name - if stderr.contains("no app") - || stderr.contains("not found") - || stderr.contains("does not exist") - || stderr.contains("No terminal available") - || stderr.contains("must use --create-name") - { - spinner.set_message(format!("Creating new toolkit: {app_name}...")); - Command::new("spin") - .args([ - "aka", - "deploy", - "--from", - ".ftl/spin.toml", - "--create-name", - &app_name, - "--no-confirm", - ]) - .current_dir(&toolkit_dir) - .output() - .context("Failed to run spin aka deploy with --create-name")? - } else { - output - } - } else { - output - }; - - spinner.finish_and_clear(); - - if !output.status.success() { - println!("{} Deployment failed", style("✗").red()); - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("{stderr}"); - } - - // Parse deployment URL - let output_str = String::from_utf8_lossy(&output.stdout); - let full_url = output_str - .lines() - .find(|line| line.contains("https://")) - .and_then(|line| { - line.split_whitespace() - .find(|word| word.starts_with("https://")) - }) - .unwrap_or("(URL not found in output)"); - - // Extract base URL (remove any path components) - let base_url = if let Some(domain_end) = full_url.find(".tech") { - &full_url[..domain_end + 5] // Include ".tech" - } else if let Some(first_slash) = full_url[8..].find('/') { - // Skip "https://" - &full_url[..8 + first_slash] - } else { - full_url - }; - - println!(); - println!("{} Toolkit deployed successfully!", style("✓").green()); - println!(); - println!("Toolkit: {name}"); - println!("URL: {base_url}"); - - // Show available routes - let manifest_path = toolkit_dir.join("toolkit.toml"); - if let Ok(manifest) = ToolkitManifest::load(&manifest_path) { - println!("Available endpoints:"); - println!(" - {base_url}/mcp (aggregates all tools)"); - for tool in &manifest.tools { - let route = &tool.route; - println!(" - {base_url}{route}/mcp"); - } - } - - Ok(()) -} - -/// Generate the gateway code for a toolkit -fn generate_gateway_code(toolkit_dir: &Path, manifest: &ToolkitManifest) -> Result<()> { - // Create gateway directory - let gateway_dir = toolkit_dir.join("gateway"); - std::fs::create_dir_all(&gateway_dir)?; - - // Generate Cargo.toml - let cargo_toml = format!( - r#"[package] -name = "{}-gateway" -version = "0.1.0" -edition = "2021" - -[dependencies] -# TODO: Add gateway dependencies -spin-sdk = "3.1.1" -serde = {{ version = "1.0", features = ["derive"] }} -serde_json = "1.0" - -[lib] -crate-type = ["cdylib"] - -[workspace]"#, - manifest.toolkit.name - ); - std::fs::write(gateway_dir.join("Cargo.toml"), cargo_toml)?; - - // Create src directory - let src_dir = gateway_dir.join("src"); - std::fs::create_dir_all(&src_dir)?; - - // Generate lib.rs with gateway configuration - let lib_rs = format!( - r#"use ftl_sdk_rs::{{ftl_mcp_gateway, gateway::{{GatewayConfig, ToolEndpoint}}, mcp::ServerInfo}}; - -// Configure the gateway with all tools in the toolkit -fn create_gateway_config() -> GatewayConfig {{ - GatewayConfig {{ - tools: vec![ -{} - ], - server_info: ServerInfo {{ - name: "{}-gateway".to_string(), - version: "{}".to_string(), - }}, - // Base URL is empty - gateway uses component IDs directly - base_url: "".to_string(), - }} -}} - -// Create the gateway component -ftl_mcp_gateway!(create_gateway_config());"#, - manifest - .tools - .iter() - .map(|tool| { - format!( - r#" ToolEndpoint {{ - name: "{}".to_string(), - route: "{}".to_string(), - description: None, - }},"#, - tool.name, tool.route - ) - }) - .collect::>() - .join("\n"), - manifest.toolkit.name, - manifest.toolkit.version - ); - std::fs::write(src_dir.join("lib.rs"), lib_rs)?; - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/unlink.rs b/packages/ftl-cli/src/commands/unlink.rs deleted file mode 100644 index 5c292af4..00000000 --- a/packages/ftl-cli/src/commands/unlink.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::process::Command; - -use anyhow::Result; -use console::style; - -use crate::common::tool_paths::validate_tool_exists; - -pub async fn execute(path: Option) -> Result<()> { - let tool_path = path.unwrap_or_else(|| ".".to_string()); - - // Validate tool directory exists - validate_tool_exists(&tool_path)?; - - // Check if spin is installed - if which::which("spin").is_err() { - anyhow::bail!( - "Spin CLI not found. Please install it from: https://developer.fermyon.com/spin/install" - ); - } - - // Check if .ftl/spin.toml exists - let spin_toml = std::path::Path::new(&tool_path).join(".ftl/spin.toml"); - if !spin_toml.exists() { - anyhow::bail!(".ftl/spin.toml not found. This tool doesn't appear to be built."); - } - - println!("{} Unlinking tool from deployment...", style("→").cyan()); - - // Run spin aka app unlink - let output = Command::new("spin") - .args(["aka", "app", "unlink", "-f", ".ftl/spin.toml"]) - .current_dir(&tool_path) - .output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("not logged in") || stderr.contains("authentication") { - anyhow::bail!("Not authenticated with FTL Edge. Please run: ftl login"); - } - if stderr.contains("not linked") || stderr.contains("No app linked") { - anyhow::bail!("This tool is not linked to any deployment."); - } - anyhow::bail!("Failed to unlink tool:\n{stderr}"); - } - - println!("{} Tool successfully unlinked", style("✓").green()); - println!(); - println!("The tool is no longer connected to any FTL Edge deployment."); - println!("You can:"); - println!(" ftl link # Link to an existing deployment"); - println!(" ftl deploy # Deploy as a new tool"); - - Ok(()) -} diff --git a/packages/ftl-cli/src/commands/up.rs b/packages/ftl-cli/src/commands/up.rs new file mode 100644 index 00000000..6a97920c --- /dev/null +++ b/packages/ftl-cli/src/commands/up.rs @@ -0,0 +1,62 @@ +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use anyhow::{Context, Result}; +use console::style; + +use crate::common::spin_installer::check_and_install_spin; + +pub async fn execute(path: Option, port: u16, build: bool) -> Result<()> { + let component_path = path.unwrap_or_else(|| PathBuf::from(".")); + + // Validate component directory exists + if !component_path.join("spin.toml").exists() { + anyhow::bail!("No spin.toml found. Not in a component or project directory?"); + } + + // Get spin path + let spin_path = check_and_install_spin().await?; + + // Build command args + let mut args = vec!["up"]; + + if build { + args.push("--build"); + } + + let listen_addr = format!("127.0.0.1:{}", port); + args.extend(["--listen", &listen_addr]); + + println!(); + println!("{} Server starting...", style("▶").green()); + println!(); + println!("{} Press Ctrl+C to stop", style("⏹").green()); + println!(); + + // Run spin up with inherited stdio so user can see logs + let mut child = Command::new(&spin_path) + .args(&args) + .current_dir(&component_path) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to start spin up")?; + + // Wait for Ctrl+C + tokio::select! { + _ = tokio::signal::ctrl_c() => { + println!(); + println!("{} Stopping server...", style("■").red()); + } + status = tokio::task::spawn_blocking(move || child.wait()) => { + if let Ok(Ok(status)) = status { + if !status.success() { + anyhow::bail!("Spin exited with status: {}", status); + } + } + } + } + + Ok(()) +} diff --git a/packages/ftl-cli/src/commands/validate.rs b/packages/ftl-cli/src/commands/validate.rs deleted file mode 100644 index 5f02853c..00000000 --- a/packages/ftl-cli/src/commands/validate.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::{path::Path, process::Command}; - -use anyhow::Result; -use tracing::info; - -use crate::manifest::ToolManifest; - -pub async fn execute(name: String) -> Result<()> { - let tool_dir = Path::new(&name); - if !tool_dir.exists() { - anyhow::bail!("Tool directory '{name}' not found"); - } - - println!("🔍 Validating tool: {name}"); - - let mut errors = Vec::new(); - let mut warnings = Vec::new(); - - // Check for required files - let manifest_path = tool_dir.join("ftl.toml"); - if !manifest_path.exists() { - errors.push("Missing ftl.toml manifest file".to_string()); - } else { - // Validate manifest - match ToolManifest::load(&manifest_path) { - Ok(manifest) => { - info!("Manifest loaded successfully"); - - // Validate manifest fields - if manifest.tool.name.is_empty() { - errors.push("Tool name cannot be empty".to_string()); - } - - if manifest.tool.description.is_empty() { - warnings.push("Tool description is empty".to_string()); - } - - // Validate version format - if !is_valid_version(&manifest.tool.version) { - let version = &manifest.tool.version; - errors.push(format!("Invalid version format: {version}")); - } - } - Err(e) => { - errors.push(format!("Invalid ftl.toml: {e}")); - } - } - } - - // Check spin.toml - let spin_path = tool_dir.join("spin.toml"); - if !spin_path.exists() { - errors.push("Missing spin.toml file".to_string()); - } - - // Check handler directory structure - let handler_path = tool_dir.join("handler"); - if !handler_path.exists() { - errors.push("Missing handler directory".to_string()); - } else { - // Check for handler Cargo.toml or package.json - let cargo_path = handler_path.join("Cargo.toml"); - let package_path = handler_path.join("package.json"); - - if !cargo_path.exists() && !package_path.exists() { - errors.push("Missing Cargo.toml or package.json in handler directory".to_string()); - } - - // Check for WIT file - let wit_path = handler_path.join("wit/mcp.wit"); - if !wit_path.exists() { - errors.push("Missing wit/mcp.wit file in handler directory".to_string()); - } - } - - // Check handler source files - let handler_src = handler_path.join("src"); - if !handler_src.exists() { - errors.push("Missing src directory in handler".to_string()); - } else { - // Check for main source file (lib.rs, index.ts, or index.js) - let lib_rs = handler_src.join("lib.rs"); - let index_ts = handler_src.join("index.ts"); - let index_js = handler_src.join("index.js"); - - if !lib_rs.exists() && !index_ts.exists() && !index_js.exists() { - errors.push("Missing main source file (lib.rs, index.ts, or index.js) in handler/src".to_string()); - } else if lib_rs.exists() { - // Validate Rust handler - match std::fs::read_to_string(&lib_rs) { - Ok(content) => { - if !content.contains("impl Guest for") { - warnings.push("No Guest trait implementation found in handler/src/lib.rs".to_string()); - } - if !content.contains("wit_bindgen::generate!") { - errors.push("Missing wit_bindgen::generate! macro in handler/src/lib.rs".to_string()); - } - } - Err(e) => { - errors.push(format!("Failed to read handler/src/lib.rs: {e}")); - } - } - } - } - - // Try to run build check based on handler type - println!("\n📦 Running build check..."); - let cargo_path = handler_path.join("Cargo.toml"); - let package_path = handler_path.join("package.json"); - - let check_result = if cargo_path.exists() { - Command::new("cargo") - .current_dir(&handler_path) - .arg("check") - .arg("--target") - .arg("wasm32-wasip1") - .output() - } else if package_path.exists() { - Command::new("npm") - .current_dir(&handler_path) - .arg("install") - .arg("--dry-run") - .output() - } else { - Err(std::io::Error::new(std::io::ErrorKind::NotFound, "No build file found")) - }; - - match check_result { - Ok(output) => { - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - errors.push(format!("Cargo check failed:\n{stderr}")); - } else { - println!("✅ Build check passed"); - } - } - Err(e) => { - warnings.push(format!("Could not run cargo check: {e}")); - } - } - - // Print results - println!("\n📋 Validation Results:"); - - if errors.is_empty() && warnings.is_empty() { - println!("✅ All checks passed!"); - Ok(()) - } else { - if !warnings.is_empty() { - println!("\n⚠️ Warnings ({})", warnings.len()); - for warning in warnings { - println!(" - {warning}"); - } - } - - if !errors.is_empty() { - println!("\n❌ Errors ({})", errors.len()); - let error_count = errors.len(); - for error in errors { - println!(" - {error}"); - } - anyhow::bail!("Validation failed with {error_count} error(s)"); - } - - Ok(()) - } -} - -fn is_valid_version(version: &str) -> bool { - // Basic semver validation - let parts: Vec<&str> = version.split('.').collect(); - if parts.len() != 3 { - return false; - } - - parts.iter().all(|part| part.parse::().is_ok()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_version_validation() { - assert!(is_valid_version("1.0.0")); - assert!(is_valid_version("0.0.1")); - assert!(is_valid_version("10.20.30")); - - assert!(!is_valid_version("1.0")); - assert!(!is_valid_version("1.0.0.0")); - assert!(!is_valid_version("1.a.0")); - assert!(!is_valid_version("")); - } -} diff --git a/packages/ftl-cli/src/commands/watch.rs b/packages/ftl-cli/src/commands/watch.rs deleted file mode 100644 index b581aeb7..00000000 --- a/packages/ftl-cli/src/commands/watch.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::time::Duration; - -use anyhow::Result; -use tracing::{debug, info, warn}; - -use crate::{ - commands::build, - common::{ - manifest_utils::validate_and_load_manifest, - tool_paths::validate_tool_exists, - watch_utils::{Debouncer, setup_file_watcher}, - }, -}; - -pub async fn execute(tool_path: String) -> Result<()> { - // Validate tool exists - validate_tool_exists(&tool_path)?; - let manifest = validate_and_load_manifest(&tool_path)?; - - info!("Watching tool: {} for changes...", manifest.tool.name); - - // Initial build - println!("🔨 Initial build..."); - build::execute(Some(tool_path.clone()), None).await?; - - // Set up file watcher - let (tx, rx) = std::sync::mpsc::channel(); - let _watcher = setup_file_watcher(&tool_path, tx)?; - - println!("👀 Watching for changes... (Press Ctrl+C to stop)"); - println!(" Watching:"); - println!(" - {tool_path}/src/"); - println!(" - {tool_path}/Cargo.toml"); - println!(" - {tool_path}/ftl.toml"); - - // Process file change events - let mut debouncer = Debouncer::new(Duration::from_millis(500)); - - loop { - match rx.recv() { - Ok(event) => { - debug!("File change detected: {:?}", event.paths); - - // Debounce rapid changes - if !debouncer.should_trigger() { - continue; - } - - // Display changed files - for path in &event.paths { - if let Ok(rel_path) = path.strip_prefix(&tool_path) { - println!("📝 Changed: {}", rel_path.display()); - } - } - - // Rebuild - println!("🔨 Rebuilding..."); - match build::execute(Some(tool_path.clone()), None).await { - Ok(_) => println!("✅ Build successful"), - Err(e) => { - println!("❌ Build failed: {e}"); - warn!("Build error: {}", e); - } - } - } - Err(e) => { - warn!("Watch error: {}", e); - break; - } - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use notify::{Event, EventKind, event::ModifyKind}; - - use crate::common::watch_utils::should_rebuild; - - #[test] - fn test_should_rebuild() { - // Should rebuild for Rust files - let event = - Event::new(EventKind::Modify(ModifyKind::Any)).add_path(PathBuf::from("src/main.rs")); - assert!(should_rebuild(&event)); - - // Should rebuild for TOML files - let event = - Event::new(EventKind::Modify(ModifyKind::Any)).add_path(PathBuf::from("Cargo.toml")); - assert!(should_rebuild(&event)); - - // Should not rebuild for non-code files - let event = - Event::new(EventKind::Modify(ModifyKind::Any)).add_path(PathBuf::from("README.md")); - assert!(!should_rebuild(&event)); - } -} diff --git a/packages/ftl-cli/src/common/build_utils.rs b/packages/ftl-cli/src/common/build_utils.rs deleted file mode 100644 index c771a59d..00000000 --- a/packages/ftl-cli/src/common/build_utils.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::{path::Path, process::Command}; - -use anyhow::{Context, Result}; -use tracing::info; - -/// Optimize a WASM binary using wasm-opt -pub fn optimize_wasm(wasm_path: &Path, optimization_flags: &[String]) -> Result<()> { - // Check if wasm-opt is available - if which::which("wasm-opt").is_err() { - anyhow::bail!("wasm-opt not found. Install it with: cargo install wasm-opt"); - } - - let temp_path = wasm_path.with_extension("wasm.tmp"); - - let mut cmd = Command::new("wasm-opt"); - cmd.arg(wasm_path).arg("-o").arg(&temp_path); - - // Add optimization flags - for flag in optimization_flags { - cmd.arg(flag); - } - - info!("Optimizing WASM with flags: {optimization_flags:?}"); - - let output = cmd.output().context("Failed to execute wasm-opt")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("wasm-opt failed:\n{stderr}"); - } - - // Replace original with optimized version - std::fs::rename(&temp_path, wasm_path) - .context("Failed to replace WASM with optimized version")?; - - Ok(()) -} - -/// Get the size of a file in bytes -pub fn get_file_size(path: &Path) -> Result { - let metadata = std::fs::metadata(path).with_context(|| { - let display = path.display(); - format!("Failed to get metadata for {display}") - })?; - Ok(metadata.len()) -} - -/// Format file size in human-readable format -pub fn format_size(size: u64) -> String { - const UNITS: &[&str] = &["B", "KB", "MB", "GB"]; - let mut size = size as f64; - let mut unit_idx = 0; - - while size >= 1024.0 && unit_idx < UNITS.len() - 1 { - size /= 1024.0; - unit_idx += 1; - } - - format!("{:.2} {}", size, UNITS[unit_idx]) -} diff --git a/packages/ftl-cli/src/common/config.rs b/packages/ftl-cli/src/common/config.rs deleted file mode 100644 index 87775078..00000000 --- a/packages/ftl-cli/src/common/config.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::{fs, path::PathBuf}; - -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct FtlConfig { - pub username: Option, -} - -impl FtlConfig { - /// Get the path to the global FTL config directory - fn config_dir() -> Result { - let home = - dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?; - Ok(home.join(".ftl")) - } - - /// Get the path to the config file - fn config_file() -> Result { - Ok(Self::config_dir()?.join("config.json")) - } - - /// Load config from disk - pub fn load() -> Result { - let config_file = Self::config_file()?; - - if !config_file.exists() { - return Ok(Self::default()); - } - - let content = fs::read_to_string(&config_file).with_context(|| { - let display = config_file.display(); - format!("Failed to read config from {display}") - })?; - - let config = - serde_json::from_str(&content).with_context(|| "Failed to parse config file")?; - - Ok(config) - } - - /// Save config to disk - pub fn save(&self) -> Result<()> { - let config_dir = Self::config_dir()?; - fs::create_dir_all(&config_dir).with_context(|| { - let display = config_dir.display(); - format!("Failed to create config directory at {display}") - })?; - - let config_file = Self::config_file()?; - let content = serde_json::to_string_pretty(self)?; - - fs::write(&config_file, content).with_context(|| { - let display = config_file.display(); - format!("Failed to write config to {display}") - })?; - - Ok(()) - } - - /// Get the app prefix for deployments - pub fn get_app_prefix(&self) -> String { - self.username - .as_ref() - .map(|u| format!("{u}-")) - .unwrap_or_else(|| "ftl-".to_string()) - } -} diff --git a/packages/ftl-cli/src/common/deploy_utils.rs b/packages/ftl-cli/src/common/deploy_utils.rs deleted file mode 100644 index 1638263e..00000000 --- a/packages/ftl-cli/src/common/deploy_utils.rs +++ /dev/null @@ -1,16 +0,0 @@ -use anyhow::Result; - -use crate::common::{config::FtlConfig, manifest_utils::load_manifest_and_name}; - -/// Infer the deployed app name from the current directory -/// This combines the username prefix with the tool name -pub fn infer_app_name(tool_path: &str) -> Result { - // Load manifest to get tool name - let (_manifest, tool_name) = load_manifest_and_name(tool_path)?; - - // Load config to get username prefix - let config = FtlConfig::load().unwrap_or_default(); - - // Generate the full app name - Ok(format!("{}{}", config.get_app_prefix(), tool_name)) -} diff --git a/packages/ftl-cli/src/common/manifest_utils.rs b/packages/ftl-cli/src/common/manifest_utils.rs deleted file mode 100644 index c9db1c15..00000000 --- a/packages/ftl-cli/src/common/manifest_utils.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::path::Path; - -use anyhow::{Context, Result}; - -use super::tool_paths::{get_manifest_path, validate_tool_exists}; -use crate::manifest::ToolManifest; - -/// Load a tool manifest from a tool directory -pub fn load_tool_manifest>(tool_path: P) -> Result { - let manifest_path = get_manifest_path(&tool_path); - ToolManifest::load(&manifest_path).with_context(|| { - let display = manifest_path.display(); - format!("Failed to load manifest from '{display}'") - }) -} - -/// Validate that a tool exists and load its manifest -pub fn validate_and_load_manifest>(tool_path: P) -> Result { - validate_tool_exists(&tool_path)?; - load_tool_manifest(tool_path) -} - -/// Load and validate a tool manifest, returning both the manifest and resolved -/// tool name -pub fn load_manifest_and_name>(tool_path: P) -> Result<(ToolManifest, String)> { - let manifest = validate_and_load_manifest(&tool_path)?; - let tool_name = manifest.tool.name.clone(); - Ok((manifest, tool_name)) -} diff --git a/packages/ftl-cli/src/common/mod.rs b/packages/ftl-cli/src/common/mod.rs index cb4e1fd8..67462516 100644 --- a/packages/ftl-cli/src/common/mod.rs +++ b/packages/ftl-cli/src/common/mod.rs @@ -1,8 +1 @@ -pub mod build_utils; -pub mod config; -pub mod deploy_utils; -pub mod manifest_utils; pub mod spin_installer; -pub mod spin_utils; -pub mod tool_paths; -pub mod watch_utils; diff --git a/packages/ftl-cli/src/common/spin_installer.rs b/packages/ftl-cli/src/common/spin_installer.rs index 9c82ada3..f4a4e351 100644 --- a/packages/ftl-cli/src/common/spin_installer.rs +++ b/packages/ftl-cli/src/common/spin_installer.rs @@ -12,6 +12,25 @@ use tracing::{debug, info}; pub const SPIN_REQUIRED_VERSION: &str = "3.3.1"; const SPIN_RELEASES_URL: &str = "https://github.com/fermyon/spin/releases/download"; +/// Get the path to spin if it exists (does not install) +pub fn get_spin_path() -> Result { + // First check if FTL-managed Spin is installed in ~/.ftl/bin + let home_dir = dirs::home_dir().context("Could not determine home directory")?; + let ftl_bin_dir = home_dir.join(".ftl").join("bin"); + let spin_path = ftl_bin_dir.join("spin"); + + if spin_path.exists() { + return Ok(spin_path); + } + + // If no FTL-managed version, check if spin is available in PATH + if let Ok(system_spin_path) = which::which("spin") { + return Ok(system_spin_path); + } + + anyhow::bail!("Spin not found") +} + pub async fn check_and_install_spin() -> Result { // First check if FTL-managed Spin is installed in ~/.ftl/bin let home_dir = dirs::home_dir().context("Could not determine home directory")?; diff --git a/packages/ftl-cli/src/common/spin_utils.rs b/packages/ftl-cli/src/common/spin_utils.rs deleted file mode 100644 index e2e65c10..00000000 --- a/packages/ftl-cli/src/common/spin_utils.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::{ - path::Path, - process::{Child, Command, Stdio}, -}; - -use anyhow::{Context, Result}; -use tracing::{debug, info}; - -use super::spin_installer::check_and_install_spin; - -/// Start a spin server for development (with spin path provided) -pub fn start_spin_server_with_path>( - spin_path: &Path, - tool_path: P, - port: u16, - spin_toml_path: Option<&Path>, -) -> Result { - let spin_toml = spin_toml_path - .map(|p| p.to_path_buf()) - .unwrap_or_else(|| tool_path.as_ref().join(".ftl/spin.toml")); - - info!("Starting Spin server on port {port}"); - - let child = Command::new(spin_path) - .arg("up") - .arg("--listen") - .arg(format!("127.0.0.1:{port}")) - .arg("-f") - .arg(&spin_toml) - .current_dir(tool_path) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .context("Failed to start Spin server")?; - - Ok(child) -} - -/// Deploy to FTL EdgeWorkers using Spin -pub async fn deploy_to_akamai>( - tool_path: P, - app_name: Option<&str>, -) -> Result { - let spin_path = check_and_install_spin().await?; - check_akamai_auth(&spin_path).await?; - - let tool_path = tool_path.as_ref(); - let spin_toml = tool_path.join(".ftl/spin.toml"); - - // Check if spin.toml exists - if !spin_toml.exists() { - anyhow::bail!("spin.toml not found at {spin_toml:?}. Did you build the tool first?"); - } - - // Get absolute path for spin.toml to avoid relative path issues - let spin_toml_abs = spin_toml - .canonicalize() - .context("Failed to get absolute path for spin.toml")?; - - // Check if app is already linked by running 'spin aka app status' - debug!("Checking if app is already linked from: {spin_toml_abs:?}"); - let status_output = Command::new(&spin_path) - .args([ - "aka", - "app", - "status", - "-f", - &spin_toml_abs.to_string_lossy(), - ]) - .current_dir(tool_path) - .output() - .context("Failed to run spin aka app status")?; - - let app_linked = status_output.status.success(); - - if !app_linked { - // Log the error for debugging but don't fail - this is expected for first - // deployment - let stderr = String::from_utf8_lossy(&status_output.stderr); - debug!("App not linked (expected for first deployment): {}", stderr); - } - - // If app is linked, deploy without --create-name - if app_linked { - debug!("App is linked, deploying without --create-name"); - let output = Command::new(&spin_path) - .args([ - "aka", - "deploy", - "--from", - &spin_toml_abs.to_string_lossy(), - "--no-confirm", - ]) - .current_dir(tool_path) - .output()?; - - if output.status.success() { - let stdout = String::from_utf8_lossy(&output.stdout); - return parse_deployment_info(&stdout); - } else { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Deployment failed:\n{stderr}"); - } - } - - // App doesn't exist or isn't linked, need to create/deploy with --create-name - let app_name = app_name.ok_or_else(|| { - anyhow::anyhow!("Tool/toolkit not linked. First deployment requires a name") - })?; - - info!("Creating new tool(kit): {app_name}"); - - let output = Command::new(&spin_path) - .args([ - "aka", - "deploy", - "--from", - &spin_toml_abs.to_string_lossy(), - "--create-name", - app_name, - "--no-confirm", - ]) - .current_dir(tool_path) - .output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Deployment failed:\n{}", stderr); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - parse_deployment_info(&stdout) -} - -/// Check if Akamai CLI is authenticated -pub async fn check_akamai_auth(spin_path: &Path) -> Result { - let output = Command::new(spin_path) - .args(["aka", "apps", "list"]) - .output() - .context("Failed to check Akamai authentication")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - if stderr.contains("not logged in") || stderr.contains("authentication") { - anyhow::bail!("Not authenticated with Akamai. Please run: ftl login"); - } - } - - Ok(true) -} - -#[derive(Debug)] -pub struct DeploymentInfo { - pub app_name: String, - pub url: String, -} - -fn parse_deployment_info(output: &str) -> Result { - // Parse deployment output to extract URL and app name - // Example output: "- string-formatter: https://...aka.fermyon.tech/mcp (wildcard)" - let url = output - .lines() - .find(|line| line.contains("http")) - .and_then(|line| { - // Extract URL from the line - line.split_whitespace().find(|s| s.starts_with("http")) - }) - .ok_or_else(|| anyhow::anyhow!("Could not parse deployment URL"))?; - - // Extract app name from the route line "- app-name: https://..." - let app_name = output - .lines() - .find(|line| line.contains("http") && line.contains("-")) - .and_then(|line| { - // Extract the part between "- " and ":" - line.trim_start_matches('-') - .split(':') - .next() - .map(|s| s.trim().to_string()) - }) - .unwrap_or_else(|| "ftl-tool".to_string()); - - Ok(DeploymentInfo { - app_name, - url: url.to_string(), - }) -} diff --git a/packages/ftl-cli/src/common/tool_paths.rs b/packages/ftl-cli/src/common/tool_paths.rs deleted file mode 100644 index cc65e659..00000000 --- a/packages/ftl-cli/src/common/tool_paths.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::path::{Path, PathBuf}; - -use anyhow::{Context, Result}; - -/// Get the path to a tool's manifest file -pub fn get_manifest_path>(tool_path: P) -> PathBuf { - tool_path.as_ref().join("ftl.toml") -} - -/// Get the path to a tool's .ftl directory -pub fn get_ftl_dir>(tool_path: P) -> PathBuf { - tool_path.as_ref().join(".ftl") -} - -/// Get the path to a tool's WASM binary -pub fn get_wasm_path>(tool_path: P, tool_name: &str, profile: &str) -> PathBuf { - let wasm_filename = format!("{}.wasm", tool_name.replace('-', "_")); - let profile_dir = get_profile_dir(profile); - - tool_path - .as_ref() - .join("target") - .join("wasm32-wasip1") - .join(profile_dir) - .join(wasm_filename) -} - -/// Get the path to a tool's WASM binary based on language -pub fn get_wasm_path_for_language>( - tool_path: P, - tool_name: &str, - profile: &str, - language: crate::language::Language, -) -> PathBuf { - use crate::language::Language; - - match language { - Language::Rust => { - let wasm_filename = format!("{}.wasm", tool_name.replace('-', "_")); - let profile_dir = get_profile_dir(profile); - tool_path - .as_ref() - .join("target") - .join("wasm32-wasip1") - .join(profile_dir) - .join(wasm_filename) - } - Language::JavaScript | Language::TypeScript => { - // JavaScript/TypeScript tools output to dist directory - tool_path - .as_ref() - .join("dist") - .join(format!("{tool_name}.wasm")) - } - } -} - -/// Get the profile directory name based on the profile -pub fn get_profile_dir(profile: &str) -> &str { - match profile { - "dev" => "debug", - profile => profile, - } -} - -/// Validate that a tool directory exists -pub fn validate_tool_exists>(tool_path: P) -> Result<()> { - let path = tool_path.as_ref(); - if !path.exists() { - let display = path.display(); - anyhow::bail!("Tool directory '{display}' not found"); - } - - let manifest_path = get_manifest_path(path); - if !manifest_path.exists() { - let display = path.display(); - anyhow::bail!("No ftl.toml found in '{display}'. Is this an FTL tool directory?"); - } - - Ok(()) -} - -/// Get the path to spin.toml in the .ftl directory -pub fn get_spin_toml_path>(tool_path: P) -> PathBuf { - get_ftl_dir(tool_path).join("spin.toml") -} - -/// Ensure the .ftl directory exists -pub fn ensure_ftl_dir>(tool_path: P) -> Result { - let ftl_dir = get_ftl_dir(tool_path); - std::fs::create_dir_all(&ftl_dir).with_context(|| { - let display = ftl_dir.display(); - format!("Failed to create .ftl directory at {display}") - })?; - Ok(ftl_dir) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_profile_dir_mapping() { - assert_eq!(get_profile_dir("dev"), "debug"); - assert_eq!(get_profile_dir("release"), "release"); - assert_eq!(get_profile_dir("custom"), "custom"); - } - - #[test] - fn test_wasm_filename() { - let path = get_wasm_path(".", "my-tool", "release"); - assert!(path.to_str().unwrap().contains("my_tool.wasm")); - } -} diff --git a/packages/ftl-cli/src/common/watch_utils.rs b/packages/ftl-cli/src/common/watch_utils.rs deleted file mode 100644 index 9a40e914..00000000 --- a/packages/ftl-cli/src/common/watch_utils.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::{ - path::Path, - sync::mpsc::Sender, - time::{Duration, Instant}, -}; - -use anyhow::Result; -use notify::{Event, EventKind, RecursiveMode, Watcher}; -use tracing::debug; - -/// Check if a file system event should trigger a rebuild -pub fn should_rebuild(event: &Event) -> bool { - match event.kind { - EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => { - event.paths.iter().any(|path| { - if let Some(ext) = path.extension() { - matches!(ext.to_str(), Some("rs") | Some("toml") | Some("lock")) - } else { - false - } - }) - } - _ => false, - } -} - -/// Set up file watcher for a tool directory -pub fn setup_file_watcher>( - tool_path: P, - tx: Sender, -) -> Result { - let mut watcher = notify::recommended_watcher(move |res: Result| { - if let Ok(event) = res { - if should_rebuild(&event) { - debug!("File change detected: {:?}", event.paths); - let _ = tx.send(event); - } - } - })?; - - let tool_path = tool_path.as_ref(); - - // Watch source directory - let src_dir = tool_path.join("src"); - if src_dir.exists() { - watcher.watch(&src_dir, RecursiveMode::Recursive)?; - } - - // Watch Cargo files - let cargo_toml = tool_path.join("Cargo.toml"); - if cargo_toml.exists() { - watcher.watch(&cargo_toml, RecursiveMode::NonRecursive)?; - } - - let cargo_lock = tool_path.join("Cargo.lock"); - if cargo_lock.exists() { - watcher.watch(&cargo_lock, RecursiveMode::NonRecursive)?; - } - - // Watch ftl.toml - let ftl_toml = tool_path.join("ftl.toml"); - if ftl_toml.exists() { - watcher.watch(&ftl_toml, RecursiveMode::NonRecursive)?; - } - - Ok(watcher) -} - -/// Debouncer to prevent rapid rebuilds -pub struct Debouncer { - last_trigger: Instant, - duration: Duration, -} - -impl Debouncer { - pub fn new(duration: Duration) -> Self { - Self { - last_trigger: Instant::now() - .checked_sub(duration) - .unwrap_or(Instant::now()), - duration, - } - } - - /// Check if enough time has passed since last trigger - pub fn should_trigger(&mut self) -> bool { - let now = Instant::now(); - if now.duration_since(self.last_trigger) >= self.duration { - self.last_trigger = now; - true - } else { - false - } - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use notify::event::ModifyKind; - - use super::*; - - #[test] - fn test_should_rebuild_rust_files() { - let event = - Event::new(EventKind::Modify(ModifyKind::Any)).add_path(PathBuf::from("src/main.rs")); - assert!(should_rebuild(&event)); - } - - #[test] - fn test_should_rebuild_toml_files() { - let event = - Event::new(EventKind::Modify(ModifyKind::Any)).add_path(PathBuf::from("Cargo.toml")); - assert!(should_rebuild(&event)); - } - - #[test] - fn test_should_not_rebuild_other_files() { - let event = - Event::new(EventKind::Modify(ModifyKind::Any)).add_path(PathBuf::from("README.md")); - assert!(!should_rebuild(&event)); - } - - #[test] - fn test_debouncer() { - let mut debouncer = Debouncer::new(Duration::from_millis(100)); - - // First trigger should succeed - assert!(debouncer.should_trigger()); - - // Immediate second trigger should fail - assert!(!debouncer.should_trigger()); - - // After waiting, should succeed - std::thread::sleep(Duration::from_millis(150)); - assert!(debouncer.should_trigger()); - } -} diff --git a/packages/ftl-cli/src/language.rs b/packages/ftl-cli/src/language.rs index 916931f9..8232b0d3 100644 --- a/packages/ftl-cli/src/language.rs +++ b/packages/ftl-cli/src/language.rs @@ -1,33 +1,18 @@ -use std::{fmt, path::Path}; - -use anyhow::Result; use serde::{Deserialize, Serialize}; +use std::fmt; -use crate::manifest::Manifest; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Language { - #[default] Rust, JavaScript, TypeScript, } -impl fmt::Display for Language { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Language::Rust => write!(f, "rust"), - Language::JavaScript => write!(f, "javascript"), - Language::TypeScript => write!(f, "typescript"), - } - } -} - impl Language { pub fn from_str(s: &str) -> Option { match s.to_lowercase().as_str() { - "rust" => Some(Language::Rust), + "rust" | "rs" => Some(Language::Rust), "javascript" | "js" => Some(Language::JavaScript), "typescript" | "ts" => Some(Language::TypeScript), _ => None, @@ -35,51 +20,18 @@ impl Language { } } -pub trait LanguageSupport: Send + Sync { - fn new_project(&self, name: &str, description: &str, template: &str, path: &Path) - -> Result<()>; - fn build(&self, manifest: &Manifest, path: &Path) -> Result<()>; - fn test(&self, manifest: &Manifest, path: &Path) -> Result<()>; - fn validate_environment(&self) -> Result<()>; -} - -pub mod javascript; -pub mod rust; -pub mod typescript; - -use self::{javascript::JavaScriptSupport, rust::RustSupport, typescript::TypeScriptSupport}; - -pub fn get_language_support(language: Language) -> Box { - match language { - Language::Rust => Box::new(RustSupport::new()), - Language::JavaScript => Box::new(JavaScriptSupport::new()), - Language::TypeScript => Box::new(TypeScriptSupport::new()), - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackageManager { - Npm, - Yarn, - Pnpm, -} - -impl PackageManager { - pub fn detect(path: &Path) -> Self { - if path.join("pnpm-lock.yaml").exists() { - PackageManager::Pnpm - } else if path.join("yarn.lock").exists() { - PackageManager::Yarn - } else { - PackageManager::Npm +impl fmt::Display for Language { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Language::Rust => write!(f, "Rust"), + Language::JavaScript => write!(f, "JavaScript"), + Language::TypeScript => write!(f, "TypeScript"), } } +} - pub fn run_command(&self, script: &str) -> String { - match self { - PackageManager::Npm => format!("npm run {script}"), - PackageManager::Yarn => format!("yarn {script}"), - PackageManager::Pnpm => format!("pnpm {script}"), - } +impl Default for Language { + fn default() -> Self { + Language::TypeScript } } diff --git a/packages/ftl-cli/src/language/javascript.rs b/packages/ftl-cli/src/language/javascript.rs deleted file mode 100644 index 7990e8b1..00000000 --- a/packages/ftl-cli/src/language/javascript.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::{path::Path, process::Command}; - -use anyhow::{Context, Result}; - -use crate::{ - common::spin_installer::check_and_install_spin, - language::{LanguageSupport, PackageManager}, - manifest::Manifest, -}; - -pub struct JavaScriptSupport; - -impl JavaScriptSupport { - pub fn new() -> Self { - Self - } - -} - -impl LanguageSupport for JavaScriptSupport { - fn new_project( - &self, - _name: &str, - _description: &str, - _template: &str, - _path: &Path, - ) -> Result<()> { - // This is now handled by spin templates in the new command - anyhow::bail!("Direct project creation is deprecated. Use 'ftl new' command instead.") - } - - fn build(&self, _manifest: &Manifest, path: &Path) -> Result<()> { - // Get spin path using blocking runtime - let spin_path = tokio::runtime::Handle::try_current() - .ok() - .and_then(|handle| { - tokio::task::block_in_place(|| handle.block_on(check_and_install_spin()).ok()) - }) - .unwrap_or_else(|| { - // If no runtime exists, create one - let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); - rt.block_on(check_and_install_spin()) - .expect("Failed to install Spin") - }); - - // Run spin build with spin.toml from .ftl directory - let spin_toml_path = path.join(".ftl/spin.toml"); - - // Ensure the spin.toml exists - if !spin_toml_path.exists() { - anyhow::bail!("spin.toml not found at: {}", spin_toml_path.display()); - } - - // Convert to absolute path to avoid issues with relative paths - let absolute_spin_toml = spin_toml_path - .canonicalize() - .context("Failed to resolve spin.toml path")?; - - let output = Command::new(&spin_path) - .args(["build", "-f", absolute_spin_toml.to_str().unwrap()]) - .current_dir(path) - .output() - .context("Failed to run spin build")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Build failed:\n{stderr}"); - } - - Ok(()) - } - - fn test(&self, _manifest: &Manifest, path: &Path) -> Result<()> { - let pm = PackageManager::detect(path); - let test_cmd = pm.run_command("test"); - let mut cmd_parts = test_cmd.split_whitespace(); - let output = Command::new(cmd_parts.next().unwrap()) - .args(cmd_parts) - .current_dir(path) - .output() - .context("Failed to run tests")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - if !stdout.is_empty() { - println!("\nOutput:\n{stdout}"); - } - if !stderr.is_empty() { - println!("\nErrors:\n{stderr}"); - } - - anyhow::bail!("Tests failed"); - } - - println!("{}", String::from_utf8_lossy(&output.stdout)); - Ok(()) - } - - fn validate_environment(&self) -> Result<()> { - // Check if Node.js is installed - let output = Command::new("node") - .arg("--version") - .output() - .context("Node.js is not installed. Please install Node.js from https://nodejs.org")?; - - let version = String::from_utf8_lossy(&output.stdout); - let version_parts: Vec<&str> = version.trim().trim_start_matches('v').split('.').collect(); - - if let Some(major) = version_parts.first().and_then(|v| v.parse::().ok()) { - if major < 18 { - let version = version.trim(); - anyhow::bail!( - "Node.js version {version} is too old. Please install Node.js 18 or later." - ); - } - } - - Ok(()) - } -} diff --git a/packages/ftl-cli/src/language/rust.rs b/packages/ftl-cli/src/language/rust.rs deleted file mode 100644 index b57a9190..00000000 --- a/packages/ftl-cli/src/language/rust.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::{path::Path, process::Command}; - -use anyhow::{Context, Result}; - -use crate::{language::LanguageSupport, manifest::Manifest}; - -pub struct RustSupport; - -impl RustSupport { - pub fn new() -> Self { - Self - } -} - -impl LanguageSupport for RustSupport { - fn new_project( - &self, - _name: &str, - _description: &str, - _template: &str, - _path: &Path, - ) -> Result<()> { - // This is now handled by spin templates in the new command - anyhow::bail!("Direct project creation is deprecated. Use 'ftl new' command instead.") - } - - fn build(&self, manifest: &Manifest, path: &Path) -> Result<()> { - // Check for Rust toolchain - self.validate_environment()?; - - // Build the project - let output = Command::new("cargo") - .args(["build", "--target", "wasm32-wasip1", "--release"]) - .current_dir(path) - .output() - .context("Failed to execute cargo build")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Build failed:\n{stderr}"); - } - - // Run wasm-opt if available - let wasm_path = path - .join("target/wasm32-wasip1/release") - .join(format!("{}.wasm", manifest.tool.name.replace('-', "_"))); - - if wasm_path.exists() { - self.optimize_wasm(&wasm_path)?; - } - - Ok(()) - } - - fn test(&self, _manifest: &Manifest, path: &Path) -> Result<()> { - let output = Command::new("cargo") - .arg("test") - .current_dir(path) - .output() - .context("Failed to execute cargo test")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Tests failed:\n{stderr}"); - } - - Ok(()) - } - - fn validate_environment(&self) -> Result<()> { - // Check if Rust is installed - Command::new("rustc") - .arg("--version") - .output() - .context("Rust is not installed. Please install Rust from https://rustup.rs")?; - - // Check if the wasm32-wasip1 target is installed - let output = Command::new("rustup") - .args(["target", "list", "--installed"]) - .output() - .context("Failed to check installed Rust targets")?; - - let installed_targets = String::from_utf8_lossy(&output.stdout); - if !installed_targets.contains("wasm32-wasip1") { - anyhow::bail!( - "The wasm32-wasip1 target is not installed. Please run: rustup target add \ - wasm32-wasip1" - ); - } - - Ok(()) - } -} - -impl RustSupport { - fn optimize_wasm(&self, wasm_path: &Path) -> Result<()> { - // Check if wasm-opt is available - if Command::new("wasm-opt").arg("--version").output().is_ok() { - println!("Optimizing WASM with wasm-opt..."); - - let output = Command::new("wasm-opt") - .args([ - "-O3", - "--enable-simd", - "--enable-bulk-memory", - wasm_path.to_str().unwrap(), - "-o", - wasm_path.to_str().unwrap(), - ]) - .output() - .context("Failed to run wasm-opt")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - eprintln!("Warning: wasm-opt optimization failed:\n{stderr}"); - } - } - - Ok(()) - } -} diff --git a/packages/ftl-cli/src/language/typescript.rs b/packages/ftl-cli/src/language/typescript.rs deleted file mode 100644 index 94279f9f..00000000 --- a/packages/ftl-cli/src/language/typescript.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::{path::Path, process::Command}; - -use anyhow::{Context, Result}; - -use crate::{ - common::spin_installer::check_and_install_spin, - language::{LanguageSupport, PackageManager}, - manifest::Manifest, -}; - -pub struct TypeScriptSupport; - -impl TypeScriptSupport { - pub fn new() -> Self { - Self - } - -} - -impl LanguageSupport for TypeScriptSupport { - fn new_project( - &self, - _name: &str, - _description: &str, - _template: &str, - _path: &Path, - ) -> Result<()> { - // This is now handled by spin templates in the new command - anyhow::bail!("Direct project creation is deprecated. Use 'ftl new' command instead.") - } - - fn build(&self, _manifest: &Manifest, path: &Path) -> Result<()> { - // Get spin path using blocking runtime - let spin_path = tokio::runtime::Handle::try_current() - .ok() - .and_then(|handle| { - tokio::task::block_in_place(|| handle.block_on(check_and_install_spin()).ok()) - }) - .unwrap_or_else(|| { - // If no runtime exists, create one - let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime"); - rt.block_on(check_and_install_spin()) - .expect("Failed to install Spin") - }); - - // Run spin build with spin.toml from .ftl directory - let spin_toml_path = path.join(".ftl/spin.toml"); - - // Ensure the spin.toml exists - if !spin_toml_path.exists() { - let display = spin_toml_path.display(); - anyhow::bail!("spin.toml not found at: {display}"); - } - - // Convert to absolute path to avoid issues with relative paths - let absolute_spin_toml = spin_toml_path - .canonicalize() - .context("Failed to resolve spin.toml path")?; - - let output = Command::new(&spin_path) - .args(["build", "-f", absolute_spin_toml.to_str().unwrap()]) - .current_dir(path) - .output() - .context("Failed to run spin build")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Build failed:\n{stderr}"); - } - - Ok(()) - } - - fn test(&self, _manifest: &Manifest, path: &Path) -> Result<()> { - let pm = PackageManager::detect(path); - let test_cmd = pm.run_command("test"); - let mut cmd_parts = test_cmd.split_whitespace(); - let output = Command::new(cmd_parts.next().unwrap()) - .args(cmd_parts) - .current_dir(path) - .output() - .context("Failed to run tests")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - if !stdout.is_empty() { - println!("\nOutput:\n{stdout}"); - } - if !stderr.is_empty() { - println!("\nErrors:\n{stderr}"); - } - - anyhow::bail!("Tests failed"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - println!("{stdout}"); - Ok(()) - } - - fn validate_environment(&self) -> Result<()> { - // Check if Node.js is installed - let output = Command::new("node") - .arg("--version") - .output() - .context("Node.js is not installed. Please install Node.js from https://nodejs.org")?; - - let version = String::from_utf8_lossy(&output.stdout); - let version_parts: Vec<&str> = version.trim().trim_start_matches('v').split('.').collect(); - - if let Some(major) = version_parts.first().and_then(|v| v.parse::().ok()) { - if major < 18 { - let version = version.trim(); - anyhow::bail!( - "Node.js version {version} is too old. Please install Node.js 18 or later." - ); - } - } - - Ok(()) - } -} diff --git a/packages/ftl-cli/src/main.rs b/packages/ftl-cli/src/main.rs index 722f73a2..22f54e5b 100644 --- a/packages/ftl-cli/src/main.rs +++ b/packages/ftl-cli/src/main.rs @@ -8,11 +8,10 @@ mod commands; mod common; mod language; mod manifest; -mod spin_generator; #[derive(Parser)] #[command(name = "ftl")] -#[command(about = "FTL - WebAssembly MCP tools")] +#[command(about = "Build and deploy Model Context Protocol (MCP) servers on WebAssembly")] #[command(version)] #[command(author)] struct Cli { @@ -26,185 +25,170 @@ struct Cli { #[derive(Subcommand)] enum Command { - /// Create a new tool from template - New { - /// Name of the tool - name: String, - /// Description of the tool - #[arg(short, long)] - description: Option, - /// Programming language to use (rust, javascript) - #[arg(short, long)] - language: Option, + /// Initialize a new MCP project + Init { + /// Name of the project + name: Option, + + /// Create in current directory + #[arg(long)] + here: bool, }, - /// Build a tool - Build { - /// Name of the tool to build (defaults to current directory) + /// Add a component to the current project + Add { + /// Name of the component name: Option, - /// Build profile to use + + /// Component description #[arg(short, long)] - profile: Option, - /// Start serving after build completes + description: Option, + + /// Language (rust, typescript, javascript) #[arg(short, long)] - serve: bool, - }, + language: Option, - /// Serve a tool locally - Serve { - /// Name of the tool to serve (defaults to current directory) - name: Option, - /// Port to serve on - #[arg(short, long, default_value = "3000")] - port: u16, - /// Build before serving + /// HTTP route for the component #[arg(short, long)] - build: bool, - }, + route: Option, - /// Run tests for a tool - Test { - /// Name of the tool to test (defaults to current directory) - name: Option, - }, + /// Use a Git repository as the template source + #[arg(long, conflicts_with = "dir", conflicts_with = "tar")] + git: Option, - /// Deploy a tool - Deploy { - /// Name of the tool to deploy (defaults to current directory) - name: Option, - }, + /// The branch to use from the Git repository + #[arg(long, requires = "git")] + branch: Option, - /// Export a tool as a standalone WASM component - Export { - /// Name of the tool to export (defaults to current directory) - name: Option, - /// Output path for the component WASM file - #[arg(short, long)] - output: Option, - /// Build profile to use - #[arg(short, long)] - profile: Option, - }, + /// Use a local directory as the template source + #[arg(long, conflicts_with = "git", conflicts_with = "tar")] + dir: Option, - /// Watch a tool for changes and rebuild - Watch { - /// Name of the tool to watch (defaults to current directory) - name: Option, + /// Use a tarball as the template source + #[arg(long, conflicts_with = "git", conflicts_with = "dir")] + tar: Option, }, - /// Validate tool configuration - Validate { - /// Name of the tool to validate (defaults to current directory) - name: Option, - }, + /// Build the component or project + Build { + /// Build in release mode + #[arg(short, long)] + release: bool, - /// Show binary size information - Size { - /// Name of the tool (defaults to current directory) - name: Option, - /// Show detailed analysis including sections and imports + /// Path to component (defaults to current directory) #[arg(short, long)] - verbose: bool, + path: Option, }, - /// List deployed tools and toolkits - List, + /// Run the component locally + Up { + /// Build before running + #[arg(long)] + build: bool, - /// Login to FTL Edge - Login, + /// Port to serve on + #[arg(short, long, default_value = "3000")] + port: u16, - /// Get status of a deployed tool or toolkit - Status { - /// Name of the tool or toolkit (defaults to current directory) - name: Option, + /// Path to component (defaults to current directory) + #[arg(long)] + path: Option, }, - /// Delete a deployed tool or toolkit - Delete { - /// Name of the tool or toolkit (defaults to current directory) - name: Option, - /// Skip confirmation prompt + /// Run tests + Test { + /// Path to component (defaults to current directory) #[arg(short, long)] - yes: bool, + path: Option, }, - /// View logs from a deployed tool or toolkit - Logs { - /// Name of the tool or toolkit (defaults to current directory) - name: Option, - /// Follow log output (not yet supported by spin) - #[arg(short, long, hide = true)] - follow: bool, - /// Number of lines to show from the end + /// Publish component to registry + Publish { + /// Registry URL (defaults to ghcr.io) #[arg(short, long)] - tail: Option, - }, + registry: Option, - /// Link current tool to an existing deployment - Link { - /// Name of the deployed tool to link to - name: String, - /// Path to the tool directory (defaults to current directory) + /// Tag/version to publish #[arg(short, long)] - path: Option, + tag: Option, + + /// Path to component (defaults to current directory) + #[arg(long)] + path: Option, }, - /// Unlink current tool from its deployment - Unlink { - /// Path to the tool directory (defaults to current directory) + /// Deploy the project to FTL + Deploy { + /// Environment to deploy to #[arg(short, long)] - path: Option, + environment: Option, }, - /// Manage toolkits (collections of tools) - Toolkit { + /// Interact with component registries + Registry { #[command(subcommand)] - command: ToolkitCommand, + command: RegistryCommand, }, - /// Manage Spin installation - Spin { + /// Setup and configure FTL + Setup { #[command(subcommand)] - command: SpinCommand, + command: SetupCommand, }, } #[derive(Subcommand)] -enum SpinCommand { - /// Install Spin - Install, - /// Update Spin to the latest supported version - Update, - /// Remove Spin (if installed by FTL) - Remove, - /// Show Spin installation info +enum SetupCommand { + /// Install or update FTL templates + Templates { + /// Force reinstall even if already installed + #[arg(long)] + force: bool, + + /// Use a Git repository as the template source + #[arg(long, conflicts_with = "dir", conflicts_with = "tar")] + git: Option, + + /// The branch to use from the Git repository + #[arg(long, requires = "git")] + branch: Option, + + /// Use a local directory as the template source + #[arg(long, conflicts_with = "git", conflicts_with = "tar")] + dir: Option, + + /// Use a tarball as the template source + #[arg(long, conflicts_with = "git", conflicts_with = "dir")] + tar: Option, + }, + + /// Show current configuration Info, } #[derive(Subcommand)] -enum ToolkitCommand { - /// Build a toolkit from multiple tools - Build { - /// Name of the toolkit - #[arg(long)] - name: String, - /// Tools to include in the toolkit - tools: Vec, +enum RegistryCommand { + /// List available components + List { + /// Registry to list from + #[arg(short, long)] + registry: Option, }, - /// Serve a toolkit locally - Serve { - /// Name of the toolkit - name: String, - /// Port to serve on - #[arg(short, long, default_value = "3000")] - port: u16, + /// Search for components + Search { + /// Search query + query: String, + + /// Registry to search in + #[arg(short, long)] + registry: Option, }, - /// Deploy a toolkit to FTL Edge - Deploy { - /// Name of the toolkit - name: String, + /// Show component information + Info { + /// Component name or URL + component: String, }, } @@ -224,60 +208,44 @@ async fn main() -> Result<()> { tracing_subscriber::fmt().with_env_filter(filter).init(); match cli.command { - Command::New { + Command::Init { name, here } => commands::init::execute(name, here).await, + Command::Add { name, description, language, - } => commands::new::execute(name, description, language).await, - Command::Build { - name, - profile, - serve, + route, + git, + branch, + dir, + tar, } => { - if serve { - commands::build::execute_and_serve(name, profile).await - } else { - commands::build::execute(name, profile).await - } - } - Command::Serve { name, port, build } => { - commands::serve::execute(name.unwrap_or_else(|| ".".to_string()), port, build).await + commands::add::execute(name, description, language, route, git, branch, dir, tar).await } - Command::Test { name } => commands::test::execute(name).await, - Command::Deploy { name } => { - commands::deploy::execute(name.unwrap_or_else(|| ".".to_string())).await - } - Command::Export { - name, - output, - profile, - } => commands::export::execute(name, output, profile).await, - Command::Watch { name } => { - commands::watch::execute(name.unwrap_or_else(|| ".".to_string())).await - } - Command::Validate { name } => { - commands::validate::execute(name.unwrap_or_else(|| ".".to_string())).await - } - Command::Size { name, verbose } => { - commands::size::execute(name.unwrap_or_else(|| ".".to_string()), verbose).await - } - Command::List => commands::list::execute().await, - Command::Login => commands::login::execute().await, - Command::Status { name } => commands::status::execute(name).await, - Command::Delete { name, yes } => commands::delete::execute(name, yes).await, - Command::Logs { name, follow, tail } => commands::logs::execute(name, follow, tail).await, - Command::Link { name, path } => commands::link::execute(name, path).await, - Command::Unlink { path } => commands::unlink::execute(path).await, - Command::Toolkit { command } => match command { - ToolkitCommand::Build { name, tools } => commands::toolkit::build(name, tools).await, - ToolkitCommand::Serve { name, port } => commands::toolkit::serve(name, port).await, - ToolkitCommand::Deploy { name } => commands::toolkit::deploy(name).await, + Command::Build { release, path } => commands::build::execute(path, release).await, + Command::Up { build, port, path } => commands::up::execute(path, port, build).await, + Command::Test { path } => commands::test::execute(path).await, + Command::Publish { + registry, + tag, + path, + } => commands::publish::execute(path, registry, tag).await, + Command::Deploy { environment } => commands::deploy::execute(environment).await, + Command::Registry { command } => match command { + RegistryCommand::List { registry } => commands::registry::list(registry).await, + RegistryCommand::Search { query, registry } => { + commands::registry::search(query, registry).await + } + RegistryCommand::Info { component } => commands::registry::info(component).await, }, - Command::Spin { command } => match command { - SpinCommand::Install => commands::spin::install().await, - SpinCommand::Update => commands::spin::update().await, - SpinCommand::Remove => commands::spin::remove().await, - SpinCommand::Info => commands::spin::info().await, + Command::Setup { command } => match command { + SetupCommand::Templates { + force, + git, + branch, + dir, + tar, + } => commands::setup::templates(force, git, branch, dir, tar).await, + SetupCommand::Info => commands::setup::info().await, }, } } diff --git a/packages/ftl-cli/src/manifest.rs b/packages/ftl-cli/src/manifest.rs index 5f67f8d3..3e50dd03 100644 --- a/packages/ftl-cli/src/manifest.rs +++ b/packages/ftl-cli/src/manifest.rs @@ -5,167 +5,28 @@ use serde::{Deserialize, Serialize}; use crate::language::Language; -// Type alias for backwards compatibility -pub type Manifest = ToolManifest; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolManifest { - pub tool: ToolConfig, - #[serde(default)] - pub build: BuildConfig, - #[serde(default)] - pub optimization: OptimizationConfig, - #[serde(default)] - pub runtime: RuntimeConfig, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolConfig { - pub name: String, - pub version: String, - pub description: String, - #[serde(default)] - pub language: Language, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct BuildConfig { - #[serde(default = "default_profile")] - pub profile: String, - #[serde(default)] - pub features: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub commands: Option>, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct OptimizationConfig { - #[serde(default)] - pub flags: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct RuntimeConfig { - #[serde(default)] - pub allowed_hosts: Vec, -} - +/// Component manifest (ftl.toml) #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolkitManifest { - pub toolkit: ToolkitConfig, - pub tools: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub gateway: Option, +pub struct ComponentManifest { + pub component: ComponentConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolkitConfig { +pub struct ComponentConfig { pub name: String, pub version: String, pub description: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ToolkitTool { - pub name: String, - pub route: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GatewayConfig { - #[serde(default = "default_gateway_enabled")] - pub enabled: bool, - #[serde(default = "default_gateway_route")] - pub route: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub server_name: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub server_version: Option, + pub language: Option, } -fn default_gateway_enabled() -> bool { - false -} - -fn default_gateway_route() -> String { - "/gateway".to_string() -} - -fn default_profile() -> String { - "release".to_string() -} - -impl ToolManifest { - pub fn load>(path: P) -> Result { - let content = std::fs::read_to_string(path.as_ref()) - .with_context(|| format!("Failed to read manifest from {:?}", path.as_ref()))?; - - toml::from_str(&content) - .with_context(|| format!("Failed to parse manifest from {:?}", path.as_ref())) - } - - pub fn validate(&self) -> Result<()> { - // Validate tool name (should be lowercase with hyphens) - if !self - .tool - .name - .chars() - .all(|c| c.is_lowercase() || c == '-' || c.is_numeric()) - { - anyhow::bail!("Tool name must be lowercase with hyphens (e.g., my-tool)"); - } - - // Validate version - semver::Version::parse(&self.tool.version).context("Invalid version format")?; - - Ok(()) - } -} - -impl ToolkitManifest { - pub fn load>(path: P) -> Result { - let content = std::fs::read_to_string(path.as_ref()) - .with_context(|| format!("Failed to read toolkit manifest from {:?}", path.as_ref()))?; +impl ComponentManifest { + pub fn load>(base_path: P) -> Result { + let manifest_path = base_path.as_ref().join("ftl.toml"); + let content = std::fs::read_to_string(&manifest_path) + .with_context(|| format!("Failed to read ftl.toml from {:?}", manifest_path))?; toml::from_str(&content) - .with_context(|| format!("Failed to parse toolkit manifest from {:?}", path.as_ref())) - } - - pub fn save>(&self, path: P) -> Result<()> { - let content = - toml::to_string_pretty(self).context("Failed to serialize toolkit manifest")?; - - std::fs::write(path.as_ref(), content) - .with_context(|| format!("Failed to write toolkit manifest to {:?}", path.as_ref())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_manifest_parsing() { - let manifest_str = r#" -[tool] -name = "json-query" -version = "1.0.0" -description = "Query JSON data" - -[build] -profile = "release" -features = ["simd"] - -[optimization] -flags = ["-O4", "--enable-simd"] - -[runtime] -allowed_hosts = [] -"#; - - let manifest: ToolManifest = toml::from_str(manifest_str).unwrap(); - assert_eq!(manifest.tool.name, "json-query"); - assert_eq!(manifest.build.profile, "release"); - assert_eq!(manifest.optimization.flags.len(), 2); + .with_context(|| format!("Failed to parse ftl.toml from {:?}", manifest_path)) } } diff --git a/packages/ftl-cli/src/spin_generator.rs b/packages/ftl-cli/src/spin_generator.rs deleted file mode 100644 index 3476d78a..00000000 --- a/packages/ftl-cli/src/spin_generator.rs +++ /dev/null @@ -1,211 +0,0 @@ -use std::path::Path; - -use anyhow::Result; - -use crate::{ - language::Language, - manifest::{ToolManifest, ToolkitManifest}, -}; - -pub struct SpinConfig { - pub content: String, -} - -impl SpinConfig { - pub fn from_tool(manifest: &ToolManifest, wasm_path: &Path) -> Result { - // Build command based on language and profile - let build_command = match manifest.tool.language { - Language::Rust => { - // Note: Cargo uses "dev" as the profile name but "debug" as the directory name - let cargo_profile = if manifest.build.profile == "debug" { - "dev" - } else { - &manifest.build.profile - }; - let features = if manifest.build.features.is_empty() { - String::new() - } else { - let features_str = manifest.build.features.join(","); - format!(" --features {features_str}") - }; - format!("cargo build --target wasm32-wasip1 --profile {cargo_profile}{features}") - } - Language::JavaScript | Language::TypeScript => { - // For JS/TS, we use npm run build which is defined in package.json - "npm run build".to_string() - } - }; - - let content = format!( - r#"spin_manifest_version = 2 - -[application] -name = "{}" -version = "{}" -description = "{}" -authors = ["FTL Tool"] - -[[trigger.http]] -route = "/mcp/..." -component = "{}" - -[component.{}] -source = "{}" -allowed_outbound_hosts = {:?} -build.command = "{}" -"#, - manifest.tool.name, - manifest.tool.version, - manifest.tool.description, - manifest.tool.name, - manifest.tool.name, - wasm_path.display(), - manifest.runtime.allowed_hosts, - build_command - ); - - Ok(Self { content }) - } - - pub fn from_toolkit( - manifest: &ToolkitManifest, - tool_paths: &[(String, String)], - ) -> Result { - let mut content = format!( - r#"spin_manifest_version = 2 - -[application] -name = "{}" -version = "{}" -description = "{}" -authors = ["FTL Toolkit"] - -[application.trigger.http] -base = "/" -"#, - manifest.toolkit.name, manifest.toolkit.version, manifest.toolkit.description - ); - - // Add gateway component first - content.push_str( - r#" -# Gateway component that aggregates all tools -[[trigger.http]] -id = "gateway" -component = "gateway" -route = "/mcp" -"#, - ); - - // Add triggers and components for each tool - for tool in &manifest.tools { - content.push_str(&format!( - r#" -[[trigger.http]] -id = "{}" -component = "{}" -route = "{}/mcp" -"#, - tool.name, tool.name, tool.route - )); - } - - // Add gateway component definition - content.push_str( - r#" -[component.gateway] -source = "../gateway.wasm" -# Gateway needs to communicate with local tools -allowed_outbound_hosts = ["http://*.spin.internal"] -[component.gateway.build] -command = "cargo build --target wasm32-wasip1 --release --manifest-path=gateway/Cargo.toml" -"#, - ); - - // Add component definitions for tools - for (tool_name, wasm_path) in tool_paths { - let allowed_hosts = manifest - .tools - .iter() - .find(|t| t.name == *tool_name) - .map(|_| Vec::::new()) - .unwrap_or_default(); - - content.push_str(&format!( - r#" -[component.{tool_name}] -source = "{wasm_path}" -allowed_outbound_hosts = {allowed_hosts:?} -"# - )); - } - - Ok(Self { content }) - } - - pub fn save>(&self, path: P) -> Result<()> { - std::fs::write(path, &self.content)?; - Ok(()) - } -} - -pub fn generate_development_config(tool_name: &str, port: u16, wasm_path: &Path) -> Result { - Ok(format!( - r#"# Development configuration for {} on port {} -spin_manifest_version = 2 - -[application] -name = "{}-dev" -version = "0.0.0" -description = "Development server for {}" -authors = ["FTL Developer"] - -[[trigger.http]] -route = "/mcp/..." -component = "{}" - -[component.{}] -source = "{}" -allowed_outbound_hosts = [] -build.command = "cargo build --target wasm32-wasip1 --release" -build.watch = ["src/**/*.rs", "Cargo.toml", "ftl.toml"] -"#, - tool_name, - port, - tool_name, - tool_name, - tool_name, - tool_name, - wasm_path.display() - )) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - language::Language, - manifest::{BuildConfig, OptimizationConfig, RuntimeConfig, ToolConfig, ToolManifest}, - }; - - #[test] - fn test_tool_spin_generation() { - let manifest = ToolManifest { - tool: ToolConfig { - name: "test_tool".to_string(), - version: "1.0.0".to_string(), - description: "Test tool".to_string(), - language: Language::Rust, - }, - build: BuildConfig::default(), - optimization: OptimizationConfig::default(), - runtime: RuntimeConfig::default(), - }; - - let wasm_path = Path::new("test.wasm"); - let config = SpinConfig::from_tool(&manifest, wasm_path).unwrap(); - - assert!(config.content.contains("test_tool")); - assert!(config.content.contains("test.wasm")); - } -} diff --git a/packages/ftl-cli/src/templates/javascript/content/ftl.toml b/packages/ftl-cli/src/templates/javascript/content/ftl.toml index cccf19e9..598495d4 100644 --- a/packages/ftl-cli/src/templates/javascript/content/ftl.toml +++ b/packages/ftl-cli/src/templates/javascript/content/ftl.toml @@ -1,24 +1,4 @@ -[tool] +[component] name = "{{project-name | kebab_case}}" version = "0.1.0" -description = "{{project-description}}" - -[build] -# Build commands are run from the handler directory -commands = [ - "cd handler && npm install", - "cd handler && npm run build" -] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] \ No newline at end of file +description = "{{project-description}}" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/package.json b/packages/ftl-cli/src/templates/javascript/content/handler/package.json index 0570f375..b79f7e43 100644 --- a/packages/ftl-cli/src/templates/javascript/content/handler/package.json +++ b/packages/ftl-cli/src/templates/javascript/content/handler/package.json @@ -9,7 +9,7 @@ "build:types": "jco types ./.wit/mcp.wit -o dist/generated", "build:bundle": "esbuild src/index.js --bundle --format=esm --platform=node --outfile=dist/index.js", "build:component": "jco componentize dist/index.js --wit ./.wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", - "test": "vitest", + "test": "vitest run", "clean": "rm -rf dist" }, "dependencies": { diff --git a/packages/ftl-cli/src/templates/rust/content/ftl.toml b/packages/ftl-cli/src/templates/rust/content/ftl.toml index 3b4e9c9c..598495d4 100644 --- a/packages/ftl-cli/src/templates/rust/content/ftl.toml +++ b/packages/ftl-cli/src/templates/rust/content/ftl.toml @@ -1,23 +1,4 @@ -[tool] +[component] name = "{{project-name | kebab_case}}" version = "0.1.0" -description = "{{project-description}}" - -[build] -# Build commands are run from the handler directory -commands = [ - "cargo build --release --target wasm32-wasip1" -] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] \ No newline at end of file +description = "{{project-description}}" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/ftl.toml b/packages/ftl-cli/src/templates/typescript/content/ftl.toml index cccf19e9..598495d4 100644 --- a/packages/ftl-cli/src/templates/typescript/content/ftl.toml +++ b/packages/ftl-cli/src/templates/typescript/content/ftl.toml @@ -1,24 +1,4 @@ -[tool] +[component] name = "{{project-name | kebab_case}}" version = "0.1.0" -description = "{{project-description}}" - -[build] -# Build commands are run from the handler directory -commands = [ - "cd handler && npm install", - "cd handler && npm run build" -] - -[optimization] -# See https://github.com/WebAssembly/binaryen/blob/version_123/test/lit/help/wasm-opt.test -flags = [ - "-O4", - "-Oz", -] - -[runtime] -# List of external hosts this tool is allowed to make HTTP requests to. -# Use exact hostnames or patterns with wildcards (e.g., "*.googleapis.com"). -# Leave empty to deny all external requests. -allowed_hosts = [] \ No newline at end of file +description = "{{project-description}}" \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/package.json b/packages/ftl-cli/src/templates/typescript/content/handler/package.json index a0122893..7e703717 100644 --- a/packages/ftl-cli/src/templates/typescript/content/handler/package.json +++ b/packages/ftl-cli/src/templates/typescript/content/handler/package.json @@ -10,7 +10,7 @@ "build:js": "tsc", "build:bundle": "esbuild dist/index.js --bundle --format=esm --platform=node --outfile=dist/bundled.js", "build:component": "jco componentize dist/bundled.js --wit ./.wit/mcp.wit --world-name mcp-handler --out dist/handler.wasm --disable all", - "test": "vitest", + "test": "vitest run", "clean": "rm -rf dist" }, "dependencies": { diff --git a/packages/ftl-sdk-rust/Cargo.toml b/packages/ftl-sdk-rust/Cargo.toml index ceae1f99..56c0ee1c 100644 --- a/packages/ftl-sdk-rust/Cargo.toml +++ b/packages/ftl-sdk-rust/Cargo.toml @@ -2,7 +2,7 @@ name = "ftl-sdk" version = "0.2.0" edition = "2021" -description = "FTL SDK for building MCP tools" +description = "Rust SDK for building MCP components with FTL" license = "MIT" repository = "https://github.com/fastertools/ftl-cli" diff --git a/packages/ftl-sdk-typescript/package.json b/packages/ftl-sdk-typescript/package.json index 9b14551a..636971ea 100644 --- a/packages/ftl-sdk-typescript/package.json +++ b/packages/ftl-sdk-typescript/package.json @@ -2,7 +2,7 @@ "name": "@fastertools/ftl-sdk", "version": "0.1.0", "type": "module", - "description": "FTL SDK for building MCP tools", + "description": "TypeScript SDK for building MCP components with FTL", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { From 27962f3824ba0ff43f50db343e1be05882a80a87 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 01:59:59 -0700 Subject: [PATCH 04/21] feat: New CLI --- .cargo/config.toml | 16 --- ARCHITECTURE_RECOMMENDATIONS.md | 140 ---------------------- packages/ftl-cli/src/commands/add.rs | 44 ++++--- packages/ftl-cli/src/commands/build.rs | 2 +- packages/ftl-cli/src/commands/deploy.rs | 2 +- packages/ftl-cli/src/commands/init.rs | 2 +- packages/ftl-cli/src/commands/publish.rs | 4 +- packages/ftl-cli/src/commands/registry.rs | 5 +- packages/ftl-cli/src/commands/up.rs | 2 +- packages/ftl-cli/src/language.rs | 8 +- packages/ftl-cli/src/main.rs | 12 +- packages/ftl-cli/src/manifest.rs | 4 +- 12 files changed, 50 insertions(+), 191 deletions(-) delete mode 100644 .cargo/config.toml delete mode 100644 ARCHITECTURE_RECOMMENDATIONS.md diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 4da77139..00000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,16 +0,0 @@ -[alias] -# Install development tools -install-just = "install just" -install-dev-tools = "install just cargo-watch" - -# Individual CI commands -fmt-check = "fmt --all -- --check" -lint = "clippy --all-targets --all-features -- -D warnings" -test-all = "test --all-features" - -# Development helpers -fix-clippy = "clippy --all-targets --all-features --fix --allow-dirty --allow-staged" -fix-fmt = "fmt --all" - -# Build commands -build-wasm = "build --package ftl-sdk-rs --target wasm32-wasip1" \ No newline at end of file diff --git a/ARCHITECTURE_RECOMMENDATIONS.md b/ARCHITECTURE_RECOMMENDATIONS.md deleted file mode 100644 index 4e6d0f58..00000000 --- a/ARCHITECTURE_RECOMMENDATIONS.md +++ /dev/null @@ -1,140 +0,0 @@ -# FTL CLI Architecture Recommendations - -## Executive Summary - -After analyzing the new template system and existing CLI implementation, I recommend a significant architectural shift to align FTL with the new component-based MCP server model. The key change is moving from managing full Spin applications to managing individual MCP components that can be composed into Spin applications. - -## Current State Analysis - -### New Template System -- Templates now generate **standalone WebAssembly components** implementing the MCP protocol -- Each component is designed to be published to a registry (e.g., `ghcr.io/username/component:version`) -- Components use a pre-published `mcp-gateway` component for HTTP->MCP translation -- Templates include Makefiles with `registry-push` targets for publishing - -### SDK Architecture -- Three SDKs (Rust, TypeScript, JavaScript) provide consistent APIs for building MCP handlers -- SDKs handle tool registration, resource management, and prompt handling -- Clean separation between MCP protocol implementation and user code - -### Existing CLI Issues -1. **Tool-centric model** - Commands like `ftl new`, `ftl build`, `ftl deploy` assume managing complete tools -2. **Toolkit abstraction** - The toolkit concept doesn't align with Spin's component composition model -3. **Custom spin.toml generation** - Overly complex for the new component model -4. **Missing component lifecycle** - No support for publishing/versioning components - -## Recommended Architecture - -### 1. Embrace Component-First Design - -**Remove the "tool" abstraction entirely.** Instead, work with: -- **MCP Components**: Individual WebAssembly components implementing MCP features -- **Spin Projects**: Compositions of multiple MCP components - -### 2. New CLI Command Structure - -```bash -# Component Development -ftl init # Create new MCP component project -ftl dev # Run component in development mode -ftl test # Test the component -ftl build # Build the component -ftl publish # Publish to registry - -# Component & Project Management -ftl init # Create empty Spin project (wraps spin new -t http-empty) -ftl add # Add component to current project -ftl up # Serve the project locally -ftl deploy # Deploy to FTL - -# Component Management -ftl registry list # List available components -ftl registry search # Search for components -ftl registry info # Show component details -``` - -### 3. Simplified Workflow - -**For single component development:** -```bash -ftl init my-weather-tool --lang typescript -cd my-weather-tool -ftl dev # Starts at localhost:3000/mcp -ftl publish # Publishes to registry -``` - -**For multi-component projects:** -```bash -ftl init my-assistant -cd my-assistant -ftl add github-tool --language typescript -ftl add weather-tool --language typescript -ftl add my-custom-tool --language rust -ftl up # All components available -ftl deploy -``` - -### 4. Key Implementation Changes - -#### Remove Complexity -1. **Delete toolkit functionality** - It's redundant with Spin's component model -2. **Simplify manifest handling** - Use ftl.toml only for component metadata -3. **Remove custom spin.toml generation** - Let Spin handle this via templates - -#### Add Component Features -1. **Registry integration** - First-class support for publishing/consuming components -2. **Version management** - Handle component versioning properly -3. **Dependency resolution** - Ensure compatible component versions - -#### Improve Developer Experience -1. **Template installation** - Auto-install templates on first use -2. **Component scaffolding** - Quick generators for common patterns -3. **Testing utilities** - Built-in MCP protocol testing - -### 5. Migration Path - -1. **Phase 1**: Add new commands alongside existing ones -2. **Phase 2**: Deprecate old commands with migration guides -3. **Phase 3**: Remove deprecated commands in next major version - -### 6. Technical Details - -#### Component Publishing -- Use `wkg` (WebAssembly package manager) for registry operations -- Support multiple registries (GitHub Container Registry, Docker Hub, etc.) -- Include metadata in published components (description, version, MCP features) - -#### Local Development -- Use Spin's built-in development server -- Provide MCP-specific development tools (request inspection, mock clients) -- Hot reload support where possible - -#### App Composition -- Leverage Spin's component composition features -- Support both local and registry components -- Handle component dependency conflicts - -## Benefits - -1. **Simplicity** - Aligns with Spin's model instead of fighting it -2. **Modularity** - True component reusability across projects -3. **Ecosystem** - Enables component marketplace/discovery -4. **Standards** - Follows WebAssembly component model standards -5. **Future-proof** - Ready for WASI Preview 2 and component model evolution - -## Risks and Mitigations - -1. **Breaking changes** - Mitigate with careful migration path and tooling -2. **Learning curve** - Provide excellent documentation and examples -3. **Registry dependency** - Support local-only workflows as fallback - -## Next Steps - -1. Validate approach with key stakeholders -2. Create proof-of-concept for new command structure -3. Design component registry integration -4. Plan migration strategy for existing users - -## Conclusion - -This architecture positions FTL as a best-in-class tool for building and deploying MCP servers on WebAssembly. By embracing Spin's component model rather than abstracting over it, we can provide a simpler, more powerful developer experience while enabling a rich ecosystem of reusable MCP components. \ No newline at end of file diff --git a/packages/ftl-cli/src/commands/add.rs b/packages/ftl-cli/src/commands/add.rs index 00d4d7b7..eba8c85f 100644 --- a/packages/ftl-cli/src/commands/add.rs +++ b/packages/ftl-cli/src/commands/add.rs @@ -7,16 +7,28 @@ use dialoguer::{Input, Select, theme::ColorfulTheme}; use crate::{common::spin_installer::check_and_install_spin, language::Language}; -pub async fn execute( - name: Option, - description: Option, - language: Option, - route: Option, - git: Option, - branch: Option, - dir: Option, - tar: Option, -) -> Result<()> { +pub struct AddOptions { + pub name: Option, + pub description: Option, + pub language: Option, + pub route: Option, + pub git: Option, + pub branch: Option, + pub dir: Option, + pub tar: Option, +} + +pub async fn execute(options: AddOptions) -> Result<()> { + let AddOptions { + name, + description, + language, + route, + git, + branch, + dir, + tar, + } = options; // Check if we're in a Spin project directory if !PathBuf::from("spin.toml").exists() { anyhow::bail!("No spin.toml found. Not in a Spin project directory? Run 'ftl init' first."); @@ -95,7 +107,7 @@ pub async fn execute( None => { // Convert component name to kebab-case for the route let kebab_name = component_name.replace('_', "-").to_lowercase(); - let default_route = format!("/{}/mcp", kebab_name); + let default_route = format!("/{kebab_name}/mcp"); Input::::with_theme(&ColorfulTheme::default()) .with_prompt("HTTP route") .default(default_route) @@ -137,9 +149,9 @@ pub async fn execute( spin_cmd.args([ "--accept-defaults", "--value", - &format!("project-description={}", description), + &format!("project-description={description}"), "--value", - &format!("route={}", route), + &format!("route={route}"), &component_name, ]); @@ -200,9 +212,9 @@ pub async fn execute( // Success message based on language let main_file = match selected_language { - Language::Rust => format!("{}/src/lib.rs", component_name), - Language::JavaScript => format!("{}/src/index.js", component_name), - Language::TypeScript => format!("{}/src/index.ts", component_name), + Language::Rust => format!("{component_name}/src/lib.rs"), + Language::JavaScript => format!("{component_name}/src/index.js"), + Language::TypeScript => format!("{component_name}/src/index.ts"), }; println!( diff --git a/packages/ftl-cli/src/commands/build.rs b/packages/ftl-cli/src/commands/build.rs index 54b35908..489d583d 100644 --- a/packages/ftl-cli/src/commands/build.rs +++ b/packages/ftl-cli/src/commands/build.rs @@ -75,7 +75,7 @@ pub async fn execute(path: Option, release: bool) -> Result<()> { let profile = if release { "release" } else { "debug" }; Command::new("cargo") .args(["component", "build", "--target", "wasm32-wasip1"]) - .arg(format!("--{}", profile)) + .arg(format!("--{profile}")) .current_dir(component_path.join("handler")) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) diff --git a/packages/ftl-cli/src/commands/deploy.rs b/packages/ftl-cli/src/commands/deploy.rs index a2b99245..2d6f1373 100644 --- a/packages/ftl-cli/src/commands/deploy.rs +++ b/packages/ftl-cli/src/commands/deploy.rs @@ -12,7 +12,7 @@ pub async fn execute(environment: Option) -> Result<()> { style("→").cyan(), environment .as_ref() - .map(|e| format!(" to {}", e)) + .map(|e| format!(" to {e}")) .unwrap_or_default() ); diff --git a/packages/ftl-cli/src/commands/init.rs b/packages/ftl-cli/src/commands/init.rs index d540479e..00b16a38 100644 --- a/packages/ftl-cli/src/commands/init.rs +++ b/packages/ftl-cli/src/commands/init.rs @@ -82,7 +82,7 @@ pub async fn execute(name: Option, here: bool) -> Result<()> { let cd_instruction = if here { "" } else { - &format!("cd {} && ", project_name) + &format!("cd {project_name} && ") }; println!( diff --git a/packages/ftl-cli/src/commands/publish.rs b/packages/ftl-cli/src/commands/publish.rs index be0087d4..d20e2468 100644 --- a/packages/ftl-cli/src/commands/publish.rs +++ b/packages/ftl-cli/src/commands/publish.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; use anyhow::{Context, Result}; @@ -130,7 +130,7 @@ pub async fn execute( Ok(()) } -fn find_wasm_file(component_path: &PathBuf, manifest: &ComponentManifest) -> Result { +fn find_wasm_file(component_path: &Path, manifest: &ComponentManifest) -> Result { // Check common locations let candidates = vec![ // Rust diff --git a/packages/ftl-cli/src/commands/registry.rs b/packages/ftl-cli/src/commands/registry.rs index 180d21a9..5238c37a 100644 --- a/packages/ftl-cli/src/commands/registry.rs +++ b/packages/ftl-cli/src/commands/registry.rs @@ -40,10 +40,7 @@ pub async fn search(query: String, registry: Option) -> Result<()> { ); println!(); println!("For now, you can search at:"); - println!( - " - GitHub: https://github.com/search?q=mcp+{}&type=registrypackages", - query - ); + println!(" - GitHub: https://github.com/search?q=mcp+{query}&type=registrypackages"); Ok(()) } diff --git a/packages/ftl-cli/src/commands/up.rs b/packages/ftl-cli/src/commands/up.rs index 6a97920c..d872acc0 100644 --- a/packages/ftl-cli/src/commands/up.rs +++ b/packages/ftl-cli/src/commands/up.rs @@ -24,7 +24,7 @@ pub async fn execute(path: Option, port: u16, build: bool) -> Result<() args.push("--build"); } - let listen_addr = format!("127.0.0.1:{}", port); + let listen_addr = format!("127.0.0.1:{port}"); args.extend(["--listen", &listen_addr]); println!(); diff --git a/packages/ftl-cli/src/language.rs b/packages/ftl-cli/src/language.rs index 8232b0d3..2a9235e9 100644 --- a/packages/ftl-cli/src/language.rs +++ b/packages/ftl-cli/src/language.rs @@ -3,9 +3,11 @@ use std::fmt; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum Language { Rust, JavaScript, + #[default] TypeScript, } @@ -29,9 +31,3 @@ impl fmt::Display for Language { } } } - -impl Default for Language { - fn default() -> Self { - Language::TypeScript - } -} diff --git a/packages/ftl-cli/src/main.rs b/packages/ftl-cli/src/main.rs index 22f54e5b..99001565 100644 --- a/packages/ftl-cli/src/main.rs +++ b/packages/ftl-cli/src/main.rs @@ -219,7 +219,17 @@ async fn main() -> Result<()> { dir, tar, } => { - commands::add::execute(name, description, language, route, git, branch, dir, tar).await + commands::add::execute(commands::add::AddOptions { + name, + description, + language, + route, + git, + branch, + dir, + tar, + }) + .await } Command::Build { release, path } => commands::build::execute(path, release).await, Command::Up { build, port, path } => commands::up::execute(path, port, build).await, diff --git a/packages/ftl-cli/src/manifest.rs b/packages/ftl-cli/src/manifest.rs index 3e50dd03..c43f86fc 100644 --- a/packages/ftl-cli/src/manifest.rs +++ b/packages/ftl-cli/src/manifest.rs @@ -24,9 +24,9 @@ impl ComponentManifest { pub fn load>(base_path: P) -> Result { let manifest_path = base_path.as_ref().join("ftl.toml"); let content = std::fs::read_to_string(&manifest_path) - .with_context(|| format!("Failed to read ftl.toml from {:?}", manifest_path))?; + .with_context(|| format!("Failed to read ftl.toml from {manifest_path:?}"))?; toml::from_str(&content) - .with_context(|| format!("Failed to parse ftl.toml from {:?}", manifest_path)) + .with_context(|| format!("Failed to parse ftl.toml from {manifest_path:?}")) } } From 3dee93ff2399698d411513242e0772e6860d0197 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 02:05:36 -0700 Subject: [PATCH 05/21] fix: workflows --- .github/dependabot.yml | 30 +++++ .github/workflows/README.md | 46 +++++++ .github/workflows/check-docs.yml | 53 ++++++++ .github/workflows/check-sdk-compatibility.yml | 121 +++++++++++++++++ .github/workflows/check-versions.yml | 59 +++++++++ .github/workflows/ci.yml | 45 ++++--- .github/workflows/release.yml | 37 ++++-- .github/workflows/test-templates.yml | 122 ++++++++++++++++++ 8 files changed, 480 insertions(+), 33 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/check-docs.yml create mode 100644 .github/workflows/check-sdk-compatibility.yml create mode 100644 .github/workflows/check-versions.yml create mode 100644 .github/workflows/test-templates.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b2b4a03d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,30 @@ +version: 2 +updates: + # Enable version updates for Rust dependencies + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + groups: + cargo-dependencies: + patterns: + - "*" + + # Enable version updates for npm dependencies (TypeScript SDK) + - package-ecosystem: "npm" + directory: "/packages/ftl-sdk-typescript" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + groups: + npm-dependencies: + patterns: + - "*" + + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 \ No newline at end of file diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..b990c400 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,46 @@ +# GitHub Workflows + +This directory contains the CI/CD workflows for the FTL project. + +## Workflows + +### Core Workflows + +- **[ci.yml](./ci.yml)** - Main CI pipeline that runs on every push and PR + - Linting (rustfmt, clippy) + - Building and testing the CLI + - Testing TypeScript and Rust SDKs + - Security audit + +- **[release.yml](./release.yml)** - Release pipeline triggered by version tags + - Builds binaries for Linux, macOS (x86_64 and aarch64) + - Creates GitHub releases + - Publishes to crates.io and npm + +### Quality Checks + +- **[test-templates.yml](./test-templates.yml)** - Tests the project templates + - Creates projects with `ftl init` + - Adds components with `ftl add` for all languages + - Builds and tests each component type + +- **[check-sdk-compatibility.yml](./check-sdk-compatibility.yml)** - Ensures SDK versions match templates + - Verifies Rust SDK version in Rust template + - Verifies TypeScript SDK version in TypeScript/JavaScript templates + - Tests template compilation with SDKs + +- **[check-versions.yml](./check-versions.yml)** - Checks version consistency + - Ensures workspace and CLI versions match + - Warns about SDK version mismatches in templates + +- **[check-docs.yml](./check-docs.yml)** - Documentation checks + - Verifies README links are valid + - Ensures CLI help matches documentation + - Checks template READMEs exist + +## Dependabot + +The [dependabot.yml](../dependabot.yml) configuration keeps dependencies updated: +- Cargo dependencies (weekly) +- npm dependencies for TypeScript SDK (weekly) +- GitHub Actions (weekly) \ No newline at end of file diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml new file mode 100644 index 00000000..06423744 --- /dev/null +++ b/.github/workflows/check-docs.yml @@ -0,0 +1,53 @@ +name: Check Documentation + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + check-readme: + name: Check README files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check for broken links in main README + run: | + # Basic check for broken markdown links + grep -oE '\[([^\]]+)\]\(([^)]+)\)' README.md | grep -v http | while read -r link; do + path=$(echo "$link" | sed -E 's/\[[^\]]+\]\(([^)]+)\)/\1/') + if [[ "$path" == "#"* ]]; then + continue # Skip anchor links + fi + if [[ ! -f "$path" ]]; then + echo "Error: Broken link in README.md: $path" + exit 1 + fi + done + + - name: Check CLI help matches README + run: | + cd packages/ftl-cli + cargo build --release + + # Generate help text + ./target/release/ftl --help > /tmp/ftl-help.txt + + # Check if key commands are documented + for cmd in init add build up test publish deploy registry setup; do + if ! grep -q "^ $cmd" /tmp/ftl-help.txt; then + echo "Error: Command '$cmd' not found in ftl --help" + exit 1 + fi + done + + - name: Check template READMEs exist + run: | + for lang in rust typescript javascript; do + if [[ ! -f "packages/ftl-cli/src/templates/$lang/content/README.md" ]]; then + echo "Error: Missing README.md for $lang template" + exit 1 + fi + done \ No newline at end of file diff --git a/.github/workflows/check-sdk-compatibility.yml b/.github/workflows/check-sdk-compatibility.yml new file mode 100644 index 00000000..9abf22a0 --- /dev/null +++ b/.github/workflows/check-sdk-compatibility.yml @@ -0,0 +1,121 @@ +name: Check SDK Compatibility + +on: + push: + branches: [ main ] + paths: + - 'packages/ftl-sdk-*/**' + - 'packages/ftl-cli/src/templates/**' + pull_request: + branches: [ main ] + paths: + - 'packages/ftl-sdk-*/**' + - 'packages/ftl-cli/src/templates/**' + +jobs: + check-rust-sdk: + name: Check Rust SDK Compatibility + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + + - name: Check Rust template uses correct SDK version + run: | + SDK_VERSION=$(grep '^version' packages/ftl-sdk-rust/Cargo.toml | cut -d'"' -f2) + TEMPLATE_VERSION=$(grep 'ftl-sdk' packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml | cut -d'"' -f2) + + if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then + echo "Error: Rust SDK version ($SDK_VERSION) does not match template version ($TEMPLATE_VERSION)" + echo "Please update packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml" + exit 1 + fi + + - name: Build Rust SDK + run: | + cd packages/ftl-sdk-rust + cargo build --target wasm32-wasip1 + + - name: Test Rust template compilation + run: | + cd packages/ftl-cli/src/templates/rust/content/handler + cargo build --target wasm32-wasip1 + + check-typescript-sdk: + name: Check TypeScript SDK Compatibility + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Check TypeScript template uses correct SDK version + run: | + SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") + TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/typescript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']") + + # Remove any ^ or ~ prefix from template version + TEMPLATE_VERSION=${TEMPLATE_VERSION#^} + TEMPLATE_VERSION=${TEMPLATE_VERSION#~} + + if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then + echo "Error: TypeScript SDK version ($SDK_VERSION) does not match template version ($TEMPLATE_VERSION)" + echo "Please update packages/ftl-cli/src/templates/typescript/content/handler/package.json" + exit 1 + fi + + - name: Build TypeScript SDK + run: | + cd packages/ftl-sdk-typescript + npm ci + npm run build + + - name: Test TypeScript template compilation + run: | + cd packages/ftl-cli/src/templates/typescript/content/handler + # Update dependency to use local SDK + node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:../../../../../../ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" + npm install + npm run build + + check-javascript-sdk: + name: Check JavaScript SDK Compatibility + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Check JavaScript template uses TypeScript SDK + run: | + # JavaScript template should use the TypeScript SDK + SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") + TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/javascript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']") + + # Remove any ^ or ~ prefix from template version + TEMPLATE_VERSION=${TEMPLATE_VERSION#^} + TEMPLATE_VERSION=${TEMPLATE_VERSION#~} + + if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then + echo "Error: TypeScript SDK version ($SDK_VERSION) does not match JavaScript template version ($TEMPLATE_VERSION)" + echo "Please update packages/ftl-cli/src/templates/javascript/content/handler/package.json" + exit 1 + fi + + - name: Test JavaScript template compilation + run: | + cd packages/ftl-cli/src/templates/javascript/content/handler + # Update dependency to use local SDK + node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:../../../../../../ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" + npm install + npm run build \ No newline at end of file diff --git a/.github/workflows/check-versions.yml b/.github/workflows/check-versions.yml new file mode 100644 index 00000000..94e77ae9 --- /dev/null +++ b/.github/workflows/check-versions.yml @@ -0,0 +1,59 @@ +name: Check Version Consistency + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + check-versions: + name: Check Version Consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check Cargo.toml versions match + run: | + # Get workspace version + WORKSPACE_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2) + echo "Workspace version: $WORKSPACE_VERSION" + + # Check ftl-cli version + CLI_VERSION=$(grep '^version' packages/ftl-cli/Cargo.toml | head -1 | cut -d'"' -f2) + echo "CLI version: $CLI_VERSION" + + if [ "$WORKSPACE_VERSION" != "$CLI_VERSION" ]; then + echo "Error: Workspace version ($WORKSPACE_VERSION) does not match CLI version ($CLI_VERSION)" + exit 1 + fi + + - name: Check SDK dependency versions in templates + run: | + # Check Rust SDK version + RUST_SDK_VERSION=$(grep '^version' packages/ftl-sdk-rust/Cargo.toml | cut -d'"' -f2) + RUST_TEMPLATE_VERSION=$(grep 'ftl-sdk' packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + + echo "Rust SDK version: $RUST_SDK_VERSION" + echo "Rust template SDK version: $RUST_TEMPLATE_VERSION" + + if [ "$RUST_SDK_VERSION" != "$RUST_TEMPLATE_VERSION" ]; then + echo "Warning: Rust SDK version mismatch" + fi + + # Check TypeScript SDK version + TS_SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") + TS_TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/typescript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']" | sed 's/[\^~]//') + JS_TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/javascript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']" | sed 's/[\^~]//') + + echo "TypeScript SDK version: $TS_SDK_VERSION" + echo "TypeScript template SDK version: $TS_TEMPLATE_VERSION" + echo "JavaScript template SDK version: $JS_TEMPLATE_VERSION" + + if [ "$TS_SDK_VERSION" != "$TS_TEMPLATE_VERSION" ]; then + echo "Warning: TypeScript SDK version mismatch in TypeScript template" + fi + + if [ "$TS_SDK_VERSION" != "$JS_TEMPLATE_VERSION" ]; then + echo "Warning: TypeScript SDK version mismatch in JavaScript template" + fi \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1620e277..f5fde1c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,19 +23,19 @@ jobs: components: rustfmt, clippy - name: Cache cargo registry - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} @@ -44,7 +44,7 @@ jobs: run: cargo fmt --all -- --check - name: Run clippy - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --all -- -D warnings test: name: Test @@ -59,19 +59,19 @@ jobs: run: rustup target add wasm32-wasip1 - name: Cache cargo registry - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo index - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} @@ -82,12 +82,14 @@ jobs: - name: Run tests run: cargo test --all-features - - name: Build WASM targets + - name: Test build templates run: | - cargo build --package ftl-sdk-rs --target wasm32-wasip1 + # Test that the embedded templates are valid + cd packages/ftl-cli + cargo build --release - test-typescript-sdk: - name: Test TypeScript SDK + test-sdks: + name: Test SDKs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -97,20 +99,23 @@ jobs: with: node-version: '20' - - name: Install dependencies + - name: Test TypeScript SDK run: | - cd packages/ftl-sdk-ts + cd packages/ftl-sdk-typescript npm ci - - - name: Build TypeScript SDK - run: | - cd packages/ftl-sdk-ts npm run build + npm test - - name: Run tests + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + + - name: Test Rust SDK run: | - cd packages/ftl-sdk-ts - npm test + cd packages/ftl-sdk-rust + cargo test + cargo build --target wasm32-wasip1 security: name: Security Audit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31d4207c..8ec85026 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,7 +42,7 @@ jobs: - name: Validate version matches Cargo.toml run: | - CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2) + CARGO_VERSION=$(grep '^version' packages/ftl-cli/Cargo.toml | head -1 | cut -d'"' -f2) if [ "$CARGO_VERSION" != "${{ steps.version.outputs.VERSION }}" ]; then echo "Error: Tag version (${{ steps.version.outputs.VERSION }}) does not match Cargo.toml version ($CARGO_VERSION)" exit 1 @@ -112,19 +112,30 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable - - name: Publish ftl-sdk-rs - if: steps.check-sdk.outputs.skip != 'true' + - name: Publish ftl-sdk run: | - cd packages/ftl-sdk-rs - cargo publish + cd packages/ftl-sdk-rust + # Check if this version is already published + if cargo search ftl-sdk --limit 1 | grep -q "^ftl-sdk = \"$(grep '^version' Cargo.toml | cut -d'"' -f2)\""; then + echo "ftl-sdk version already published, skipping" + else + cargo publish + fi env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + - name: Wait for ftl-sdk to be available + run: sleep 30 + - name: Publish ftl-cli - if: steps.check-cli.outputs.skip != 'true' run: | cd packages/ftl-cli - cargo publish + # Check if this version is already published + if cargo search ftl-cli --limit 1 | grep -q "^ftl-cli = \"$(grep '^version' Cargo.toml | cut -d'"' -f2)\""; then + echo "ftl-cli version already published, skipping" + else + cargo publish + fi env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} @@ -144,7 +155,7 @@ jobs: - name: Validate version matches run: | - PACKAGE_VERSION=$(node -p "require('./packages/ftl-sdk-ts/package.json').version") + PACKAGE_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") TAG_VERSION="${GITHUB_REF#refs/tags/v}" if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then echo "Error: Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)" @@ -153,22 +164,22 @@ jobs: - name: Install dependencies run: | - cd packages/ftl-sdk-ts + cd packages/ftl-sdk-typescript npm ci - name: Build TypeScript SDK run: | - cd packages/ftl-sdk-ts + cd packages/ftl-sdk-typescript npm run build - name: Run tests run: | - cd packages/ftl-sdk-ts + cd packages/ftl-sdk-typescript npm test - name: Publish to npm run: | - cd packages/ftl-sdk-ts - npm publish + cd packages/ftl-sdk-typescript + npm publish --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml new file mode 100644 index 00000000..f80b5f15 --- /dev/null +++ b/.github/workflows/test-templates.yml @@ -0,0 +1,122 @@ +name: Test Templates + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test-templates: + name: Test Templates + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Build ftl CLI + run: | + cd packages/ftl-cli + cargo build --release + + - name: Install Spin + run: | + wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz + tar -xzf spin-v2.8.0-linux-amd64.tar.gz + sudo mv spin /usr/local/bin/ + spin --version + + - name: Install FTL templates + run: | + spin templates install --dir packages/ftl-cli/src/templates --upgrade + + - name: Test ftl init + run: | + cd /tmp + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + cd test-project + test -f spin.toml + test -f .gitignore + + - name: Test ftl add with Rust template + run: | + cd /tmp/test-project + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-rust --language rust --description "Test Rust component" + test -d test-rust + test -f test-rust/ftl.toml + test -f test-rust/handler/Cargo.toml + + - name: Build Rust component + run: | + cd /tmp/test-project/test-rust + make build + test -f handler/target/wasm32-wasip1/release/test_rust.wasm + + - name: Test Rust component + run: | + cd /tmp/test-project/test-rust + make test + + - name: Test ftl add with TypeScript template + run: | + cd /tmp/test-project + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-ts --language typescript --description "Test TypeScript component" + test -d test-ts + test -f test-ts/ftl.toml + test -f test-ts/handler/package.json + + - name: Build TypeScript component + run: | + cd /tmp/test-project/test-ts + make build + test -f handler/dist/handler.wasm + + - name: Test TypeScript component + run: | + cd /tmp/test-project/test-ts + make test + + - name: Test ftl add with JavaScript template + run: | + cd /tmp/test-project + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-js --language javascript --description "Test JavaScript component" + test -d test-js + test -f test-js/ftl.toml + test -f test-js/handler/package.json + + - name: Build JavaScript component + run: | + cd /tmp/test-project/test-js + make build + test -f handler/dist/handler.wasm + + - name: Test JavaScript component + run: | + cd /tmp/test-project/test-js + make test + + - name: Test ftl build at project level + run: | + cd /tmp/test-project + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build + + - name: Test project structure + run: | + cd /tmp/test-project + # Verify spin.toml has all components + grep -q "test-rust" spin.toml + grep -q "test-ts" spin.toml + grep -q "test-js" spin.toml + # Verify routes + grep -q "/test-rust/mcp" spin.toml + grep -q "/test-ts/mcp" spin.toml + grep -q "/test-js/mcp" spin.toml \ No newline at end of file From 28646d3a0fafa7d344291f7acd96608641d2c452 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 02:32:32 -0700 Subject: [PATCH 06/21] feat: Add watch command --- .github/workflows/test-templates.yml | 6 +- README.md | 5 +- docs/cli-reference.md | 19 +++++- docs/getting-started.md | 6 +- packages/ftl-cli/README.md | 13 ++-- packages/ftl-cli/src/commands/add.rs | 2 +- packages/ftl-cli/src/commands/init.rs | 2 +- packages/ftl-cli/src/commands/mod.rs | 1 + packages/ftl-cli/src/commands/watch.rs | 65 +++++++++++++++++++ packages/ftl-cli/src/main.rs | 12 ++++ .../metadata/snippets/component.txt | 5 +- .../rust/metadata/snippets/component.txt | 5 +- .../metadata/snippets/component.txt | 5 +- 13 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 packages/ftl-cli/src/commands/watch.rs diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml index f80b5f15..a3f81594 100644 --- a/.github/workflows/test-templates.yml +++ b/.github/workflows/test-templates.yml @@ -119,4 +119,8 @@ jobs: # Verify routes grep -q "/test-rust/mcp" spin.toml grep -q "/test-ts/mcp" spin.toml - grep -q "/test-js/mcp" spin.toml \ No newline at end of file + grep -q "/test-js/mcp" spin.toml + + - name: Test ftl watch command exists + run: | + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl watch --help \ No newline at end of file diff --git a/README.md b/README.md index 86f94318..6aa5bf2c 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,8 @@ cd my-assistant # Add a component ftl add weather-tool --language typescript -# Build and run locally -ftl build -ftl up +# Start development server with auto-rebuild +ftl watch # Publish to registry ftl publish diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 356cf6c9..0ff21aaf 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -111,7 +111,24 @@ ftl up [OPTIONS] **Example:** ```bash -ftl up --build --port 8080 +ftl up --port 8080 +``` + +### `ftl watch` + +Build and run the component, automatically rebuilding when files change. + +```bash +ftl watch [OPTIONS] +``` + +**Options:** +- `-p, --port `: Port to serve on (default: 3000) +- `--path `: Path to component directory + +**Example:** +```bash +ftl watch --port 8080 ``` ### `ftl test` diff --git a/docs/getting-started.md b/docs/getting-started.md index db831fec..57bf5391 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -110,10 +110,10 @@ This compiles all components in your project into optimized WebAssembly modules. ## 6. Test Locally -Run your project locally for testing: +Run your project locally with automatic rebuilds: ```bash -ftl up +ftl watch ``` Test it with a curl request: @@ -157,7 +157,7 @@ ftl add news-tool --language typescript ftl add calculator --language rust # Run the project with all components -ftl up --build +ftl watch ``` ## 9. Deploy to Production diff --git a/packages/ftl-cli/README.md b/packages/ftl-cli/README.md index c3984b9f..02da1543 100644 --- a/packages/ftl-cli/README.md +++ b/packages/ftl-cli/README.md @@ -22,11 +22,8 @@ cd my-assistant # Add a component ftl add weather-tool --language typescript -# Build the components -ftl build - -# Run locally -ftl up +# Start development server with auto-rebuild +ftl watch # Publish to registry ftl publish @@ -64,6 +61,12 @@ Options: - `--build` - Build before running - `--port ` - Port to serve on (default: 3000) +#### `ftl watch` +Build and run the component, automatically rebuilding when files change. + +Options: +- `--port ` - Port to serve on (default: 3000) + #### `ftl test` Run component tests. diff --git a/packages/ftl-cli/src/commands/add.rs b/packages/ftl-cli/src/commands/add.rs index eba8c85f..e066f105 100644 --- a/packages/ftl-cli/src/commands/add.rs +++ b/packages/ftl-cli/src/commands/add.rs @@ -228,7 +228,7 @@ pub async fn execute(options: AddOptions) -> Result<()> { {} cd {} && make build # Build component -{} ftl up --build # Build all components and start development server"#, +{} ftl watch # Start development server with auto-rebuild"#, style("✓").green(), selected_language, style("📁").blue(), diff --git a/packages/ftl-cli/src/commands/init.rs b/packages/ftl-cli/src/commands/init.rs index 00b16a38..45a6683c 100644 --- a/packages/ftl-cli/src/commands/init.rs +++ b/packages/ftl-cli/src/commands/init.rs @@ -94,7 +94,7 @@ pub async fn execute(name: Option, here: bool) -> Result<()> { {} Next steps: {}ftl add # Add a component to the project - ftl up --build # Run the project locally + ftl watch # Start development server with auto-rebuild {} Example: {}ftl add weather-api --language typescript diff --git a/packages/ftl-cli/src/commands/mod.rs b/packages/ftl-cli/src/commands/mod.rs index c4fa2436..5a6cc4b4 100644 --- a/packages/ftl-cli/src/commands/mod.rs +++ b/packages/ftl-cli/src/commands/mod.rs @@ -7,3 +7,4 @@ pub mod registry; pub mod setup; pub mod test; pub mod up; +pub mod watch; diff --git a/packages/ftl-cli/src/commands/watch.rs b/packages/ftl-cli/src/commands/watch.rs new file mode 100644 index 00000000..d3e63ad3 --- /dev/null +++ b/packages/ftl-cli/src/commands/watch.rs @@ -0,0 +1,65 @@ +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use anyhow::{Context, Result}; +use console::style; + +use crate::common::spin_installer::check_and_install_spin; + +pub async fn execute(path: Option, port: u16) -> Result<()> { + let component_path = path.unwrap_or_else(|| PathBuf::from(".")); + + // Validate component directory exists + if !component_path.join("spin.toml").exists() { + anyhow::bail!("No spin.toml found. Not in a component or project directory?"); + } + + // Get spin path + let spin_path = check_and_install_spin().await?; + + // Build command args + let listen_addr = format!("127.0.0.1:{port}"); + // Pass arguments through to spin up + let args = vec!["watch", "--", "--listen", &listen_addr]; + + println!(); + println!("{} Starting development server with auto-rebuild...", style("▶").green()); + println!(); + println!("{} Watching for file changes:", style("👁").cyan()); + println!(" • Rust components: src/**/*.rs, Cargo.toml"); + println!(" • TypeScript components: src/**/*.ts, package.json, tsconfig.json"); + println!(" • JavaScript components: src/**/*.js, package.json"); + println!(); + println!("{} Server starting at: http://{}", style("🌐").blue(), listen_addr); + println!(); + println!("{} Note: Routes will be displayed after components are built", style("ℹ").blue()); + println!("{} Press Ctrl+C to stop", style("⏹").yellow()); + println!(); + + // Run spin watch with inherited stdio so user can see logs + let mut child = Command::new(&spin_path) + .args(&args) + .current_dir(&component_path) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to start spin watch")?; + + // Wait for Ctrl+C + tokio::select! { + _ = tokio::signal::ctrl_c() => { + println!(); + println!("{} Stopping development server...", style("■").red()); + } + status = tokio::task::spawn_blocking(move || child.wait()) => { + if let Ok(Ok(status)) = status { + if !status.success() { + anyhow::bail!("Spin watch exited with status: {}", status); + } + } + } + } + + Ok(()) +} \ No newline at end of file diff --git a/packages/ftl-cli/src/main.rs b/packages/ftl-cli/src/main.rs index 99001565..96f86b69 100644 --- a/packages/ftl-cli/src/main.rs +++ b/packages/ftl-cli/src/main.rs @@ -95,6 +95,17 @@ enum Command { path: Option, }, + /// Build and run the component, rebuilding on file changes + Watch { + /// Port to serve on + #[arg(short, long, default_value = "3000")] + port: u16, + + /// Path to component (defaults to current directory) + #[arg(long)] + path: Option, + }, + /// Run tests Test { /// Path to component (defaults to current directory) @@ -233,6 +244,7 @@ async fn main() -> Result<()> { } Command::Build { release, path } => commands::build::execute(path, release).await, Command::Up { build, port, path } => commands::up::execute(path, port, build).await, + Command::Watch { port, path } => commands::watch::execute(path, port).await, Command::Test { path } => commands::test::execute(path).await, Command::Publish { registry, diff --git a/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt index 89a39094..2188f00c 100644 --- a/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt +++ b/packages/ftl-cli/src/templates/javascript/metadata/snippets/component.txt @@ -5,7 +5,10 @@ component = "mcp-gateway-{{project-name | kebab_case}}" [component.mcp-gateway-{{project-name | kebab_case}}] # Using the published gateway component source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } -build.command = "cd {{project-name | kebab_case}} && make build" + +[component.mcp-gateway-{{project-name | kebab_case}}.build] +command = "cd {{project-name | kebab_case}} && make build" +watch = ["{{project-name | kebab_case}}/handler/src/**/*.js", "{{project-name | kebab_case}}/handler/package.json", "{{project-name | kebab_case}}/ftl.toml"] [component.mcp-gateway-{{project-name | kebab_case}}.dependencies] # Link to the local handler component diff --git a/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt index aff6d2e2..fc037817 100644 --- a/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt +++ b/packages/ftl-cli/src/templates/rust/metadata/snippets/component.txt @@ -5,7 +5,10 @@ component = "mcp-gateway-{{project-name | kebab_case}}" [component.mcp-gateway-{{project-name | kebab_case}}] # Using the published gateway component source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } -build.command = "cd {{project-name | kebab_case}} && make build" + +[component.mcp-gateway-{{project-name | kebab_case}}.build] +command = "cd {{project-name | kebab_case}} && make build" +watch = ["{{project-name | kebab_case}}/handler/src/**/*.rs", "{{project-name | kebab_case}}/handler/Cargo.toml", "{{project-name | kebab_case}}/ftl.toml"] [component.mcp-gateway-{{project-name | kebab_case}}.dependencies] # Link to the local handler component diff --git a/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt b/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt index 89a39094..3ee8c4de 100644 --- a/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt +++ b/packages/ftl-cli/src/templates/typescript/metadata/snippets/component.txt @@ -5,7 +5,10 @@ component = "mcp-gateway-{{project-name | kebab_case}}" [component.mcp-gateway-{{project-name | kebab_case}}] # Using the published gateway component source = { registry = "ghcr.io", package = "bowlofarugula:mcp-gateway", version = "0.1.0" } -build.command = "cd {{project-name | kebab_case}} && make build" + +[component.mcp-gateway-{{project-name | kebab_case}}.build] +command = "cd {{project-name | kebab_case}} && make build" +watch = ["{{project-name | kebab_case}}/handler/src/**/*.ts", "{{project-name | kebab_case}}/handler/package.json", "{{project-name | kebab_case}}/handler/tsconfig.json", "{{project-name | kebab_case}}/ftl.toml"] [component.mcp-gateway-{{project-name | kebab_case}}.dependencies] # Link to the local handler component From 08e7a3f7d90557a4044b074b9773b5d31e858653 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 02:37:36 -0700 Subject: [PATCH 07/21] fix: fmt --- packages/ftl-cli/src/commands/watch.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/ftl-cli/src/commands/watch.rs b/packages/ftl-cli/src/commands/watch.rs index d3e63ad3..91d49dd0 100644 --- a/packages/ftl-cli/src/commands/watch.rs +++ b/packages/ftl-cli/src/commands/watch.rs @@ -23,16 +23,17 @@ pub async fn execute(path: Option, port: u16) -> Result<()> { let args = vec!["watch", "--", "--listen", &listen_addr]; println!(); - println!("{} Starting development server with auto-rebuild...", style("▶").green()); + println!( + "{} Starting development server with auto-rebuild...", + style("▶").green() + ); println!(); - println!("{} Watching for file changes:", style("👁").cyan()); - println!(" • Rust components: src/**/*.rs, Cargo.toml"); - println!(" • TypeScript components: src/**/*.ts, package.json, tsconfig.json"); - println!(" • JavaScript components: src/**/*.js, package.json"); + println!("{} Watching for file changes:", style("👀").cyan()); println!(); - println!("{} Server starting at: http://{}", style("🌐").blue(), listen_addr); - println!(); - println!("{} Note: Routes will be displayed after components are built", style("ℹ").blue()); + println!( + "{} Routes will be displayed after components are built", + style("ℹ").blue() + ); println!("{} Press Ctrl+C to stop", style("⏹").yellow()); println!(); @@ -62,4 +63,4 @@ pub async fn execute(path: Option, port: u16) -> Result<()> { } Ok(()) -} \ No newline at end of file +} From 1bbf6feb2e653982a53ba3f0f2acd15df4bbc84c Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 12:09:42 -0700 Subject: [PATCH 08/21] fix: ci --- .github/workflows/check-sdk-compatibility.yml | 42 ++++++++++++++++--- packages/ftl-sdk-typescript/package.json | 1 + 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-sdk-compatibility.yml b/.github/workflows/check-sdk-compatibility.yml index 9abf22a0..1a3fba9e 100644 --- a/.github/workflows/check-sdk-compatibility.yml +++ b/.github/workflows/check-sdk-compatibility.yml @@ -42,7 +42,17 @@ jobs: - name: Test Rust template compilation run: | - cd packages/ftl-cli/src/templates/rust/content/handler + # Create a temporary directory with the template + TEMP_DIR=$(mktemp -d) + cp -r packages/ftl-cli/src/templates/rust/content/handler/* $TEMP_DIR/ + + # Replace template variables with test values + cd $TEMP_DIR + sed -i 's/{{project-name | snake_case}}/test_component/g' Cargo.toml + sed -i 's/{{project-name | kebab_case}}/test-component/g' Cargo.toml + sed -i 's/{{authors}}/Test Author/g' Cargo.toml + + # Build the component cargo build --target wasm32-wasip1 check-typescript-sdk: @@ -79,9 +89,17 @@ jobs: - name: Test TypeScript template compilation run: | - cd packages/ftl-cli/src/templates/typescript/content/handler + # Create a temporary directory with the template + TEMP_DIR=$(mktemp -d) + cp -r packages/ftl-cli/src/templates/typescript/content/handler/* $TEMP_DIR/ + + # Replace template variables with test values + cd $TEMP_DIR + sed -i 's/{{project-name | kebab_case}}/test-component/g' package.json + sed -i 's/{{project-description}}/Test component/g' package.json + # Update dependency to use local SDK - node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:../../../../../../ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" + node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:$GITHUB_WORKSPACE/packages/ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" npm install npm run build @@ -112,10 +130,24 @@ jobs: exit 1 fi + - name: Build TypeScript SDK + run: | + cd packages/ftl-sdk-typescript + npm ci + npm run build + - name: Test JavaScript template compilation run: | - cd packages/ftl-cli/src/templates/javascript/content/handler + # Create a temporary directory with the template + TEMP_DIR=$(mktemp -d) + cp -r packages/ftl-cli/src/templates/javascript/content/handler/* $TEMP_DIR/ + + # Replace template variables with test values + cd $TEMP_DIR + sed -i 's/{{project-name | kebab_case}}/test-component/g' package.json + sed -i 's/{{project-description}}/Test component/g' package.json + # Update dependency to use local SDK - node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:../../../../../../ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" + node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:$GITHUB_WORKSPACE/packages/ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" npm install npm run build \ No newline at end of file diff --git a/packages/ftl-sdk-typescript/package.json b/packages/ftl-sdk-typescript/package.json index 636971ea..045ee801 100644 --- a/packages/ftl-sdk-typescript/package.json +++ b/packages/ftl-sdk-typescript/package.json @@ -17,6 +17,7 @@ "scripts": { "build": "tsc", "clean": "rm -rf dist", + "test": "echo 'No tests configured yet'", "prepublishOnly": "npm run build" }, "devDependencies": { From 7ad00a0e381f7f2187f7fd3028f267c024f1b6ea Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 12:16:59 -0700 Subject: [PATCH 09/21] fix: ci --- .github/workflows/README.md | 8 +- .github/workflows/check-sdk-compatibility.yml | 153 ----------------- .github/workflows/check-sdk-versions.yml | 75 ++++++++ .github/workflows/test-e2e.yml | 161 ++++++++++++++++++ 4 files changed, 242 insertions(+), 155 deletions(-) delete mode 100644 .github/workflows/check-sdk-compatibility.yml create mode 100644 .github/workflows/check-sdk-versions.yml create mode 100644 .github/workflows/test-e2e.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md index b990c400..3596d0f5 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -24,10 +24,14 @@ This directory contains the CI/CD workflows for the FTL project. - Adds components with `ftl add` for all languages - Builds and tests each component type -- **[check-sdk-compatibility.yml](./check-sdk-compatibility.yml)** - Ensures SDK versions match templates +- **[test-e2e.yml](./test-e2e.yml)** - End-to-end tests for each language + - Tests full ftl workflow: init, add, build, test + - Runs for Rust, TypeScript, and JavaScript + - Ensures templates work with actual ftl commands + +- **[check-sdk-versions.yml](./check-sdk-versions.yml)** - Ensures SDK versions match templates - Verifies Rust SDK version in Rust template - Verifies TypeScript SDK version in TypeScript/JavaScript templates - - Tests template compilation with SDKs - **[check-versions.yml](./check-versions.yml)** - Checks version consistency - Ensures workspace and CLI versions match diff --git a/.github/workflows/check-sdk-compatibility.yml b/.github/workflows/check-sdk-compatibility.yml deleted file mode 100644 index 1a3fba9e..00000000 --- a/.github/workflows/check-sdk-compatibility.yml +++ /dev/null @@ -1,153 +0,0 @@ -name: Check SDK Compatibility - -on: - push: - branches: [ main ] - paths: - - 'packages/ftl-sdk-*/**' - - 'packages/ftl-cli/src/templates/**' - pull_request: - branches: [ main ] - paths: - - 'packages/ftl-sdk-*/**' - - 'packages/ftl-cli/src/templates/**' - -jobs: - check-rust-sdk: - name: Check Rust SDK Compatibility - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 - - - name: Check Rust template uses correct SDK version - run: | - SDK_VERSION=$(grep '^version' packages/ftl-sdk-rust/Cargo.toml | cut -d'"' -f2) - TEMPLATE_VERSION=$(grep 'ftl-sdk' packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml | cut -d'"' -f2) - - if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then - echo "Error: Rust SDK version ($SDK_VERSION) does not match template version ($TEMPLATE_VERSION)" - echo "Please update packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml" - exit 1 - fi - - - name: Build Rust SDK - run: | - cd packages/ftl-sdk-rust - cargo build --target wasm32-wasip1 - - - name: Test Rust template compilation - run: | - # Create a temporary directory with the template - TEMP_DIR=$(mktemp -d) - cp -r packages/ftl-cli/src/templates/rust/content/handler/* $TEMP_DIR/ - - # Replace template variables with test values - cd $TEMP_DIR - sed -i 's/{{project-name | snake_case}}/test_component/g' Cargo.toml - sed -i 's/{{project-name | kebab_case}}/test-component/g' Cargo.toml - sed -i 's/{{authors}}/Test Author/g' Cargo.toml - - # Build the component - cargo build --target wasm32-wasip1 - - check-typescript-sdk: - name: Check TypeScript SDK Compatibility - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Check TypeScript template uses correct SDK version - run: | - SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") - TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/typescript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']") - - # Remove any ^ or ~ prefix from template version - TEMPLATE_VERSION=${TEMPLATE_VERSION#^} - TEMPLATE_VERSION=${TEMPLATE_VERSION#~} - - if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then - echo "Error: TypeScript SDK version ($SDK_VERSION) does not match template version ($TEMPLATE_VERSION)" - echo "Please update packages/ftl-cli/src/templates/typescript/content/handler/package.json" - exit 1 - fi - - - name: Build TypeScript SDK - run: | - cd packages/ftl-sdk-typescript - npm ci - npm run build - - - name: Test TypeScript template compilation - run: | - # Create a temporary directory with the template - TEMP_DIR=$(mktemp -d) - cp -r packages/ftl-cli/src/templates/typescript/content/handler/* $TEMP_DIR/ - - # Replace template variables with test values - cd $TEMP_DIR - sed -i 's/{{project-name | kebab_case}}/test-component/g' package.json - sed -i 's/{{project-description}}/Test component/g' package.json - - # Update dependency to use local SDK - node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:$GITHUB_WORKSPACE/packages/ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" - npm install - npm run build - - check-javascript-sdk: - name: Check JavaScript SDK Compatibility - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Check JavaScript template uses TypeScript SDK - run: | - # JavaScript template should use the TypeScript SDK - SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") - TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/javascript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']") - - # Remove any ^ or ~ prefix from template version - TEMPLATE_VERSION=${TEMPLATE_VERSION#^} - TEMPLATE_VERSION=${TEMPLATE_VERSION#~} - - if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then - echo "Error: TypeScript SDK version ($SDK_VERSION) does not match JavaScript template version ($TEMPLATE_VERSION)" - echo "Please update packages/ftl-cli/src/templates/javascript/content/handler/package.json" - exit 1 - fi - - - name: Build TypeScript SDK - run: | - cd packages/ftl-sdk-typescript - npm ci - npm run build - - - name: Test JavaScript template compilation - run: | - # Create a temporary directory with the template - TEMP_DIR=$(mktemp -d) - cp -r packages/ftl-cli/src/templates/javascript/content/handler/* $TEMP_DIR/ - - # Replace template variables with test values - cd $TEMP_DIR - sed -i 's/{{project-name | kebab_case}}/test-component/g' package.json - sed -i 's/{{project-description}}/Test component/g' package.json - - # Update dependency to use local SDK - node -e "const pkg = require('./package.json'); pkg.dependencies['@fastertools/ftl-sdk'] = 'file:$GITHUB_WORKSPACE/packages/ftl-sdk-typescript'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" - npm install - npm run build \ No newline at end of file diff --git a/.github/workflows/check-sdk-versions.yml b/.github/workflows/check-sdk-versions.yml new file mode 100644 index 00000000..dcf06946 --- /dev/null +++ b/.github/workflows/check-sdk-versions.yml @@ -0,0 +1,75 @@ +name: Check SDK Versions + +on: + push: + branches: [ main ] + paths: + - 'packages/ftl-sdk-*/**' + - 'packages/ftl-cli/src/templates/**' + pull_request: + branches: [ main ] + paths: + - 'packages/ftl-sdk-*/**' + - 'packages/ftl-cli/src/templates/**' + +jobs: + check-versions: + name: Check SDK Version Consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Check Rust SDK version in template + run: | + SDK_VERSION=$(grep '^version' packages/ftl-sdk-rust/Cargo.toml | cut -d'"' -f2) + TEMPLATE_VERSION=$(grep 'ftl-sdk' packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml | cut -d'"' -f2) + + echo "Rust SDK version: $SDK_VERSION" + echo "Rust template SDK version: $TEMPLATE_VERSION" + + if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then + echo "Error: Rust SDK version ($SDK_VERSION) does not match template version ($TEMPLATE_VERSION)" + echo "Please update packages/ftl-cli/src/templates/rust/content/handler/Cargo.toml" + exit 1 + fi + + - name: Check TypeScript SDK version in TypeScript template + run: | + SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") + TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/typescript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']") + + # Remove any ^ or ~ prefix from template version + TEMPLATE_VERSION=${TEMPLATE_VERSION#^} + TEMPLATE_VERSION=${TEMPLATE_VERSION#~} + + echo "TypeScript SDK version: $SDK_VERSION" + echo "TypeScript template SDK version: $TEMPLATE_VERSION" + + if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then + echo "Error: TypeScript SDK version ($SDK_VERSION) does not match template version ($TEMPLATE_VERSION)" + echo "Please update packages/ftl-cli/src/templates/typescript/content/handler/package.json" + exit 1 + fi + + - name: Check TypeScript SDK version in JavaScript template + run: | + SDK_VERSION=$(node -p "require('./packages/ftl-sdk-typescript/package.json').version") + TEMPLATE_VERSION=$(node -p "require('./packages/ftl-cli/src/templates/javascript/content/handler/package.json').dependencies['@fastertools/ftl-sdk']") + + # Remove any ^ or ~ prefix from template version + TEMPLATE_VERSION=${TEMPLATE_VERSION#^} + TEMPLATE_VERSION=${TEMPLATE_VERSION#~} + + echo "TypeScript SDK version: $SDK_VERSION" + echo "JavaScript template SDK version: $TEMPLATE_VERSION" + + if [ "$SDK_VERSION" != "$TEMPLATE_VERSION" ]; then + echo "Error: TypeScript SDK version ($SDK_VERSION) does not match JavaScript template version ($TEMPLATE_VERSION)" + echo "Please update packages/ftl-cli/src/templates/javascript/content/handler/package.json" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml new file mode 100644 index 00000000..ef5f10b2 --- /dev/null +++ b/.github/workflows/test-e2e.yml @@ -0,0 +1,161 @@ +name: E2E Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test-rust-e2e: + name: E2E Test - Rust + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + + - name: Build ftl CLI + run: | + cd packages/ftl-cli + cargo build --release + + - name: Install Spin + run: | + wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz + tar -xzf spin-v2.8.0-linux-amd64.tar.gz + sudo mv spin /usr/local/bin/ + + - name: Setup ftl templates + run: | + cd packages/ftl-cli + ./target/release/ftl setup templates --force + + - name: Test Rust workflow + run: | + # Create a test project + WORK_DIR=$(mktemp -d) + cd $WORK_DIR + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + cd test-project + + # Add a Rust component + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-rust --language rust --description "Test Rust component" + + # Build the component + cd test-rust + make build + + # Run tests + make test + + # Build at project level + cd .. + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build + + test-typescript-e2e: + name: E2E Test - TypeScript + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Build ftl CLI + run: | + cd packages/ftl-cli + cargo build --release + + - name: Install Spin + run: | + wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz + tar -xzf spin-v2.8.0-linux-amd64.tar.gz + sudo mv spin /usr/local/bin/ + + - name: Setup ftl templates + run: | + cd packages/ftl-cli + ./target/release/ftl setup templates --force + + - name: Test TypeScript workflow + run: | + # Create a test project + WORK_DIR=$(mktemp -d) + cd $WORK_DIR + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + cd test-project + + # Add a TypeScript component + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-ts --language typescript --description "Test TypeScript component" + + # Build the component + cd test-ts + make build + + # Run tests + make test + + # Build at project level + cd .. + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build + + test-javascript-e2e: + name: E2E Test - JavaScript + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Build ftl CLI + run: | + cd packages/ftl-cli + cargo build --release + + - name: Install Spin + run: | + wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz + tar -xzf spin-v2.8.0-linux-amd64.tar.gz + sudo mv spin /usr/local/bin/ + + - name: Setup ftl templates + run: | + cd packages/ftl-cli + ./target/release/ftl setup templates --force + + - name: Test JavaScript workflow + run: | + # Create a test project + WORK_DIR=$(mktemp -d) + cd $WORK_DIR + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + cd test-project + + # Add a JavaScript component + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-js --language javascript --description "Test JavaScript component" + + # Build the component + cd test-js + make build + + # Run tests + make test + + # Build at project level + cd .. + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build \ No newline at end of file From 7f8ef401174abb3e60e46c1dc61e55ea3119b685 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 12:32:42 -0700 Subject: [PATCH 10/21] fix: ci --- .github/workflows/test-e2e.yml | 27 ++++++++++++--------------- .github/workflows/test-templates.yml | 10 ++++------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index ef5f10b2..21ae6a94 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -23,11 +23,10 @@ jobs: cd packages/ftl-cli cargo build --release - - name: Install Spin - run: | - wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz - tar -xzf spin-v2.8.0-linux-amd64.tar.gz - sudo mv spin /usr/local/bin/ + - name: Setup Spin + uses: fermyon/actions/spin/setup@v1 + with: + version: v2.8.0 - name: Setup ftl templates run: | @@ -75,11 +74,10 @@ jobs: cd packages/ftl-cli cargo build --release - - name: Install Spin - run: | - wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz - tar -xzf spin-v2.8.0-linux-amd64.tar.gz - sudo mv spin /usr/local/bin/ + - name: Setup Spin + uses: fermyon/actions/spin/setup@v1 + with: + version: v2.8.0 - name: Setup ftl templates run: | @@ -127,11 +125,10 @@ jobs: cd packages/ftl-cli cargo build --release - - name: Install Spin - run: | - wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz - tar -xzf spin-v2.8.0-linux-amd64.tar.gz - sudo mv spin /usr/local/bin/ + - name: Setup Spin + uses: fermyon/actions/spin/setup@v1 + with: + version: v2.8.0 - name: Setup ftl templates run: | diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml index a3f81594..51289b4c 100644 --- a/.github/workflows/test-templates.yml +++ b/.github/workflows/test-templates.yml @@ -28,12 +28,10 @@ jobs: cd packages/ftl-cli cargo build --release - - name: Install Spin - run: | - wget -q https://github.com/fermyon/spin/releases/download/v2.8.0/spin-v2.8.0-linux-amd64.tar.gz - tar -xzf spin-v2.8.0-linux-amd64.tar.gz - sudo mv spin /usr/local/bin/ - spin --version + - name: Setup Spin + uses: fermyon/actions/spin/setup@v1 + with: + version: v2.8.0 - name: Install FTL templates run: | From 416e2c0f83c33421c4cdef4f7904cab270ed0c7d Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 12:50:07 -0700 Subject: [PATCH 11/21] fix: ci --- .github/workflows/test-e2e.yml | 6 +++--- .github/workflows/test-templates.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 21ae6a94..f5710f7e 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Spin uses: fermyon/actions/spin/setup@v1 with: - version: v2.8.0 + version: v3.3.1 - name: Setup ftl templates run: | @@ -77,7 +77,7 @@ jobs: - name: Setup Spin uses: fermyon/actions/spin/setup@v1 with: - version: v2.8.0 + version: v3.3.1 - name: Setup ftl templates run: | @@ -128,7 +128,7 @@ jobs: - name: Setup Spin uses: fermyon/actions/spin/setup@v1 with: - version: v2.8.0 + version: v3.3.1 - name: Setup ftl templates run: | diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml index 51289b4c..bd607218 100644 --- a/.github/workflows/test-templates.yml +++ b/.github/workflows/test-templates.yml @@ -31,7 +31,7 @@ jobs: - name: Setup Spin uses: fermyon/actions/spin/setup@v1 with: - version: v2.8.0 + version: v3.3.1 - name: Install FTL templates run: | From e7921c68d2b5dc4e64295b3f2a334c2d39f7d135 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 12:54:04 -0700 Subject: [PATCH 12/21] fix: ci --- .github/workflows/test-e2e.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f5710f7e..fb848352 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -30,8 +30,7 @@ jobs: - name: Setup ftl templates run: | - cd packages/ftl-cli - ./target/release/ftl setup templates --force + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl setup templates --force - name: Test Rust workflow run: | @@ -81,8 +80,7 @@ jobs: - name: Setup ftl templates run: | - cd packages/ftl-cli - ./target/release/ftl setup templates --force + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl setup templates --force - name: Test TypeScript workflow run: | @@ -132,8 +130,7 @@ jobs: - name: Setup ftl templates run: | - cd packages/ftl-cli - ./target/release/ftl setup templates --force + $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl setup templates --force - name: Test JavaScript workflow run: | From 7fd63b934748d44833ec82845f757bcfe882708f Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 12:58:37 -0700 Subject: [PATCH 13/21] fix: ci --- .github/workflows/test-e2e.yml | 39 +++++++++++++--------------- .github/workflows/test-templates.yml | 17 ++++++------ 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index fb848352..f9b6e7f9 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -18,10 +18,9 @@ jobs: with: targets: wasm32-wasip1 - - name: Build ftl CLI + - name: Install ftl CLI run: | - cd packages/ftl-cli - cargo build --release + cargo install --path packages/ftl-cli - name: Setup Spin uses: fermyon/actions/spin/setup@v1 @@ -30,18 +29,18 @@ jobs: - name: Setup ftl templates run: | - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl setup templates --force + ftl setup templates --force - name: Test Rust workflow run: | # Create a test project WORK_DIR=$(mktemp -d) cd $WORK_DIR - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + ftl init test-project cd test-project # Add a Rust component - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-rust --language rust --description "Test Rust component" + ftl add test-rust --language rust --description "Test Rust component" # Build the component cd test-rust @@ -52,7 +51,7 @@ jobs: # Build at project level cd .. - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build + ftl build test-typescript-e2e: name: E2E Test - TypeScript @@ -68,10 +67,9 @@ jobs: with: node-version: '20' - - name: Build ftl CLI + - name: Install ftl CLI run: | - cd packages/ftl-cli - cargo build --release + cargo install --path packages/ftl-cli - name: Setup Spin uses: fermyon/actions/spin/setup@v1 @@ -80,18 +78,18 @@ jobs: - name: Setup ftl templates run: | - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl setup templates --force + ftl setup templates --force - name: Test TypeScript workflow run: | # Create a test project WORK_DIR=$(mktemp -d) cd $WORK_DIR - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + ftl init test-project cd test-project # Add a TypeScript component - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-ts --language typescript --description "Test TypeScript component" + ftl add test-ts --language typescript --description "Test TypeScript component" # Build the component cd test-ts @@ -102,7 +100,7 @@ jobs: # Build at project level cd .. - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build + ftl build test-javascript-e2e: name: E2E Test - JavaScript @@ -118,10 +116,9 @@ jobs: with: node-version: '20' - - name: Build ftl CLI + - name: Install ftl CLI run: | - cd packages/ftl-cli - cargo build --release + cargo install --path packages/ftl-cli - name: Setup Spin uses: fermyon/actions/spin/setup@v1 @@ -130,18 +127,18 @@ jobs: - name: Setup ftl templates run: | - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl setup templates --force + ftl setup templates --force - name: Test JavaScript workflow run: | # Create a test project WORK_DIR=$(mktemp -d) cd $WORK_DIR - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + ftl init test-project cd test-project # Add a JavaScript component - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-js --language javascript --description "Test JavaScript component" + ftl add test-js --language javascript --description "Test JavaScript component" # Build the component cd test-js @@ -152,4 +149,4 @@ jobs: # Build at project level cd .. - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build \ No newline at end of file + ftl build \ No newline at end of file diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml index bd607218..e148029d 100644 --- a/.github/workflows/test-templates.yml +++ b/.github/workflows/test-templates.yml @@ -23,10 +23,9 @@ jobs: with: node-version: '20' - - name: Build ftl CLI + - name: Install ftl CLI run: | - cd packages/ftl-cli - cargo build --release + cargo install --path packages/ftl-cli - name: Setup Spin uses: fermyon/actions/spin/setup@v1 @@ -40,7 +39,7 @@ jobs: - name: Test ftl init run: | cd /tmp - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl init test-project + ftl init test-project cd test-project test -f spin.toml test -f .gitignore @@ -48,7 +47,7 @@ jobs: - name: Test ftl add with Rust template run: | cd /tmp/test-project - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-rust --language rust --description "Test Rust component" + ftl add test-rust --language rust --description "Test Rust component" test -d test-rust test -f test-rust/ftl.toml test -f test-rust/handler/Cargo.toml @@ -67,7 +66,7 @@ jobs: - name: Test ftl add with TypeScript template run: | cd /tmp/test-project - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-ts --language typescript --description "Test TypeScript component" + ftl add test-ts --language typescript --description "Test TypeScript component" test -d test-ts test -f test-ts/ftl.toml test -f test-ts/handler/package.json @@ -86,7 +85,7 @@ jobs: - name: Test ftl add with JavaScript template run: | cd /tmp/test-project - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl add test-js --language javascript --description "Test JavaScript component" + ftl add test-js --language javascript --description "Test JavaScript component" test -d test-js test -f test-js/ftl.toml test -f test-js/handler/package.json @@ -105,7 +104,7 @@ jobs: - name: Test ftl build at project level run: | cd /tmp/test-project - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl build + ftl build - name: Test project structure run: | @@ -121,4 +120,4 @@ jobs: - name: Test ftl watch command exists run: | - $GITHUB_WORKSPACE/packages/ftl-cli/target/release/ftl watch --help \ No newline at end of file + ftl watch --help \ No newline at end of file From 0bd08c291529c623e73cce372ff64503058ce4db Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 13:12:32 -0700 Subject: [PATCH 14/21] fix: ci --- Cargo.lock | 79 ++++++++++++------- packages/ftl-cli/Cargo.toml | 1 + packages/ftl-cli/src/common/spin_installer.rs | 68 +++++++++++++--- 3 files changed, 108 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55d6ea39..3acf1dbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,6 +108,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -221,9 +232,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.27" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -305,19 +316,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "console" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys 0.60.2", -] - [[package]] name = "constant_time_eq" version = "0.3.1" @@ -496,7 +494,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ - "console 0.15.11", + "console", "shell-words", "tempfile", "thiserror 1.0.69", @@ -655,9 +653,10 @@ name = "ftl-cli" version = "0.0.18" dependencies = [ "anyhow", + "atty", "cargo_metadata", "clap", - "console 0.15.11", + "console", "dialoguer", "dirs", "flate2", @@ -831,6 +830,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1111,14 +1119,14 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.12" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adb2ee6ad319a912210a36e56e3623555817bcc877a7e6e8802d1d69c4d8056" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "console 0.16.0", + "console", + "number_prefix", "portable-atomic", "unicode-width", - "unit-prefix", "web-time", ] @@ -1151,6 +1159,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1407,6 +1426,12 @@ dependencies = [ "num-modular", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.36.7" @@ -2162,16 +2187,18 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio 1.0.4", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -2398,12 +2425,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" -[[package]] -name = "unit-prefix" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" - [[package]] name = "untrusted" version = "0.9.0" diff --git a/packages/ftl-cli/Cargo.toml b/packages/ftl-cli/Cargo.toml index 6d609f72..e1008deb 100644 --- a/packages/ftl-cli/Cargo.toml +++ b/packages/ftl-cli/Cargo.toml @@ -43,6 +43,7 @@ indicatif = "0.17" dialoguer = "0.11" console = "0.15" semver = "1.0" +atty = "0.2" futures-util = "0.3" flate2 = "1.0" tar = "0.4" diff --git a/packages/ftl-cli/src/common/spin_installer.rs b/packages/ftl-cli/src/common/spin_installer.rs index f4a4e351..cc0410e5 100644 --- a/packages/ftl-cli/src/common/spin_installer.rs +++ b/packages/ftl-cli/src/common/spin_installer.rs @@ -46,8 +46,17 @@ pub async fn check_and_install_spin() -> Result { // If no FTL-managed version, check if spin is available in PATH if let Ok(system_spin_path) = which::which("spin") { debug!("Found system Spin in PATH at: {:?}", system_spin_path); - // Even if system spin exists, we should install our own version for consistency - info!("System Spin found, but FTL will install its own version for version consistency"); + + // Check if system spin version is compatible + if let Ok(version) = get_spin_version(&system_spin_path) { + if is_version_compatible(&version, SPIN_REQUIRED_VERSION)? { + debug!("System Spin version {} is compatible with required version {}", version, SPIN_REQUIRED_VERSION); + ensure_akamai_plugin(&system_spin_path)?; + return Ok(system_spin_path); + } else { + info!("System Spin version {} is older than required version {}", version, SPIN_REQUIRED_VERSION); + } + } } // Need to install @@ -59,17 +68,23 @@ pub async fn check_and_install_spin() -> Result { if which::which("spin").is_ok() { eprintln!(); - eprintln!("Note: System Spin detected, but FTL will install its own version"); - eprintln!("to ensure compatibility. Your system installation won't be affected."); + eprintln!("Note: System Spin detected, but it's not compatible with FTL requirements."); + eprintln!("FTL will install its own version. Your system installation won't be affected."); } - let should_install = Confirm::new() - .with_prompt("Would you like to install Spin now?") - .default(true) - .interact()?; - - if !should_install { - anyhow::bail!("Spin installation is required to continue"); + // Check if we're in a terminal + if atty::is(atty::Stream::Stdin) { + let should_install = Confirm::new() + .with_prompt("Would you like to install Spin now?") + .default(true) + .interact()?; + + if !should_install { + anyhow::bail!("Spin installation is required to continue"); + } + } else { + // Non-interactive mode (CI, scripts, etc) + eprintln!("Running in non-interactive mode, proceeding with installation..."); } } @@ -326,3 +341,34 @@ fn ensure_akamai_plugin(spin_path: &PathBuf) -> Result<()> { Ok(()) } + +fn get_spin_version(spin_path: &PathBuf) -> Result { + let output = Command::new(spin_path) + .arg("--version") + .output() + .context("Failed to get spin version")?; + + if !output.status.success() { + anyhow::bail!("Failed to get spin version"); + } + + let version_str = String::from_utf8_lossy(&output.stdout); + // Parse version from output like "spin 3.3.1 (6fd46d4 2025-06-17)" + if let Some(version) = version_str.split_whitespace().nth(1) { + Ok(version.to_string()) + } else { + anyhow::bail!("Could not parse spin version from: {}", version_str) + } +} + +fn is_version_compatible(actual: &str, required: &str) -> Result { + use semver::Version; + + let actual_version = Version::parse(actual) + .with_context(|| format!("Failed to parse actual version: {}", actual))?; + let required_version = Version::parse(required) + .with_context(|| format!("Failed to parse required version: {}", required))?; + + // Check if actual version is >= required version + Ok(actual_version >= required_version) +} From 9e7525441bbf4fbe4052fd7dac6d67eaa893bc67 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 13:17:41 -0700 Subject: [PATCH 15/21] fix: ci --- .github/workflows/test-e2e.yml | 12 ++++++++ .github/workflows/test-templates.yml | 6 +++- packages/ftl-cli/src/common/spin_installer.rs | 28 ++++++++++++------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f9b6e7f9..f64036a6 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -27,6 +27,10 @@ jobs: with: version: v3.3.1 + - name: Install Spin templates + run: | + spin templates install --git https://github.com/fermyon/spin --upgrade + - name: Setup ftl templates run: | ftl setup templates --force @@ -76,6 +80,10 @@ jobs: with: version: v3.3.1 + - name: Install Spin templates + run: | + spin templates install --git https://github.com/fermyon/spin --upgrade + - name: Setup ftl templates run: | ftl setup templates --force @@ -125,6 +133,10 @@ jobs: with: version: v3.3.1 + - name: Install Spin templates + run: | + spin templates install --git https://github.com/fermyon/spin --upgrade + - name: Setup ftl templates run: | ftl setup templates --force diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml index e148029d..1964353f 100644 --- a/.github/workflows/test-templates.yml +++ b/.github/workflows/test-templates.yml @@ -32,9 +32,13 @@ jobs: with: version: v3.3.1 + - name: Install Spin templates + run: | + spin templates install --git https://github.com/fermyon/spin --upgrade + - name: Install FTL templates run: | - spin templates install --dir packages/ftl-cli/src/templates --upgrade + spin templates install --dir packages/ftl-cli/src --upgrade - name: Test ftl init run: | diff --git a/packages/ftl-cli/src/common/spin_installer.rs b/packages/ftl-cli/src/common/spin_installer.rs index cc0410e5..a8ced538 100644 --- a/packages/ftl-cli/src/common/spin_installer.rs +++ b/packages/ftl-cli/src/common/spin_installer.rs @@ -46,15 +46,21 @@ pub async fn check_and_install_spin() -> Result { // If no FTL-managed version, check if spin is available in PATH if let Ok(system_spin_path) = which::which("spin") { debug!("Found system Spin in PATH at: {:?}", system_spin_path); - + // Check if system spin version is compatible if let Ok(version) = get_spin_version(&system_spin_path) { if is_version_compatible(&version, SPIN_REQUIRED_VERSION)? { - debug!("System Spin version {} is compatible with required version {}", version, SPIN_REQUIRED_VERSION); + debug!( + "System Spin version {} is compatible with required version {}", + version, SPIN_REQUIRED_VERSION + ); ensure_akamai_plugin(&system_spin_path)?; return Ok(system_spin_path); } else { - info!("System Spin version {} is older than required version {}", version, SPIN_REQUIRED_VERSION); + info!( + "System Spin version {} is older than required version {}", + version, SPIN_REQUIRED_VERSION + ); } } } @@ -69,7 +75,9 @@ pub async fn check_and_install_spin() -> Result { if which::which("spin").is_ok() { eprintln!(); eprintln!("Note: System Spin detected, but it's not compatible with FTL requirements."); - eprintln!("FTL will install its own version. Your system installation won't be affected."); + eprintln!( + "FTL will install its own version. Your system installation won't be affected." + ); } // Check if we're in a terminal @@ -347,11 +355,11 @@ fn get_spin_version(spin_path: &PathBuf) -> Result { .arg("--version") .output() .context("Failed to get spin version")?; - + if !output.status.success() { anyhow::bail!("Failed to get spin version"); } - + let version_str = String::from_utf8_lossy(&output.stdout); // Parse version from output like "spin 3.3.1 (6fd46d4 2025-06-17)" if let Some(version) = version_str.split_whitespace().nth(1) { @@ -363,12 +371,12 @@ fn get_spin_version(spin_path: &PathBuf) -> Result { fn is_version_compatible(actual: &str, required: &str) -> Result { use semver::Version; - + let actual_version = Version::parse(actual) - .with_context(|| format!("Failed to parse actual version: {}", actual))?; + .with_context(|| format!("Failed to parse actual version: {actual}"))?; let required_version = Version::parse(required) - .with_context(|| format!("Failed to parse required version: {}", required))?; - + .with_context(|| format!("Failed to parse required version: {required}"))?; + // Check if actual version is >= required version Ok(actual_version >= required_version) } From f3ebf0eca4f224ac9e0ce12454edace293179b59 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 13:26:51 -0700 Subject: [PATCH 16/21] fix: ci --- .github/workflows/test-e2e.yml | 6 +-- packages/ftl-cli/src/commands/add.rs | 64 +++++++++------------------- 2 files changed, 22 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index f64036a6..2af38c60 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -44,7 +44,7 @@ jobs: cd test-project # Add a Rust component - ftl add test-rust --language rust --description "Test Rust component" + ftl add test-rust --language rust --description "Test Rust component" --route /test-rust/mcp # Build the component cd test-rust @@ -97,7 +97,7 @@ jobs: cd test-project # Add a TypeScript component - ftl add test-ts --language typescript --description "Test TypeScript component" + ftl add test-ts --language typescript --description "Test TypeScript component" --route /test-ts/mcp # Build the component cd test-ts @@ -150,7 +150,7 @@ jobs: cd test-project # Add a JavaScript component - ftl add test-js --language javascript --description "Test JavaScript component" + ftl add test-js --language javascript --description "Test JavaScript component" --route /test-js/mcp # Build the component cd test-js diff --git a/packages/ftl-cli/src/commands/add.rs b/packages/ftl-cli/src/commands/add.rs index e066f105..3bf2ab3d 100644 --- a/packages/ftl-cli/src/commands/add.rs +++ b/packages/ftl-cli/src/commands/add.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::process::Command; use anyhow::{Context, Result}; +use atty; use console::style; use dialoguer::{Input, Select, theme::ColorfulTheme}; @@ -108,10 +109,17 @@ pub async fn execute(options: AddOptions) -> Result<()> { // Convert component name to kebab-case for the route let kebab_name = component_name.replace('_', "-").to_lowercase(); let default_route = format!("/{kebab_name}/mcp"); - Input::::with_theme(&ColorfulTheme::default()) - .with_prompt("HTTP route") - .default(default_route) - .interact_text()? + + // Check if we're in a terminal + if atty::is(atty::Stream::Stdin) { + Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("HTTP route") + .default(default_route) + .interact_text()? + } else { + // Non-interactive mode - use default + default_route + } } }; @@ -164,47 +172,13 @@ pub async fn execute(options: AddOptions) -> Result<()> { if !using_custom_template && (stderr.contains("no such template") || stderr.contains("template not found")) { - println!(); - println!( - "{} FTL templates not found. Installing...", - style("→").yellow() - ); - - // Install templates - let template_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); - - let install_output = Command::new(&spin_path) - .args([ - "templates", - "install", - "--dir", - template_dir.to_str().unwrap(), - "--upgrade", - ]) - .output() - .context("Failed to install templates")?; - - if !install_output.status.success() { - anyhow::bail!( - "Failed to install templates:\n{}", - String::from_utf8_lossy(&install_output.stderr) - ); - } - - println!("{} Templates installed successfully!", style("✓").green()); - println!(); - - // Retry spin add - let retry_output = spin_cmd - .output() - .context("Failed to run spin add after template installation")?; - - if !retry_output.status.success() { - anyhow::bail!( - "Failed to add component:\n{}", - String::from_utf8_lossy(&retry_output.stderr) - ); - } + eprintln!(); + eprintln!("{} FTL templates not found.", style("✗").red()); + eprintln!(); + eprintln!("Please install the FTL templates by running:"); + eprintln!(" ftl setup templates"); + eprintln!(); + anyhow::bail!("FTL templates not installed"); } else { anyhow::bail!("Failed to add component:\n{}", stderr); } From 1c62246a4bddead99610910a2f42856bdf98d1c2 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 13:49:50 -0700 Subject: [PATCH 17/21] fix: ci --- docs/cli-reference.md | 1 + packages/ftl-cli/src/commands/build.rs | 27 ++++++++++++++++++++++++++ packages/ftl-cli/src/commands/setup.rs | 14 +++++++++++++ 3 files changed, 42 insertions(+) diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 0ff21aaf..113073f6 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -231,6 +231,7 @@ Displays: - FTL CLI version - Spin installation status - Template installation status +- cargo-component installation status - wkg availability ## Registry Commands diff --git a/packages/ftl-cli/src/commands/build.rs b/packages/ftl-cli/src/commands/build.rs index 489d583d..000801db 100644 --- a/packages/ftl-cli/src/commands/build.rs +++ b/packages/ftl-cli/src/commands/build.rs @@ -73,6 +73,33 @@ pub async fn execute(path: Option, release: bool) -> Result<()> { } else if component_path.join("handler/Cargo.toml").exists() { // Rust component let profile = if release { "release" } else { "debug" }; + + // Check if cargo-component is installed + let cargo_component_check = Command::new("cargo") + .args(["component", "--version"]) + .output(); + + if cargo_component_check.is_err() || !cargo_component_check.unwrap().status.success() { + println!("{} cargo-component not found, installing...", style("→").dim()); + println!(" This is required for building Rust WebAssembly components"); + println!(" This may take a few minutes on first install"); + + let install_status = Command::new("cargo") + .args(["install", "cargo-component"]) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .context("Failed to install cargo-component")? + .wait()?; + + if !install_status.success() { + anyhow::bail!("Failed to install cargo-component"); + } + + println!("{} cargo-component installed successfully!", style("✓").green()); + } + Command::new("cargo") .args(["component", "build", "--target", "wasm32-wasip1"]) .arg(format!("--{profile}")) diff --git a/packages/ftl-cli/src/commands/setup.rs b/packages/ftl-cli/src/commands/setup.rs index 683743b2..131f8fdc 100644 --- a/packages/ftl-cli/src/commands/setup.rs +++ b/packages/ftl-cli/src/commands/setup.rs @@ -167,6 +167,20 @@ pub async fn info() -> Result<()> { } println!(); + // Check for cargo-component + match Command::new("cargo").args(["component", "--version"]).output() { + Ok(output) => { + let version = String::from_utf8_lossy(&output.stdout); + println!("cargo-component: {} {}", style("✓").green(), version.trim()); + } + Err(_) => { + println!("cargo-component: {} Not installed", style("✗").red()); + println!(" Required for building Rust components"); + println!(" Will be installed automatically when building Rust components"); + } + } + println!(); + // Check for wkg match Command::new("wkg").arg("--version").output() { Ok(output) => { From 252786fdd27f5a4eb355a990e6b07d5e557f397c Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 13:58:48 -0700 Subject: [PATCH 18/21] fix: ci --- .github/workflows/test-e2e.yml | 8 +-- packages/ftl-cli/src/commands/build.rs | 27 ---------- packages/ftl-cli/src/commands/setup.rs | 5 +- .../content/handler/test/handler.test.js | 46 ++++++++-------- .../src/templates/rust/content/Makefile | 14 ++++- .../content/handler/test/handler.test.ts | 52 ++++++++----------- 6 files changed, 62 insertions(+), 90 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 2af38c60..b71c6906 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -1,4 +1,4 @@ -name: E2E Tests +name: CLI Tests on: push: @@ -8,7 +8,7 @@ on: jobs: test-rust-e2e: - name: E2E Test - Rust + name: CLI Test - Rust runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -58,7 +58,7 @@ jobs: ftl build test-typescript-e2e: - name: E2E Test - TypeScript + name: CLI Test - TypeScript runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -111,7 +111,7 @@ jobs: ftl build test-javascript-e2e: - name: E2E Test - JavaScript + name: CLI Test - JavaScript runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/packages/ftl-cli/src/commands/build.rs b/packages/ftl-cli/src/commands/build.rs index 000801db..489d583d 100644 --- a/packages/ftl-cli/src/commands/build.rs +++ b/packages/ftl-cli/src/commands/build.rs @@ -73,33 +73,6 @@ pub async fn execute(path: Option, release: bool) -> Result<()> { } else if component_path.join("handler/Cargo.toml").exists() { // Rust component let profile = if release { "release" } else { "debug" }; - - // Check if cargo-component is installed - let cargo_component_check = Command::new("cargo") - .args(["component", "--version"]) - .output(); - - if cargo_component_check.is_err() || !cargo_component_check.unwrap().status.success() { - println!("{} cargo-component not found, installing...", style("→").dim()); - println!(" This is required for building Rust WebAssembly components"); - println!(" This may take a few minutes on first install"); - - let install_status = Command::new("cargo") - .args(["install", "cargo-component"]) - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn() - .context("Failed to install cargo-component")? - .wait()?; - - if !install_status.success() { - anyhow::bail!("Failed to install cargo-component"); - } - - println!("{} cargo-component installed successfully!", style("✓").green()); - } - Command::new("cargo") .args(["component", "build", "--target", "wasm32-wasip1"]) .arg(format!("--{profile}")) diff --git a/packages/ftl-cli/src/commands/setup.rs b/packages/ftl-cli/src/commands/setup.rs index 131f8fdc..e3980005 100644 --- a/packages/ftl-cli/src/commands/setup.rs +++ b/packages/ftl-cli/src/commands/setup.rs @@ -168,7 +168,10 @@ pub async fn info() -> Result<()> { println!(); // Check for cargo-component - match Command::new("cargo").args(["component", "--version"]).output() { + match Command::new("cargo") + .args(["component", "--version"]) + .output() + { Ok(output) => { let version = String::from_utf8_lossy(&output.stdout); println!("cargo-component: {} {}", style("✓").green(), version.trim()); diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js b/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js index 5a15fb1e..23db8ae7 100644 --- a/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js +++ b/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js @@ -1,38 +1,34 @@ import { describe, it, expect } from 'vitest'; -import { Handler } from '../src/index.js'; +import { tools } from '../src/features.js'; -describe('Handler', () => { - describe('listTools', () => { - it('should return tool metadata', () => { - const tools = Handler.listTools(); - expect(tools).toHaveLength(1); - expect(tools[0].name).toBe('{{project-name | snake_case}}'); - expect(tools[0].description).toBe('{{project-description}}'); +describe('{{project-name}} MCP Handler', () => { + describe('Tools', () => { + it('should export at least one tool', () => { + expect(tools).toBeDefined(); + expect(Object.keys(tools).length).toBeGreaterThan(0); }); - }); - describe('callTool', () => { - it('should process input correctly', () => { - const args = JSON.stringify({ input: 'test input' }); - const result = Handler.callTool('{{project-name | snake_case}}', args); - - expect(result.tag).toBe('text'); - expect(result.val).toContain('test input'); + it('should have the {{project-name | snake_case}} tool', () => { + expect(tools['{{project-name | snake_case}}']).toBeDefined(); + expect(tools['{{project-name | snake_case}}'].description).toBe('{{project-description}}'); }); - it('should handle invalid JSON', () => { - const result = Handler.callTool('{{project-name | snake_case}}', 'invalid json'); + it('should handle valid input for {{project-name | snake_case}} tool', async () => { + const tool = tools['{{project-name | snake_case}}']; + expect(tool.handler).toBeDefined(); - expect(result.tag).toBe('error'); - expect(result.val.code).toBe(-32602); + const result = await tool.handler({ input: 'test input' }); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('test input'); }); - it('should handle unknown tool', () => { - const args = JSON.stringify({ input: 'test' }); - const result = Handler.callTool('unknown_tool', args); + it('should handle missing input gracefully', async () => { + const tool = tools['{{project-name | snake_case}}']; - expect(result.tag).toBe('error'); - expect(result.val.code).toBe(-32601); + const result = await tool.handler({}); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); }); }); }); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/Makefile b/packages/ftl-cli/src/templates/rust/content/Makefile index 58d60285..edc4f220 100644 --- a/packages/ftl-cli/src/templates/rust/content/Makefile +++ b/packages/ftl-cli/src/templates/rust/content/Makefile @@ -1,6 +1,16 @@ -.PHONY: build test clean registry-push +.PHONY: build test clean registry-push check-cargo-component -build: +# Check if cargo-component is installed, install if not +check-cargo-component: + @command -v cargo-component >/dev/null 2>&1 || { \ + echo "cargo-component not found, installing..."; \ + echo "This is required for building Rust WebAssembly components"; \ + echo "This may take a few minutes on first install"; \ + cargo install cargo-component; \ + echo "cargo-component installed successfully!"; \ + } + +build: check-cargo-component cd handler && cargo component build --release --target wasm32-wasip1 test: diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts b/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts index c9b1b765..ec074971 100644 --- a/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts +++ b/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts @@ -1,44 +1,34 @@ import { describe, it, expect } from 'vitest'; -import { Handler } from '../src/index'; +import { tools } from '../src/features'; -describe('Handler', () => { - describe('listTools', () => { - it('should return tool metadata', () => { - const tools = Handler.listTools(); - expect(tools).toHaveLength(1); - expect(tools[0].name).toBe('{{project-name | snake_case}}'); - expect(tools[0].description).toBe('{{project-description}}'); +describe('{{project-name}} MCP Handler', () => { + describe('Tools', () => { + it('should export at least one tool', () => { + expect(tools).toBeDefined(); + expect(Object.keys(tools).length).toBeGreaterThan(0); }); - }); - describe('callTool', () => { - it('should process input correctly', () => { - const args = JSON.stringify({ input: 'test input' }); - const result = Handler.callTool('{{project-name | snake_case}}', args); - - expect(result.tag).toBe('text'); - if (result.tag === 'text') { - expect(result.val).toContain('test input'); - } + it('should have the {{project-name | snake_case}} tool', () => { + expect(tools['{{project-name | snake_case}}']).toBeDefined(); + expect(tools['{{project-name | snake_case}}'].description).toBe('{{project-description}}'); }); - it('should handle invalid JSON', () => { - const result = Handler.callTool('{{project-name | snake_case}}', 'invalid json'); + it('should handle valid input for {{project-name | snake_case}} tool', async () => { + const tool = tools['{{project-name | snake_case}}']; + expect(tool.handler).toBeDefined(); - expect(result.tag).toBe('error'); - if (result.tag === 'error') { - expect(result.val.code).toBe(-32602); - } + const result = await tool.handler({ input: 'test input' }); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('test input'); }); - it('should handle unknown tool', () => { - const args = JSON.stringify({ input: 'test' }); - const result = Handler.callTool('unknown_tool', args); + it('should handle missing input gracefully', async () => { + const tool = tools['{{project-name | snake_case}}']; - expect(result.tag).toBe('error'); - if (result.tag === 'error') { - expect(result.val.code).toBe(-32601); - } + const result = await tool.handler({}); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); }); }); }); \ No newline at end of file From 0f2738e110aa6d31f8440c506058d1c1a066ed83 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 14:30:45 -0700 Subject: [PATCH 19/21] fix: ci --- .../content/handler/test/handler.test.js | 27 +++++++++++-------- .../content/handler/test/handler.test.ts | 27 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js b/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js index 23db8ae7..a7bff3fc 100644 --- a/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js +++ b/packages/ftl-cli/src/templates/javascript/content/handler/test/handler.test.js @@ -5,30 +5,35 @@ describe('{{project-name}} MCP Handler', () => { describe('Tools', () => { it('should export at least one tool', () => { expect(tools).toBeDefined(); - expect(Object.keys(tools).length).toBeGreaterThan(0); + expect(Array.isArray(tools)).toBe(true); + expect(tools.length).toBeGreaterThan(0); }); - it('should have the {{project-name | snake_case}} tool', () => { - expect(tools['{{project-name | snake_case}}']).toBeDefined(); - expect(tools['{{project-name | snake_case}}'].description).toBe('{{project-description}}'); + it('should have the echo tool', () => { + const echoTool = tools.find(t => t.name === 'echo'); + expect(echoTool).toBeDefined(); + expect(echoTool?.description).toBe('Echo a message back to the user'); }); - it('should handle valid input for {{project-name | snake_case}} tool', async () => { - const tool = tools['{{project-name | snake_case}}']; - expect(tool.handler).toBeDefined(); + it('should handle valid input for echo tool', async () => { + const echoTool = tools.find(t => t.name === 'echo'); + expect(echoTool).toBeDefined(); + expect(echoTool?.execute).toBeDefined(); - const result = await tool.handler({ input: 'test input' }); + const result = await echoTool?.execute({ message: 'test input' }); expect(result).toBeDefined(); expect(typeof result).toBe('string'); expect(result).toContain('test input'); }); - it('should handle missing input gracefully', async () => { - const tool = tools['{{project-name | snake_case}}']; + it('should handle missing message with default', async () => { + const echoTool = tools.find(t => t.name === 'echo'); + expect(echoTool).toBeDefined(); - const result = await tool.handler({}); + const result = await echoTool?.execute({}); expect(result).toBeDefined(); expect(typeof result).toBe('string'); + expect(result).toContain('Hello, world!'); }); }); }); \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts b/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts index ec074971..fcc548f7 100644 --- a/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts +++ b/packages/ftl-cli/src/templates/typescript/content/handler/test/handler.test.ts @@ -5,30 +5,35 @@ describe('{{project-name}} MCP Handler', () => { describe('Tools', () => { it('should export at least one tool', () => { expect(tools).toBeDefined(); - expect(Object.keys(tools).length).toBeGreaterThan(0); + expect(Array.isArray(tools)).toBe(true); + expect(tools.length).toBeGreaterThan(0); }); - it('should have the {{project-name | snake_case}} tool', () => { - expect(tools['{{project-name | snake_case}}']).toBeDefined(); - expect(tools['{{project-name | snake_case}}'].description).toBe('{{project-description}}'); + it('should have the echo tool', () => { + const echoTool = tools.find(t => t.name === 'echo'); + expect(echoTool).toBeDefined(); + expect(echoTool?.description).toBe('Echo a message back to the user'); }); - it('should handle valid input for {{project-name | snake_case}} tool', async () => { - const tool = tools['{{project-name | snake_case}}']; - expect(tool.handler).toBeDefined(); + it('should handle valid input for echo tool', async () => { + const echoTool = tools.find(t => t.name === 'echo'); + expect(echoTool).toBeDefined(); + expect(echoTool?.execute).toBeDefined(); - const result = await tool.handler({ input: 'test input' }); + const result = await echoTool?.execute({ message: 'test input' }); expect(result).toBeDefined(); expect(typeof result).toBe('string'); expect(result).toContain('test input'); }); - it('should handle missing input gracefully', async () => { - const tool = tools['{{project-name | snake_case}}']; + it('should handle missing message with default', async () => { + const echoTool = tools.find(t => t.name === 'echo'); + expect(echoTool).toBeDefined(); - const result = await tool.handler({}); + const result = await echoTool?.execute({}); expect(result).toBeDefined(); expect(typeof result).toBe('string'); + expect(result).toContain('Hello, world!'); }); }); }); \ No newline at end of file From ca2a96c270827bb88ed9779cc43eda7555f2eee6 Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 14:44:07 -0700 Subject: [PATCH 20/21] fix: ci --- .github/workflows/check-docs.yml | 53 -------- .github/workflows/check-versions.yml | 17 ++- .../workflows/{test-e2e.yml => test-cli.yml} | 3 + .github/workflows/test-templates.yml | 127 ------------------ .../src/templates/rust/content/Makefile | 9 +- 5 files changed, 22 insertions(+), 187 deletions(-) delete mode 100644 .github/workflows/check-docs.yml rename .github/workflows/{test-e2e.yml => test-cli.yml} (97%) delete mode 100644 .github/workflows/test-templates.yml diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml deleted file mode 100644 index 06423744..00000000 --- a/.github/workflows/check-docs.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Check Documentation - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - check-readme: - name: Check README files - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Check for broken links in main README - run: | - # Basic check for broken markdown links - grep -oE '\[([^\]]+)\]\(([^)]+)\)' README.md | grep -v http | while read -r link; do - path=$(echo "$link" | sed -E 's/\[[^\]]+\]\(([^)]+)\)/\1/') - if [[ "$path" == "#"* ]]; then - continue # Skip anchor links - fi - if [[ ! -f "$path" ]]; then - echo "Error: Broken link in README.md: $path" - exit 1 - fi - done - - - name: Check CLI help matches README - run: | - cd packages/ftl-cli - cargo build --release - - # Generate help text - ./target/release/ftl --help > /tmp/ftl-help.txt - - # Check if key commands are documented - for cmd in init add build up test publish deploy registry setup; do - if ! grep -q "^ $cmd" /tmp/ftl-help.txt; then - echo "Error: Command '$cmd' not found in ftl --help" - exit 1 - fi - done - - - name: Check template READMEs exist - run: | - for lang in rust typescript javascript; do - if [[ ! -f "packages/ftl-cli/src/templates/$lang/content/README.md" ]]; then - echo "Error: Missing README.md for $lang template" - exit 1 - fi - done \ No newline at end of file diff --git a/.github/workflows/check-versions.yml b/.github/workflows/check-versions.yml index 94e77ae9..11dd57e3 100644 --- a/.github/workflows/check-versions.yml +++ b/.github/workflows/check-versions.yml @@ -20,12 +20,19 @@ jobs: echo "Workspace version: $WORKSPACE_VERSION" # Check ftl-cli version - CLI_VERSION=$(grep '^version' packages/ftl-cli/Cargo.toml | head -1 | cut -d'"' -f2) - echo "CLI version: $CLI_VERSION" + CLI_VERSION_LINE=$(grep '^version' packages/ftl-cli/Cargo.toml | head -1) - if [ "$WORKSPACE_VERSION" != "$CLI_VERSION" ]; then - echo "Error: Workspace version ($WORKSPACE_VERSION) does not match CLI version ($CLI_VERSION)" - exit 1 + # Check if CLI uses workspace version + if echo "$CLI_VERSION_LINE" | grep -q "workspace = true"; then + echo "CLI version: inherited from workspace ($WORKSPACE_VERSION)" + else + CLI_VERSION=$(echo "$CLI_VERSION_LINE" | cut -d'"' -f2) + echo "CLI version: $CLI_VERSION" + + if [ "$WORKSPACE_VERSION" != "$CLI_VERSION" ]; then + echo "Error: Workspace version ($WORKSPACE_VERSION) does not match CLI version ($CLI_VERSION)" + exit 1 + fi fi - name: Check SDK dependency versions in templates diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-cli.yml similarity index 97% rename from .github/workflows/test-e2e.yml rename to .github/workflows/test-cli.yml index b71c6906..dbc53791 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-cli.yml @@ -18,6 +18,9 @@ jobs: with: targets: wasm32-wasip1 + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main + - name: Install ftl CLI run: | cargo install --path packages/ftl-cli diff --git a/.github/workflows/test-templates.yml b/.github/workflows/test-templates.yml deleted file mode 100644 index 1964353f..00000000 --- a/.github/workflows/test-templates.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Test Templates - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test-templates: - name: Test Templates - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install ftl CLI - run: | - cargo install --path packages/ftl-cli - - - name: Setup Spin - uses: fermyon/actions/spin/setup@v1 - with: - version: v3.3.1 - - - name: Install Spin templates - run: | - spin templates install --git https://github.com/fermyon/spin --upgrade - - - name: Install FTL templates - run: | - spin templates install --dir packages/ftl-cli/src --upgrade - - - name: Test ftl init - run: | - cd /tmp - ftl init test-project - cd test-project - test -f spin.toml - test -f .gitignore - - - name: Test ftl add with Rust template - run: | - cd /tmp/test-project - ftl add test-rust --language rust --description "Test Rust component" - test -d test-rust - test -f test-rust/ftl.toml - test -f test-rust/handler/Cargo.toml - - - name: Build Rust component - run: | - cd /tmp/test-project/test-rust - make build - test -f handler/target/wasm32-wasip1/release/test_rust.wasm - - - name: Test Rust component - run: | - cd /tmp/test-project/test-rust - make test - - - name: Test ftl add with TypeScript template - run: | - cd /tmp/test-project - ftl add test-ts --language typescript --description "Test TypeScript component" - test -d test-ts - test -f test-ts/ftl.toml - test -f test-ts/handler/package.json - - - name: Build TypeScript component - run: | - cd /tmp/test-project/test-ts - make build - test -f handler/dist/handler.wasm - - - name: Test TypeScript component - run: | - cd /tmp/test-project/test-ts - make test - - - name: Test ftl add with JavaScript template - run: | - cd /tmp/test-project - ftl add test-js --language javascript --description "Test JavaScript component" - test -d test-js - test -f test-js/ftl.toml - test -f test-js/handler/package.json - - - name: Build JavaScript component - run: | - cd /tmp/test-project/test-js - make build - test -f handler/dist/handler.wasm - - - name: Test JavaScript component - run: | - cd /tmp/test-project/test-js - make test - - - name: Test ftl build at project level - run: | - cd /tmp/test-project - ftl build - - - name: Test project structure - run: | - cd /tmp/test-project - # Verify spin.toml has all components - grep -q "test-rust" spin.toml - grep -q "test-ts" spin.toml - grep -q "test-js" spin.toml - # Verify routes - grep -q "/test-rust/mcp" spin.toml - grep -q "/test-ts/mcp" spin.toml - grep -q "/test-js/mcp" spin.toml - - - name: Test ftl watch command exists - run: | - ftl watch --help \ No newline at end of file diff --git a/packages/ftl-cli/src/templates/rust/content/Makefile b/packages/ftl-cli/src/templates/rust/content/Makefile index edc4f220..70584dd8 100644 --- a/packages/ftl-cli/src/templates/rust/content/Makefile +++ b/packages/ftl-cli/src/templates/rust/content/Makefile @@ -5,8 +5,13 @@ check-cargo-component: @command -v cargo-component >/dev/null 2>&1 || { \ echo "cargo-component not found, installing..."; \ echo "This is required for building Rust WebAssembly components"; \ - echo "This may take a few minutes on first install"; \ - cargo install cargo-component; \ + if command -v cargo-binstall >/dev/null 2>&1; then \ + echo "Using cargo-binstall for faster installation"; \ + cargo binstall cargo-component -y; \ + else \ + echo "cargo-binstall not found, installing from source (this may take a few minutes)"; \ + cargo install cargo-component; \ + fi; \ echo "cargo-component installed successfully!"; \ } From 7fe54c23d8f95773f301f145131840fa768e34af Mon Sep 17 00:00:00 2001 From: bowlofarugula Date: Sun, 6 Jul 2025 14:57:17 -0700 Subject: [PATCH 21/21] fix: release and docs --- .github/workflows/release.yml | 17 +- README.md | 89 ++++-- docs/README.md | 103 +++++++ docs/components.md | 509 ++++++++++++++++++++++++++++++++ docs/deployment.md | 466 ++++++++++++++++++++++++++++++ docs/introduction.md | 121 ++++++-- docs/publishing.md | 356 +++++++++++++++++++++++ docs/quickstart.md | 212 ++++++++++++++ docs/sdk-reference.md | 525 ++++++++++++++++++++++++++++++++++ 9 files changed, 2354 insertions(+), 44 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/components.md create mode 100644 docs/deployment.md create mode 100644 docs/publishing.md create mode 100644 docs/quickstart.md create mode 100644 docs/sdk-reference.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ec85026..9c2bc433 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,10 @@ jobs: target: aarch64-apple-darwin binary: ftl asset_name: ftl-aarch64-apple-darwin + - os: windows-latest + target: x86_64-pc-windows-msvc + binary: ftl.exe + asset_name: ftl-x86_64-pc-windows-msvc.exe steps: - uses: actions/checkout@v4 @@ -42,9 +46,10 @@ jobs: - name: Validate version matches Cargo.toml run: | - CARGO_VERSION=$(grep '^version' packages/ftl-cli/Cargo.toml | head -1 | cut -d'"' -f2) + # Since ftl-cli uses workspace version, check the root Cargo.toml + CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2) if [ "$CARGO_VERSION" != "${{ steps.version.outputs.VERSION }}" ]; then - echo "Error: Tag version (${{ steps.version.outputs.VERSION }}) does not match Cargo.toml version ($CARGO_VERSION)" + echo "Error: Tag version (${{ steps.version.outputs.VERSION }}) does not match workspace version ($CARGO_VERSION)" exit 1 fi shell: bash @@ -84,9 +89,11 @@ jobs: run: | mkdir -p release-assets for dir in binaries/*/; do + asset_name=$(basename "$dir") if [ -f "${dir}ftl" ]; then - asset_name=$(basename "$dir") cp "${dir}ftl" "release-assets/${asset_name}" + elif [ -f "${dir}ftl.exe" ]; then + cp "${dir}ftl.exe" "release-assets/${asset_name}" fi done ls -la release-assets/ @@ -131,7 +138,9 @@ jobs: run: | cd packages/ftl-cli # Check if this version is already published - if cargo search ftl-cli --limit 1 | grep -q "^ftl-cli = \"$(grep '^version' Cargo.toml | cut -d'"' -f2)\""; then + # Since ftl-cli uses workspace version, get version from root + WORKSPACE_VERSION=$(grep '^version' ../../Cargo.toml | head -1 | cut -d'"' -f2) + if cargo search ftl-cli --limit 1 | grep -q "^ftl-cli = \"$WORKSPACE_VERSION\""; then echo "ftl-cli version already published, skipping" else cargo publish diff --git a/README.md b/README.md index 6aa5bf2c..6b62a6bf 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Build and deploy Model Context Protocol (MCP) servers on WebAssembly -FTL is a developer platform for building and deploying [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers as WebAssembly components that run on the [Fermyon Spin](https://www.fermyon.com/spin) platform. +FTL is a developer platform for building and deploying [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers as WebAssembly components. It provides a complete workflow for creating, testing, composing, and deploying MCP components using the [Fermyon Spin](https://www.fermyon.com/spin) platform. ## Quick Start @@ -31,7 +31,11 @@ ftl add weather-tool --language typescript # Start development server with auto-rebuild ftl watch -# Publish to registry +# Run tests +ftl test + +# Build and deploy +ftl build --release ftl publish ``` @@ -40,8 +44,10 @@ ftl publish - **Component-First Architecture**: Build MCP servers as reusable WebAssembly components - **Multi-Language Support**: Write components in Rust, TypeScript, or JavaScript - **Registry Publishing**: Share components via OCI registries (GitHub, Docker Hub) -- **Project Composition**: Combine multiple MCP components into a single Spin project -- **Edge Deployment**: Deploy to FTL or self-host with Spin +- **Project Composition**: Combine multiple MCP components into a single deployable unit +- **Automatic Dependency Management**: Tools like cargo-component installed on-demand +- **Hot Reload Development**: Auto-rebuild on file changes with `ftl watch` +- **Edge Deployment**: Deploy anywhere Spin runs ## Creating MCP Projects @@ -92,13 +98,18 @@ fn get_tools() -> Vec { } ``` -## Component Lifecycle +## Component Development Workflow ### 1. Development ```bash -ftl build # Build all components (from project root) -ftl test # Run tests -ftl up --port 3000 # Run locally +# From component directory +ftl build # Build the component +ftl test # Run component tests +ftl watch # Auto-rebuild on changes + +# From project root (with spin.toml) +ftl build # Build all components +ftl up --port 3000 # Run the composed application ``` ### 2. Publishing @@ -116,13 +127,19 @@ ftl publish --registry docker.io --tag latest ftl init my-assistant cd my-assistant -# Add components -ftl add weather-tool --language typescript -ftl add github-tool --language rust -ftl add my-custom-tool --language javascript +# Add components with custom routes +ftl add weather-tool --language typescript --route /weather +ftl add github-tool --language rust --route /github +ftl add calculator --language javascript --route /calc + +# Each component gets its own MCP endpoint +# /weather/mcp - Weather tool MCP endpoint +# /github/mcp - GitHub tool MCP endpoint +# /calc/mcp - Calculator MCP endpoint # Run the composed project -ftl up --build +ftl watch # Development with auto-rebuild +ftl up # Production mode ``` ### 4. Deployment @@ -140,13 +157,36 @@ FTL leverages the WebAssembly component model and Spin platform: ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ MCP Client │────▶│ Spin Project │────▶│ MCP Component │ +│ MCP Client │────▶│ Spin Runtime │────▶│ MCP Component │ │ (AI Agent) │ │ (HTTP Router) │ │ (WASM Module) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ - ├────▶ Component 1 (Rust) - ├────▶ Component 2 (TypeScript) - └────▶ Component 3 (JavaScript) + ├── /weather/mcp ──▶ Weather Component (TypeScript) + ├── /github/mcp ──▶ GitHub Component (Rust) + └── /calc/mcp ──▶ Calculator Component (JavaScript) +``` + +### Project Structure + +``` +my-assistant/ +├── spin.toml # Spin manifest (project root) +├── weather-tool/ # TypeScript component +│ ├── ftl.toml # Component metadata +│ ├── Makefile # Build automation +│ └── handler/ # Component source +│ ├── package.json +│ └── src/ +├── github-tool/ # Rust component +│ ├── ftl.toml +│ ├── Makefile +│ └── handler/ +│ ├── Cargo.toml +│ └── src/ +└── calculator/ # JavaScript component + ├── ftl.toml + ├── Makefile + └── handler/ ``` Each component: @@ -159,19 +199,24 @@ Each component: ## Prerequisites - **Rust toolchain** (for FTL CLI) -- **Language toolchains**: - - Rust: cargo with wasm32-wasip1 target +- **Language-specific requirements**: + - Rust: cargo with wasm32-wasip1 target (cargo-component auto-installed) - TypeScript/JavaScript: Node.js 20+ -- **wkg** for publishing ([install](https://github.com/bytecodealliance/wasm-pkg-tools)) -- **Spin** (auto-installed by FTL) +- **Optional**: + - wkg for publishing ([install](https://github.com/bytecodealliance/wasm-pkg-tools)) + - cargo-binstall for faster tool installation +- **Auto-installed**: + - Spin runtime (prompted on first use) + - cargo-component (for Rust components) ## Documentation - [Getting Started Guide](./docs/introduction.md) +- [CLI Reference](./docs/cli-reference.md) - [Component Development](./docs/components.md) - [Publishing Components](./docs/publishing.md) - [Project Composition](./docs/composition.md) -- [API Reference](./docs/api.md) +- [SDK Reference](./docs/sdk-reference.md) ## Development diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..f3daccfb --- /dev/null +++ b/docs/README.md @@ -0,0 +1,103 @@ +# FTL Documentation + +Welcome to the FTL documentation! This directory contains comprehensive guides for building and deploying MCP servers with FTL. + +## 📚 Documentation Structure + +### Getting Started +- **[Introduction](./introduction.md)** - Overview of FTL and core concepts +- **[Quick Start Guide](./quickstart.md)** - Get up and running in 5 minutes +- **[CLI Reference](./cli-reference.md)** - Complete command documentation + +### Development Guides +- **[Component Development](./components.md)** - Deep dive into building MCP components +- **[SDK Reference](./sdk-reference.md)** - TypeScript/JavaScript and Rust SDK APIs +- **[Publishing Components](./publishing.md)** - Share components via OCI registries + +### Deployment & Operations +- **[Deployment Guide](./deployment.md)** - Deploy to various environments +- **[Project Composition](./composition.md)** - Combine multiple components + +### Additional Resources +- **[API Reference](./api.md)** - MCP protocol implementation details +- **[Troubleshooting](./troubleshooting.md)** - Common issues and solutions + +## 🚀 Quick Links + +### For Beginners +1. Start with the [Introduction](./introduction.md) to understand FTL +2. Follow the [Quick Start Guide](./quickstart.md) to build your first component +3. Explore the [CLI Reference](./cli-reference.md) for available commands + +### For Component Developers +1. Read [Component Development](./components.md) for best practices +2. Reference the [SDK documentation](./sdk-reference.md) for your language +3. Learn to [publish components](./publishing.md) for sharing + +### For DevOps/Deployment +1. Check the [Deployment Guide](./deployment.md) for platform options +2. Learn about [monitoring and operations](./deployment.md#monitoring--logging) +3. Review [security best practices](./deployment.md#security-best-practices) + +## 📖 Documentation Conventions + +### Code Examples + +We provide examples in multiple languages: + +**TypeScript:** +```typescript +import { createTool } from '@fastertools/ftl-sdk'; +``` + +**Rust:** +```rust +use ftl_sdk::*; +``` + +**Bash:** +```bash +ftl init my-project +``` + +### Icons Used + +- 📚 Documentation/Learning +- 🚀 Getting Started/Quick Actions +- 💡 Tips and Best Practices +- ⚠️ Warnings and Important Notes +- 🔧 Configuration and Setup +- 🔒 Security-related Information + +## 🤝 Contributing + +We welcome contributions to improve our documentation! + +### How to Contribute + +1. **Find an issue**: Check for documentation issues or create one +2. **Make changes**: Edit the relevant `.md` files +3. **Test examples**: Ensure code examples work correctly +4. **Submit PR**: Create a pull request with your changes + +### Documentation Style Guide + +- Use clear, concise language +- Include practical examples +- Provide both TypeScript and Rust examples where applicable +- Test all code examples +- Keep formatting consistent + +## 📞 Getting Help + +- **GitHub Issues**: Report bugs or request features +- **Discussions**: Ask questions and share ideas +- **Discord**: Join our community for real-time help + +## 🔄 Version Compatibility + +This documentation is for FTL version 0.0.18 and above. For older versions, check the version tags in the repository. + +--- + +Happy building with FTL! 🎉 \ No newline at end of file diff --git a/docs/components.md b/docs/components.md new file mode 100644 index 00000000..251413a5 --- /dev/null +++ b/docs/components.md @@ -0,0 +1,509 @@ +# Component Development + +This guide covers everything you need to know about developing MCP components with FTL. + +## Component Structure + +Every FTL component follows a consistent structure: + +``` +my-component/ +├── ftl.toml # Component metadata +├── Makefile # Build automation +├── handler/ # Component source code +│ ├── [package.json | Cargo.toml] # Language-specific manifest +│ ├── src/ # Source files +│ └── test/ # Test files +└── [.wit/] # WebAssembly Interface Types (generated) +``` + +### ftl.toml + +The component metadata file: + +```toml +name = "my-component" +version = "0.1.0" +description = "My awesome MCP component" +route = "/my-component" +``` + +### Makefile + +Standard targets for all components: +- `make build` - Build the component +- `make test` - Run tests +- `make clean` - Clean build artifacts + +## Language-Specific Development + +### TypeScript Components + +#### Project Setup + +```bash +ftl add my-tool --language typescript +cd my-tool/handler +``` + +#### Implementation + +```typescript +// src/features.ts +import { createTool, createResource, createPrompt } from '@fastertools/ftl-sdk'; + +// Define tools +export const tools = [ + createTool({ + name: 'get_weather', + description: 'Get weather for a location', + inputSchema: { + type: 'object', + properties: { + location: { type: 'string', description: 'City name' }, + units: { type: 'string', enum: ['celsius', 'fahrenheit'] } + }, + required: ['location'] + }, + execute: async (args) => { + // Implementation + return `Weather in ${args.location}: 72°F`; + } + }) +]; + +// Define resources +export const resources = [ + createResource({ + uri: 'weather://current', + name: 'Current Weather Data', + description: 'Real-time weather information', + mimeType: 'application/json', + read: async () => { + return JSON.stringify({ temp: 72, conditions: 'sunny' }); + } + }) +]; + +// Define prompts +export const prompts = [ + createPrompt({ + name: 'weather_report', + description: 'Generate a weather report', + arguments: [ + { name: 'location', description: 'Location for weather', required: true } + ], + resolve: async (args) => { + return [ + { role: 'user', content: `What's the weather in ${args.location}?` }, + { role: 'assistant', content: `I'll check the weather for ${args.location}.` } + ]; + } + }) +]; +``` + +#### Testing + +```typescript +// test/weather.test.ts +import { describe, it, expect } from 'vitest'; +import { tools } from '../src/features'; + +describe('Weather Tool', () => { + it('should return weather data', async () => { + const weatherTool = tools.find(t => t.name === 'get_weather'); + const result = await weatherTool?.execute({ + location: 'San Francisco' + }); + expect(result).toContain('San Francisco'); + }); +}); +``` + +### Rust Components + +#### Project Setup + +```bash +ftl add my-tool --language rust +cd my-tool/handler +``` + +#### Implementation + +```rust +// src/lib.rs +use ftl_sdk::*; +use serde::{Deserialize, Serialize}; + +// Define handler +create_handler!( + tools: get_tools, + resources: get_resources, + prompts: get_prompts +); + +// Tool implementation +#[derive(Deserialize)] +struct WeatherArgs { + location: String, + units: Option, +} + +fn get_weather(args: WeatherArgs) -> Result { + Ok(format!("Weather in {}: 72°F", args.location)) +} + +// Export tools +fn get_tools() -> Vec { + vec![ + tool!( + "get_weather", + "Get weather for a location", + json!({ + "type": "object", + "properties": { + "location": { "type": "string" }, + "units": { "type": "string" } + }, + "required": ["location"] + }), + get_weather + ) + ] +} + +// Export resources +fn get_resources() -> Vec { + vec![ + resource!( + "weather://current", + "Current Weather Data", + "application/json", + || Ok(r#"{"temp": 72, "conditions": "sunny"}"#.to_string()) + ) + ] +} + +// Export prompts +fn get_prompts() -> Vec { + vec![] +} +``` + +#### Testing + +```rust +// src/lib.rs (test module) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_weather_tool() { + let args = WeatherArgs { + location: "San Francisco".to_string(), + units: None, + }; + let result = get_weather(args).unwrap(); + assert!(result.contains("San Francisco")); + } +} +``` + +### JavaScript Components + +JavaScript components follow the same pattern as TypeScript but without type annotations: + +```javascript +// src/features.js +import { createTool } from '@fastertools/ftl-sdk'; + +export const tools = [ + createTool({ + name: 'calculate', + description: 'Perform calculations', + inputSchema: { + type: 'object', + properties: { + expression: { type: 'string' } + }, + required: ['expression'] + }, + execute: async (args) => { + // Simple example - in production use a safe parser + try { + const result = eval(args.expression); + return `Result: ${result}`; + } catch (error) { + return `Error: Invalid expression`; + } + } + }) +]; +``` + +## Component Dependencies + +### Rust Components + +Add dependencies to `handler/Cargo.toml`: + +```toml +[dependencies] +ftl-sdk = "0.2.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +reqwest = { version = "0.11", features = ["json"] } +tokio = { version = "1", features = ["full"] } +``` + +Note: cargo-component is automatically installed when building Rust components. + +### TypeScript/JavaScript Components + +Add dependencies to `handler/package.json`: + +```json +{ + "dependencies": { + "@fastertools/ftl-sdk": "^0.1.0", + "axios": "^1.6.0", + "dotenv": "^16.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "vitest": "^1.0.0" + } +} +``` + +## Build Process + +### Development Builds + +```bash +# Build a single component +cd my-component +make build + +# Build all components (from project root) +ftl build +``` + +### Production Builds + +```bash +# Build with optimizations +ftl build --release +``` + +### Automatic Rebuilds + +```bash +# Watch for changes and rebuild +ftl watch +``` + +## Testing + +### Unit Tests + +Each language has its own test runner: + +```bash +# Run tests for a component +cd my-component +make test + +# Run all tests (from project root) +ftl test +``` + +### Integration Testing + +Test your component with MCP clients: + +```javascript +// test-client.js +import { Client } from '@modelcontextprotocol/sdk'; + +const client = new Client({ + url: 'http://localhost:3000/my-component/mcp' +}); + +// List tools +const tools = await client.listTools(); +console.log(tools); + +// Call a tool +const result = await client.callTool('my_tool', { + input: 'test data' +}); +console.log(result); +``` + +## Best Practices + +### 1. Error Handling + +Always handle errors gracefully: + +```typescript +execute: async (args) => { + try { + const result = await someOperation(args); + return JSON.stringify(result); + } catch (error) { + return JSON.stringify({ + error: error.message, + code: 'OPERATION_FAILED' + }); + } +} +``` + +### 2. Input Validation + +Use JSON Schema for comprehensive validation: + +```typescript +inputSchema: { + type: 'object', + properties: { + email: { + type: 'string', + format: 'email', + description: 'User email address' + }, + age: { + type: 'integer', + minimum: 0, + maximum: 150 + } + }, + required: ['email'] +} +``` + +### 3. Async Operations + +Handle async operations properly: + +```rust +async fn fetch_data(url: String) -> Result> { + let response = reqwest::get(&url).await?; + let body = response.text().await?; + Ok(body) +} +``` + +### 4. Resource Management + +Clean up resources properly: + +```typescript +let connection; +try { + connection = await createConnection(); + return await connection.query(args.query); +} finally { + if (connection) { + await connection.close(); + } +} +``` + +### 5. Documentation + +Document your tools thoroughly: + +```typescript +createTool({ + name: 'analyze_data', + description: 'Analyze data using various statistical methods. ' + + 'Supports CSV, JSON, and Excel formats. ' + + 'Returns summary statistics and visualizations.', + // ... +}) +``` + +## Advanced Topics + +### WebAssembly Interface Types + +FTL generates WIT files for language interop: + +```wit +// Generated .wit/mcp.wit +interface mcp-handler { + record tool { + name: string, + description: string, + input-schema: string, + } + + list-tools: func() -> list + call-tool: func(name: string, args: string) -> result +} +``` + +### Component Composition + +Combine multiple components in `spin.toml`: + +```toml +[[component]] +id = "weather" +route = "/weather/..." +source = "weather-tool/handler/target/wasm32-wasip1/release/handler.wasm" + +[[component]] +id = "news" +route = "/news/..." +source = "news-tool/handler/dist/handler.wasm" +``` + +### Performance Optimization + +1. **Minimize dependencies**: Only include what you need +2. **Use streaming**: For large responses, consider streaming +3. **Cache results**: Implement caching for expensive operations +4. **Profile your code**: Use language-specific profiling tools + +## Troubleshooting + +### Build Errors + +```bash +# Clean and rebuild +make clean +make build + +# Check for missing dependencies +npm install # for JS/TS +cargo check # for Rust +``` + +### Runtime Errors + +Check the Spin logs: +```bash +ftl up --follow +``` + +### Test Failures + +Run tests with verbose output: +```bash +npm test -- --reporter=verbose # JS/TS +cargo test -- --nocapture # Rust +``` + +## Next Steps + +- [Publishing Components](./publishing.md) - Share your components +- [SDK Reference](./sdk-reference.md) - Detailed API documentation +- [Deployment Guide](./deployment.md) - Deploy to production \ No newline at end of file diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 00000000..6262e77a --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,466 @@ +# Deployment Guide + +This guide covers deploying FTL projects to various environments. + +## Deployment Options + +FTL projects can be deployed anywhere Spin runs: + +1. **Fermyon Cloud** - Managed Spin platform +2. **Kubernetes** - Using Spin Operator +3. **Self-hosted** - On your own infrastructure +4. **Edge platforms** - Cloudflare, Fastly, etc. + +## Local Development + +### Development Server + +```bash +# Start with auto-rebuild +ftl watch + +# Start on specific port +ftl up --port 8080 + +# Build before running +ftl up --build +``` + +### Production Mode + +```bash +# Build with optimizations +ftl build --release + +# Run in production mode +ftl up --port 80 +``` + +## Fermyon Cloud Deployment + +### Prerequisites + +1. Install Spin CLI: + ```bash + curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash + ``` + +2. Login to Fermyon Cloud: + ```bash + spin login + ``` + +### Deploy + +```bash +# From project root (with spin.toml) +spin deploy + +# Custom app name +spin deploy --app-name my-mcp-server + +# Deploy to specific environment +spin deploy --environment production +``` + +### Environment Variables + +Set environment variables: + +```bash +spin cloud variables set API_KEY="your-key" +spin cloud variables set DATABASE_URL="postgres://..." +``` + +Or use `.env` file: + +```bash +spin deploy --variables-file .env.production +``` + +### Custom Domains + +```bash +# Add custom domain +spin cloud domain add api.example.com + +# List domains +spin cloud domain list +``` + +## Kubernetes Deployment + +### Using Spin Operator + +1. Install Spin Operator: + ```bash + kubectl apply -f https://github.com/fermyon/spin-operator/releases/download/v0.1.0/spin-operator.yaml + ``` + +2. Create SpinApp resource: + ```yaml + # spinapp.yaml + apiVersion: core.spinoperator.dev/v1alpha1 + kind: SpinApp + metadata: + name: my-mcp-server + spec: + image: "ghcr.io/username/my-mcp-server:latest" + replicas: 3 + variables: + - name: API_KEY + valueFrom: + secretKeyRef: + name: api-secrets + key: api-key + ``` + +3. Deploy: + ```bash + kubectl apply -f spinapp.yaml + ``` + +### Using Docker + +Build Docker image with Spin: + +```dockerfile +# Dockerfile +FROM scratch +COPY spin.toml . +COPY weather-tool/handler/target/wasm32-wasip1/release/handler.wasm ./weather-tool/handler.wasm +COPY github-tool/handler/target/wasm32-wasip1/release/handler.wasm ./github-tool/handler.wasm +``` + +```bash +# Build and push +docker build -t myregistry/my-mcp-server:latest . +docker push myregistry/my-mcp-server:latest +``` + +## Self-Hosted Deployment + +### Systemd Service + +Create service file: + +```ini +# /etc/systemd/system/mcp-server.service +[Unit] +Description=MCP Server +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/mcp-server +ExecStart=/usr/local/bin/spin up --port 3000 +Restart=always +Environment="RUST_LOG=info" + +[Install] +WantedBy=multi-user.target +``` + +Enable and start: + +```bash +sudo systemctl enable mcp-server +sudo systemctl start mcp-server +``` + +### Docker Compose + +```yaml +# docker-compose.yml +version: '3.8' + +services: + mcp-server: + image: ghcr.io/fermyon/spin:latest + command: up --from ghcr.io/username/my-mcp-server:latest + ports: + - "3000:3000" + environment: + - API_KEY=${API_KEY} + restart: unless-stopped +``` + +### Reverse Proxy + +Nginx configuration: + +```nginx +server { + listen 80; + server_name api.example.com; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} +``` + +## Environment Configuration + +### Using spin.toml + +```toml +# spin.toml +[variables] +api_endpoint = { default = "https://api.example.com" } +log_level = { default = "info", secret = false } +api_key = { required = true, secret = true } + +[[component]] +id = "weather" +environment = { + API_ENDPOINT = "{{ api_endpoint }}", + LOG_LEVEL = "{{ log_level }}", + API_KEY = "{{ api_key }}" +} +``` + +### Runtime Variables + +Set at deployment: + +```bash +# Fermyon Cloud +spin deploy --variable api_key="secret-key" + +# Self-hosted +API_KEY="secret-key" spin up +``` + +## Monitoring & Logging + +### Application Logs + +```bash +# Fermyon Cloud +spin cloud logs --follow + +# Self-hosted with systemd +journalctl -u mcp-server -f + +# Docker +docker logs -f mcp-server +``` + +### Health Checks + +Add health endpoint: + +```typescript +// TypeScript component +export const tools = [ + createTool({ + name: '_health', + description: 'Health check endpoint', + inputSchema: { type: 'object' }, + execute: async () => { + return JSON.stringify({ + status: 'healthy', + timestamp: new Date().toISOString() + }); + } + }) +]; +``` + +### Metrics + +Use OpenTelemetry: + +```rust +// Rust component +use opentelemetry::{global, metrics::*}; + +let meter = global::meter("mcp-component"); +let counter = meter + .u64_counter("requests_total") + .with_description("Total requests") + .init(); + +counter.add(1, &[KeyValue::new("method", "tool_call")]); +``` + +## Security Best Practices + +### 1. HTTPS/TLS + +Always use HTTPS in production: + +```nginx +server { + listen 443 ssl http2; + ssl_certificate /etc/ssl/certs/cert.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + # ... rest of config +} +``` + +### 2. Authentication + +Implement authentication for MCP endpoints: + +```typescript +execute: async (args, context) => { + const authHeader = context.headers['authorization']; + if (!isValidAuth(authHeader)) { + throw new Error('Unauthorized'); + } + // ... tool logic +} +``` + +### 3. Rate Limiting + +Protect against abuse: + +```typescript +const rateLimiter = new Map(); + +execute: async (args, context) => { + const clientId = context.clientId; + if (isRateLimited(clientId)) { + throw new Error('Rate limit exceeded'); + } + // ... tool logic +} +``` + +### 4. Secrets Management + +Never hardcode secrets: + +```bash +# Use environment variables +export API_KEY="secret-key" + +# Use secret management services +vault kv get secret/mcp/api-key + +# Use Kubernetes secrets +kubectl create secret generic api-secrets --from-literal=api-key=secret-key +``` + +## Scaling + +### Horizontal Scaling + +```yaml +# Kubernetes +spec: + replicas: 5 + +# Docker Swarm +docker service scale mcp-server=5 + +# Fermyon Cloud +spin cloud app scale 5 +``` + +### Load Balancing + +```nginx +upstream mcp_servers { + server 127.0.0.1:3001; + server 127.0.0.1:3002; + server 127.0.0.1:3003; +} + +server { + location / { + proxy_pass http://mcp_servers; + } +} +``` + +## CI/CD Integration + +### GitHub Actions + +```yaml +name: Deploy + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install FTL + run: cargo install ftl-cli + + - name: Build + run: ftl build --release + + - name: Deploy to Fermyon Cloud + env: + SPIN_AUTH_TOKEN: ${{ secrets.SPIN_AUTH_TOKEN }} + run: spin deploy --app-name production-mcp +``` + +### GitLab CI + +```yaml +deploy: + stage: deploy + script: + - cargo install ftl-cli + - ftl build --release + - spin deploy + only: + - main +``` + +## Troubleshooting + +### Common Issues + +1. **Port already in use** + ```bash + # Find process using port + lsof -i :3000 + # Kill process + kill -9 + ``` + +2. **Component crashes** + - Check logs for errors + - Verify environment variables + - Test locally first + +3. **Performance issues** + - Monitor resource usage + - Optimize component code + - Scale horizontally + +### Debug Mode + +Enable debug logging: + +```bash +# Local +RUST_LOG=debug ftl up + +# Fermyon Cloud +spin cloud variables set RUST_LOG=debug +``` + +## Next Steps + +- [Monitoring Guide](./monitoring.md) - Set up observability +- [Security Guide](./security.md) - Secure your deployment +- [Performance Guide](./performance.md) - Optimize for production \ No newline at end of file diff --git a/docs/introduction.md b/docs/introduction.md index dea406da..0cbe390d 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,37 +1,122 @@ # Introduction -Welcome to the FTL documentation! This document provides a comprehensive overview of the FTL project, its goals, and its core concepts. +Welcome to FTL! This guide will help you understand what FTL is, how it works, and how to get started building MCP servers as WebAssembly components. ## What is FTL? -FTL is a framework for building and deploying high-performance, low-latency tools for AI agents. It is designed to solve the "Action Latency Bottleneck," which is the problem of slow tool execution that can limit the performance of AI agents in real-time applications. +FTL (Faster Tools Library) is a developer platform for building and deploying Model Context Protocol (MCP) servers as WebAssembly components. It provides a complete workflow for creating, testing, composing, and deploying MCP components that can be used by AI agents and assistants. -FTL provides a complete developer experience for the entire lifecycle of creating, testing, and deploying WebAssembly-based tools. It is built on a foundation of Rust, WebAssembly, and the Model Context Protocol (MCP), and it is designed to be fast, secure, and portable. +FTL solves several key challenges in MCP development: +- **Multi-language Support**: Write MCP servers in Rust, TypeScript, or JavaScript +- **Component Composition**: Combine multiple MCP servers into a single deployable unit +- **Edge Deployment**: Deploy your MCP servers anywhere using Spin's WebAssembly runtime +- **Developer Experience**: Hot reload, automatic dependency management, and intuitive CLI ## Core Concepts -### Tools +### MCP Components + +An **MCP component** is a WebAssembly module that implements the Model Context Protocol. Each component can expose: +- **Tools**: Functions that AI agents can call to perform actions +- **Resources**: Data sources that AI agents can read +- **Prompts**: Reusable prompt templates for AI interactions + +### Projects + +A **project** is a collection of MCP components that are deployed together. Projects use Spin's manifest format to define routing and configuration for each component. + +### Component Development + +FTL provides SDKs for building MCP components in multiple languages: + +**TypeScript/JavaScript:** +```typescript +import { createHandler } from '@fastertools/ftl-sdk'; + +export const handler = createHandler({ + tools: [...], // Your MCP tools + resources: [...], // Your MCP resources + prompts: [...] // Your MCP prompts +}); +``` + +**Rust:** +```rust +use ftl_sdk::*; + +create_handler!( + tools: get_tools, + resources: get_resources, + prompts: get_prompts +); +``` + +### WebAssembly Runtime + +FTL uses [Spin](https://www.fermyon.com/spin) as its WebAssembly runtime, providing: +- Secure sandboxing for each component +- HTTP routing between components +- Fast cold starts and execution +- Deploy anywhere Spin runs + +## Architecture Overview + +``` +┌─────────────────┐ +│ AI Agent │ +│ (Claude, GPT-4) │ +└────────┬────────┘ + │ MCP Protocol + ▼ +┌─────────────────┐ +│ Spin Runtime │ +│ (HTTP Router) │ +└────────┬────────┘ + │ + ┌────┴────┬─────────┬─────────┐ + ▼ ▼ ▼ ▼ +┌────────┐┌────────┐┌────────┐┌────────┐ +│Weather ││GitHub ││Database││Custom │ +│Tool ││Tool ││Tool ││Tool │ +│(TS) ││(Rust) ││(JS) ││(Any) │ +└────────┘└────────┘└────────┘└────────┘ +``` + +Each component: +- Runs in its own WebAssembly sandbox +- Has its own HTTP route (e.g., `/weather/mcp`) +- Can be developed and tested independently +- Can be published and shared via OCI registries -A **tool** is a self-contained piece of code that performs a specific task. Tools are implemented in Rust by implementing the `ftl_sdk_rs::Tool` trait. They are compiled to WebAssembly and can be deployed to any Wasm-compliant runtime. +## Why FTL? -### Toolkits +### For MCP Developers -A **toolkit** is a collection of tools that are deployed together as a single unit. This allows you to create more complex agent capabilities by composing multiple tools. +- **Language Choice**: Use your preferred language (Rust, TypeScript, JavaScript) +- **Fast Iteration**: Hot reload with `ftl watch` for rapid development +- **Easy Testing**: Built-in test runners for each language +- **Simple Deployment**: One command to build and deploy -### FTL Core +### For AI Applications -**FTL Core** is an open-source library of composable, low-level utilities for performance-sensitive AI agents. It provides the building blocks for creating tools, as well as a standard library of pre-built tools for common tasks. +- **Composability**: Mix and match components from different sources +- **Performance**: WebAssembly provides near-native execution speed +- **Security**: Components run in isolated sandboxes +- **Portability**: Deploy anywhere - edge, cloud, or on-premise -### FTL Edge +### For Teams -**FTL Edge** is a managed platform for deploying and serving tools. It provides a global network of edge servers that can execute tools with sub-millisecond compute overhead. +- **Component Marketplace**: Share components via OCI registries +- **Version Control**: Standard Git workflows for collaboration +- **Independent Development**: Teams can work on components separately +- **Unified Deployment**: Compose components into cohesive applications -## Why FTL? +## Getting Started + +Ready to build your first MCP component? Continue to the [Quick Start Guide](./quickstart.md) or dive into the [CLI Reference](./cli-reference.md). -FTL is designed to be the best way to build and deploy high-performance tools for AI agents. It offers a number of advantages over other tool-building frameworks: +## Learn More -- **Performance:** FTL tools are written in Rust and compiled to WebAssembly, which provides near-native performance. -- **Security:** FTL tools are sandboxed by default using the WebAssembly component model. -- **Portability:** FTL tools can be deployed to any Wasm-compliant runtime. -- **Developer Experience:** The `ftl` CLI provides a seamless developer experience for creating, testing, and deploying tools. -- **Open Core:** FTL is an open-core project, which means that the core technology is open source and available to everyone. +- [Model Context Protocol](https://modelcontextprotocol.io) - The protocol specification +- [Spin Documentation](https://developer.fermyon.com/spin) - The WebAssembly runtime +- [WebAssembly Component Model](https://component-model.bytecodealliance.org/) - The component standard \ No newline at end of file diff --git a/docs/publishing.md b/docs/publishing.md new file mode 100644 index 00000000..a628ffe6 --- /dev/null +++ b/docs/publishing.md @@ -0,0 +1,356 @@ +# Publishing Components + +This guide covers how to publish and share your FTL components via OCI registries. + +## Prerequisites + +Before publishing, you'll need: +1. An OCI registry account (GitHub, Docker Hub, etc.) +2. Authentication configured for your registry +3. `wkg` tool installed (optional but recommended) + +## Registry Setup + +### GitHub Container Registry (ghcr.io) + +1. Create a Personal Access Token: + - Go to GitHub Settings → Developer settings → Personal access tokens + - Create a token with `write:packages` permission + +2. Login to the registry: + ```bash + echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin + ``` + +### Docker Hub + +1. Create an account at [hub.docker.com](https://hub.docker.com) + +2. Login to the registry: + ```bash + docker login + ``` + +## Publishing Workflow + +### 1. Build Your Component + +Ensure your component builds successfully: + +```bash +cd my-component +ftl build --release +``` + +### 2. Update Metadata + +Edit your component's `ftl.toml`: + +```toml +name = "weather-tool" +version = "1.0.0" +description = "Real-time weather data for AI agents" +authors = ["Your Name "] +license = "MIT" +repository = "https://github.com/username/weather-tool" +keywords = ["weather", "mcp", "tool"] +``` + +### 3. Test Locally + +Run final tests before publishing: + +```bash +ftl test +ftl up --port 3000 +``` + +### 4. Publish to Registry + +#### Default Registry (ghcr.io) + +```bash +# Publish with a version tag +ftl publish --tag v1.0.0 + +# This publishes to: ghcr.io/YOUR_USERNAME/weather-tool:v1.0.0 +``` + +#### Custom Registry + +```bash +# Publish to Docker Hub +ftl publish --registry docker.io --tag latest + +# Publish to a private registry +ftl publish --registry registry.company.com --tag v1.0.0 +``` + +## Version Management + +### Semantic Versioning + +Follow semantic versioning for your components: +- **Major** (1.0.0): Breaking changes +- **Minor** (0.1.0): New features, backward compatible +- **Patch** (0.0.1): Bug fixes + +### Version Tags + +```bash +# Publish specific version +ftl publish --tag v1.2.3 + +# Publish latest tag +ftl publish --tag latest + +# Publish with multiple tags +ftl publish --tag v1.2.3 +ftl publish --tag v1.2 +ftl publish --tag v1 +ftl publish --tag latest +``` + +## Using Published Components + +### In FTL Projects + +Reference published components in your project: + +```bash +# Add a published component to your project +ftl add weather --from ghcr.io/username/weather-tool:v1.0.0 +``` + +### Direct Usage with Spin + +```toml +# spin.toml +[[component]] +id = "weather" +source = { registry = "ghcr.io/username/weather-tool:v1.0.0" } +route = "/weather/..." +``` + +## Component Discovery + +### Public Registries + +Browse public components: +- GitHub: `https://github.com/orgs/ORG/packages` +- Docker Hub: `https://hub.docker.com/search` + +### Component Metadata + +Well-documented components include: +- Clear descriptions +- Usage examples +- API documentation +- License information + +## Best Practices + +### 1. Documentation + +Include comprehensive documentation: + +```markdown +# Weather Tool + +Real-time weather data for AI agents. + +## Installation + +```bash +ftl add weather --from ghcr.io/username/weather-tool:latest +``` + +## Tools + +- `get_weather`: Get current weather for a location +- `get_forecast`: Get weather forecast + +## Usage + +```typescript +const result = await callTool('get_weather', { + location: 'San Francisco', + units: 'fahrenheit' +}); +``` +``` + +### 2. Changelog + +Maintain a CHANGELOG.md: + +```markdown +# Changelog + +## [1.2.0] - 2024-01-15 +### Added +- Support for weather alerts +- Metric units option + +### Fixed +- Timezone handling for forecasts + +## [1.1.0] - 2024-01-01 +### Added +- 7-day forecast capability +``` + +### 3. Testing + +Include example tests: + +```typescript +// examples/test-weather.ts +import { Client } from '@modelcontextprotocol/sdk'; + +const client = new Client({ + url: 'http://localhost:3000/weather/mcp' +}); + +const weather = await client.callTool('get_weather', { + location: 'London' +}); +console.log(weather); +``` + +### 4. Security + +- Never include secrets in components +- Use environment variables for configuration +- Document required permissions +- Keep dependencies updated + +### 5. Component Size + +Optimize component size: +- Minimize dependencies +- Use tree-shaking for JavaScript +- Enable release optimizations +- Consider splitting large components + +## Advanced Publishing + +### Multi-Architecture Support + +Build for multiple architectures: + +```bash +# Build for multiple targets +ftl build --target wasm32-wasip1 +ftl build --target wasm32-wasip2 +``` + +### Automated Publishing + +GitHub Actions example: + +```yaml +name: Publish Component + +on: + push: + tags: + - 'v*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install FTL + run: cargo install ftl-cli + + - name: Build component + run: ftl build --release + + - name: Login to ghcr.io + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Publish + run: ftl publish --tag ${GITHUB_REF#refs/tags/} +``` + +### Component Signing + +Sign your components for security: + +```bash +# Sign with cosign +cosign sign ghcr.io/username/weather-tool:v1.0.0 + +# Verify signature +cosign verify ghcr.io/username/weather-tool:v1.0.0 +``` + +## Registry Management + +### List Published Versions + +```bash +# Using wkg +wkg list ghcr.io/username/weather-tool + +# Using docker +docker run --rm gcr.io/go-containerregistry/crane ls ghcr.io/username/weather-tool +``` + +### Delete Versions + +```bash +# Delete specific version +wkg delete ghcr.io/username/weather-tool:v0.1.0 + +# Delete using docker +docker run --rm gcr.io/go-containerregistry/crane delete ghcr.io/username/weather-tool:v0.1.0 +``` + +## Troubleshooting + +### Authentication Issues + +```bash +# Check current auth +docker config get-credential-helpers + +# Re-authenticate +docker logout ghcr.io +docker login ghcr.io +``` + +### Publishing Failures + +Common issues and solutions: + +1. **Permission denied** + - Check registry authentication + - Verify token permissions + +2. **Version already exists** + - Use a different version tag + - Delete existing version if needed + +3. **Size limits** + - Optimize component size + - Check registry limits + +### Registry Debugging + +```bash +# Verbose output +ftl publish --tag v1.0.0 --verbose + +# Check component size +du -h my-component/handler/target/wasm32-wasip1/release/*.wasm +``` + +## Next Steps + +- [Component Development](./components.md) - Build better components +- [Deployment Guide](./deployment.md) - Deploy published components +- [CLI Reference](./cli-reference.md) - Publishing command options \ No newline at end of file diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 00000000..c66cf948 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,212 @@ +# Quick Start Guide + +This guide will walk you through creating your first MCP component with FTL in under 5 minutes. + +## Prerequisites + +Before you begin, ensure you have: +- Rust toolchain installed ([rustup.rs](https://rustup.rs)) +- Node.js 20+ (for TypeScript/JavaScript components) + +## Installation + +Install the FTL CLI using cargo: + +```bash +cargo install ftl-cli +``` + +Or if you have cargo-binstall for faster installation: + +```bash +cargo binstall ftl-cli +``` + +## Create Your First Project + +### 1. Initialize a Project + +```bash +ftl init my-assistant +cd my-assistant +``` + +This creates a new FTL project with: +- `spin.toml` - The Spin manifest file +- `.gitignore` - Git ignore configuration + +### 2. Add a Component + +Let's add a TypeScript component that provides a simple echo tool: + +```bash +ftl add echo-tool --language typescript --description "A simple echo tool" +``` + +FTL will: +- Create the component directory structure +- Install the TypeScript SDK +- Set up the build configuration +- Add the component to your Spin manifest + +### 3. Explore the Component + +Navigate to your component: + +```bash +cd echo-tool +``` + +The component structure: +``` +echo-tool/ +├── ftl.toml # Component metadata +├── Makefile # Build commands +└── handler/ # Component source + ├── package.json # Node dependencies + ├── src/ + │ ├── index.ts # Main handler + │ └── features.ts # Tools, resources, prompts + └── test/ # Component tests +``` + +### 4. Customize Your Tool + +Open `handler/src/features.ts` and modify the echo tool: + +```typescript +import { createTool } from '@fastertools/ftl-sdk'; + +export const tools = [ + createTool({ + name: 'echo', + description: 'Echo a message with enthusiasm!', + inputSchema: { + type: 'object', + properties: { + message: { + type: 'string', + description: 'Message to echo back' + }, + excitement: { + type: 'number', + description: 'Excitement level (1-10)', + minimum: 1, + maximum: 10 + } + }, + required: ['message'] + }, + execute: async (args) => { + const message = args.message || 'Hello, world!'; + const excitement = args.excitement || 5; + const exclamation = '!'.repeat(excitement); + return `Echo: ${message}${exclamation}`; + } + }), +]; +``` + +### 5. Test Your Component + +Run the component tests: + +```bash +make test +``` + +### 6. Run Your Project + +Go back to the project root and start the development server: + +```bash +cd .. +ftl watch +``` + +Your MCP server is now running! The echo tool is available at: +- `http://localhost:3000/echo-tool/mcp` + +### 7. Test with an MCP Client + +You can test your MCP server using any MCP client. Here's a quick test using curl: + +```bash +# List available tools +curl -X POST http://localhost:3000/echo-tool/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/list", + "id": 1 + }' + +# Call the echo tool +curl -X POST http://localhost:3000/echo-tool/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "method": "tools/call", + "params": { + "name": "echo", + "arguments": { + "message": "Hello FTL", + "excitement": 8 + } + }, + "id": 2 + }' +``` + +## Next Steps + +### Add More Components + +Try adding components in different languages: + +```bash +# Add a Rust component +ftl add rust-tool --language rust --description "A Rust-powered tool" + +# Add a JavaScript component +ftl add js-tool --language javascript --description "A JavaScript tool" +``` + +### Build for Production + +```bash +# Build all components in release mode +ftl build --release + +# Run in production mode +ftl up --port 8080 +``` + +### Publish Your Component + +```bash +# Publish to GitHub Container Registry +ftl publish --tag v1.0.0 + +# Or publish to Docker Hub +ftl publish --registry docker.io --tag latest +``` + +## Common Commands + +| Command | Description | +|---------|-------------| +| `ftl init` | Create a new project | +| `ftl add` | Add a component to the project | +| `ftl build` | Build components | +| `ftl test` | Run component tests | +| `ftl watch` | Start dev server with hot reload | +| `ftl up` | Run the project | +| `ftl publish` | Publish to registry | + +## Learn More + +- [Component Development](./components.md) - Deep dive into building components +- [CLI Reference](./cli-reference.md) - Complete command documentation +- [Publishing Guide](./publishing.md) - Share your components +- [Deployment Guide](./deployment.md) - Deploy to production \ No newline at end of file diff --git a/docs/sdk-reference.md b/docs/sdk-reference.md new file mode 100644 index 00000000..f8fefe2c --- /dev/null +++ b/docs/sdk-reference.md @@ -0,0 +1,525 @@ +# SDK Reference + +This document provides a complete reference for the FTL SDKs in TypeScript/JavaScript and Rust. + +## TypeScript/JavaScript SDK + +### Installation + +```bash +npm install @fastertools/ftl-sdk +``` + +### Core Functions + +#### `createHandler(features)` + +Creates an MCP handler with the specified features. + +```typescript +import { createHandler } from '@fastertools/ftl-sdk'; + +export const handler = createHandler({ + tools: [...], // Array of tools + resources: [...], // Array of resources + prompts: [...] // Array of prompts +}); +``` + +#### `createTool(config)` + +Creates a tool that can be called by AI agents. + +```typescript +const tool = createTool({ + name: string, // Unique tool identifier + description: string, // Human-readable description + inputSchema: object, // JSON Schema for input validation + execute: async (args) => string // Tool implementation +}); +``` + +**Example:** +```typescript +const calculateTool = createTool({ + name: 'calculate', + description: 'Perform mathematical calculations', + inputSchema: { + type: 'object', + properties: { + expression: { + type: 'string', + description: 'Mathematical expression to evaluate' + } + }, + required: ['expression'] + }, + execute: async (args) => { + const result = evaluateExpression(args.expression); + return `Result: ${result}`; + } +}); +``` + +#### `createResource(config)` + +Creates a resource that can be read by AI agents. + +```typescript +const resource = createResource({ + uri: string, // Resource URI + name: string, // Display name + description?: string, // Resource description + mimeType?: string, // Content MIME type + read: async () => string // Resource reader +}); +``` + +**Example:** +```typescript +const configResource = createResource({ + uri: 'config://app-settings', + name: 'Application Settings', + description: 'Current application configuration', + mimeType: 'application/json', + read: async () => { + const config = await loadConfig(); + return JSON.stringify(config, null, 2); + } +}); +``` + +#### `createPrompt(config)` + +Creates a reusable prompt template. + +```typescript +const prompt = createPrompt({ + name: string, // Prompt identifier + description?: string, // Prompt description + arguments?: Array<{ // Prompt arguments + name: string, + description?: string, + required?: boolean + }>, + resolve: async (args) => PromptMessage[] +}); + +interface PromptMessage { + role: 'user' | 'assistant'; + content: string; +} +``` + +**Example:** +```typescript +const analysisPrompt = createPrompt({ + name: 'analyze_code', + description: 'Generate code analysis prompt', + arguments: [ + { name: 'language', description: 'Programming language', required: true }, + { name: 'code', description: 'Code to analyze', required: true } + ], + resolve: async (args) => { + return [ + { + role: 'user', + content: `Analyze this ${args.language} code:\n\n${args.code}` + }, + { + role: 'assistant', + content: 'I\'ll analyze this code for bugs, performance, and best practices.' + } + ]; + } +}); +``` + +### Types + +```typescript +interface Tool { + name: string; + description: string; + inputSchema: object; + execute: (args: TArgs) => string | Promise; +} + +interface Resource { + uri: string; + name: string; + description?: string; + mimeType?: string; + read: () => string | Promise; +} + +interface Prompt { + name: string; + description?: string; + arguments?: Array<{ + name: string; + description?: string; + required?: boolean; + }>; + resolve: (args: TArgs) => PromptMessage[] | Promise; +} +``` + +## Rust SDK + +### Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +ftl-sdk = "0.2.0" +``` + +### Core Macros + +#### `create_handler!` + +Creates an MCP handler with the specified functions. + +```rust +use ftl_sdk::*; + +create_handler!( + tools: get_tools, + resources: get_resources, + prompts: get_prompts +); + +fn get_tools() -> Vec { vec![] } +fn get_resources() -> Vec { vec![] } +fn get_prompts() -> Vec { vec![] } +``` + +#### `tool!` + +Creates a tool definition. + +```rust +tool!( + name: &str, // Tool name + description: &str, // Tool description + schema: serde_json::Value, // Input schema + handler: fn // Handler function +) +``` + +**Example:** +```rust +use serde::{Deserialize, Serialize}; +use serde_json::json; + +#[derive(Deserialize)] +struct CalculateArgs { + expression: String, +} + +fn calculate(args: CalculateArgs) -> Result { + let result = evaluate_expression(&args.expression)?; + Ok(format!("Result: {}", result)) +} + +fn get_tools() -> Vec { + vec![ + tool!( + "calculate", + "Perform mathematical calculations", + json!({ + "type": "object", + "properties": { + "expression": { + "type": "string", + "description": "Mathematical expression" + } + }, + "required": ["expression"] + }), + calculate + ) + ] +} +``` + +#### `resource!` + +Creates a resource definition. + +```rust +resource!( + uri: &str, // Resource URI + name: &str, // Display name + mime_type: &str, // MIME type + reader: fn() -> Result +) +``` + +**Example:** +```rust +fn read_config() -> Result { + let config = load_configuration()?; + serde_json::to_string_pretty(&config) + .map_err(|e| e.to_string()) +} + +fn get_resources() -> Vec { + vec![ + resource!( + "config://settings", + "Application Settings", + "application/json", + read_config + ) + ] +} +``` + +#### `prompt!` + +Creates a prompt definition. + +```rust +prompt!( + name: &str, // Prompt name + description: &str, // Description + arguments: Vec, // Arguments + resolver: fn // Resolver function +) +``` + +**Example:** +```rust +#[derive(Deserialize)] +struct AnalyzeArgs { + language: String, + code: String, +} + +fn analyze_prompt(args: AnalyzeArgs) -> Result, String> { + Ok(vec![ + PromptMessage { + role: Role::User, + content: format!("Analyze this {} code:\n\n{}", + args.language, args.code), + }, + PromptMessage { + role: Role::Assistant, + content: "I'll analyze this code for bugs and improvements.".to_string(), + } + ]) +} + +fn get_prompts() -> Vec { + vec![ + prompt!( + "analyze_code", + "Generate code analysis prompt", + vec![ + PromptArg::new("language", "Programming language", true), + PromptArg::new("code", "Code to analyze", true), + ], + analyze_prompt + ) + ] +} +``` + +### Types + +```rust +pub struct Tool { + pub name: String, + pub description: String, + pub input_schema: serde_json::Value, + pub handler: Box Result>, +} + +pub struct Resource { + pub uri: String, + pub name: String, + pub description: Option, + pub mime_type: Option, + pub reader: Box Result>, +} + +pub struct Prompt { + pub name: String, + pub description: Option, + pub arguments: Vec, + pub resolver: Box Result, String>>, +} + +pub struct PromptArg { + pub name: String, + pub description: Option, + pub required: bool, +} + +pub struct PromptMessage { + pub role: Role, + pub content: String, +} + +pub enum Role { + User, + Assistant, +} +``` + +## Error Handling + +### TypeScript/JavaScript + +Return error information as part of the response: + +```typescript +execute: async (args) => { + try { + const result = await riskyOperation(args); + return JSON.stringify({ success: true, data: result }); + } catch (error) { + return JSON.stringify({ + success: false, + error: error.message, + code: error.code || 'UNKNOWN_ERROR' + }); + } +} +``` + +### Rust + +Use `Result` for error handling: + +```rust +fn my_tool(args: MyArgs) -> Result { + match risky_operation(&args) { + Ok(result) => Ok(format!("Success: {}", result)), + Err(e) => Err(format!("Error: {}", e)), + } +} +``` + +## Best Practices + +### 1. Input Validation + +Always validate inputs using JSON Schema: + +```typescript +inputSchema: { + type: 'object', + properties: { + url: { + type: 'string', + format: 'uri', + pattern: '^https?://' + }, + timeout: { + type: 'integer', + minimum: 1, + maximum: 30000 + } + }, + required: ['url'], + additionalProperties: false +} +``` + +### 2. Async Operations + +Handle async operations properly: + +```typescript +// TypeScript +execute: async (args) => { + const results = await Promise.all([ + fetchData(args.url1), + fetchData(args.url2) + ]); + return JSON.stringify(results); +} +``` + +```rust +// Rust +use tokio::runtime::Runtime; + +fn async_tool(args: Args) -> Result { + let rt = Runtime::new().map_err(|e| e.to_string())?; + rt.block_on(async { + let data = fetch_data(&args.url).await?; + Ok(format!("Data: {}", data)) + }) +} +``` + +### 3. Resource Cleanup + +Always clean up resources: + +```typescript +let client; +try { + client = await createClient(args.config); + return await client.query(args.query); +} finally { + if (client) { + await client.close(); + } +} +``` + +### 4. Structured Responses + +Return structured data when possible: + +```typescript +execute: async (args) => { + const result = { + status: 'success', + data: { + count: 42, + items: ['a', 'b', 'c'] + }, + metadata: { + timestamp: new Date().toISOString(), + version: '1.0.0' + } + }; + return JSON.stringify(result, null, 2); +} +``` + +## Migration Guide + +### From MCP SDK to FTL SDK + +If you're migrating from the standard MCP SDK: + +**Before (MCP SDK):** +```typescript +const server = new Server({ + name: 'my-server', + version: '1.0.0' +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { tools: [...] }; +}); +``` + +**After (FTL SDK):** +```typescript +export const handler = createHandler({ + tools: [...], + resources: [...], + prompts: [...] +}); +``` + +The FTL SDK handles all the MCP protocol details for you! \ No newline at end of file