diff --git a/crates/codegen/src/main.rs b/crates/codegen/src/main.rs index c6fc08148..8a9196788 100644 --- a/crates/codegen/src/main.rs +++ b/crates/codegen/src/main.rs @@ -68,7 +68,7 @@ fn main() { generator .generate( - PathBuf::from("package/src/api-types.ts"), + PathBuf::from("package/src/internal/raw-api-types.ts"), TypeScriptRenderer::default(), ) .unwrap(); diff --git a/crates/pdk-api/src/api/build_source.rs b/crates/pdk-api/src/api/build_source.rs index b80d0a795..37aa070a4 100644 --- a/crates/pdk-api/src/api/build_source.rs +++ b/crates/pdk-api/src/api/build_source.rs @@ -37,14 +37,16 @@ api_struct!( api_enum!( /// The location in which source code can be acquired. - #[serde(tag = "type", rename_all = "kebab-case")] + #[serde(tag = "type")] pub enum SourceLocation { /// Downloaded from an archive. #[cfg_attr(feature = "schematic", schema(nested))] + #[serde(rename = "kebab-case")] Archive(ArchiveSource), /// Cloned from a Git repository. #[cfg_attr(feature = "schematic", schema(nested))] + #[serde(rename = "kebab-case")] Git(GitSource), } ); @@ -80,42 +82,56 @@ impl CommandInstruction { api_enum!( /// An instruction to execute. - #[serde(tag = "type", content = "instruction", rename_all = "kebab-case")] + #[serde(tag = "type", content = "instruction")] pub enum BuildInstruction { /// Update a file and make it executable. + #[serde(rename = "kebab-case")] MakeExecutable(PathBuf), /// Move a file from source to destination. + #[serde(rename = "kebab-case")] MoveFile(PathBuf, PathBuf), /// Remove a directory. + #[serde(rename = "kebab-case")] RemoveDir(PathBuf), /// Remove a file. + #[serde(rename = "kebab-case")] RemoveFile(PathBuf), /// Request (curl, wget, etc) a script and download to the host. + #[serde(rename = "kebab-case")] RequestScript(String), /// Execute a command as a child process. #[cfg_attr(feature = "schematic", schema(nested))] + #[serde(rename = "kebab-case")] RunCommand(Box), } ); api_enum!( /// Is required and must exist in the current environment. - #[serde(tag = "type", content = "requirement", rename_all = "kebab-case")] + #[serde(tag = "type", content = "requirement")] pub enum BuildRequirement { + #[serde(rename = "kebab-case")] CommandExistsOnPath(String), + #[serde(rename = "kebab-case")] ManualIntercept(String), // url + #[serde(rename = "kebab-case")] GitConfigSetting(String, String), + #[serde(rename = "kebab-case")] GitVersion(VersionReq), + #[serde(rename = "kebab-case")] PythonVersion(VersionReq), + #[serde(rename = "kebab-case")] RubyVersion(VersionReq), // macOS + #[serde(rename = "kebab-case")] XcodeCommandLineTools, // Windows + #[serde(rename = "kebab-case")] WindowsDeveloperMode, } ); diff --git a/crates/pdk-api/src/api/mod.rs b/crates/pdk-api/src/api/mod.rs index b66cc2727..3d28ae441 100644 --- a/crates/pdk-api/src/api/mod.rs +++ b/crates/pdk-api/src/api/mod.rs @@ -34,6 +34,7 @@ api_enum!( #[default] Language, DependencyManager, + #[serde(rename = "CLI")] CLI, } ); diff --git a/crates/warpgate-api/src/host_funcs.rs b/crates/warpgate-api/src/host_funcs.rs index 17e462342..60ea6354b 100644 --- a/crates/warpgate-api/src/host_funcs.rs +++ b/crates/warpgate-api/src/host_funcs.rs @@ -5,11 +5,13 @@ use rustc_hash::FxHashMap; api_enum!( /// Target where host logs should be written to. #[derive(Default)] - #[serde(rename_all = "lowercase")] pub enum HostLogTarget { + #[serde(rename = "stderr")] Stderr, + #[serde(rename = "stdout")] Stdout, #[default] + #[serde(rename = "tracing")] Tracing, } ); diff --git a/crates/warpgate-api/src/lib.rs b/crates/warpgate-api/src/lib.rs index 774f7370d..cf40519f7 100644 --- a/crates/warpgate-api/src/lib.rs +++ b/crates/warpgate-api/src/lib.rs @@ -16,6 +16,7 @@ macro_rules! api_struct { #[derive(Clone, Debug, Default, serde::Deserialize, PartialEq, serde::Serialize)] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] #[serde(default)] + #[serde(rename_all = "snake_case")] $struct }; } @@ -26,6 +27,7 @@ macro_rules! api_enum { ($struct:item) => { #[derive(Clone, Debug, serde::Deserialize, PartialEq, serde::Serialize)] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] + #[serde(rename_all = "PascalCase")] $struct }; } diff --git a/package/.gitignore b/package/.gitignore new file mode 100644 index 000000000..7951405f8 --- /dev/null +++ b/package/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/package/.prototools b/package/.prototools new file mode 100644 index 000000000..58cda4a64 --- /dev/null +++ b/package/.prototools @@ -0,0 +1,9 @@ +node = "20.12.1" +npm = "10.5.0" +extism-js = "1.0.0-rc9" + +[plugins] +extism-js = "source:./extism-js.toml" + +# silly workaround for root .prototools potentially pointing at non-existing plugin +wasm-test = "source:./extism-js.toml" \ No newline at end of file diff --git a/package/CONTRIBUTING.md b/package/CONTRIBUTING.md new file mode 100644 index 000000000..00c05ff11 --- /dev/null +++ b/package/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing to @moonrepo/proto-pdk + +## Prerequisites + +Everything except [binaryen](https://github.com/WebAssembly/binaryen) is currently +managed via the .prototools. + +## Building + +```shell +npm run build +``` + +## Testing + +```shell +# run tests and watch for changes +npm test + +# run tests and exit +npm test run +``` + +## E2E tests + +Refer to [the test plugin](./test/README.md). diff --git a/package/README.md b/package/README.md new file mode 100644 index 000000000..ae3b03eff --- /dev/null +++ b/package/README.md @@ -0,0 +1,36 @@ +# @moonrepo/proto-pdk + +A plugin development kit for creating proto WASM plugins using JS/TS. + +> [!IMPORTANT] +> The Extism JavaScript PDK does not currently provide filesystem APIs, hence +> some functionality, such as custom checksumming, is difficult to achieve using +> this PDK. +> +> If you need this you're better off using the [Rust PDK](../crates/pdk) for now. + +## Prerequisites + +You will need: + +- [extism-js >=v1.0.0-rc9](https://github.com/extism/js-pdk) + +## Installation + +Not yet published, please check back later. + + + +## Usage + +[The test plugin](./test) is currently the best reference for a complete TypeScript-based example, +until we get the PDK properly documented. + +Neither TypeScript or ESBuild is a requirement. A bundler (like ESBuild) is however, and the bundled output +should target at ES2020 or below, while using CommonJS as the format. + +It is strongly recommended to minify your bundled JavaScript, as it greatly affects the final WASM file size. diff --git a/package/extism-js.toml b/package/extism-js.toml new file mode 100644 index 000000000..c96e83ea4 --- /dev/null +++ b/package/extism-js.toml @@ -0,0 +1,24 @@ +name = "extism-js" +type = "cli" + +[resolve] +git-url = "https://github.com/extism/js-pdk" + +[platform.linux] +archs = ["x86_64", "aarch64"] +download-file = "extism-js-{arch}-linux-v{version}.gz" +checksum-file = "extism-js-{arch}-linux-v{version}.gz.sha256" +bin-path = "extism-js-{arch}-linux-v{version}" + +[platform.macos] +archs = ["x86_64", "aarch64"] +download-file = "extism-js-{arch}-macos-v{version}.gz" +checksum-file = "extism-js-{arch}-macos-v{version}.gz.sha256" +bin-path = "extism-js-{arch}-macos-v{version}" + +[platform.windows] +archs = [] + +[install] +download-url = "https://github.com/extism/js-pdk/releases/download/v{version}/{download_file}" +checksum-url = "https://github.com/extism/js-pdk/releases/download/v{version}/{checksum_file}" \ No newline at end of file diff --git a/package/package-lock.json b/package/package-lock.json new file mode 100644 index 000000000..9d2fb48b8 --- /dev/null +++ b/package/package-lock.json @@ -0,0 +1,1625 @@ +{ + "name": "@moonrepo/proto-pdk", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@moonrepo/proto-pdk", + "dependencies": { + "@extism/js-pdk": "^1.0.1", + "@types/node": "^20.12.7", + "@types/semver": "^7.5.8", + "pathe": "^1.1.2", + "semver": "^7.6.0", + "type-fest": "^4.15.0", + "urlpattern-polyfill": "^8.0.2" + }, + "devDependencies": { + "typescript": "^5.5.0-beta", + "vitest": "^1.5.0" + }, + "engines": { + "node": "20.12.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@extism/js-pdk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.0.1.tgz", + "integrity": "sha512-YJWfHGeOuJnQw4V8NPNHvbSr6S8iDd2Ga6VEukwlRP7tu62ozTxIgokYw8i+rajD/16zz/gK0KYARBpm2qPAmQ==" + }, + "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, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.2.tgz", + "integrity": "sha512-ahxSgCkAEk+P/AVO0vYr7DxOD3CwAQrT0Go9BJyGQ9Ef0QxVOfjDZMiF4Y2s3mLyPrjonchIMH/tbWHucJMykQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.2.tgz", + "integrity": "sha512-lAarIdxZWbFSHFSDao9+I/F5jDaKyCqAPMq5HqnfpBw8dKDiCaaqM0lq5h1pQTLeIqueeay4PieGR5jGZMWprw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.2.tgz", + "integrity": "sha512-SWsr8zEUk82KSqquIMgZEg2GE5mCSfr9sE/thDROkX6pb3QQWPp8Vw8zOq2GyxZ2t0XoSIUlvHDkrf5Gmf7x3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.2.tgz", + "integrity": "sha512-o/HAIrQq0jIxJAhgtIvV5FWviYK4WB0WwV91SLUnsliw1lSAoLsmgEEgRWzDguAFeUEUUoIWXiJrPqU7vGiVkA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.2.tgz", + "integrity": "sha512-nwlJ65UY9eGq91cBi6VyDfArUJSKOYt5dJQBq8xyLhvS23qO+4Nr/RreibFHjP6t+5ap2ohZrUJcHv5zk5ju/g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.2.tgz", + "integrity": "sha512-Pg5TxxO2IVlMj79+c/9G0LREC9SY3HM+pfAwX7zj5/cAuwrbfj2Wv9JbMHIdPCfQpYsI4g9mE+2Bw/3aeSs2rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.2.tgz", + "integrity": "sha512-cAOTjGNm84gc6tS02D1EXtG7tDRsVSDTBVXOLbj31DkwfZwgTPYZ6aafSU7rD/4R2a34JOwlF9fQayuTSkoclA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.2.tgz", + "integrity": "sha512-4RyT6v1kXb7C0fn6zV33rvaX05P0zHoNzaXI/5oFHklfKm602j+N4mn2YvoezQViRLPnxP8M1NaY4s/5kXO5cw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.2.tgz", + "integrity": "sha512-KNUH6jC/vRGAKSorySTyc/yRYlCwN/5pnMjXylfBniwtJx5O7X17KG/0efj8XM3TZU7raYRXJFFReOzNmL1n1w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.2.tgz", + "integrity": "sha512-xPV4y73IBEXToNPa3h5lbgXOi/v0NcvKxU0xejiFw6DtIYQqOTMhZ2DN18/HrrP0PmiL3rGtRG9gz1QE8vFKXQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.2.tgz", + "integrity": "sha512-QBhtr07iFGmF9egrPOWyO5wciwgtzKkYPNLVCFZTmr4TWmY0oY2Dm/bmhHjKRwZoGiaKdNcKhFtUMBKvlchH+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.2.tgz", + "integrity": "sha512-8zfsQRQGH23O6qazZSFY5jP5gt4cFvRuKTpuBsC1ZnSWxV8ZKQpPqOZIUtdfMOugCcBvFGRa1pDC/tkf19EgBw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.2.tgz", + "integrity": "sha512-H4s8UjgkPnlChl6JF5empNvFHp77Jx+Wfy2EtmYPe9G22XV+PMuCinZVHurNe8ggtwoaohxARJZbaH/3xjB/FA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.2.tgz", + "integrity": "sha512-djqpAjm/i8erWYF0K6UY4kRO3X5+T4TypIqw60Q8MTqSBaQNpNXDhxdjpZ3ikgb+wn99svA7jxcXpiyg9MUsdw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.2.tgz", + "integrity": "sha512-teAqzLT0yTYZa8ZP7zhFKEx4cotS8Tkk5XiqNMJhD4CpaWB1BHARE4Qy+RzwnXvSAYv+Q3jAqCVBS+PS+Yee8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "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 + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" + }, + "node_modules/@vitest/expect": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz", + "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz", + "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.5.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz", + "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==", + "dev": true, + "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.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dev": true, + "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.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "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, + "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, + "engines": { + "node": "*" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "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.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "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, + "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, + "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/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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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, + "engines": { + "node": "*" + } + }, + "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, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=16.17.0" + } + }, + "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, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "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 + }, + "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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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, + "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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "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, + "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, + "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==" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "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, + "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/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.2.tgz", + "integrity": "sha512-WkeoTWvuBoFjFAhsEOHKRoZ3r9GfTyhh7Vff1zwebEFLEFjT1lG3784xEgKiTa7E+e70vsC81roVL2MP4tgEEQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.2", + "@rollup/rollup-android-arm64": "4.14.2", + "@rollup/rollup-darwin-arm64": "4.14.2", + "@rollup/rollup-darwin-x64": "4.14.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.2", + "@rollup/rollup-linux-arm64-gnu": "4.14.2", + "@rollup/rollup-linux-arm64-musl": "4.14.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.2", + "@rollup/rollup-linux-riscv64-gnu": "4.14.2", + "@rollup/rollup-linux-s390x-gnu": "4.14.2", + "@rollup/rollup-linux-x64-gnu": "4.14.2", + "@rollup/rollup-linux-x64-musl": "4.14.2", + "@rollup/rollup-win32-arm64-msvc": "4.14.2", + "@rollup/rollup-win32-ia32-msvc": "4.14.2", + "@rollup/rollup-win32-x64-msvc": "4.14.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "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, + "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, + "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 + }, + "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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "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 + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", + "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.3.tgz", + "integrity": "sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==", + "dev": true, + "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, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.15.0.tgz", + "integrity": "sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.5.0-dev.20240510", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.0-dev.20240510.tgz", + "integrity": "sha512-3gFPNJ5OUK03t5wwejsRTnUPIuZMCu2feY3b3TvQd98IVIJWQmiOUdEBWQaCpwXTtgHS6veyVSbCIDvh0HQwYg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==" + }, + "node_modules/vite": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.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": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz", + "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==", + "dev": true, + "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.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "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.5.0", + "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.5.0", + "@vitest/ui": "1.5.0", + "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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package/package.json b/package/package.json index 17b345e72..095cd1a4c 100644 --- a/package/package.json +++ b/package/package.json @@ -1,3 +1,36 @@ { - "name": "@moonrepo/proto-pdk" + "name": "@moonrepo/proto-pdk", + "description": "A plugin development kit for creating proto WASM plugins.", + "type": "module", + "module": "./lib/index.js", + "types": "./lib/index.d.ts", + "scripts": { + "build": "tsc", + "test": "vitest", + "lint": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit" + }, + "dependencies": { + "@extism/js-pdk": "^1.0.1", + "@types/node": "^20.12.7", + "@types/semver": "^7.5.8", + "pathe": "^1.1.2", + "semver": "^7.6.0", + "type-fest": "^4.15.0", + "urlpattern-polyfill": "^8.0.2" + }, + "//": "typescript 5.5 beta is used as we rely on the new preserve attribute for triple-slash directives", + "devDependencies": { + "typescript": "^5.5.0-beta", + "vitest": "^1.5.0" + }, + "exports": { + ".": { + "default": "./lib/index.js", + "types": "./lib/index.d.ts" + } + }, + "engines": { + "node": "20.12.1" + }, + "packageManager": "npm@10.5.1" } diff --git a/package/src/api.ts b/package/src/api.ts new file mode 100644 index 000000000..c86eb0699 --- /dev/null +++ b/package/src/api.ts @@ -0,0 +1,18 @@ +import type * as raw from "./internal/raw-api-types"; +import type { VersionSpec } from "./versions"; +import type { SemVer } from "semver"; +import type { OverrideProperties } from "type-fest"; + +/** Information about the current state of the tool. */ +export type ToolContext = OverrideProperties< + raw.ToolContext, + { + /** The version of proto (the core crate) calling plugin functions. */ + proto_version: SemVer | null; + + /** Current version. Will be a "latest" alias if not resolved. */ + version: VersionSpec; + } +>; + +export type { PluginType } from "./internal/raw-api-types"; diff --git a/package/src/errors.ts b/package/src/errors.ts new file mode 100644 index 000000000..3937e796c --- /dev/null +++ b/package/src/errors.ts @@ -0,0 +1,81 @@ +export type PluginErrorOptions = { + /** + * The original cause of the error. + * + * Homebrew version of ES2022 Error.cause, which we can't use as the + * Extism doesn't yet support it. + */ + cause?: unknown; + returnCode?: number; +}; + +/** + * An error containing a return code for Extism. + * + * NOTE: There's currently no way in the JS PDK to set an error along + * with the specified return code. Thus this is currently semi useless. + */ +export class PluginError extends Error { + returnCode: number; + cause?: unknown; + + constructor(message?: string, options: PluginErrorOptions = {}) { + super(message); + + this.returnCode = options.returnCode ?? 1; + + if ("cause" in options) { + this.cause = options.cause; + } + + if (this.cause instanceof Error && "stack" in this.cause) { + if ("stack" in this) { + // suuuper gross, but otherwise we lose the stack due to lack of native `Error.cause` + this.stack = `${this.stack.split("\n").slice(0, 2).join("\n")}\n${ + this.cause.stack + }`; + } else { + this.stack = this.cause.stack; + } + } + } +} + +export class UnsupportedOSError extends PluginError { + constructor( + { tool, os }: { tool: string; os: string }, + options?: PluginErrorOptions + ) { + super(`Unable to install ${tool}, unsupported OS ${os}.`, options); + } +} + +export class UnsupportedArchError extends PluginError { + constructor( + { tool, arch }: { tool: string; arch: string }, + options?: PluginErrorOptions + ) { + super( + `Unable to install ${tool}, unsupported architecture ${arch}.`, + options + ); + } +} + +export class UnsupportedCanaryError extends PluginError { + constructor({ tool }: { tool: string }, options?: PluginErrorOptions) { + super(`${tool} does not support canary/nightly versions.`, options); + } +} + +export class UnsupportedTargetError extends PluginError { + constructor( + { tool, arch, os }: { tool: string; arch: string; os: string }, + options?: PluginErrorOptions + ) { + super( + `"Unable to install ${tool}, unsupported architecture ${arch} for ${os}."`, + options + ); + } +} diff --git a/package/src/helpers.ts b/package/src/helpers.ts new file mode 100644 index 000000000..851b71313 --- /dev/null +++ b/package/src/helpers.ts @@ -0,0 +1,82 @@ +import { UnsupportedOSError, UnsupportedTargetError } from "./errors"; +import type { HostArch, HostEnvironment, HostOS } from "./warpgate/api"; + +type Permutations = Partial>; + +/** + * Validate the current host OS and architecture against the + * supported list of target permutations. + */ +export function checkSupportedOsAndArch( + tool: string, + { os, arch }: HostEnvironment, + permutations: Permutations +) { + const archs = permutations[os]; + + if (!archs) { + throw new UnsupportedOSError({ tool, os }); + } + + if (!archs.includes(arch)) { + throw new UnsupportedTargetError({ tool, arch, os }); + } +} + +/** + * Return a Rust target triple for the current host OS and architecture. + */ +export function getTargetTriple( + { os, arch, libc }: HostEnvironment, + tool: string +): string { + switch (os) { + case "linux": + return `${toRustArch(arch)}-unknown-linux-${ + libc === "musl" ? "musl" : "gnu" + }`; + case "macos": + return `${toRustArch(arch)}-apple-darwin`; + case "windows": + return `${toRustArch(arch)}-pc-windows-msvc`; + default: + throw new UnsupportedTargetError({ tool, os, arch }); + } +} + +/** + * Get proto tool configuration that was configured in a `.prototools` file. + */ +export function getToolConfig( + defaults: Record = {} +): Record { + const json = Config.get("proto_tool_config"); + + if (!json) return defaults; + + try { + return { + ...defaults, + ...JSON.parse(json), + }; + } catch { + console.error("Unable to parse tool config"); + return defaults; + } +} + +/** + * Convert to a [`std::env::costs::ARCH`] compatible string. + */ +export function toRustArch(arch: HostArch): string { + switch (arch) { + case "x64": + return "x86_64"; + case "arm64": + return "aarch64"; + case "longarm64": + return "loongarch64"; + default: + return arch; + } +} diff --git a/package/src/index.ts b/package/src/index.ts new file mode 100644 index 000000000..79823d3ac --- /dev/null +++ b/package/src/index.ts @@ -0,0 +1,15 @@ +/// +/// + +import "./internal/patch-console"; + +import path from "pathe"; +export { path }; + +export * from "./api"; +export * from "./errors"; +export * from "./helpers"; +export * from "./warpgate/host-functions"; +export * from "./plugin-functions"; +export * from "./versions"; +export * from "./warpgate"; diff --git a/package/src/internal/create-plugin-function.ts b/package/src/internal/create-plugin-function.ts new file mode 100644 index 000000000..e63c5263b --- /dev/null +++ b/package/src/internal/create-plugin-function.ts @@ -0,0 +1,79 @@ +import { SemVer } from "semver"; + +export type CreatePluginFnFactoryOptions = { + /** + * Postprocessor for the plugin function input to turn raw JSON values into class instances. + */ + reviveInput?: (input: any) => I; + + /** + * Preprocessor for the plugin function output, before stringifying it with JSON.stringify. + * + * Currently unused as we opt for implementing toJSON in our classes when possible, + * as it greatly simplifies defining these plugin function creators. + */ + replaceOutput?: (output: O) => any; +}; + +/** + * Creates a new plugin function helper method. + */ +export function createPluginFnFactory({ + reviveInput = (input) => input, + replaceOutput = (output) => output, +}: CreatePluginFnFactoryOptions = {}) { + function getInput(): I { + const inputString = Host.inputString(); + let input: unknown = undefined; + + try { + input = JSON.parse(inputString); + } catch {} + + return reviveInput ? reviveInput(input) : (input as I); + } + + function setOutput(output: O) { + const replaced = replaceOutput ? replaceOutput(output) : output; + const outputString = JSON.stringify(replaced, baseJsonReplacer) ?? ""; + + if (outputString) { + Host.outputString(outputString); + } + } + + // I would've liked to allow returning Promise, but then the JS PDK gets confused + // when an error is thrown and returns success with neither an output nor error. + return function createPluginFn(impl: (input: I) => O) { + return () => { + try { + const input = getInput(); + const output = impl(input); + setOutput(output as O); + } catch (e) { + // The pdk automatically calls error_set with the message of unhandled errors and uses + // -1 as the return code. There's no way for us to change that return code currently, + // but this is where we'd do it. + throw e; + } + }; + }; +} + +/** + * In our own classes, we just implement toJSON(), but externally owned + * classes, such as SemVer, we need to handle manually. + * + * The alternative would be monkey-patching their prototype or needing to + * always manually handle them via `replaceOutput` function in our plugin + * function creators. + * + * Note: this is called during JSON.stringify, so _after_ `replaceOutput`. + */ +const baseJsonReplacer = (_key: string, value: any) => { + if (value instanceof SemVer) { + return value.toString(); + } + + return value; +}; diff --git a/package/src/internal/helpers.ts b/package/src/internal/helpers.ts new file mode 100644 index 000000000..de60a5959 --- /dev/null +++ b/package/src/internal/helpers.ts @@ -0,0 +1,12 @@ +import { SemVer } from "semver"; +import type * as raw from "./raw-api-types"; +import type { ToolContext } from "../api"; +import { VersionSpec } from "../versions"; + +export function reviveToolContext(input: raw.ToolContext): ToolContext { + return { + ...input, + proto_version: input.proto_version ? new SemVer(input.proto_version) : null, + version: VersionSpec.parse(input.version), + }; +} diff --git a/package/src/internal/patch-console.ts b/package/src/internal/patch-console.ts new file mode 100644 index 000000000..ca5a6738e --- /dev/null +++ b/package/src/internal/patch-console.ts @@ -0,0 +1,3 @@ +// remove these if https://github.com/extism/js-pdk/pull/68 is merged +(console as any).info ??= console.log; +(console as any).debug ??= console.log; diff --git a/package/src/api-types.ts b/package/src/internal/raw-api-types.ts similarity index 91% rename from package/src/api-types.ts rename to package/src/internal/raw-api-types.ts index 17b763f85..1965b3453 100644 --- a/package/src/api-types.ts +++ b/package/src/internal/raw-api-types.ts @@ -5,15 +5,15 @@ /** Information about the current state of the tool. */ export interface ToolContext { /** The version of proto (the core crate) calling plugin functions. */ - protoVersion: string | null; + proto_version: string | null; /** Virtual path to the tool's installation directory. */ - toolDir: string; + tool_dir: string; /** Current version. Will be a "latest" alias if not resolved. */ version: string; } /** Supported types of plugins. */ -export type PluginType = 'language' | 'dependency-manager' | 'cli'; +export type PluginType = 'Language' | 'DependencyManager' | 'CLI'; /** Input passed to the `register_tool` function. */ export interface ToolMetadataInput { @@ -24,31 +24,31 @@ export interface ToolMetadataInput { /** Controls aspects of the tool inventory. */ export interface ToolInventoryMetadata { /** Disable progress bars when installing or uninstalling tools. */ - disableProgressBars: boolean; + disable_progress_bars: boolean; /** * Override the tool inventory directory (where all versions are installed). * This is an advanced feature and should only be used when absolutely necessary. */ - overrideDir: string | null; + override_dir: string | null; /** Suffix to append to all versions when labeling directories. */ - versionSuffix: string | null; + version_suffix: string | null; } /** Output returned by the `register_tool` function. */ export interface ToolMetadataOutput { /** Default alias or version to use as a fallback. */ - defaultVersion: string | null; + default_version: string | null; /** Controls aspects of the tool inventory. */ inventory: ToolInventoryMetadata; /** Human readable name of the tool. */ name: string; /** Version of the plugin. */ - pluginVersion: string | null; + plugin_version: string | null; /** * Names of commands that will self-upgrade the tool, * and should be blocked from happening. */ - selfUpgradeCommands: string[]; + self_upgrade_commands: string[]; /** Type of the tool. */ type: PluginType; } @@ -83,7 +83,7 @@ export interface NativeInstallInput { /** Current tool context. */ context: ToolContext; /** Virtual directory to install to. */ - installDir: string; + install_dir: string; } /** Output returned by the `native_install` function. */ @@ -93,7 +93,7 @@ export interface NativeInstallOutput { /** Whether the install was successful. */ installed: boolean; /** Whether to skip the install process or not. */ - skipInstall: boolean; + skip_install: boolean; } /** Input passed to the `native_uninstall` function. */ @@ -107,7 +107,7 @@ export interface NativeUninstallOutput { /** Error message if the uninstall failed. */ error: string | null; /** Whether to skip the uninstall process or not. */ - skipUninstall: boolean; + skip_uninstall: boolean; /** Whether the install was successful. */ uninstalled: boolean; } @@ -117,7 +117,7 @@ export interface DownloadPrebuiltInput { /** Current tool context. */ context: ToolContext; /** Virtual directory to install to. */ - installDir: string; + install_dir: string; } /** Output returned by the `download_prebuilt` function. */ @@ -126,26 +126,26 @@ export interface DownloadPrebuiltOutput { * Name of the direct folder within the archive that contains the tool, * and will be removed when unpacking the archive. */ - archivePrefix: string | null; + archive_prefix: string | null; /** * File name of the checksum to download. If not provided, * will attempt to extract it from the URL. */ - checksumName: string | null; + checksum_name: string | null; /** Public key to use for checksum verification. */ - checksumPublicKey: string | null; + checksum_public_key: string | null; /** * A secure URL to download the checksum file for verification. * If the tool does not support checksum verification, this setting can be omitted. */ - checksumUrl: string | null; + checksum_url: string | null; /** * File name of the archive to download. If not provided, * will attempt to extract it from the URL. */ - downloadName: string | null; + download_name: string | null; /** A secure URL to download the tool/archive. */ - downloadUrl: string; + download_url: string; } /** Input passed to the `unpack_archive` function. */ @@ -153,19 +153,19 @@ export interface UnpackArchiveInput { /** Current tool context. */ context: ToolContext; /** Virtual path to the downloaded file. */ - inputFile: string; + input_file: string; /** Virtual directory to unpack the archive into, or copy the binary to. */ - outputDir: string; + output_dir: string; } /** Output returned by the `verify_checksum` function. */ export interface VerifyChecksumInput { /** Virtual path to the checksum file. */ - checksumFile: string; + checksum_file: string; /** Current tool context. */ context: ToolContext; /** Virtual path to the downloaded file. */ - downloadFile: string; + download_file: string; } /** Output returned by the `verify_checksum` function. */ @@ -187,7 +187,7 @@ export interface ExecutableConfig { * The executable path to use for symlinking binaries instead of `exe_path`. * This should only be used when `exe_path` is a non-standard executable. */ - exeLinkPath: string | null; + exe_link_path: string | null; /** * The file to execute, relative from the tool directory. * Does *not* support virtual paths. @@ -199,19 +199,19 @@ export interface ExecutableConfig { * - For primary shim, this field is ignored. * - For secondary shims, the file to execute. */ - exePath: string | null; + exe_path: string | null; /** Do not symlink a binary in `~/.proto/bin`. */ - noBin: boolean; + no_bin: boolean; /** Do not generate a shim in `~/.proto/shims`. */ - noShim: boolean; + no_shim: boolean; /** The parent executable name required to execute the local executable path. */ - parentExeName: string | null; + parent_exe_name: string | null; /** Custom args to append to user-provided args within the generated shim. */ - shimAfterArgs: StringOrVec | null; + shim_after_args: StringOrVec | null; /** Custom args to prepend to user-provided args within the generated shim. */ - shimBeforeArgs: StringOrVec | null; + shim_before_args: StringOrVec | null; /** Custom environment variables to set when executing the shim. */ - shimEnvVars: Record | null; + shim_env_vars: Record | null; } /** Output returned by the `locate_executables` function. */ @@ -220,12 +220,12 @@ export interface LocateExecutablesOutput { * List of directory paths to find the globals installation directory. * Each path supports environment variable expansion. */ - globalsLookupDirs: string[]; + globals_lookup_dirs: string[]; /** * A string that all global binaries are prefixed with, and will be removed * when listing and filtering available globals. */ - globalsPrefix: string | null; + globals_prefix: string | null; /** * Configures the primary/default executable to create. * If not provided, a primary shim and binary will *not* be created. @@ -282,7 +282,7 @@ export interface SyncManifestInput { /** Output returned by the `sync_manifest` function. */ export interface SyncManifestOutput { /** Whether to skip the syncing process or not. */ - skipSync: boolean; + skip_sync: boolean; /** * List of versions that are currently installed. Will replace * what is currently in the manifest. @@ -295,7 +295,7 @@ export interface SyncShellProfileInput { /** Current tool context. */ context: ToolContext; /** Arguments passed after `--` that was directly passed to the tool's binary. */ - passthroughArgs: string[]; + passthrough_args: string[]; } /** Output returned by the `sync_shell_profile` function. */ @@ -304,13 +304,13 @@ export interface SyncShellProfileOutput { * An environment variable to check for in the shell profile. * If the variable exists, injecting path and exports will be avoided. */ - checkVar: string; + check_var: string; /** A mapping of environment variables that will be injected as exports. */ - exportVars: Record | null; + export_vars: Record | null; /** A list of paths to prepend to the `PATH` environment variable. */ - extendPath: string[] | null; + extend_path: string[] | null; /** Whether to skip the syncing process or not. */ - skipSync: boolean; + skip_sync: boolean; } /** @@ -321,7 +321,7 @@ export interface InstallHook { /** Current tool context. */ context: ToolContext; /** Arguments passed after `--` that was directly passed to the tool's binary. */ - passthroughArgs: string[]; + passthrough_args: string[]; /** Whether the resolved version was pinned */ pinned: boolean; } @@ -334,11 +334,11 @@ export interface RunHook { /** Current tool context. */ context: ToolContext; /** Path to the global packages directory for the tool, if found. */ - globalsDir: string | null; + globals_dir: string | null; /** A prefix applied to the file names of globally installed packages. */ - globalsPrefix: string | null; + globals_prefix: string | null; /** Arguments passed after `--` that was directly passed to the tool's binary. */ - passthroughArgs: string[]; + passthrough_args: string[]; } /** Output returned from the `pre_run` hook. */ @@ -512,17 +512,17 @@ export interface ExecCommandInput { /** Environment variables to pass to the command. */ env: Record; /** Mark the command as executable before executing. */ - setExecutable: boolean; + set_executable: boolean; /** Stream the output instead of capturing it. */ stream: boolean; /** Override the current working directory. */ - workingDir: string | null; + working_dir: string | null; } /** Output returned from the `exec_command` host function. */ export interface ExecCommandOutput { command: string; - exitCode: number; + exit_code: number; stderr: string; stdout: string; } @@ -530,7 +530,7 @@ export interface ExecCommandOutput { /** Information about the host environment (the current runtime). */ export interface HostEnvironment { arch: SystemArch; - homeDir: string; + home_dir: string; libc: SystemLibc; os: SystemOS; } diff --git a/package/src/plugin-functions/detect-version.ts b/package/src/plugin-functions/detect-version.ts new file mode 100644 index 000000000..8bb5d640e --- /dev/null +++ b/package/src/plugin-functions/detect-version.ts @@ -0,0 +1,10 @@ +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; + +/** Output returned by the `detect_version_files` function. */ +export type DetectVersionFilesOutput = raw.DetectVersionOutput; + +export const createDetectVersionFiles = createPluginFnFactory< + never, + DetectVersionFilesOutput +>(); diff --git a/package/src/plugin-functions/download-prebuilt.ts b/package/src/plugin-functions/download-prebuilt.ts new file mode 100644 index 000000000..15d67d11f --- /dev/null +++ b/package/src/plugin-functions/download-prebuilt.ts @@ -0,0 +1,30 @@ +import type { OverrideProperties, SetRequired } from "type-fest"; +import type { ToolContext } from "../api"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; + +/** Input passed to the `download_prebuilt` function. */ +export type DownloadPrebuiltInput = OverrideProperties< + raw.DownloadPrebuiltInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `download_prebuilt` function. */ +export type DownloadPrebuiltOutput = SetRequired< + Partial, + "download_url" +>; + +export const createDownloadPrebuilt = createPluginFnFactory< + DownloadPrebuiltInput, + DownloadPrebuiltOutput +>({ + reviveInput: (input: raw.DownloadPrebuiltInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/index.ts b/package/src/plugin-functions/index.ts new file mode 100644 index 000000000..aabe909ba --- /dev/null +++ b/package/src/plugin-functions/index.ts @@ -0,0 +1,15 @@ +export * from "./detect-version"; +export * from "./download-prebuilt"; +export * from "./install-hook"; +export * from "./load-versions"; +export * from "./locate-executables"; +export * from "./native-install"; +export * from "./native-uninstall"; +export * from "./parse-version-file"; +export * from "./register-tool"; +export * from "./resolve-version"; +export * from "./run-hook"; +export * from "./sync-manifest"; +export * from "./sync-shell-profile"; +export * from "./unpack-archive"; +export * from "./verify-checksum"; diff --git a/package/src/plugin-functions/install-hook.ts b/package/src/plugin-functions/install-hook.ts new file mode 100644 index 000000000..3c6f93f68 --- /dev/null +++ b/package/src/plugin-functions/install-hook.ts @@ -0,0 +1,30 @@ +import type { OverrideProperties } from "type-fest"; +import type { ToolContext } from "../api"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type * as raw from "../internal/raw-api-types"; + +/** + * Input passed to the `pre_install` and `post_install` hooks, + * while a `proto install` command is running. + */ +export type InstallHookInput = OverrideProperties< + raw.InstallHook, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +export const createInstallHook = createPluginFnFactory({ + reviveInput: (input: raw.InstallHook) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); + +export type PreInstallHookInput = InstallHookInput; +export const createPreInstallHook = createInstallHook; + +export type PostInstallHookInput = InstallHookInput; +export const createPostInstallHook = createInstallHook; diff --git a/package/src/plugin-functions/load-versions.ts b/package/src/plugin-functions/load-versions.ts new file mode 100644 index 000000000..47f0c563f --- /dev/null +++ b/package/src/plugin-functions/load-versions.ts @@ -0,0 +1,40 @@ +import type { OverrideProperties } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { UnresolvedVersionSpec, type VersionLike } from "../versions"; + +/** Input passed to the `load_versions` function. */ +export type LoadVersionsInput = OverrideProperties< + raw.LoadVersionsInput, + { + /** The alias or version currently being resolved. */ + initial: UnresolvedVersionSpec; + } +>; + +/** Output returned by the `load_versions` function. */ +export type LoadVersionsOutput = OverrideProperties< + raw.LoadVersionsOutput, + { + /** Mapping of aliases (channels, etc) to a version. */ + aliases?: Record; + + /** Latest canary version. */ + canary?: VersionLike | null; + + /** Latest stable version. */ + latest?: VersionLike | null; + + /** List of available production versions to install. */ + versions: VersionLike[]; + } +>; + +export const createLoadVersions = createPluginFnFactory< + LoadVersionsInput, + LoadVersionsOutput +>({ + reviveInput: (input: raw.LoadVersionsInput) => ({ + initial: UnresolvedVersionSpec.parse(input.initial), + }), +}); diff --git a/package/src/plugin-functions/locate-executables.ts b/package/src/plugin-functions/locate-executables.ts new file mode 100644 index 000000000..3cb1e91bc --- /dev/null +++ b/package/src/plugin-functions/locate-executables.ts @@ -0,0 +1,48 @@ +import type { OverrideProperties, SetRequired } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; + +/** Configuration for generated shim and symlinked binary files. */ +export type ExecutableConfig = SetRequired< + Partial, + "exe_path" +>; + +/** Input passed to the `locate_executables` function. */ +export type LocateExecutablesInput = OverrideProperties< + raw.LocateExecutablesInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `locate_executables` function. */ +export type LocateExecutablesOutput = OverrideProperties< + Partial, + { + /** + * Configures the primary/default executable to create. + * If not provided, a primary shim and binary will *not* be created. + */ + primary?: ExecutableConfig | null; + + /** + * Configures secondary/additional executables to create. + * The map key is the name of the shim/binary file. + */ + secondary?: Record; + } +>; + +export const createLocateExecutables = createPluginFnFactory< + LocateExecutablesInput, + LocateExecutablesOutput +>({ + reviveInput: (input: raw.LocateExecutablesInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/native-install.ts b/package/src/plugin-functions/native-install.ts new file mode 100644 index 000000000..1cbfccbc8 --- /dev/null +++ b/package/src/plugin-functions/native-install.ts @@ -0,0 +1,27 @@ +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; +import type { OverrideProperties } from "type-fest"; + +/** Input passed to the `native_install` function. */ +export type NativeInstallInput = OverrideProperties< + raw.NativeInstallInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `native_install` function. */ +export type NativeInstallOutput = raw.NativeInstallOutput; + +export const createNativeInstall = createPluginFnFactory< + NativeInstallInput, + NativeInstallOutput +>({ + reviveInput: (input: raw.NativeInstallInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/native-uninstall.ts b/package/src/plugin-functions/native-uninstall.ts new file mode 100644 index 000000000..a66725520 --- /dev/null +++ b/package/src/plugin-functions/native-uninstall.ts @@ -0,0 +1,27 @@ +import type { OverrideProperties } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; + +/** Input passed to the `native_uninstall` function. */ +export type NativeUninstallInput = OverrideProperties< + raw.NativeUninstallInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `native_uninstall` function. */ +export type NativeUninstallOutput = raw.NativeUninstallOutput; + +export const createNativeUninstall = createPluginFnFactory< + NativeUninstallInput, + NativeUninstallOutput +>({ + reviveInput: (input: raw.NativeUninstallInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/parse-version-file.ts b/package/src/plugin-functions/parse-version-file.ts new file mode 100644 index 000000000..2d630959f --- /dev/null +++ b/package/src/plugin-functions/parse-version-file.ts @@ -0,0 +1,24 @@ +import type { OverrideProperties } from "type-fest"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import type * as raw from "../internal/raw-api-types"; +import type { UnresolvedVersionLike } from "../versions"; + +/** Input passed to the `parse_version_file` function. */ +export type ParseVersionFileInput = raw.ParseVersionFileInput; + +/** Output returned by the `parse_version_file` function. */ +export type ParseVersionFileOutput = OverrideProperties< + raw.ParseVersionFileOutput, + { + /** + * The version that was extracted from the file. + * Can be a semantic version or a version requirement/range. + */ + version?: UnresolvedVersionLike | null; + } +>; + +export const createParseVersionFile = createPluginFnFactory< + ParseVersionFileInput, + ParseVersionFileOutput +>(); diff --git a/package/src/plugin-functions/register-tool.ts b/package/src/plugin-functions/register-tool.ts new file mode 100644 index 000000000..e79eb622c --- /dev/null +++ b/package/src/plugin-functions/register-tool.ts @@ -0,0 +1,30 @@ +import type { OverrideProperties } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import type { UnresolvedVersionLike } from "../versions"; + +/** Controls aspects of the tool inventory. */ +export type ToolInventoryMetadata = Partial; + +/** Input passed to the `register_tool` function. */ +export type ToolMetadataInput = raw.ToolMetadataInput; + +/** Output returned by the `register_tool` function. */ +export type ToolMetadataOutput = OverrideProperties< + raw.ToolMetadataOutput, + { + /** Controls aspects of the tool inventory. */ + inventory?: ToolInventoryMetadata; + + /** Default alias or version to use as a fallback. */ + default_version?: UnresolvedVersionLike | null; + } +>; + +export const createRegisterTool = createPluginFnFactory< + ToolMetadataInput, + ToolMetadataOutput +>(); + +export type RegisterToolInput = ToolMetadataInput; +export type RegisterToolOutput = ToolMetadataOutput; diff --git a/package/src/plugin-functions/resolve-version.ts b/package/src/plugin-functions/resolve-version.ts new file mode 100644 index 000000000..35e74c4f2 --- /dev/null +++ b/package/src/plugin-functions/resolve-version.ts @@ -0,0 +1,42 @@ +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { + UnresolvedVersionSpec, + type UnresolvedVersionLike, + type VersionLike, +} from "../versions"; +import type { OverrideProperties } from "type-fest"; + +/** Input passed to the `resolve_version` function. */ +export type ResolveVersionInput = OverrideProperties< + raw.ResolveVersionInput, + { + /** The alias or version currently being resolved. */ + initial: UnresolvedVersionSpec; + } +>; + +/** Output returned by the `resolve_version` function. */ +export type ResolveVersionOutput = OverrideProperties< + raw.ResolveVersionOutput, + { + /** New alias or version candidate to resolve. */ + candidate?: UnresolvedVersionLike | null; + + /** + * An explicitly resolved version to be used as-is. + * Note: Only use this field if you know what you're doing! + */ + version?: VersionLike | null; + } +>; + +export const createResolveVersion = createPluginFnFactory< + ResolveVersionInput, + ResolveVersionOutput +>({ + reviveInput: (input: raw.ResolveVersionInput) => ({ + ...input, + initial: UnresolvedVersionSpec.parse(input.initial), + }), +}); diff --git a/package/src/plugin-functions/run-hook.ts b/package/src/plugin-functions/run-hook.ts new file mode 100644 index 000000000..1fd01a1dd --- /dev/null +++ b/package/src/plugin-functions/run-hook.ts @@ -0,0 +1,33 @@ +import type { OverrideProperties } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; + +/** + * Input passed to the `pre_run` hook, before a `proto run` command + * or language binary is ran. + */ +export type RunHookInput = OverrideProperties< + raw.RunHook, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned from the `pre_run` hook. */ +export type RunHookOutput = raw.RunHookResult; + +export const createRunHook = createPluginFnFactory( + { + reviveInput: (input: raw.RunHook) => ({ + ...input, + context: reviveToolContext(input.context), + }), + } +); + +export type PreRunHookInput = RunHookInput; +export type PreRunHookOutput = RunHookOutput; +export const createPreRunHook = createRunHook; diff --git a/package/src/plugin-functions/sync-manifest.ts b/package/src/plugin-functions/sync-manifest.ts new file mode 100644 index 000000000..5d9bb7b5d --- /dev/null +++ b/package/src/plugin-functions/sync-manifest.ts @@ -0,0 +1,37 @@ +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { OverrideProperties } from "type-fest"; +import type { VersionLike } from "../versions"; +import type { ToolContext } from "../api"; + +/** Input passed to the `sync_manifest` function. */ +export type SyncManifestInput = OverrideProperties< + raw.SyncManifestInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `sync_manifest` function. */ +export type SyncManifestOutput = OverrideProperties< + raw.SyncManifestOutput, + { + /** + * List of versions that are currently installed. Will replace + * what is currently in the manifest. + */ + versions?: VersionLike[] | null; + } +>; + +export const createSyncManifest = createPluginFnFactory< + SyncManifestInput, + SyncManifestOutput +>({ + reviveInput: (input: raw.SyncManifestInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/sync-shell-profile.ts b/package/src/plugin-functions/sync-shell-profile.ts new file mode 100644 index 000000000..ad27f4635 --- /dev/null +++ b/package/src/plugin-functions/sync-shell-profile.ts @@ -0,0 +1,27 @@ +import type { OverrideProperties } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; + +/** Input passed to the `sync_shell_profile` function. */ +export type SyncShellProfileInput = OverrideProperties< + raw.SyncShellProfileInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `sync_shell_profile` function. */ +export type SyncShellProfileOutput = raw.SyncShellProfileOutput; + +export const createSyncShellProfile = createPluginFnFactory< + SyncShellProfileInput, + SyncShellProfileOutput +>({ + reviveInput: (input: raw.SyncShellProfileInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/unpack-archive.ts b/package/src/plugin-functions/unpack-archive.ts new file mode 100644 index 000000000..a11c21196 --- /dev/null +++ b/package/src/plugin-functions/unpack-archive.ts @@ -0,0 +1,24 @@ +import type { OverrideProperties } from "type-fest"; +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; + +/** Input passed to the `unpack_archive` function. */ +export type UnpackArchiveInput = OverrideProperties< + raw.UnpackArchiveInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +export const createUnpackArchive = createPluginFnFactory< + UnpackArchiveInput, + void +>({ + reviveInput: (input: raw.UnpackArchiveInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/plugin-functions/verify-checksum.ts b/package/src/plugin-functions/verify-checksum.ts new file mode 100644 index 000000000..a9e994422 --- /dev/null +++ b/package/src/plugin-functions/verify-checksum.ts @@ -0,0 +1,27 @@ +import type * as raw from "../internal/raw-api-types"; +import { createPluginFnFactory } from "../internal/create-plugin-function"; +import { reviveToolContext } from "../internal/helpers"; +import type { ToolContext } from "../api"; +import type { OverrideProperties } from "type-fest"; + +/** Output returned by the `verify_checksum` function. */ +export type VerifyChecksumInput = OverrideProperties< + raw.VerifyChecksumInput, + { + /** Current tool context. */ + context: ToolContext; + } +>; + +/** Output returned by the `verify_checksum` function. */ +export type VerifyChecksumOutput = raw.VerifyChecksumOutput; + +export const createVerifyChecksum = createPluginFnFactory< + VerifyChecksumInput, + VerifyChecksumOutput +>({ + reviveInput: (input: raw.VerifyChecksumInput) => ({ + ...input, + context: reviveToolContext(input.context), + }), +}); diff --git a/package/src/runtime/imports.d.ts b/package/src/runtime/imports.d.ts new file mode 100644 index 000000000..746353817 --- /dev/null +++ b/package/src/runtime/imports.d.ts @@ -0,0 +1,10 @@ +declare module "extism:host" { + interface user { + from_virtual_path(path: PTR): PTR; + to_virtual_path(path: PTR): PTR; + host_log(input: PTR): void; + exec_command(input: PTR): PTR; + get_env_var(name: PTR): PTR; + set_env_var(name: PTR, value: PTR): void; + } +} diff --git a/package/src/versions/index.ts b/package/src/versions/index.ts new file mode 100644 index 000000000..659b11383 --- /dev/null +++ b/package/src/versions/index.ts @@ -0,0 +1,3 @@ +export * from "./resolved-spec"; +export * from "./unresolved-spec"; +export * from "./types"; diff --git a/package/src/versions/resolved-spec.test.ts b/package/src/versions/resolved-spec.test.ts new file mode 100644 index 000000000..fbf45ddf6 --- /dev/null +++ b/package/src/versions/resolved-spec.test.ts @@ -0,0 +1,42 @@ +import { test, expect } from "vitest"; +import { VersionSpec } from "./resolved-spec"; + +expect.addEqualityTesters([(a, b) => a instanceof VersionSpec && a.equals(b)]); + +test("canary", () => { + expect(VersionSpec.parse("canary")).toEqual(new VersionSpec.Canary()); +}); + +test("alias", () => { + expect(VersionSpec.parse("latest")).toEqual(new VersionSpec.Alias("latest")); + expect(VersionSpec.parse("stable")).toEqual(new VersionSpec.Alias("stable")); + expect(VersionSpec.parse("legacy-2023")).toEqual( + new VersionSpec.Alias("legacy-2023") + ); + expect(VersionSpec.parse("future/202x")).toEqual( + new VersionSpec.Alias("future/202x") + ); +}); + +test("versions", () => { + expect(VersionSpec.parse("v1.2.3")).toEqual(new VersionSpec.Version("1.2.3")); + expect(VersionSpec.parse("1.2.3")).toEqual(new VersionSpec.Version("1.2.3")); +}); + +test("error when missing patch", () => { + expect(() => { + VersionSpec.parse("1.2"); + }).toThrowError("Invalid Version: 1"); +}); + +test("error when missing minor", () => { + expect(() => { + VersionSpec.parse("1"); + }).toThrowError("Invalid Version: 1"); +}); + +test("error when invalid char", () => { + expect(() => { + VersionSpec.parse("%"); + }).toThrowError("Invalid Version: %"); +}); diff --git a/package/src/versions/resolved-spec.ts b/package/src/versions/resolved-spec.ts new file mode 100644 index 000000000..b9cd2c6a5 --- /dev/null +++ b/package/src/versions/resolved-spec.ts @@ -0,0 +1,93 @@ +import { SemVer } from "semver"; +import { cleanVersionString, isAliasName } from "./utils"; +import { UnresolvedVersionSpec } from "./unresolved-spec"; + +// TODO: Maybe make the SemVer library an internal detail and hide it as it +// would be nice to later switch it for a tree-shakeable alternative. + +/** + * Represents a resolved version or alias. + */ +export abstract class VersionSpec { + abstract toUnresolvedSpec(): UnresolvedVersionSpec; + abstract toString(): string; + + toJSON() { + return this.toString(); + } + + equals(other: { toString(): string }): boolean { + return this.toString() === other.toString(); + } + + /** + * Parse the provided string into a resolved specification based + * on the following rules, in order: + * + * - If the value "canary", map as `Canary` variant. + * - If an alpha-numeric value that starts with a character, map as `Alias`. + * - Else parse with [`Version`], and map as `Version`. + */ + static parse(value: string): VersionSpec { + if (value == "canary") { + return new VersionSpec.Canary(); + } + + value = cleanVersionString(value); + + if (isAliasName(value)) { + return new VersionSpec.Alias(value); + } + + return new VersionSpec.Version(value); + } +} + +export namespace VersionSpec { + /** + * An alias that is used as a map to a version. + */ + export class Alias extends VersionSpec { + constructor(public readonly alias: string) { + super(); + } + + override toString(): string { + return this.alias; + } + + override toUnresolvedSpec(): UnresolvedVersionSpec { + return new UnresolvedVersionSpec.Alias(this.alias); + } + } + + /** + * A special canary target. + */ + export class Canary extends Alias { + constructor() { + super("canary"); + } + } + + /** + * A fully-qualified semantic version. + */ + export class Version extends VersionSpec { + public readonly version: SemVer; + + constructor(version: SemVer | string) { + super(); + + this.version = new SemVer(version); + } + + override toString(): string { + return this.version.toString(); + } + + override toUnresolvedSpec(): UnresolvedVersionSpec { + return new UnresolvedVersionSpec.Version(this.version); + } + } +} diff --git a/package/src/versions/types.ts b/package/src/versions/types.ts new file mode 100644 index 000000000..2ffa6d54e --- /dev/null +++ b/package/src/versions/types.ts @@ -0,0 +1,17 @@ +import type { SemVer } from "semver"; +import type { VersionSpec } from "./resolved-spec"; +import type { UnresolvedVersionSpec } from "./unresolved-spec"; + +// TODO: should we allow passing raw strings as version-likes? +export type VersionLike = + | VersionSpec.Version + | UnresolvedVersionSpec.Version + | SemVer; + +export type ResolvedVersionLike = + | VersionSpec + | VersionLike + | UnresolvedVersionSpec.Alias + | UnresolvedVersionSpec.Canary; + +export type UnresolvedVersionLike = UnresolvedVersionSpec | ResolvedVersionLike; diff --git a/package/src/versions/unresolved-spec.test.ts b/package/src/versions/unresolved-spec.test.ts new file mode 100644 index 000000000..8383b3781 --- /dev/null +++ b/package/src/versions/unresolved-spec.test.ts @@ -0,0 +1,72 @@ +import { test, expect } from "vitest"; +import { UnresolvedVersionSpec } from "./unresolved-spec"; + +expect.addEqualityTesters([ + (a, b) => a instanceof UnresolvedVersionSpec && a.equals(b), +]); + +test("canary", () => { + expect(UnresolvedVersionSpec.parse("canary")).toEqual( + new UnresolvedVersionSpec.Canary() + ); +}); + +test("aliases", () => { + expect(UnresolvedVersionSpec.parse("latest")).toEqual( + new UnresolvedVersionSpec.Alias("latest") + ); + expect(UnresolvedVersionSpec.parse("stable")).toEqual( + new UnresolvedVersionSpec.Alias("stable") + ); + expect(UnresolvedVersionSpec.parse("legacy-2023")).toEqual( + new UnresolvedVersionSpec.Alias("legacy-2023") + ); + expect(UnresolvedVersionSpec.parse("future/202x")).toEqual( + new UnresolvedVersionSpec.Alias("future/202x") + ); +}); + +test("versions", () => { + expect(UnresolvedVersionSpec.parse("v1.2.3")).toEqual( + new UnresolvedVersionSpec.Version("1.2.3") + ); + expect(UnresolvedVersionSpec.parse("1.2.3")).toEqual( + new UnresolvedVersionSpec.Version("1.2.3") + ); +}); + +test("requirements", () => { + expect(UnresolvedVersionSpec.parse("1.2")).toEqual( + new UnresolvedVersionSpec.Req("~1.2") + ); + expect(UnresolvedVersionSpec.parse("1")).toEqual( + new UnresolvedVersionSpec.Req("~1") + ); + expect(UnresolvedVersionSpec.parse("1.2.*")).toEqual( + new UnresolvedVersionSpec.Req("~1.2") + ); + expect(UnresolvedVersionSpec.parse("1.*")).toEqual( + new UnresolvedVersionSpec.Req("~1") + ); + expect(UnresolvedVersionSpec.parse(">1")).toEqual( + new UnresolvedVersionSpec.Req(">1") + ); + expect(UnresolvedVersionSpec.parse("<=1")).toEqual( + new UnresolvedVersionSpec.Req("<=1") + ); + expect(UnresolvedVersionSpec.parse("1, 2")).toEqual( + new UnresolvedVersionSpec.Req("1 2") + ); + expect(UnresolvedVersionSpec.parse("1,2")).toEqual( + new UnresolvedVersionSpec.Req("1 2") + ); + expect(UnresolvedVersionSpec.parse("1 2")).toEqual( + new UnresolvedVersionSpec.Req("1 2") + ); +}); + +test("any requirements", () => { + expect(UnresolvedVersionSpec.parse("^1.2 || ~1 || 3,4")).toEqual( + new UnresolvedVersionSpec.ReqAny("~1 || ^1.2 || 3 4") + ); +}); diff --git a/package/src/versions/unresolved-spec.ts b/package/src/versions/unresolved-spec.ts new file mode 100644 index 000000000..aedf05bb4 --- /dev/null +++ b/package/src/versions/unresolved-spec.ts @@ -0,0 +1,175 @@ +import { type Comparator, Range, SemVer } from "semver"; +import { cleanVersionString, isAliasName } from "./utils"; +import { VersionSpec } from "./resolved-spec"; +import { PluginError } from "../errors"; + +/** + * Represents an unresolved version or alias that must be resolved + * to a fully-qualified and semantic result. + */ +export abstract class UnresolvedVersionSpec { + /** + * Convert the current unresolved specification to a resolved specification. + * Note that this *does not* actually resolve or validate against a manifest, + * and instead simply constructs the [`VersionSpec`]. + * + * Furthermore, the `Req` and `ReqAny` variants will throw an error, as they + * are not resolved or valid versions. + */ + abstract toResolvedSpec(): VersionSpec; + + abstract toString(): string; + + toJSON() { + return this.toString(); + } + + equals(other: { toString(): string }): boolean { + return this.toString() === other.toString(); + } + + /** + * Parse the provided string into an unresolved specification based + * on the following rules, in order: + * + * - If the value "canary", map as `Canary` variant. + * - If an alpha-numeric value that starts with a character, map as `Alias`. + * - If contains `||`, split and parse each item with [`VersionReq`], + * and map as `ReqAny`. + * - If contains `,` or ` ` (space), parse with [`VersionReq`], and map as `Req`. + * - If starts with `=`, `^`, `~`, `>`, `<`, or `*`, parse with [`VersionReq`], + * and map as `Req`. + * - Else parse with [`Version`], and map as `Version`. + */ + static parse(value: string): UnresolvedVersionSpec { + if (value == "canary") { + return new UnresolvedVersionSpec.Canary(); + } + + value = cleanVersionString(value); + + if (isAliasName(value)) { + return new UnresolvedVersionSpec.Alias(value); + } + + // OR requirements (Node.js) + if (value.includes("||")) { + return new UnresolvedVersionSpec.ReqAny(value); + } + + // AND requirements + if (value.includes(",")) { + return new UnresolvedVersionSpec.Req(value); + } + + if (["=", "^", "~", ">", "<", "*"].includes(value.charAt(0))) { + return new UnresolvedVersionSpec.Req(value); + } + + const dotCount = [...value].reduce( + (dots, char) => (char === "." ? dots + 1 : dots), + 0 + ); + + if (dotCount < 2) { + return new UnresolvedVersionSpec.Req(`~${value}`); + } + + return new UnresolvedVersionSpec.Version(value); + } +} + +export namespace UnresolvedVersionSpec { + /** + * An alias that is used as a map to a version. + */ + export class Alias extends UnresolvedVersionSpec { + constructor(public readonly alias: string) { + super(); + } + + override toResolvedSpec(): VersionSpec { + return new VersionSpec.Alias(this.alias); + } + + override toString(): string { + return this.alias; + } + } + + /** + * A special canary target. + */ + export class Canary extends Alias { + constructor() { + super("canary"); + } + + override toResolvedSpec(): VersionSpec { + return new VersionSpec.Canary(); + } + } + + /** + * A fully-qualified semantic version. + */ + export class Version extends UnresolvedVersionSpec { + public readonly version: SemVer; + + constructor(version: SemVer | string) { + super(); + + this.version = new SemVer(version); + } + + override toResolvedSpec(): VersionSpec { + return new VersionSpec.Version(this.version); + } + + override toString(): string { + return this.version.toString(); + } + } + + /** + * A list of requirements to match any against (joined by `||`). + */ + export class ReqAny extends UnresolvedVersionSpec { + public readonly range: Range; + + constructor(range: Range | string) { + super(); + + // This is pretty nasty, but we presort the ranges and comparators, + // so the equality check is just a string comparison. + const unsortedRange = new Range(range); + const sortedRaw = unsortedRange.set + .map((comparators) => [...comparators].sort().join(" ")) + .sort() + .join("||"); + + this.range = new Range(sortedRaw); + } + + override toResolvedSpec(): VersionSpec { + throw new PluginError("invalid operation"); + } + + override toString(): string { + return this.range.range; + } + } + + /** + * A partial version, requirement, or range (`^`, `~`, etc). + */ + export class Req extends ReqAny { + get comparators() { + return this.range.set[0]; + } + + constructor(...comparators: readonly (Comparator | string)[]) { + super(comparators.map((c) => c.toString()).join(" ")); + } + } +} diff --git a/package/src/versions/utils.test.ts b/package/src/versions/utils.test.ts new file mode 100644 index 000000000..171b9bd8e --- /dev/null +++ b/package/src/versions/utils.test.ts @@ -0,0 +1,54 @@ +import { describe, test, expect } from "vitest"; +import { isAliasName, cleanVersionString } from "./utils"; // Replace 'your_file_name' with the actual file name containing the functions + +describe("isAliasName", () => { + test("checks alias", () => { + expect(isAliasName("foo")).toBe(true); + expect(isAliasName("foo.bar")).toBe(true); + expect(isAliasName("foo/bar")).toBe(true); + expect(isAliasName("foo-bar")).toBe(true); + expect(isAliasName("foo_bar-baz")).toBe(true); + expect(isAliasName("alpha.1")).toBe(true); + expect(isAliasName("beta-0")).toBe(true); + expect(isAliasName("rc-1.2.3")).toBe(true); + expect(isAliasName("next-2023")).toBe(true); + + expect(isAliasName("1.2.3")).toBe(false); + expect(isAliasName("1.2")).toBe(false); + expect(isAliasName("1")).toBe(false); + expect(isAliasName("1-3")).toBe(false); + }); +}); + +describe("cleanVersionString", () => { + test("cleans string", () => { + expect(cleanVersionString("v1.2.3")).toBe("1.2.3"); + expect(cleanVersionString("V1.2.3")).toBe("1.2.3"); + + expect(cleanVersionString("1.2.*")).toBe("1.2"); + expect(cleanVersionString("1.*.*")).toBe("1"); + expect(cleanVersionString("*")).toBe("*"); + + expect(cleanVersionString(">= 1.2.3")).toBe(">=1.2.3"); + expect(cleanVersionString("> 1.2.3")).toBe(">1.2.3"); + expect(cleanVersionString("<1.2.3")).toBe("<1.2.3"); + expect(cleanVersionString("<= 1.2.3")).toBe("<=1.2.3"); + + expect(cleanVersionString("1.2, 3")).toBe("1.2 3"); + expect(cleanVersionString("1,3, 4")).toBe("1 3 4"); + expect(cleanVersionString("1,2")).toBe("1 2"); + expect(cleanVersionString("1 && 2")).toBe("1 2"); + }); + + test("handles commas", () => { + expect(cleanVersionString("1,2")).toBe("1 2"); + expect(cleanVersionString("1 2")).toBe("1 2"); + expect(cleanVersionString("1 2")).toBe("1 2"); + expect(cleanVersionString("1,2")).toBe("1 2"); + expect(cleanVersionString("1 ,2")).toBe("1 2"); + expect(cleanVersionString("1, 2")).toBe("1 2"); + expect(cleanVersionString("1 , 2")).toBe("1 2"); + expect(cleanVersionString("1 , 2")).toBe("1 2"); + expect(cleanVersionString("1, 2")).toBe("1 2"); + }); +}); diff --git a/package/src/versions/utils.ts b/package/src/versions/utils.ts new file mode 100644 index 000000000..8d1b1b96b --- /dev/null +++ b/package/src/versions/utils.ts @@ -0,0 +1,35 @@ +const aliasRegex = /[a-zA-Z][a-zA-Z0-9\-_/\.\*]/; + +/** + * Returns true if the provided value is an alias. An alias is a word that + * maps to version, for example, "latest" -> "1.2.3". + * + * Is considered an alias if the string is alpha-numeric, starts with a + * character, and supports `-`, `_`, `/`, `.`, and `*` characters. + */ +export function isAliasName(value: string): boolean { + return aliasRegex.test(value); +} + +/** + * Cleans a potential version string by removing a leading `v` or `V`, + * removing each occurence of `.*`, and removing invalid commas. + */ +export function cleanVersionString(value: string): string { + let version = value.trim(); + + if (version.includes("||")) { + return version.split("||").map(cleanVersionString).join(" || "); + } + + version = version.replace(/\.\*/g, "").replace(/&&/g, " "); + + if (version.startsWith("v") || version.startsWith("V")) { + version = version.substring(1); + } + + version = version.replace(/([><]=?)\s+(\d)/g, "$1$2"); + version = version.replace(/[, ]+/g, " "); + + return version; +} diff --git a/package/src/warpgate/api.ts b/package/src/warpgate/api.ts new file mode 100644 index 000000000..94022da17 --- /dev/null +++ b/package/src/warpgate/api.ts @@ -0,0 +1,15 @@ +import type * as raw from "../internal/raw-api-types"; + +/** Architecture of the host environment. */ +export type HostArch = raw.SystemArch; + +/** Libc being used in the host environment. */ +export type HostLibc = raw.SystemLibc; + +/** Operating system of the host environment. */ +export type HostOS = raw.SystemOS; + +export type { + HostEnvironment, + TestEnvironment, +} from "../internal/raw-api-types"; diff --git a/package/src/warpgate/funcs.ts b/package/src/warpgate/funcs.ts new file mode 100644 index 000000000..bdb9faa58 --- /dev/null +++ b/package/src/warpgate/funcs.ts @@ -0,0 +1,186 @@ +import { PluginError } from "../errors"; +import { execCommand } from "./host-functions"; +import type { HostEnvironment, TestEnvironment } from "./api"; + +/** + * Fetch the provided request and return a response object. + */ +export function fetch( + req: HttpRequest, + body?: string | ArrayBufferLike +): HttpResponse { + console.debug(`Fetching ${req.url}`); + + body = typeof body === "string" ? new TextEncoder().encode(body) : body; + + try { + return Http.request( + { + // The pdk _attempts_ to default to GET when missing, but it seems to turn + // into 'undefined' or an empty string instead. + method: "GET", + ...req, + }, + body + ); + } catch (e) { + throw new PluginError(`Failed to make request to ${req.url}`, { + cause: e, + }); + } +} + +/** + * Fetch the provided URL and deserialize the response as JSON. + */ +export function fetchUrl(url: string | URL): T { + const response = fetch({ url: url.toString() }); + return JSON.parse(response.body); +} +// +/** + * Fetch the provided URL and deserialize the response as bytes. + */ +export function fetchUrlBytes(url: string | URL): Uint8Array { + const text = fetchUrlText(url); + return new TextEncoder().encode(text); +} + +/** + * Fetch the provided URL and return the text response. + */ +export function fetchUrlText(url: string | URL): string { + const response = fetch({ url: url.toString() }); + return response.body; +} + +/** + * Fetch the provided URL, deserialize the response as JSON, + * and cache the response in memory for subsequent WASM function calls. + */ +export function fetchUrlWithCache(url: string | URL): T { + url = url.toString(); + + const cachedBody = Var.getString(url); + if (cachedBody) { + console.debug( + `Reading ${url} from cache (length = ${cachedBody.length})` + ); + + return JSON.parse(cachedBody); + } + + const body = fetchUrlText(url); + + console.debug( + `Writing ${url} to cache (length = ${body.length})` + ); + + Var.set(url, body); + return JSON.parse(body); +} + +/** + * Load all git tags from the provided remote URL. + * The `git` binary must exist on the current machine. + */ +export function loadGitTags(url: string | URL): string[] { + url = url.toString(); + + console.debug(`Loading Git tags from remote ${url}`); + + const output = execCommand({ + command: "git", + args: ["ls-remote", "--tags", "--sort", "version:refname", url], + }); + + const tags: string[] = []; + + if (output.exit_code !== 0) { + console.debug("Failed to load Git tags"); + return tags; + } + + for (const line of output.stdout.split("\n")) { + // https://superuser.com/questions/1445823/what-does-mean-in-the-tags + if (line.endsWith("^{}")) { + continue; + } + + const parts = line.split("\t"); + if (parts.length < 2) { + continue; + } + + const prefix = "refs/tags/"; + if (parts[1]?.startsWith(prefix)) { + tags.push(parts[1].substring(prefix.length)); + } + } + + console.debug(`Loaded ${tags.length} Git tags`); + return tags; +} + +/** + * Check whether a command exists or not on the host machine. + */ +export function commandExists(env: HostEnvironment, command: string): boolean { + console.debug( + `Checking if command ${command} exists on the host` + ); + + const result = + env.os === "windows" + ? execCommand({ + command: "powershell", + args: ["-Command", `Get-Command ${command}`], + }) + : execCommand({ + command: "which", + args: [command], + }); + + if (result.exit_code === 0) { + console.debug("Command does exist"); + return true; + } + + console.debug("Command does NOT exist"); + return false; +} + +/** + * Return the ID for the current plugin. + */ +export function getPluginId(): string { + const id = Config.get("plugin_id"); + + if (!id) throw new PluginError("Missing plugin ID!"); + + return id; +} + +/** + * Return information about the host environment. + */ +export function getHostEnvironment(): HostEnvironment { + const config = Config.get("host_environment"); + + if (!config) throw new PluginError("Missing host environment!"); + + return JSON.parse(config) as HostEnvironment; +} + +/** + * Return information about the testing environment. + */ +export function getTestEnvironment(): TestEnvironment | null { + const config = Config.get("test_environment"); + + if (config) { + return JSON.parse(config) as TestEnvironment; + } + + return null; +} diff --git a/package/src/warpgate/host-functions.ts b/package/src/warpgate/host-functions.ts new file mode 100644 index 000000000..8289be765 --- /dev/null +++ b/package/src/warpgate/host-functions.ts @@ -0,0 +1,94 @@ +import type * as raw from "../internal/raw-api-types"; +import type { SetRequired } from "type-fest"; + +const { + exec_command, + host_log, + get_env_var, + set_env_var, + from_virtual_path, + to_virtual_path, +} = Host.getFunctions(); + +/** + * Calls the `get_env_var` host function to manage environment + * variables on the host. + */ +export function getEnvVar(name: string): string { + const nameOffset = Memory.fromString(name).offset; + const valueOffset = get_env_var(nameOffset); + return Memory.find(valueOffset).readString(); +} + +/** + * Calls the `set_env_var` host function to manage environment + * variables on the host. + * + * When setting `PATH`, the provided value will append to `PATH`, + * not overwrite it. Supports both `;` and `:` delimiters. + */ +export function setEnvVar(name: string, value: string): void { + const nameOffset = Memory.fromString(name).offset; + const valueOffset = Memory.fromString(value).offset; + set_env_var(nameOffset, valueOffset); +} + +/** + * Calls `from_virtual_path` on the host to convert the provided value to a real path + * from a virtual path. + */ +export function fromVirtualPath(virtualPath: string): string { + const virtualPathOffset = Memory.fromString(virtualPath).offset; + const pathOffset = from_virtual_path(virtualPathOffset); + return Memory.find(pathOffset).readString(); +} + +/** + * Calls `to_virtual_path` on the host to convert the provided value to a virtual path + * from a real path. + */ +export function toVirtualPath(realPath: string): string { + const pathOffset = Memory.fromString(realPath).offset; + const virtualPathOffset = to_virtual_path(pathOffset); + return Memory.find(virtualPathOffset).readString(); +} + +export type ExecCommandInput = SetRequired< + Partial, + "command" +>; +export type ExecCommandOutput = raw.ExecCommandOutput; + +/** + * Calls the `exec_command` host function to execute a command on + * the host as a synchronous child process. + */ +export function execCommand(input: ExecCommandInput): ExecCommandOutput { + const inputMemory = Memory.fromJsonObject({ + args: [], + env: {}, + set_executable: false, + stream: false, + working_dir: null, + ...input, + } satisfies raw.ExecCommandInput as any); // TODO: remove `any` once upstream type is fixed + + const outputOffset = exec_command(inputMemory.offset); + return Memory.find(outputOffset).readJsonObject(); +} + +export type HostLogInput = SetRequired, "message">; +export type HostLogTarget = raw.HostLogTarget; + +/** + * Calls the `host_log` host function to log a message to the host's terminal. + */ +export function hostLog(input: HostLogInput): void { + const inputMemory = Memory.fromJsonObject({ + target: "tracing", + data: {}, + ...input, + } satisfies raw.HostLogInput as any); // TODO: remove `any` once upstream type is fixed + + host_log(inputMemory.offset); +} diff --git a/package/src/warpgate/index.ts b/package/src/warpgate/index.ts new file mode 100644 index 000000000..6b9cf29a6 --- /dev/null +++ b/package/src/warpgate/index.ts @@ -0,0 +1,3 @@ +export * from "./api"; +export * from "./funcs"; +export * from "./host-functions"; diff --git a/package/test/.gitignore b/package/test/.gitignore new file mode 100644 index 000000000..77738287f --- /dev/null +++ b/package/test/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/package/test/.prototools b/package/test/.prototools new file mode 100644 index 000000000..15ac6bf37 --- /dev/null +++ b/package/test/.prototools @@ -0,0 +1,9 @@ +[plugins] +wasm-test = "source:./dist/plugin.wasm" + +# [tools.wasm-test] +# number = 123 +# string = "foo" +# boolean = true +# list = ["a", "b", "c"] +# map = { "a" = 123 } diff --git a/package/test/README.md b/package/test/README.md new file mode 100644 index 000000000..2e7536c44 --- /dev/null +++ b/package/test/README.md @@ -0,0 +1,12 @@ +# @moonrepo/proto-pdk test plugin + +## Prerequisites + +Everything except [binaryen](https://github.com/WebAssembly/binaryen) is currently +managed via the parent [.prototools](../.prototools). + +## Building + +```shell +npm run build +``` diff --git a/package/test/esbuild.mjs b/package/test/esbuild.mjs new file mode 100644 index 000000000..e7a01ba1e --- /dev/null +++ b/package/test/esbuild.mjs @@ -0,0 +1,13 @@ +import { build } from "esbuild"; + +await build({ + entryPoints: ["src/index.ts"], + outdir: "dist", + bundle: true, + // Definitely minify for production builds, as each kb of js for some reason makes the wasm 18kb larger. + minify: true, + minifyIdentifiers: true, // currently likely to create broken builds see https://github.com/extism/js-pdk/pull/75 + // keepNames: true, // useful for stacktraces but adds about 10% extra js + format: "cjs", // extism-js only supports cjs currently + target: ["es2020"], // don't go over es2020 because quickjs doesn't support it +}); diff --git a/package/test/package-lock.json b/package/test/package-lock.json new file mode 100644 index 000000000..c3342b105 --- /dev/null +++ b/package/test/package-lock.json @@ -0,0 +1,477 @@ +{ + "name": "proto-plugin-test", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "proto-plugin-test", + "dependencies": { + "@moonrepo/proto-pdk": ".." + }, + "devDependencies": { + "binaryen": "^117.0.0", + "esbuild": "^0.20.2", + "typescript": "^5.4.4" + } + }, + "..": { + "name": "@moonrepo/proto-pdk", + "dependencies": { + "@extism/js-pdk": "^1.0.1", + "@types/node": "^20.12.7", + "@types/semver": "^7.5.8", + "pathe": "^1.1.2", + "semver": "^7.6.0", + "type-fest": "^4.15.0", + "urlpattern-polyfill": "^8.0.2" + }, + "devDependencies": { + "typescript": "^5.5.0-beta", + "vitest": "^1.5.0" + }, + "engines": { + "node": "20.12.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@moonrepo/proto-pdk": { + "resolved": "..", + "link": true + }, + "node_modules/binaryen": { + "version": "117.0.0", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-117.0.0.tgz", + "integrity": "sha512-1D+O881OXxY737WPKfIgEscCn3vWGqTsd0m5nGKzvbtadVYw5pZ3eebineH/oV5c/rAW80Bojrsa6firSSIsUw==", + "dev": true, + "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/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package/test/package.json b/package/test/package.json new file mode 100644 index 000000000..ab654f9e9 --- /dev/null +++ b/package/test/package.json @@ -0,0 +1,18 @@ +{ + "name": "proto-plugin-test", + "type": "commonjs", + "scripts": { + "build": "npm run build:js && npm run build:wasm", + "build:js": "node esbuild.mjs", + "build:wasm": "extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@moonrepo/proto-pdk": ".." + }, + "devDependencies": { + "binaryen": "^117.0.0", + "esbuild": "^0.20.2", + "typescript": "^5.4.4" + } +} diff --git a/package/test/src/index.d.ts b/package/test/src/index.d.ts new file mode 100644 index 000000000..1e62ba9bb --- /dev/null +++ b/package/test/src/index.d.ts @@ -0,0 +1,22 @@ +declare module "main" { + export function detect_version_files(): I32; + export function download_prebuilt(): I32; + export function load_versions(): I32; + export function locate_executables(): I32; + export function parse_version_file(): I32; + export function register_tool(): I32; + export function resolve_version(): I32; + export function verify_checksum(): I32; +} + +// These imports unfortunately have to be copied, as extism-js only parses this file. +declare module "extism:host" { + interface user { + from_virtual_path(path: PTR): PTR; + to_virtual_path(path: PTR): PTR; + host_log(input: PTR): void; + exec_command(input: PTR): PTR; + get_env_var(name: PTR): PTR; + set_env_var(name: PTR, value: PTR): void; + } +} diff --git a/package/test/src/index.ts b/package/test/src/index.ts new file mode 100644 index 000000000..4e88c773e --- /dev/null +++ b/package/test/src/index.ts @@ -0,0 +1,176 @@ +import { + DetectVersionFilesOutput, + DownloadPrebuiltInput, + DownloadPrebuiltOutput, + LoadVersionsInput, + LoadVersionsOutput, + LocateExecutablesInput, + LocateExecutablesOutput, + ParseVersionFileInput, + ParseVersionFileOutput, + RegisterToolInput, + RegisterToolOutput, + ResolveVersionInput, + ResolveVersionOutput, + UnresolvedVersionSpec, + VerifyChecksumInput, + VerifyChecksumOutput, + VersionLike, + VersionSpec, + createDetectVersionFiles, + createDownloadPrebuilt, + createLoadVersions, + createLocateExecutables, + createParseVersionFile, + createRegisterTool, + createResolveVersion, + createVerifyChecksum, + fetchUrl, + getHostEnvironment, + getToolConfig, + hostLog, +} from "@moonrepo/proto-pdk"; + +export const register_tool = createRegisterTool( + (input: RegisterToolInput): RegisterToolOutput => { + // TODO: hostLog is a bit awkward to use + hostLog({ target: "stdout", message: `Registering tool: ${input.id}` }); + + const config = getToolConfig(); + hostLog({ message: `Config = ${JSON.stringify(config)}` }); + + return { + name: "WASM Test", + default_version: new UnresolvedVersionSpec.Alias("latest"), + plugin_version: "1.0.0", + type: "CLI", + self_upgrade_commands: [], + }; + } +); + +// Detector + +export const detect_version_files = createDetectVersionFiles( + (): DetectVersionFilesOutput => { + return { + files: [".proto-wasm-version", ".protowasmrc"], + ignore: ["node_modules"], + }; + } +); + +export const parse_version_file = createParseVersionFile( + ({ content, file }: ParseVersionFileInput): ParseVersionFileOutput => { + let version: UnresolvedVersionSpec | null = null; + + if (file === ".proto-wasm-version") { + if (content.startsWith("version=")) { + version = UnresolvedVersionSpec.parse(content.slice(8)); + } + } else { + version = UnresolvedVersionSpec.parse(content); + } + + return { + version, + }; + } +); + +// Downloader + +export const download_prebuilt = createDownloadPrebuilt( + ({ context }: DownloadPrebuiltInput): DownloadPrebuiltOutput => { + const env = getHostEnvironment(); + const version = context.version; + const arch = env.arch; + + let prefix: string; + if (env.os === "linux") prefix = `node-v${version}-linux-${arch}`; + else if (env.os === "macos") prefix = `node-v${version}-darwin-${arch}`; + else if (env.os === "windows") prefix = `node-v${version}-win-${arch}`; + else throw new Error("Not implemented"); + + const filename = + env.os === "windows" ? `${prefix}.zip` : `${prefix}.tar.xz`; + + return { + archive_prefix: prefix, + download_url: `https://nodejs.org/dist/v${version}/${filename}`, + download_name: filename, + checksum_url: `https://nodejs.org/dist/v${version}/SHASUMS256.txt`, + }; + } +); + +export const locate_executables = createLocateExecutables( + (_: LocateExecutablesInput): LocateExecutablesOutput => { + const env = getHostEnvironment(); + + return { + globals_lookup_dirs: ["$WASM_ROOT/bin", "$HOME/.wasm/bin"], + primary: { + exe_path: env.os === "windows" ? "node.exe" : "bin/node", + }, + secondary: { + global1: { exe_path: "bin/global1" }, + }, + }; + } +); + +// Resolver + +type NodeDistVersion = { + version: `v${string}`; +}; + +export const load_versions = createLoadVersions( + (_: LoadVersionsInput): LoadVersionsOutput => { + const response = fetchUrl( + "https://nodejs.org/dist/index.json" + ); + + const versions = response.map( + (item) => new VersionSpec.Version(item.version.slice(1)) + ); + const latest = versions[0] ?? null; + + const aliases: Record = {}; + if (latest) aliases["latest"] = latest; + + return { + latest, + versions, + aliases, + }; + } +); + +export const resolve_version = createResolveVersion( + ({ initial }: ResolveVersionInput): ResolveVersionOutput => { + if ( + initial instanceof UnresolvedVersionSpec.Alias && + initial.alias === "node" + ) { + return { + candidate: new UnresolvedVersionSpec.Alias("latest"), + }; + } + + return {}; + } +); + +// Verifier + +export const verify_checksum = createVerifyChecksum( + (_: VerifyChecksumInput): VerifyChecksumOutput => { + // unfortunately the extism js pdk does not support filesystem access yet :( + + return { + verified: true, + }; + } +); diff --git a/package/test/tsconfig.json b/package/test/tsconfig.json new file mode 100644 index 000000000..7f3689cbd --- /dev/null +++ b/package/test/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "lib": [], + "types": ["@moonrepo/proto-pdk"], + "module": "Preserve", + "moduleResolution": "Bundler", + "noEmit": true, + + // Strictness + "strict": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["src/**/*"] +} diff --git a/package/tsconfig.json b/package/tsconfig.json new file mode 100644 index 000000000..7117f0223 --- /dev/null +++ b/package/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "noEmitOnError": true, + "declaration": true, + "outDir": "lib", + "target": "es2020", + + // extism-js only supports cjs currently, but plugin developers will use a + // bundler (e.g. esbuild as recommended). So for better tree-shaking support + // we build a ES module instead and leave the conversion to CJS to the bundler. + // + // There's unfortunately more to it though, as this alone didn't have any apparent + // impact on the final wasm size. + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": [], + "types": [], + "esModuleInterop": true, + + // Strictness + "strict": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/package/tsconfig.test.json b/package/tsconfig.test.json new file mode 100644 index 000000000..88e6aa1fc --- /dev/null +++ b/package/tsconfig.test.json @@ -0,0 +1,4 @@ +{ + "extends": ["./tsconfig.json"], + "include": ["src/**/*"] +}