diff --git a/.github/workflows/startos-iso.yaml b/.github/workflows/startos-iso.yaml index bc0fb626e..3490936b4 100644 --- a/.github/workflows/startos-iso.yaml +++ b/.github/workflows/startos-iso.yaml @@ -78,7 +78,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: "3.x" - uses: actions/setup-node@v4 with: @@ -156,7 +156,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: "3.x" - name: Install dependencies run: | @@ -187,11 +187,27 @@ jobs: run: | mkdir -p web/node_modules mkdir -p web/dist/raw - touch core/startos/bindings - touch sdk/lib/osBindings + mkdir -p core/startos/bindings + mkdir -p sdk/base/lib/osBindings + mkdir -p container-runtime/node_modules mkdir -p container-runtime/dist + mkdir -p container-runtime/dist/node_modules + mkdir -p core/startos/bindings + mkdir -p sdk/dist + mkdir -p sdk/baseDist + mkdir -p patch-db/client/node_modules + mkdir -p patch-db/client/dist + mkdir -p web/.angular + mkdir -p web/dist/raw/ui + mkdir -p web/dist/raw/install-wizard + mkdir -p web/dist/raw/setup-wizard + mkdir -p web/dist/static/ui + mkdir -p web/dist/static/install-wizard + mkdir -p web/dist/static/setup-wizard PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar + - run: git status + - name: Run iso build run: PLATFORM=${{ matrix.platform }} make iso if: ${{ matrix.platform != 'raspberrypi' }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0a5eb38e9..3f47a65a4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,7 +11,7 @@ on: - next/* env: - NODEJS_VERSION: "18.15.0" + NODEJS_VERSION: "20.16.0" ENVIRONMENT: dev-unstable jobs: diff --git a/CLEARNET.md b/CLEARNET.md new file mode 100644 index 000000000..457a2e4f7 --- /dev/null +++ b/CLEARNET.md @@ -0,0 +1,40 @@ +# Setting up clearnet for a service interface + +NOTE: this guide is for HTTPS only! Other configurations may require a more bespoke setup depending on the service. Please consult the service documentation or the Start9 Community for help with non-HTTPS applications + +## Initialize ACME certificate generation + +The following command will register your device with an ACME certificate provider, such as letsencrypt + +This only needs to be done once. + +``` +start-cli net acme init --provider=letsencrypt --contact="mailto:me@drbonez.dev" +``` + +- `provider` can be `letsencrypt`, `letsencrypt-staging` (useful if you're doing a lot of testing and want to avoid being rate limited), or the url of any provider that supports the [RFC8555](https://datatracker.ietf.org/doc/html/rfc8555) ACME api +- `contact` can be any valid contact url, typically `mailto:` urls. it can be specified multiple times to set multiple contacts + +## Whitelist a domain for ACME certificate acquisition + +The following command will tell the OS to use ACME certificates instead of system signed ones for the provided url. In this example, `testing.drbonez.dev` + +This must be done for every domain you wish to host on clearnet. + +``` +start-cli net acme domain add "testing.drbonez.dev" +``` + +## Forward clearnet port + +Go into your router settings, and map port 443 on your router to port 5443 on your start-os device. This one port should cover most use cases + +## Add domain to service host + +The following command will tell the OS to route https requests from the WAN to the provided hostname to the specified service. In this example, we are adding `testing.drbonez.dev` to the host `ui-multi` on the package `hello-world`. To see a list of available host IDs for a given package, run `start-cli package host list` + +This must be done for every domain you wish to host on clearnet. + +``` +start-cli package host hello-world address ui-multi add testing.drbonez.dev +``` diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index cb9385f41..c3c555a05 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -27,6 +27,7 @@ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash source ~/.bashrc nvm install 20 nvm use 20 +nvm alias default 20 # this prevents your machine from reverting back to another version ``` ## Cloning the repository diff --git a/Makefile b/Makefile index adcbc6bf1..132270636 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,8 @@ BASENAME := $(shell ./basename.sh) PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi) ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi) IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi) -WEB_UIS := web/dist/raw/ui web/dist/raw/setup-wizard web/dist/raw/install-wizard +WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html +COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json) BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS) DEBIAN_SRC := $(shell git ls-files debian/) @@ -16,7 +17,7 @@ COMPAT_SRC := $(shell git ls-files system-images/compat/) UTILS_SRC := $(shell git ls-files system-images/utils/) BINFMT_SRC := $(shell git ls-files system-images/binfmt/) CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE) -WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist web/patchdb-ui-seed.json sdk/dist +WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json WEB_UI_SRC := $(shell git ls-files web/projects/ui) WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard) WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard) @@ -47,7 +48,7 @@ endif .DELETE_ON_ERROR: -.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs sudo wormhole wormhole-deb test test-core test-sdk test-container-runtime +.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs sudo wormhole wormhole-deb test test-core test-sdk test-container-runtime registry all: $(ALL_TARGETS) @@ -92,17 +93,20 @@ format: test: | test-core test-sdk test-container-runtime test-core: $(CORE_SRC) $(ENVIRONMENT_FILE) - cd core && cargo build --features=test && cargo test --features=test + ./core/run-tests.sh -test-sdk: $(shell git ls-files sdk) sdk/lib/osBindings +test-sdk: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts cd sdk && make test -test-container-runtime: container-runtime/node_modules $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json +test-container-runtime: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json cd container-runtime && npm test cli: cd core && ./install-cli.sh +registry: + cd core && ./build-registrybox.sh + deb: results/$(BASENAME).deb debian/control: build/lib/depends build/lib/conflicts @@ -209,7 +213,7 @@ emulate-reflash: $(ALL_TARGETS) @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi $(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create') $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM) - $(call ssh,'sudo rm -f /media/startos/config/disk.guid') + $(call ssh,'sudo rm -f /media/startos/config/disk.guid /media/startos/config/overlay/etc/hostname') $(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"') upload-ota: results/$(BASENAME).squashfs @@ -218,34 +222,36 @@ upload-ota: results/$(BASENAME).squashfs container-runtime/debian.$(ARCH).squashfs: ARCH=$(ARCH) ./container-runtime/download-base-image.sh -container-runtime/node_modules: container-runtime/package.json container-runtime/package-lock.json sdk/dist +container-runtime/node_modules/.package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json npm --prefix container-runtime ci - touch container-runtime/node_modules + touch container-runtime/node_modules/.package-lock.json -sdk/lib/osBindings: core/startos/bindings - mkdir -p sdk/lib/osBindings - ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' > core/startos/bindings/index.ts - npm --prefix sdk exec -- prettier --config ./sdk/package.json -w ./core/startos/bindings/*.ts - rsync -ac --delete core/startos/bindings/ sdk/lib/osBindings/ - touch sdk/lib/osBindings +sdk/base/lib/osBindings/index.ts: core/startos/bindings/index.ts + mkdir -p sdk/base/lib/osBindings + rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/ + touch sdk/base/lib/osBindings/index.ts -core/startos/bindings: $(shell git ls-files core) $(ENVIRONMENT_FILE) +core/startos/bindings/index.ts: $(shell git ls-files core) $(ENVIRONMENT_FILE) rm -rf core/startos/bindings - (cd core/ && cargo test --features=test 'export_bindings_') - touch core/startos/bindings + ./core/build-ts.sh + ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts + npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts + touch core/startos/bindings/index.ts -sdk/dist: $(shell git ls-files sdk) sdk/lib/osBindings +sdk/dist/package.json sdk/baseDist/package.json: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts (cd sdk && make bundle) + touch sdk/dist/package.json + touch sdk/baseDist/package.json # TODO: make container-runtime its own makefile? -container-runtime/dist/index.js: container-runtime/node_modules $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json +container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json npm --prefix container-runtime run build -container-runtime/dist/node_modules container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist container-runtime/install-dist-deps.sh +container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh ./container-runtime/install-dist-deps.sh - touch container-runtime/dist/node_modules + touch container-runtime/dist/node_modules/.package-lock.json -container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules core/target/$(ARCH)-unknown-linux-musl/release/containerbox | sudo +container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(ARCH)-unknown-linux-musl/release/containerbox | sudo ARCH=$(ARCH) ./container-runtime/update-image.sh build/lib/depends build/lib/conflicts: build/dpkg-deps/* @@ -263,7 +269,7 @@ system-images/utils/docker-images/$(ARCH).tar: $(UTILS_SRC) system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC) cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar -core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) web/dist/static web/patchdb-ui-seed.json $(ENVIRONMENT_FILE) +core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE) ARCH=$(ARCH) ./core/build-startbox.sh touch core/target/$(ARCH)-unknown-linux-musl/release/startbox @@ -271,27 +277,28 @@ core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIR ARCH=$(ARCH) ./core/build-containerbox.sh touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox -web/node_modules/.package-lock.json: web/package.json sdk/dist +web/node_modules/.package-lock.json: web/package.json sdk/baseDist/package.json npm --prefix web ci touch web/node_modules/.package-lock.json -web/.angular: patch-db/client/dist sdk/dist web/node_modules/.package-lock.json +web/.angular/.updated: patch-db/client/dist/index.js sdk/baseDist/package.json web/node_modules/.package-lock.json rm -rf web/.angular mkdir -p web/.angular + touch web/.angular/.updated -web/dist/raw/ui: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular +web/dist/raw/ui/index.html: $(WEB_UI_SRC) $(WEB_SHARED_SRC) web/.angular/.updated npm --prefix web run build:ui - touch web/dist/raw/ui + touch web/dist/raw/ui/index.html -web/dist/raw/setup-wizard: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular +web/dist/raw/setup-wizard/index.html: $(WEB_SETUP_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated npm --prefix web run build:setup - touch web/dist/raw/setup-wizard + touch web/dist/raw/setup-wizard/index.html -web/dist/raw/install-wizard: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular +web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_SRC) web/.angular/.updated npm --prefix web run build:install-wiz - touch web/dist/raw/install-wizard + touch web/dist/raw/install-wizard/index.html -web/dist/static: $(WEB_UIS) $(ENVIRONMENT_FILE) +$(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE) ./compress-uis.sh web/config.json: $(GIT_HASH_FILE) web/config-sample.json @@ -301,13 +308,14 @@ web/patchdb-ui-seed.json: web/package.json jq '."ack-welcome" = $(shell jq '.version' web/package.json)' web/patchdb-ui-seed.json > ui-seed.tmp mv ui-seed.tmp web/patchdb-ui-seed.json -patch-db/client/node_modules: patch-db/client/package.json +patch-db/client/node_modules/.package-lock.json: patch-db/client/package.json npm --prefix patch-db/client ci - touch patch-db/client/node_modules + touch patch-db/client/node_modules/.package-lock.json -patch-db/client/dist: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules +patch-db/client/dist/index.js: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules/.package-lock.json rm -rf patch-db/client/dist npm --prefix patch-db/client run build + touch patch-db/client/dist/index.js # used by github actions compiled-$(ARCH).tar: $(COMPILED_TARGETS) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) @@ -326,4 +334,4 @@ cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console: ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs: - ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs \ No newline at end of file + ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs diff --git a/build-cargo-dep.sh b/build-cargo-dep.sh index c32e4f8ae..922dfbdf9 100755 --- a/build-cargo-dep.sh +++ b/build-cargo-dep.sh @@ -18,7 +18,7 @@ if [ -z "$ARCH" ]; then fi DOCKER_PLATFORM="linux/${ARCH}" -if [ "$ARCH" = aarch64 ]; then +if [ "$ARCH" = aarch64 ] || [ "$ARCH" = arm64 ]; then DOCKER_PLATFORM="linux/arm64" elif [ "$ARCH" = x86_64 ]; then DOCKER_PLATFORM="linux/amd64" diff --git a/build/dpkg-deps/depends b/build/dpkg-deps/depends index 62428bfdc..0f0f091b6 100644 --- a/build/dpkg-deps/depends +++ b/build/dpkg-deps/depends @@ -9,6 +9,7 @@ ca-certificates cifs-utils cryptsetup curl +dnsutils dmidecode dnsutils dosfstools @@ -48,6 +49,7 @@ smartmontools socat sqlite3 squashfs-tools +squashfs-tools-ng sudo systemd systemd-resolved diff --git a/build/lib/scripts/chroot-and-upgrade b/build/lib/scripts/chroot-and-upgrade index 3af93f5ef..8c3da37b4 100755 --- a/build/lib/scripts/chroot-and-upgrade +++ b/build/lib/scripts/chroot-and-upgrade @@ -43,6 +43,8 @@ if [ -z "$NO_SYNC" ]; then mount -t overlay \ -olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \ overlay /media/startos/next + mkdir -p /media/startos/next/media/startos/root + mount --bind /media/startos/root /media/startos/next/media/startos/root fi if [ -n "$ONLY_CREATE" ]; then @@ -75,6 +77,7 @@ umount /media/startos/next/dev umount /media/startos/next/sys umount /media/startos/next/proc umount /media/startos/next/boot +umount /media/startos/next/media/startos/root if [ "$CHROOT_RES" -eq 0 ]; then @@ -84,7 +87,12 @@ if [ "$CHROOT_RES" -eq 0 ]; then echo 'Upgrading...' - time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip + if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then + umount -R /media/startos/next + umount -R /media/startos/upper + rm -rf /media/startos/upper /media/startos/next + exit 1 + fi hash=$(b3sum /media/startos/images/next.squashfs | head -c 32) mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs ln -rsf /media/startos/images/${hash}.rootfs /media/startos/config/current.rootfs diff --git a/build/lib/scripts/prune-images b/build/lib/scripts/prune-images index 20356a28c..1203d2377 100755 --- a/build/lib/scripts/prune-images +++ b/build/lib/scripts/prune-images @@ -33,10 +33,11 @@ if [ -h /media/startos/config/current.rootfs ] && [ -e /media/startos/config/cur echo 'Pruning...' current="$(readlink -f /media/startos/config/current.rootfs)" while [[ "$(df -B1 --output=avail --sync /media/startos/images | tail -n1)" -lt "$needed" ]]; do - to_prune="$(ls -t1 /media/startos/images/*.rootfs /media/startos/images/*.squashfs | grep -v "$current" | tail -n1)" + to_prune="$(ls -t1 /media/startos/images/*.rootfs /media/startos/images/*.squashfs 2> /dev/null | grep -v "$current" | tail -n1)" if [ -e "$to_prune" ]; then echo " Pruning $to_prune" rm -rf "$to_prune" + sync else >&2 echo "Not enough space and nothing to prune!" exit 1 diff --git a/code b/code new file mode 100644 index 000000000..2a071fb30 --- /dev/null +++ b/code @@ -0,0 +1,690 @@ +{ + "id": 4228, + "value": { + "serverInfo": { + "arch": "x86_64", + "platform": "x86_64", + "id": "81260ce3-b6f2-471e-a77e-0250c11fb907", + "hostname": "tasty-mahogany", + "version": "0.3.6-alpha.7", + "lastBackup": null, + "lanAddress": "https://tasty-mahogany.local/", + "postInitMigrationTodos": [], + "torAddress": "https://4iqvmnjyqlqiq2nfavk7emhyjwhq6s454oubur4givisxblfdwxfj6ad.onion/", + "onionAddress": "4iqvmnjyqlqiq2nfavk7emhyjwhq6s454oubur4givisxblfdwxfj6ad", + "ipInfo": { + "enp8s0": { + "ipv4Range": "192.168.122.86/24", + "ipv6Range": null, + "ipv4": "192.168.122.86", + "ipv6": null + } + }, + "statusInfo": { + "backupProgress": null, + "updated": false, + "updateProgress": null, + "shuttingDown": false, + "restarting": false + }, + "wifi": { + "ssids": [], + "selected": null, + "interface": null, + "lastRegion": null + }, + "unreadNotificationCount": 0, + "passwordHash": "$argon2id$v=19$m=65536,t=3,p=1$qZNXS7Xk+qeOfi7ZO18OpA$VHr96ABySCvi/fa5p+N+SY8XJ/FyhxVp3LlBxmxQa6Y", + "pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM1ThH4SGEGzCFoKlkmMw3tNXvx975xXKEn/dNnBDEGb", + "caFingerprint": "88:CF:91:19:FC:35:18:E8:B1:8E:B1:36:53:B2:22:6A:EA:5B:58:E6:35:7A:A5:82:1B:39:1:D5:E4:F8:57:3C", + "ntpSynced": true, + "zram": true, + "governor": null, + "smtp": null, + "devices": [ + { + "class": "processor", + "product": "AMD Ryzen 9 5950X 16-Core Processor" + }, + { + "class": "processor", + "product": "AMD Ryzen 9 5950X 16-Core Processor" + }, + { + "class": "processor", + "product": "AMD Ryzen 9 5950X 16-Core Processor" + }, + { + "class": "processor", + "product": "AMD Ryzen 9 5950X 16-Core Processor" + }, + { + "class": "processor", + "product": "AMD Ryzen 9 5950X 16-Core Processor" + }, + { + "class": "processor", + "product": "AMD Ryzen 9 5950X 16-Core Processor" + }, + { + "class": "display", + "product": "Virtio 1.0 GPU" + } + ], + "packageVersionCompat": ">=0.3.0:0 <=0.3.6-alpha.7:0", + "ram": 10285481984 + }, + "packageData": { + "vaultwarden": { + "stateInfo": { + "state": "installed", + "manifest": { + "id": "vaultwarden", + "title": "Vaultwarden", + "version": "1.32.1:0", + "satisfies": [], + "releaseNotes": "* Updated to the latest upstream code with notable changes:\n - Fixed syncing and login issues with native mobile clients\n* Full change log available [here](https://github.com/dani-garcia/vaultwarden/releases/tag/1.32.1)", + "canMigrateTo": "!", + "canMigrateFrom": "*", + "license": "AGPLv3", + "wrapperRepo": "https://github.com/Start9Labs/vaultwarden-startos", + "upstreamRepo": "https://github.com/dani-garcia/vaultwarden", + "supportSite": "https://vaultwarden.discourse.group/", + "marketingSite": "https://github.com/dani-garcia/vaultwarden/", + "donationUrl": "https://www.paypal.com/paypalme/DaniGG", + "description": { + "short": "Secure password management", + "long": "Vaultwarden is a lightweight and secure password manager for storing and auto-filling sensitive information such as usernames and passwords, credit cards, identities, and notes. It is an alternative implementation of the Bitwarden server API written in Rust and compatible with upstream Bitwarden clients. All data is stored in an encrypted vault on your server." + }, + "images": { + "main": { + "source": "packed", + "arch": [ + "aarch64", + "x86_64" + ], + "emulateMissingAs": null + } + }, + "assets": [], + "volumes": [ + "main" + ], + "alerts": { + "install": null, + "uninstall": null, + "restore": null, + "start": null, + "stop": null + }, + "dependencies": {}, + "hardwareRequirements": { + "device": [], + "ram": null, + "arch": [ + "aarch64", + "x86_64" + ] + }, + "gitHash": "74a504429097e933285782e1e4c425da5895d516\n", + "osVersion": "0.3.5.1" + } + }, + "dataVersion": null, + "status": { + "main": "error", + "debug": "Error { source: \n 0: \u001b[91minvalid type: map, expected a sequence at line 1 column 1353\u001b[0m\n\nLocation:\n \u001b[35m/home/rust/src/core/models/src/errors.rs\u001b[0m:\u001b[35m509\u001b[0m\n\n ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n 0: \u001b[91mstartos::s9pk::v2\u001b[0m\u001b[91m::\u001b[0m\u001b[91mdeserialize\u001b[0m\n at \u001b[35mstartos/src/s9pk/v2/mod.rs\u001b[0m:\u001b[35m265\u001b[0m\n 1: \u001b[91mstartos::service\u001b[0m\u001b[91m::\u001b[0m\u001b[91mload\u001b[0m\n at \u001b[35mstartos/src/service/mod.rs\u001b[0m:\u001b[35m271\u001b[0m\n 2: \u001b[91mstartos::service::service_map\u001b[0m\u001b[91m::\u001b[0m\u001b[91mload\u001b[0m\n at \u001b[35mstartos/src/service/service_map.rs\u001b[0m:\u001b[35m80\u001b[0m\n\nBacktrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.\nRun with RUST_BACKTRACE=full to include source snippets., kind: Deserialization, revision: None }", + "message": "Deserialization Error: invalid type: map, expected a sequence at line 1 column 1353", + "onRebuild": "start" + }, + "registry": null, + "developerKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAIwXjctXTc37QTXRYWxLxElY1r2NhoKJRrl604Nu/0l4=\n-----END PUBLIC KEY-----\n", + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAAflBMVEUAAADh4eHZ2dnZ2dna2trZ2dnc3NzZ2dna2trZ2dnZ2dnZ2dna2trc3Nza2trb29vZ2dna2trZ2dna2trZ2dna2trZ2dkAAAA2NjaHh4dra2vR0dEaGhqjo6NeXl7Ly8u9vb0oKChGRkaxsbGXl5dRUVF7e3vFxcWpqal0dHSQl0TIAAAAFnRSTlMADejca/IbzkXAsoBeJpJUnjqmdYgyp8THagAAKQJJREFUeNrs2ttygyAUBVBAkKsoak6mfenk/3+y6VMvYyeaqAGz1y+wOZcBBgDwEnidTJDex663tvqi6EpVV9pa23ed9zK0puEMDoM3JvjOakFLVNp2PiAKJeNJ+l4LeozQLkpTMyhIbWS0gtakdD8gBgU4taMTtBVlozwxyFMToqY96CgbBjnhZnCK9qScNxgQs8CNt/QcekQIniwtPXyE4Dhq6RTlQDmJ/WBvyVeUkyoaBjvhphOUH9UHNIPt8dDnUfgnOWRgUzw4yh0ysBXe5nz3f3ItMrC6FHPs+/9RMTFYT53ZzD9H5bEbrqTNv/FPs2gFj6vHkkr/X2LEs9FrXv5vtmVwHz6U1/mniAGd4A5NpOPo8I1koVR+7f/N4q1ggdbS8ejAYJag6ZiExDBwE5fHmPymCY8I3Dj+krf+ORQikNHxv13O58v7B+0IEfhk1852G4WhMABnmTRJN3X9f8MYDBgi9f1fcNqqKkkPsREYRoV81xE3xz5bfN7Y4bean7TFuDaXxUCD2zXGZfgtw8hWfxYXJ3ZbjE3xm8Lo1peh8MjjHuHYSqk0gU/EIxF87KEo4gQBbS+roS9XTwjHVPykDJwMT5h2X60MAtpfFsTvljcIyOT8khu4pDyRtv9qSA+XbvB2hZBislWsLH+waCDiT1YIajPzbvB1j6AyHilwnmLN2wcW5DATw9xbgeUDAjuw5rqtEYUI51Q8dkBo13N9PHi7Qmi6XWU3bGDQrOKJHOE9L2bobo/gbMtrnbLmOy2R7BbC287uFfnyBgNI2yV2y5ovspHjoIR0P695YLfGEDSFBJJiIwUpoaAxiPWMmsHlAwZRtrvXEWuedGHZoMQQZrQU2K0wjKpVZjc8y+CUZZMKA1nN4gV59+ufWLixkTY4kbLmLu9GsxHcTJKho6fpJ4HHNTqJNEkdG9dPKMmVoKWDxRGTe0uFZGK+KyJ0s5p4J7B8QTcV/Y83CrY5AYoOyh1//4rR6n51YuKdwOsW3VTeTZyztlcyTfgvd+XtFaRD72lx0uPAH3RkeaQwjgrgOwGGHkbEv30NMIUYQDt5WUzS1TW6SvnJXQZyOsT1l9zSNj/MHek/xMJoO8V/B/oMf7kIp5TR6a2M4rRQ9FJFGkflG50ySLE4Jd1tpjcQvqAHGSXjKL8jkI2IkWcL7cyiF7zao4eMLVa8miPS+CGhlKGP9ZTei/Xc/SUURBmwHJUV6V9K0MtmOu+Gn9HPgYIoAylHlYr0Lx3Q0/1iEpbX6MQf3BI1jkujVroOSXtTnQbu1uhL+e9hyZGV/tyj0Nvq9z8U2W3Qm+Y5eYYPptQcmS7/4kOWO7JEAL+9EXhGAO6baN8U/wv1j717XWobBqIAHALhkl4olJ6VbCv4Ipvw/i9YZihVA3IiOStnxfT7S2aSyR4irS7JcwdLe2CSTzUR+AEGHWWqA4ebfFcELq/AwVCmDFic5/plk7+WYFFQpgrwWOW5P3i7Ao+eMtWDS44XyC7ARVGmFI6U8w7xd7ChbIHP/SIrZ/cIVg+tUkON9z51AOpBKTU84pBMm4GzGwSz9KqvsSP/JmC8Dah7eqUR7jyfdeGY9k/TX70ZeUS2NHxM73lEgGUu7WDM6n9N/1IGf+XfBfr7QKPoXzXCrfLYGYhq/3vapTTeaymV3pq6NranVFq8pxXt2gD4XAm4WyGCovcq7XkIG//z6IqSUHBGnqfCYVktCcXVH/7SPKZvAopZxhnAefTHDHGknxaNXf4jP3cFrNSUgp1nqqlLuCtjfgiSy6LgLSLRmKJ+LX9FKVh/O8qveo1AXdAY7Mg8AbeIVdGovoZpKIkC83UbzRZ1v+fP2JV1An4imqI9Kkqjgleyp6M9FGLJ/U6pW8RTdAIWXpZOQMEjz88Af/0FBqCaMB45EgIgMgHX8JMXgA1GbCiAiAAI7AbvVpiip/lZQM4Y0GNEZgkIqr+QhX4DQfuOBcZllIBve+svLACiTh4UmErSqvC3Jfb5H4BxBaZaybk+fHmOvYQF4HMMAYLOB5xdYbKBDvvUAdA4SPoZoaDzX5Lec1FtoMERzkWcE1wjhKAAiFoIMggi+KTodwQRFAAy8l9LqPViIhlf/nSiN72BV0MhZAXg5DdGLnCcjk5hkDIfjbxBLG9rMPJnf2S03i+MkM8iIhxr+XVxOpdLHIuSatqiJ58urP590TaUFI624l0O4F8A6IbBYBQlo4ptOX7abwj5/Nd4UW4LRclglBl0Bw9BzeAaB7h7EKowcwag2djywHlPZXZepI/Gm9JuGkoCXuZP5iqLADeL0/iCwzQ5vhBsiV3V68eg6V2zMTVQm7HaDtjxpPuK2G29xXc0AvxYnMI1DrPk+EIwELNW1/4YxtP4qNYtMRu8xXcMPhLRCgTtADfkfAxB2TJX35bw08fW3yltS6z60hXfQ8FDwN7w2TkOq8n5GIJHRYyULjFOH19/p9S8r7x0xffoEGA5+77QGgEMzaR5fsJ+mqP+zlPR0EwsQlzN3Ar8hKAAbDocpnnq73QbmoUG5E0EryEmAG7gP0AzvuduOpCeQZiLBQPuFUBKrNIlgmnm+rvLi0kh1IwrgjcIpCilxpYA2BOgEaO0DaWk8ELYNOABoQpKR1nEGijAgFhWUToF/pAzDbhAMEOpKIMJNOv/v2MUpWIAR8Q0IGoLkNJoO0yjeevvdC2lAQ+2jcH0v/+oKIG2hgdLAjSipI+AQoz1DAl4QIyC2PU1jqG56+/UPbErEOVhkdrXFWIYIjn//QcToHGkWhEzgyjpf3L0CnGIl8XxNH/9HVsRK0RaJu4FvyCSIk4VOGjy0uAwECeFWPeLlO4QqyBWHThopvqnT3yBaCkHgbNzxDLEagMWA30wgMUTsTKIkH4Q+I54xKsEC0vvWPB4JlaYYL1I5RoTKGJlwMM0RE6zBZOGOClM4W4KnHwA4J8E9ODi9nJbAy41sSowxepy8UrAAMC/ElCCj3lu22cDRoWIz7u3QUDAACBzJSCdilhhkrdOQMIA8Ju8M21uGgbCMDmacpWrsCvLsmzLlhP+/x+kMxwephAdq1XW8Hxmir1S9nwll08CHAjmgkVRkAVPJfAGMtFYlhbkYrEoGvLgkAY8QC4dlqUBsbRYlg4yYTgo8AqyQfxfYsCMZYFcyp8YPUI2PRamB6l4QW/6pmwFcIBcWoUFER0DWiyMaiGbQ9Eg8AGycVgaBUKZsTQe8vlUUgUC2TRYnjPI5Mdel+Lt3tZTgQzeIOKonvD6iaZ7YloL48J8BZG0yMAET0zdE41+wqsnRkRUYWXcfseUAd78674jiIRgBg7vUC4P3O1lvTjiABJRWJkZApyq6IB7rI4GgZyRAMcPoVQ/8AS/IeGCRQMCWbA6CwQo8kmBT3Adh/W5gDxGrI6DAK9K6EDFhb4nLIhjwPooCPG+RAko0AMIHAlqrI+CEAf6XTASYx9iB9IwWJ8FghypU+B7kb4PPQhjQgKM5fD9Q/EmsIiPP4qLARbr4yGCN8QekND0R5o0sEUCjA6AKhE+Agh1AcJkIR0S4A2D70gOQOzbC4sBHgkwJ8InigMQ2gWXJgtpsT4KInlHcACCHaAoWciMBNgr4VN+D0DsIEyYNNBhdRREku8CHg4gdRQuLAa0SID/9Q9f8jMAwX0wQTGgweoYiCbbBRwAQLAJBEkDHZLg938nShNQrAsQIw3ssToGkvhMcgBSXYAYaSD13fkToLsMfeh7+IHYX4EYaaBCIvwl0JGkA5A6DxciDTxjiNu/+Z6YAch0AUKkgV+xOj2k8pb6USgWF6DMvyANHJGCUVVc32v+6wCG9Hfv9T8gDZyQhD7XSX4+8l8I5dN/wMM/MBLU1Ez2UkEOleoCdneQzpBRx4/bl4UYci2rq1Q/J/77IDJaucvmpYEXuq6zVTVy389Fm0D0majp4Ynz5mOALdDPPteQRN/tuB3AmJPBq43HgBZJqBw3MraQw5FUA4Zpshr5zcalgXOZlq5m3/YpaeAJMmhN1iy33XgM8GUev1UVXMBH4nnwgg7A9OsoddOykLaUAzuzv3XCrUF7SKJrtFPZLbx507IQ4tPP2dWEcrrpIJFd4Vuhp057Re3gtZuWhTgk0VLLCeV1N0Esj6VSwKFbrDKFArelFtI3pC3Yym4NYZ5gl26AIIcSDqCzY9ljXZcNx4Cm5DCrQSKjDW2Cl3QxuC2v5jTbjQGq6DhbsR8Xe0dWgngGIY/erCzkXPbJB+YDg3HdwC9hl1d4wYbNSgOXwpI2y35g6D1xEKyQjGlLX680wa0YC+/c1nAfl3hNawIMPAf7l43KQobi9cuMdOAqJ9IcqEcyjh5KxUgDNZI487hYuMqRdisgkhnokUWMNNAgBcXkYwe4xmuaFMggEV0itxQSAyYk0XB4lbA/PCUcB6GvVCAD3PZI0DI8ND0P1HCdIyEC0F1AV6alLkIWQh0E8ly2YXq4zj0hAtDdXltmqCZCGkhcqZlnX00Q4oGmBVuQwleAfyYGUKUgLOeMFgjySNSCOZbXtpuLAdRBIMufdRDiegzYQZjeIAFdRlwtQBo485SumpoAhDkRj4R3LC7AbC0GOJZirScnAGGO1M8DaiSgy/zRm0sDW0RxZtAQxau/q4EPFabgfZG++s1lIQ1Lu66vYYPDiXAnAP0xbZHJ2s1lIQopjBy5cA+RvCfLwTuG51yQxAJV6Vmetqc12WL5FNACMacBlkVdM0JVFhZ/ZTkTgJW7AgeCVPm3V9SoWhPFkbGcayVBHykpAN1ZWZa0SkNFziw1iyUF1gTeFDgSOhd3AS11DFoRzdG1GKq1Ql//OQLsIQUb8klKK5XkAhx1DFKPkaNv6QOGLKeI2D8EvhBJNoL/uRqdj4/X82ZkIROSmOMdgO/gO5MvlwH/pRB8AwGiPZYbYGVw+AzPEgNaYIRfCuKDhiyVAP/lgMgrSGSOTHGW2Ce2VLVJLQyDqxrChmyKzUIPL57zcAepmMh1aCKj4GUjMeDCMQh0EbXCXCr9vXsgFIEBl91EVXjDlkeClqFcmVIMSX/vt/QUAKCJz3Bd3D/Tm5CFUAeBFAu5Qu/9oUAKAD6+IdHHFW3DJmQhM0PLcoo05FBEEvnHJACSSXkWH7dY4xZigGMYWrgMQ65AOidqFwBgSunGTHH/ckEULwtpkcRCME/A6im8JacA85i0F+P89Rnlx4AGyw8CVY4hV8YZUvkcOBESYLKpMykVVw0pJNEDPw6LDwIvmYZcMXaCJO4JV8MNi0kfydm4d2xQegzoGZ5QhQ0ZrpTMMkACu0wtQN+MuEJ67sufA6xwaWCDxfPUjmbIlbHpIZaXWd8Ia1zuUF5HrpZD4dJAVT5LUdmu9DmugTge0wXhsyf8AlVkRTyjbGngGUnMkbW9I2xAP0MEHxLbQJM1+Bxa8qpjYoAwaeCCJFp4jqUYMjMlfNYKugukfSNRlTFcWa2AOWRJA0ekYKP/5ECUIozBlHAX/ZGw3mM0PilyRZVEoqSBA5K4RP+mLf1Msu7hGi9jR4H9iAn0KbVTGzMSFCUN1OUfrs00ZJixhys8xuaAHlPwKalrnI0lSQMNUtAJUd3yLMbKh0hBcIdpdAl/I87LCpKFXBDLJygEQ1J+DYfIPqCm/wSnv3rErY0ELTKUKIZgSEpVvIu7GMJhKnPstrWRlZYYaSB1EJgYYztyj0TBFb7EfSLAYTK2h5XeBnZKsNci58aoDp9Db1POBENSbHGMmwVrzMAO8J1BZ+i4ldQY4JGCyvjemv5lSFu8KH4Td0P4hFkYr7X2JuARI+ctQqSBLZJo0ruLqyEZwuHryEawRx7GNtrQQmQhM5NnakfkQcE19sEiINwIYmraOpkxwHHtywFZGHu4yi5QBPDugC7lpyZCFtLyRaYOV6qtPzzEigF6h6Uxa1d8M7KQhtEvXQyWxvUQ4GVgEsCYByypDRcJ0kDF2qFcsCwegjyuVWDdHaCDC3QRGAPOSOICAXqNBbEQ5kOKHKgpufxhjLwYsCB1EFhxCzQQZlUG30O9HWD7rLGrAGmgQmTXKfS24vrDfZokvEM69gxVhBdfoThnatEbx5n924Er+13ascCJf/lXRhQmDdTkB6q2BSaI5CHxTMBkkIAfIC3iypIGGmLZk8DgkYCZIJaXgVkgoSVEWP7V40qSBk5Vk5LBU9o/0RzXz4Uz7wA3QCoKRUkDbeWyZHD86w+PgVkgTSG84iYIwPxpfjqtqd6YmBym43tI4PO6AeLxrMu/0oqSBna3GE9NjlkM8zqoCKU7QxVY/o2MBO1tBtSTYs187gN9ILpzVgRPPAuSBrY3k6hcFGOg2Qf6QNQmjSItQitIGjjf0Bt1iq323QfUAERrzEDDyokBrmg+Isfgu6wN0FSqxC5ipIHtjSsStlrjy9oITGCpNZIzUqSBzY17EorncoQfrcAvkIitFYS1lBjgbtyV9Fz/0duAIIxkjgWoDEJkId+4OxO0xmEYCptSoIWB2Z+y703L/S84y8dMZiNO8qxGnf8AtDWyJOvZUrm2LvGs5fPeLOgSXRbnW//axrWQeG1lMiYuW4zy4BkZTy1GB5rextXAiBQCaTotr/fkuRFIVaZy0ByFX/rVv8URNLlW5fWd50YgdWGpBU9kIQY8r/4lTjICEweeZjWJjc9/JyNeffMBqFdPRVutj/wwwwO00Qqrnxq4FlKtfxitRHQUuP1kMbAs1jmGN7L61cBk/XJUqXD5epAD90quGDS8CtOCJxOGDh4IR8jHgetBDaa8/1IDuIRrISehSBECGYGIA4MHILw/WwFXntBFU6hZIG8AfBxwX7kmvD+/9uoz+kaxLwRyYSiGB68BVJEspEYYsnV3YKfpgfRPolGJEfwG0IoHtgBiXhI8CEMCD3RBjHwm4mkUXhLbr0EYKqHoQJEKRYUwNCpOyO8BElnfAMhC3AEUnTDU8MAYAJ8H+D1AvX7+qzCiQWHlteeXHLSs0PM22MABKNSQllUiwBE8/FkUf3BpBqAwpklJjPLnwZdmAEwIOIBCvzuXvu3FoOBDgN8M3VfYped3nn6HVuXoY/pWaoIRfPOiDBwDFYa1zsk/TWwB6hhYYgSfAbSZ2rrrn8X4VKwWhg48fChqMcKNtxRcrl4KDjyn4cKugtCl4AqjuK9sTItBWpNa9KvQBV7BkBi0+W4ApuVghVlNqsvOC4H8N/HLwZdzIURtWht/FZPYAGYuhGy+G4DlK2Fa8xr1A0+CgIgHqlns3vKlUD4dGzLhyxQCR74K5f2HEPDB8LVwrZnN/HMcMvNkC1K89x8M4B52H4aEatWdYj4HYegRkIrw/qPcW38aFqoom+OFyxMCPeko9zRs7zEAKg6cwKA/roMvP/J1UBOPQx/wgr3n4WtKgo0wxODQfx4+GMAWsygLBQOw16kvPZPBrdsg4ukfBmCoRYzW0D59j9MgKL3Wp24X9QgqFOKuuV6NzfkczjpNoggDeFbIg6xdCyktRQDlNnG3+A07jSLX7Ncck4eOsGRamedOtVVsiqCchKKEB+LUqT2sQC3wvF9kAC1RCWI448yGIyk+haVVq73eOs+VIMo9xghLcr7SzLMdIVB1wa+WzAso1lqH6nzF2dqMEPiNRGvFr53z6MGURtIgMPW55JnWjhDoOZJyjzH2ntHBxNAolYXoz/V/SewIgR7D54ZG7T1Do9ixcZiFmemtbLp5RGBkBGps3JNz82rBZa25EjbmN9MHzgiBOYqWBTw452kXzj0TyTELM4MbCmGIQTBHDOZHx76dNzq2XX0pUqFoAeDChEAAsdLv/Dk7+IowxKAVUSPDmzphaMDCO6Qc0/j8YgAbTCC2sBZnkQQPwtAhNJFWGLq5cs5pvgwQEGh45/wSIwBkBMoCbtwL91q7okJoCmE46HuZAqFp1b7HvXvhScsrxpiFjc69jTCcEJpezdifnJt2DiwnrgmfBBjo3Z0KQ4bgLF78Eh4e3Au7QOUfPgkw0L0/FoYE8+ANkigJ7dwLV9z/P+CVAANNGyJhqBCaVtQs4PNPA9horUiPmaw+wecoDDUIiBRgUR6wuXI/uOPqPwGTgNUbN/Uitgy+EVHywHfuJ+/Ugm4KAg1JsFR1MEeEJtUzyL37yYPHAi0lAbrNGythiBCck57De3A/2Vmem3rW9q2JgnVRPItoJQFb95PPtqemnfHddmYs4Glegfjs3JRjQCvW1qQRhmeM0P5n1p7jdTZXbmCvJo6dQHD2IQ6FMHQIzkkvAly7X3giCkHEhltlV1QXJAQiEdEqBL1zA+NqQJnYSoz1+rflwlCAgEgBFt4QHg4BfjWg6mtL24Lzi9kFRbtSllL3FcbZuV+4vYGHtsjMHI3YHq4XIwQuPfJmRQsfN7fuV+7gpztYiQFKXZw7Y0Lgwghw6DCBjRuY/joobiyUR7UkwcaYELjkdzYxJjHkgDPfBpRxbWBn1Bqn49SaEDjb09Vxiak8uN94j+lUfbZ2bOw1jsextaI35q1zX2EGO/cbV9eYQ1usmx0fhYIMuGykY087/rTPz+bW/c49ZtLV8jcXcT4W6cIbVQQC9lRad5jJUAf0K8J83TwFj34bv1gYYhCwOUmL2Xxyf/AesyE2HIFa0TYi/yIBeyrFfN64P3i8wQich2pAoNnK1x8BLuInHjCbm0c34BMEeTdcgkX/BWcvDB2CU2p++N79xRPmkl5GgJxokrUwpAhOr/fhQxnIUwpacEy9EEkwDltcLEDAFrsyzOetG/C/DuG9cAUO/T4uiTCcEJxKNf7cOkcnAZVMJwGHfienTAgyhCcRxf21d//gEzxMdFEXcVlGeuoyIG/fRI5D6xBDFYCpBBTEevPwV/gpa9KPcL1qDvLG/YPbDTwQx7AsBYH+I55MCGoEJ1U8hg5CACEHVFzazcM/41uUUZp9/VJiMsOrQEIOiERWdwEB1ZuCdCahSTPi5/jZun/yiMkkIgZcQDD9NiVNKTixbiL63v2bDSaSi4gBFxDsBkcuAxZM2+cAyL6sN25gUTW4NFIuD7ZxC2Nl4Jgpb/t5cq+wwzQiK7lyE0YPSK0JgbVuKHrjXuH2GlNIzChmXRjX3RkTAjvdetT1rXuND5hAbidZChQDDsYiQC0LaTGFvRtYoAiWmSwlR2iKEDEgNSYE5rKUrMQEPrpXedzAS2PpvHQKEQNyY0JgpJuPbB7d69zrlt9ahCYLkL4VtoTAVrkoeedG2MJDa6xkEqKHWWZLCIxEd49t3QhX/gTAVs2k4hP41pYQGIvoeqRHN8a9f8PZSplrITgAwLMpITAV0d1j926UrT/k2sqZe9oeI1NCYCGi6wK2bpSrKRmApTzwKAw5UJoSAluhwTiPzhExQGhqBCYSgoKMuRECUwsNRvngPLxRMADVPDAWggxo/psfMyktfXAeHhVax6vmgSm3Wqml31IKT4Mxrh+dj3cKMoVqHtgIQd9xix2WQt3D7p2XncL0KDoPVDPKiFrzDkFpheeAUd44L7d32hZQIyzCkAkBgpLWQlNglLsr5+cTRqmKWkhi8PCOU3W1V8kA6yLHOO/cBHbwUuV9ERF7p0RITrISJxCEzACzqOjzCl5u3rsp7DGRNk8OkYGtk8kqZGDhg2t0SPIWU7lzk9hiHnmcNDPtIEdIElmFBARsLhs1SZxjJlvnwTM+gohgqiXUSlahQkCO+lnU8CicSAND3WWPUgSklhWoQcCdAOoUS7h3E9lhCfF6/rMXfXSFwELfAQzNQYk0kDBizTTgKKroR7H8HL7n2k1miyV06y1gJGcnAgFnwB0WsXXT2WAJImulAbHMxlA1K430j5/+FJBPA6v1jlGp6KEvBBZnOX58cjN4v4EXvpKRIxiNKMILgVQCwHcG9VcB/aKwn2rNPKoTJfSFwKN+BWKKEMyfBBPxwKUBxmLAF/budT1tHAgDsAIBAoGk2TbfSLZ8toHc/w1utn265IB1sCQsk7x/66cQNJZHM7Lt75vzy9Qg75idDSwVwaupMbUEc/iSXKabNmeWtrCUBG+oxdQSPMKT7EJtiC2zNYeV4gIF9XhaghKelJdpqJ/eFB2sGCRoGHmAuWhaggn8OMgLVSFuGQs8BUjScW9sxNMSLOFFIS80BS1uGAs7BQg68bwUOGT7JHkRxwMQS0uwgtrhKF6SZJ8dEGwToICNBzbAbgVzkvxHQFq2Lx1/c1wTS0twD5Wmertj56UtU/UCMPwUMN+xIe6hEPQlXmkjkro6c2AZR0vwgH5lTZ9UdSKaNNRrj/R+sEGe5zDFfeVUxVHktaReWQwtQT5sWSfrXBwLX5ULDmPzZ8bCTgGZhzn1kO1zTloigpag23fg+T47ePi2mcME4HsK4ORKkqnj+OXg1MO9PZJccecJQO/BYQIIRRZjtwRro2VdeJnrBKB3s8B/4urFJmO3BNs4ilHUwcySDXcfXQnmVTryNSD1WQ4PX466Z2rutwh0dFli3JZgPn4WajUFLJmL+/gmAOrGbQkeozkTqITeT8bcp4BYKnB/SJuC5JV9+nt7xy6AeUcgri3Zo7YEE/SiS6uhdctcreILgGbMy1FpXAQIj0NndsNcbeNaBP6nHbElWEW0MZU66GyZu3VsOQAlI36ZfSRbUn7bQ2POPHiObRVA9YgtwUNMU2EBjTvmw0Nse/Kr8TISHseOlD+Efh+IF8sF1AT51rUNgFJ0dJbTdwn2m9NZnSgBNG0X4Lvo7Jgf99AoO05EFX/VJa9E9qrBq8zxLysSRSY+Qjk4tbwUJoVjbB7xqsleieRVx19VRCS70qEIHHB/qHtWVhd4q6zok3asRUltdyWsSrxV1P7TPEC/BHS3hZ6vAcnxQVFZ/SwtBdQCFqFeFa7NihoOtsyffzBYWtkubfURkEOBAoJCRx9Uju0Cxy3zK+bR0xyDHcgCN7q6cijkFEwOBU4flM6rlAOGm98xn35iOLJQmqX2UDhSMEebP1O4V03g4BfzarnBUBmZ6wx/3BQKkgKRUDgYHtyRuQyDbW6YX08YKiFzjeH/cVR/YCAJFI6GBzcuH2jukfn2gIG4h1OsMZhew9emSygIw1iGJGMcQz0w725mGMbhCnCiz8bC12QrnOhzT8DDuhgDLZbMv0cMkpG5xHQaqaGypyD2UOGmJ29C5jLoaLeBjF4MSEIEgITKgYI4QEWGCIAEg/xgQSwXGICHCABKNZ8ZAIdKSiECgGOQHQvjFgOQlwCodClW+JaggEpjnDAkZAFD3LJQNrCWkYUacKiyBG4JpjYxp00CAyYBKxbM0wq2ErKBHo3lxbEm72rLv7PRxHKwJGD1xMJ5hC1OevqA7yyHoyXvWsuQyzVTYrAk4CcL6QGWvJxmhXZNHv4akEKpoo8KLwkqFNxXAOHLQRnZaUwbfFDLybPcOuJy3cUsSBKw2LGwHmewkZCdqjD80UooHcmzI5RKw2AuKrKTwMpiy0K7hw1OljrDk6aFmiSvJAYkHY0mmQmQBPzDgrv5BxbIWmf22I2949Tj+UR8oTNk4eH+YdjYLJnCCAXBjOxVpVFC3+rmZK/KYauOVru/1W8S8Mwu4RbGEhoiKU8RxKlP1WrSco8qKKRtRX14dhr+gT8GzN2yy3iAKU7D1PssK7M2l6RStemFWoJ79EpFRSoyb7Myy/Y1DcNhbMMuZLeBIQpMivQiLcFD//BLCgymNjt2KbuFaQoQXt2mwVuCvG/urym8DIaemNIYaYCgi8ibwB8ucEaT00VkkSUANmlASxdSvRxCloNTfHR4qehCssgSAJtt4g314UmGgpNHXKShWoL1pwu/329eIEs49SnjqQC8tZzBAO8b/N/SOmg60JInbdALf53it54gqGBi8cwubTuDXnZ28E9eyLNaNKeh8n8FaPY1efaCk3NB0MLA7JFd3hYGRM/gB1wnyFqUXluCOX4rRS3JuyNOzgWBgIl7NoZfMNDK0+CfQ0HIThyAo7cxOohOUhA4ORMEPIOJBzaONUw0Yp+NUSqq8laSB7LNKwoGvbK9aGBkfsPG8byGQhy1wtjB3fqZjWW3gKXvAPAfAE9sPNsZrETweNXINHA027Ix3cJNRl9cBjuRLABOfsHYdwA4BUCc429xy+h3AAQIgDUb3dIwAr4DIEAA/Fiy8d2sYCyCR41HRsDBPIbxZ+xpDVMRPGI9MgmGWz+xOOxm0PgOgAABsIhl/Bl7XEHpOwACBMD8kcXjaQGF7wAIEQAxjT9jdzAy/qv2YtNhoFsWl0eYiOCVY5Hh6DOt8VdEwHcABAiAnyw+twuc8x0A/gNgEeP4D2sMcVKQdBWkawBEswMowBxQUa+uRHsFIVC1KDvFP+OzKc7/Q+cA6pOUeJWKiYeAFKnmzmB8NNnzf9AcQOedXraVTrpbsE97Xn92AkuL+PJ/lzng/FlTXMumMbxRCGkQABOe/wetBukTKa5nocDxnpDaQzC59b9bBHT0HhcRvJLXmxofCU7vdfjjasafsbsFzJX0Fs+uq12Q4KTnoTclPphQ/d9Lb1DQ/7qs54jJEjgn6xRHqKymMf6M7eY4MY2Arry+XWMZzjoVBlp8MJH+v87TBuaKPed8fxr+K7p3BL3KPef1voSFzXTGn7HlGra+VgDYW8Wx/8/9eaLmJr8O5PBnfcOm5eYXXE1+00gHb+6nNv6vbuFo8uvABL78YlO0ncOLliYqgx+rLZum5xl8aEhF0ogkqTTwYjal9P+95Qo+kEKLtM0ljUDmbaqeneBgsun/e8sHeCCpT1Xit6aiC6sa7SPgJXz4NcH0z3cqyKlHPl6e0OKvPMgqMJ7bv13dzeAq1w4CQJclDZLUHM4WU6n+h00E9nQOLxRBElqOk75n3u7har5j1+DmB9xkBovshsLRJ/hJkFXgZuKX/5PbOVyUJr+upAuSJjFawsl8Cps/TD3P4UI1/Y/yqIk9TnovA3Ayu2PXxK05VBnUWEtSemlFktectHidJ6J9IaUSJ32XgQqGrnz6/+t2hcFyeks2PVGikJjmirniQM3YNnLAIuALTP9/7VYwoc0C6wJnCZO+XEEahUkPUuCsovaUAsyf2VX6haFyg8VVYdSXFaQkjLrQBXrsvfSC/7m66f+vxzUG6hTTv3b/eFXghOu2cOjf81yjVyOdx3891d5f2FxQ1PQqKVSH6MZfXzBoYBIBAv2KRBKRFBhqPeXej8Pdg3plVkCNzpKFKqNU5W2FpLOg1mSwN5U7v9wtNwilM+rKF+bX9kaRUQSxuo7ar8Z2gRCMX+IvzJP7RlHi9e4rnP6BJwFpNlrcvH2b0ScSgay/xOkfdBLIzfK1hs4yfAFtjgCiv+/ft+eHBfxrDPP13KJ9LxRx4s/i4Qud/n/czeFfZXayFjbVnU5bBnY3u4aNH7Zu7uHd3jBdFzaL+07TCHT3cLWlP7XdBp6VhgU7cJsNfMoSv7vVlVb+TWzX8Kt7VwDs15hf2d+XBBN4deWVX5PrwAI+FZXR+H/KA3OH/9XB9Hd9u1v+gE9FR791heY4ekd3ND/9rz5tvlzur30Drbss+bedu01OGATCADywGEIghBDd0U4/ddr7H7HO9B/TmppEEmCfK+wK78rq+fx1wTGnIAGOeHk9Hj8vuCiZ19bXDB1gfMcgAcbGy/rm5zYmFMb2HrwXxqVE8Zf/jTQYx+U5WDKJJs0/fAhk0AJvTx/XXH/C6GzmSx9TVRZL4Cj6l9wCVP6xFthhxsp79EshC/yN7v5VsIFjfjgl//9jRmJegOb+O/UN5kOVuPAxW5tLHqTkNxUT6d8EMNDZP8fBY8o8PfjNxgbANMFAY98yepvgXEgf/iWxLq2hQBm6+X+kvTMwjdR09D9GlUAPSE1D3yNVesuTIVX/V4WcA2qg6sfCjN/WXLBrKPXF1uutHARga6r+KljnANfFXUeRf1WVWa0JeCMK/l3nllSdlRiXtIYi36awevCAMfBG052/UUEXUO2LxHphG1i89FZQ7VPCDkZ7yXEuLv1e9FT6VLG2FnunJL+77o3Tom6p8tmo2roTeu+8UnAVFhwApPJOa2HqnkZ7UohvgG87pEol7PoAAAAASUVORK5CYII=", + "lastBackup": null, + "currentDependencies": {}, + "actions": { + "config": { + "name": "Configure", + "description": "Customize Vaultwarden", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": true, + "group": null + }, + "properties": { + "name": "Properties", + "description": "Runtime information, credentials, and other values of interest", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": false, + "group": null + } + }, + "requestedActions": {}, + "serviceInterfaces": { + "main-8080": { + "id": "main-8080", + "name": "Web Interface/Bitwarden Protocol", + "description": "Main user interface for interacting with Vaultwarden in a web browser. Also serves the bitwarden protocol.", + "hasPrimary": false, + "masked": false, + "addressInfo": { + "username": null, + "hostId": "main", + "internalPort": 8080, + "scheme": "http", + "sslScheme": "https", + "suffix": "" + }, + "type": "ui" + } + }, + "hosts": { + "main": { + "kind": "multi", + "bindings": { + "8080": { + "enabled": false, + "options": { + "preferredExternalPort": 80, + "addSsl": { + "preferredExternalPort": 443, + "alpn": { + "specified": [ + "http/1.1" + ] + } + }, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": 49152 + } + }, + "3443": { + "enabled": false, + "options": { + "preferredExternalPort": 443, + "addSsl": null, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": null + } + } + }, + "addresses": [ + { + "kind": "onion", + "address": "mibog33n4dzldkve2uqm2muqkhrbnibkpu22rms6qrrhylwssuv7h2ad" + } + ], + "hostnameInfo": {} + } + }, + "storeExposedDependents": [] + }, + "bitcoind": { + "stateInfo": { + "state": "installed", + "manifest": { + "id": "bitcoind", + "title": "Bitcoin Core", + "version": "27.1.0:0", + "satisfies": [], + "releaseNotes": "* Update Bitcoin to [v27.1](https://github.com/bitcoin/bitcoin/releases/tag/v27.1)\n* Add 'Reindex Chainstate' Action\n* Improve config descriptions and instructions\n* Notice! If Bitcoin gets stuck in \"Stopping\" status after the update, the solution is to restart your server. System -> Restart.\n", + "canMigrateTo": "!", + "canMigrateFrom": "*", + "license": "MIT", + "wrapperRepo": "https://github.com/Start9Labs/bitcoind-startos", + "upstreamRepo": "https://github.com/bitcoin/bitcoin", + "supportSite": "https://github.com/bitcoin/bitcoin/issues", + "marketingSite": "https://bitcoincore.org/", + "donationUrl": null, + "description": { + "short": "A Bitcoin Full Node by Bitcoin Core", + "long": "Bitcoin is an innovative payment network and a new kind of money. Bitcoin uses peer-to-peer technology to operate with no central authority or banks; managing transactions and the issuing of bitcoins is carried out collectively by the network. Bitcoin is open-source; its design is public, nobody owns or controls Bitcoin and everyone can take part. Through many of its unique properties, Bitcoin allows exciting uses that could not be covered by any previous payment system." + }, + "images": { + "compat": { + "source": "packed", + "arch": [ + "aarch64", + "x86_64" + ], + "emulateMissingAs": null + }, + "main": { + "source": "packed", + "arch": [ + "aarch64", + "x86_64" + ], + "emulateMissingAs": null + } + }, + "assets": [ + "compat" + ], + "volumes": [ + "main" + ], + "alerts": { + "install": null, + "uninstall": "Uninstalling Bitcoin Core will result in permanent loss of data. Without a backup, any funds stored on your node's default hot wallet will be lost forever. If you are unsure, we recommend making a backup, just to be safe.", + "restore": "Restoring Bitcoin Core will overwrite its current data. You will lose any transactions recorded in watch-only wallets, and any funds you have received to the hot wallet, since the last backup.", + "start": null, + "stop": null + }, + "dependencies": {}, + "hardwareRequirements": { + "device": [], + "ram": null, + "arch": null + }, + "gitHash": "c995ed1b85f79135fde30a0c056fdb4ee465b50d\n", + "osVersion": "0.3.4.4" + } + }, + "dataVersion": null, + "status": { + "main": "error", + "debug": "Error { source: \n 0: \u001b[91minvalid type: map, expected a sequence at line 1 column 1985\u001b[0m\n\nLocation:\n \u001b[35m/home/rust/src/core/models/src/errors.rs\u001b[0m:\u001b[35m509\u001b[0m\n\n ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n 0: \u001b[91mstartos::s9pk::v2\u001b[0m\u001b[91m::\u001b[0m\u001b[91mdeserialize\u001b[0m\n at \u001b[35mstartos/src/s9pk/v2/mod.rs\u001b[0m:\u001b[35m265\u001b[0m\n 1: \u001b[91mstartos::service\u001b[0m\u001b[91m::\u001b[0m\u001b[91mload\u001b[0m\n at \u001b[35mstartos/src/service/mod.rs\u001b[0m:\u001b[35m271\u001b[0m\n 2: \u001b[91mstartos::service::service_map\u001b[0m\u001b[91m::\u001b[0m\u001b[91mload\u001b[0m\n at \u001b[35mstartos/src/service/service_map.rs\u001b[0m:\u001b[35m80\u001b[0m\n\nBacktrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.\nRun with RUST_BACKTRACE=full to include source snippets., kind: Deserialization, revision: None }", + "message": "Deserialization Error: invalid type: map, expected a sequence at line 1 column 1985", + "onRebuild": "start" + }, + "registry": null, + "developerKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAIwXjctXTc37QTXRYWxLxElY1r2NhoKJRrl604Nu/0l4=\n-----END PUBLIC KEY-----\n", + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAB0VBMVEUAAAD4kxr3kxopFxX4lBpCKQk+KAgzIgsgHBxDKgjzkRrghhhgQAsnJyckHRH3kxn4lBvchRdOMQuBTg5wRA33kxs7OzuQVxB9TRHxjxnPfBbzkRp/TQ8rHQ3qjBncgxidXxGaXBHkiBjQfRZrRAxfOwxJLgo1IQr3kxrujhrsjRrmiRm8cRR9TQ/ujxrQfBbAcxXAdRS2bRSRWg9uRA1jPAvniRm3bRO8cRSUWBCaXRFfOws6JAn2kxrliRizbBPchBe9cRS7cBSvbRKNVQ/OfxWVWBGSXw/EdhWSWBFwRQ3ZhBd8Sw3pixnZhhawaRS/gxGaZw/3kxr////4lBr//v7//vz//Pj4nC34njH3lR7/+fL/+/X6uGj//Pr4ozv+9+35qUj/+/f4pD781qj4pkH81KP7zZT3liH3lh/5p0X4mSb+9er96c//+vP93rn++PD7y5H4min3lyL+69T83LT6umz6tWL5r1T+9uv+7dj7yo76wHr80qD8z5n7yIv6t2X3lyP+8uL+8N/7x4f6tF/5slr+9Oj+69X96tH82Kz4oTj95sn82a/7w3/5q0z+8+b827H6vXP4oDX+79z948T958z94b75rVD7xYT+9+/5HOTYAAAAUnRSTlMA/PYL/SUeFgki68wzBg/y+sAsY0v4BGlP6bHwWxLfxH5607ZBOTIZ9ObZ2JJV4b2hnY1kR0Xkmpl+bT4c8M+SyLGjhXeodFqob1K7cOq2jIRnsm3jFQAAGitJREFUeNrs3VtPE0EUB/CzbSmlhRYLgqIoKnIREEXr/Y4KeE3OjtYHo8FbovHyYNDEF2NiYmJijL75bW0RQi3t0m67u3Nm/r+v0Onuf8+cOUMWiXeO7bg1fTkzdTsXc5RSvE6V9MbOn5jae2W+bzQ/3ENgjnjHzNzsiZjipqjc1OWrhfEBArGGj89dmFDcokR69upYnECSZN+BXYrbKnHiSv8wge56zp6cVBwYZ2qkg0BTYyfTHAa1Z6SLQCvZvv0JDtX22f4UgQ46901yNNT+PqTDaMUHD3O01P4ZVA0icnavYi04Q3mCcMVHdrFWJgfxIAhNctFhDfUe7SQIXH6bJg/+WtQBlAkC1bGXdadmsQYCcu6Axv/9SmponKDNUtNavvfrcS6iTNROC5pl/kbkdhO0RfISC5XBnkHrlrazYM4+ghZ06p/6t5RJEvjTn2MjxJAG/JgT8tHXkJNoLmxOdhsbJoM6ceO69rCB0mMEjegW+NHfmNhxgq0UYmwwB3nQ245eNlziDIG9P3+ZwhKorSC65teMRB9BtW6j3/3VnB0ElZJptkysm2Bd/BBbKI22kTVDbKkL6Bop2ccWO0q2Oyiq06v9lN2VobiRRf/m5CzuFzjKULKN7DSaYPjnGNknhad/hV3WdQuMMPznCNlkfIKhimNRafAIg8VhsMuKTV8/1ChZ4BpDXRkyXRJ/f0/qIBntFMMWbpK5soYc9QmWY2xteJChIXNkJCu7PvxJGzhyLG/SUb/gGXeEZJqhKUNkkoGdDE2aMGgOcZ7B5tfAPIMvi2QEpH/fdpJ8w5Z3fbZGiZ8z1s/QAvHdYqj9t0j49yBe/20wSVKlrDrwGxwnSyJ1MLSJyHbBGYa2GSRxUPxvI4Fd48YNeoyWuG7BwwxtJasqOIBzHwHYLqZLJI7qbyASQu6vH2cIiIidAWz+V7GsIDDKEKACae4GQ6AWSGvHGGqwpii4xBC466Qtq0f+hec0aeo0Qy22rAD8/vXYsQLw+4dIwwtJrzOEaIk0g6PfjTG1W3iBIWQ3SCMFhtBpNFKsmyECedJEF0MkzpEWsgwR0WKEQA+mvkfG0eFWemuue9TRBEUOw18idZgilmGIUOQjxjH4PXLT1CQUgJm/v3jMxpih5qAAxFx0XfetMYugg5qAAkCZW2bOIkhRNMQeAFp2K7x99pSFi1EkJlmqFbfKQ+GL4BA1AXd+c9EtMWoRnKLG4ATAKreeh8+es0z91BDsAJYtu17+sEidFC7J0/9XXC8PWCSHGoAdgFVF1wsLFWoQXGTJXC/fWKp5Cs1xluyV6+Uri5WnkKRYtF8mRoBVAxQO4SOgfEWAL27x1yvW3E6qBxWgDf4iwG+3rLiyzDqbppoQADb4jQCf3BL9F0GeAtfDwvmLAPfcEv0XgaJaUAHY4DsCbFJ895P1c4gCdpelc7285to+uDU90m8RDFKgkiyddwT4zLWsRQAZiyBLm6AHZIPvCODhPuskRwG6w+L5jABe3rBWTlFgDrJ8PiOAl4+slyRVwh5wBf8RwMtL1ksvBeQSy/fD9AhQdo3+gzHQFZ4YHwHKumgVXgDVrIgAvOklgGug1tgRAcqO0DocA6sQSAS4xzoapxKUgKr4jADvpUWAv+zdd28TQRAF8KUX0bvoHQECBKKJjoQQgr94L3ZsxwTTS4BAaDEBQhIg9I4on5YzneCcb/fO3Mya30dIVr63s7Oz+GNyxP974D81SgSoWGgSNQpeCI8AXajukboIACR9Y3QOxGi/5/onb6AIEJhvAj7eAyOZeXzvLOzVJQI8hlRLTXIgRwcDbovAMQIcYZh7kGq4ScwhyPGCAcdFcKqBIkBgvZddIL38U+bxkYiLoLEiQMUw/xIgwCoy16IsggaLAEgsB66BIB0MuC4ChimjGr0RoGKZf4dALxgIWQRXMRjXCHCNYc5CsiEmAVsgSS8DjovgCcPkUY3mCBBY5N1FEEaReVRlEThGgLMMcw3C+TYMuINRXb77FAPUIQIcgXCzTUwrIMpt2mg5eR6/hEeAK6hGdwQA4m8Fx0KUXtpqff/wHAINGQECq/xqA6GTTN+LHFBswAiAuE3iQyFKG91dbsQIEBhrYpgFWW4zBqcI8Fh5BAiM86gPrI8xNGQEQKxR0nMhDOvE4wgQ2GRcSXsQro31UkJ19zg4HREgMN04OgxhpEWAq9Bhlic/AHWMANVfDvEiAgQmefIDgAzrqPnDwwIGOMcwj6DFLD9+ANpYb5lbtzvwkycRIDDJiy0AbvOfKP06RrroRQQIbPKgBuAQAdwVn5wHfIkAcKoFbII4Gf5Tze0fPYkAgXHaTwECbZTlBhSZYyxNgThvKIuiCBAYo38i7DuKoikCBObrvw2coSi3oMsExZ2A37y5JWoJqIoAgcnGwgzIVOj80EwheqCM3rsAA5w63cL0KYsAgQ06r4NXl79Soq3GjgBAk9LbgIPKn7nMFL16CGWmKS4CDeZGL1N08Sg0mWgi2gFNbhaZnuxpFX2h341SvAcMc/44U1TuhBYLfImAf7nLNGXuFKCDiWQ/9DnGdN3KQ4NtJopJUOgEU9byEfJNVzcSKrojTFurguLwSI/fBRFwWJh9A+H2eBkBv+mhAFnpvwIae0Gj6qMErbJ3hXN1DQW08pEyXJa8I5hjahgNvSjFM8F1gdEePw6dpRi3IdVudbcBouunHJc7INMQfb2AkV2kJF2QaYcJMxuKvacoJZl940v0zAW2I+0XIPAAAjWZEGOgWSulaYdAI8zglkAz2iu2sK5KAjeEC3z9ArykvftA/lNfhnWTzUEcX78A7bSXw1eFzvbjrJPzkGaKn3sApwiA37w9U2Y9iOsenuznFwC0148Bct1lulA1SqbJyyqQYwSo4umTZiZJ3sNCI011W6GZWwSornDjFZMk7Ij4gKluOjRrpj2E6HzH5DyFJENUvA5kxzEChHv4ilHpGicyzFQzFZq5RoBwhe4sE3Eckiwy1ayEZjEiQLgTfUzCMwgyx6tu0IqYESDc1XZGoGozqGQumA3au4CoCicZl6x3xrd7VwY8RnvdiK7Qzrh6IccCz5rBgOu0l4ONtl7WoKgm3KRnLlQkjhHAUmeGsWQhxzAz0EGoRnsXYO0ZQylqE5xpBtoFzdwigL0HjAVirPXsJNAtAjhoa2UM3RDDpxtBcIwAbl4xEunDBed5cyc04BgBHF1jJMKPBRf9jwBw9YjOLkCKtf8jAJz105mcLmGvjoLdIoC7LF3JmSEywfxuI1SjvQuIIU9XZUgx1aMbIS4RoAtxXGEt4ksBuzzqBntNeznE0syahDeHDf/fCxDHMTo6Ayk8mA34A+2VEVORtUg/FF5uflkEzc7TXhdiesAwGoqBW80vq6GZSwTIIy6G0ZACx0p+KNxCKhEAkeqBwhvEvcmAtFdGTOHfABXbAIw3P4yAZi4R4Apiu8owGs6DlnmSAZ/TXh7xMYSKYvAW88NiaHac9pCALJ3chxQrPWkIpr0yEtDCQemoBDX5kQFTigBAv/YFAPPdBGiWUgSI0hsmvTV4h/lmGjRLKwLgM528gBhTvZgMQnslJKGZNYifF7PEfDMfip2ivStIQoZOjkGMnT4UglOLAKAbQZMjm3zYBKQWAfIMo6EtFDBfDYNmtFdCEroZQsNpIH7cDlkOxVwiwBkkoZ+1iL8ijI2mYh8Ue8LqxEaAVxBkg6lYB8WKtIckvKSbkxBkl6kYC8Vor4Qk9HFQSk6DAxPV7wJTiwDnODglDUGBJvW7wNQiwHWG0LIJANQPB7qTyaTyLzjHUBquB381WvuQeNorIQG3GELJWWDFGGPMduiVVgR4wFA6CsEVs5SXAU7SXh6x9bAGLRHgayFgL/RKqQrQSlfXIMtk5beCaK+lgLiKdPYRsmw2xkyEWifoorX9GGLoaWUtar4AGKp7OtBJurp05y3cdLI2NV8ANOmuAxUZR6krB2vPGMdRSKN7ATC28pmnsHCFkag4Cv5G9XywE0xE84eHPYig5wmj0dAQ/MNoMw9qnWRyWj4cySFER1eRrsRGwMAozTeDi0xY5vKz7s5cAX9q+3inn0n4Qt59NSkRBVEAbnMqc7bMWoYylT7og2UOVVr6cg6D6w6CCpjQNccy55zzr5XFiLLA3Lmz043fL6B2G+bcMN1HoM8iWQizmJhgX3jw0d3XV8N9AX1R+QOAaYanBRZpy1kotEe2waoSTVG4BKhaLlthVQ9N0bYL/N0WGQ+raIq6TcDvRtntDmIrAuhpD1hvmayFUUdoSRk67ZUNMMpUBFDUFqTeWBkIo2jIKWg1UMbBpgLtUHYV+E8DzF4HMBQBzkCvjNkCsBMBDkIxuwVAK95DM7MFYCYCXIRqGasXgqxEgANQzmoB2IgAYQXaWS0AWqCnL3RfzBaAhQjw5DgMMFoA+iPAXWWvgTZmtgBC6nbqHIwwWgDULH9fUzPIpqwWQIHR3bhz6VSWicvfMPPlr7JaAC4R4Bl6HSqFTM6py4a++1VmdwJDRodful7eC5iIQM9EqM4uAEYXos7xB8+zTMA+Rd3gO7cAnCIA/tF95UOW3t29A0MyJieG3WB0BTTUfflxwIjM3v9qYLLJK2HOEaCxc2d76NXh/bBiickOMYwuRHMP7jGCzvkR2CDTYc4zRncDLR16Qn8OGlkPrrXYJMw1ArT26hrj0jgcqon5MgrmeI4A9S6F9OQWDBhvsU8kowvRvsob+vEB+m2V5bDGJQIcQSQHDtOHu1Bvm+yBNW4RIKLiI7bB9I3wmokyDdY4RYDout4zvhDKLTQ4NK7f/g9d99iK+d+AIbIYxrhFADeFHrZg/L0QzLI3N9QtArg6wBaMvxky2l6r2DOMDjF8YEyXoJjBXsGMrgdxnAjoROuYmDr22sW7RwB3jxnLPqiVEZHJMMUtAsT0krG8gFZjRax1iXKKALGVGYva+wHLRGQOTGF0PYivkmUTZveDtojIGljiFgE86M4zhsvQabuIrIAlNxldET50Z9k3m42CgfUixgYGOEUAPypswmijiGEixrYCGV0PPCnSXR4qTbE2Pv4doyvBl/t0V4RGIsZ2gtwigDev6ewaFBosvZbADqcI4M9xutI5MGak9NoIOxhdDh4dZUuWVoKrpNcmmOEWAXwK6Ooe9NkuvSbBjJQjANqZHWrpGbBIeg2DGWfS/7uzJUOnwoNEbK0DGV0Oft1jRJoHx4rYWgemHwGAh3T1GNoMlho7U2PSjwBVbMXOZuBa+W4VjFAQAYAsazR8lLi2So2d6bGMLgff3EOAujfGp8l3Q2HDK0ZXgm8lNmdoGTBGRNMy4BxaCBldEb5dZytmOgaIqFoG9DC8uR99O0AH8O4yXd2GLgPkp6VQYD9rTn45h4bu0EEO3j2kq+vQZZn8tA4KXOMv79/eQT3XO3kleHebLZiZHzFXflqP9HWzTvD4Ul0RFAO6KMO7l2zBTM+YIfLTcKTvCP8RvH/ZhZrie7qBf0/ZhKlfABFRtAxgH4JcmKerHPy7x2YMZYCM/DYSabvEJJTgX5bNmbkSMlt+W4207WMSyvCPTRnaB5grv6TfJ6bARACaPqmyNwRHyG9TkLK7TEIO/pXoTNkYOanSshd4nol4Cv/ydKTtNHCc/Gk+UnWRiSjDu3N0FkCVVfKnqUgVkwH/TtORunfEJ8mfZiFN15mIHPyju09QZbSIaNkKyjIRT+HdUbrTNVEsIzUqDgQPMRll+NZNZ9rug8yXenORnpNMBrw7pevjxDFR6g1DaipMxmH49pDO1DWLGySiJQR8YCL8R4AK4zgNTTLyt5VICxNyAp7lGccraDJf/rYLKbnFpORPH4JHhxmDtgiwQv42BinJMkm5UgFeVPKM5TBUGS29NBwHXGHivh7dn/7HvAFNBsi/NiMVIfvFyVtdiOE549F2FjxD/rUeaehi/zl4swAn1xmTupOgCVKlYiF4iv0r/+FyBdG8zDIudTMEpZEN6H/dTEN48UEF7Sl8pAfa2gSOlBoFu8E3mZr8taOvKmjmzoFrrOm4J8BUqVKxEAyYsiB8fOTtoa5jqFMpX/90hk4sbANiuDQ0GP3tMhUJgmw2nw0CeqftJHCcNLYD/S3H/4Oy7jCrpbER6Gfv+J9Q1iBsmNRo2Awsl/6LHwGokpG+zEEaih1fBM+hygLpywSko9OL4BxUGSI1ajYDfyqWetiRTkKVjPyg50Dot+KRDiwCXbuAGC+i8BnwS+cVwRnoMkRE6TPgD4UjITtFGapkpCGFXWMLNzqiCJQlAMyQhpQ2DX1mvwh03QQBFssPWs4DWiqYzgTK9gAwQBrS1TLwX+UX+2iTtg7Rm6QJ3XNEX33K0xxlncGA4dLCWCjW/fBjlpYchDIbpE9Whgccu3Qt9WskbatAmZlSY2EroG8KLhJZfQBAWhsF9c7TBn3zgkfJH7RcC4nuEk1Qdg+o1whpwzho95gmKDsFrhog7dgJ7WxEgM9QZ55U2Y+BNiKAvgBYHwE13wpo4QANOAt9Rkl7FkO3C2zmLjW4CYWGSpuWQLWATWSBrpupHyFq/P5joLRrNzQ7xmY+oqb8Zh/Toq0n5Df27r03hiiM4/hvW/S2LqVKUZdQtyCuCY2QEPcEeWapSwX9g6IoWiqVEtFSKSGlwqvVuHTT7uzsnNm5PM85z+ct7GT2O2fOeea/NSjB54CAgeAEGKUZQxOXvQyw+zLEfwjvKDE2YPD69drow9SfGbvYvQD4aw/+kP8kWCEBSvTeGEjvImA3CqKoFaWYnREKoXIC+Op97KVkjJhqQhlsZgWE9KZCAvi75aViitiqg5G1xNV4pB1YXhq6bxJbjTAzn7gySICiq17yLo8SY5thaDXxdC/SLfiul7QuZp+FnKMGpjqIJ54J8JbZ8Z8SLTC2nFh66AW5R/68INWOBeoa5rb3u0QO5lqIpQQS4CPRzXfjXV4kj4aZrvvMsgwR5Iih4AR4SP7uhjq9ee/+z4nrJneDnkFeHwEoax6i2EkMvfCC+IdYpQSg2R7c/vz4bU/whXDp1uPn/HZ8ldUJ2HILSCABuqmMpzfHXo0MTn3r63/U3dPT0329/8uHH3deDU32kiwFwJpbwOUkEsB2nfAhc39wxASQNMAhbjMFYMWDQPwJwG2IXwKWIbIaYib2BGD3MZ/45TBD/nJgAgnwiSy3ElVoJFauRUqAJ04nQA3KkndQcFQTwFgeVdlOnExpAphqRAnBW4MCE+AS+XE9AQ6jShuJD00AY0fgS+gG4eAEeEN+HE+Aephg8zExX5oA5vZgFunvhDQBDBVgjM03ZX0EJ8A4+QtOgPtkszWIxTbiYSxSArx0NwEW4x9LHgUnvCC95MfpBFiAmOwmFjQBzDSjAmHHxTUBDCE++4mB4AS4QX5cToB9iNEuyp4mgJHViFM9ZS+BBPhK9qpDrI5T1oITYID8OJwAu+FL8O6wIU0AAznE7TBlbnKkP94E6CJr5RG7E8TB9EWgCVBZM+aw6qXQ5MivsAkw7GYCFBCK5KEhk3dvaQKUtQmJWEW8XC1eBM/Ij6sJ0IQZlv4JFE1fBJoAcxQQwKZN4sWL4Dv5cTUBOpCYPSSLkwlwDnNYtRxkws0EyCFJ7STJFS/IIFmpAYnqJEGGJQ51r9JRGLNlhmypPvcSoBFlWLY7KJzhPucSYCESlydJrrzucykBWpCCgyTMlXfvHUmAVUhFG8kzfRHYnwA1SEcryTR9EdidALUoz+EMmOXpzy/WJsB+pGYvSTZ9EdiYAKdRkV3bxKsiaNBvSG0Iweo3w24rIF11pFhpQMpWkmJkH8JxeG+A1VYhFA1BS8USgNZ8Vso9OWSjlhQLi5CRpaQYyCMzp0hlbgUytINUxnYjU+tIZaoJkdk1RNBRbcicsKMCdlmO7LXOI5WRwkIwwGWQqIPawcICUploABOCt4hJlgcbB0ilrgWM6JJg6k6BlRWkUtUJZk6SStFJsHOIVGoOgaGLpPy58fvrFVCOK78/h5HiTtgAtjaQ8mF9/xWdIZWwfWBNV4QSdhbMtZBK0Caw10EqMUshAKux8nY5DBHaBU2SkyRXCyEW6qGxBGxthRwSB4kxtwuiNJGKVTOEOUYqRjsgji4KxmgNBNIFgdjMh0jtOkosFsvrIZU+DMRgOwQ7T6pKByGa7ha2afd/FPN1XbgKOSan/6rRqiHg5t8/tw/PC3QBltBNItIPf1arVseIGGuT9PJPnwf/cnvxX/8G4lMQsffLTH0jqVCsqX+dKhnRCliqQd8OhbB1Eex1jlQg8Wv/lXToynCgJWwmf82iuwVTcgIO2KQ3gTKWCN35oyPG45D54Pc05XW+cImaLXCJviGcYz0cU7eY1Iy1cvd96m6x6s0TcOo/Ec2kfrd3fz0Jw0AAwLvBGKJz4AQWQZibgIiAgEIi4gR58EFfmn7/zyIxMTHGqIv70+vd72nv7dpr73bDFfx9VaB9gI/AfPSdiCbySwGtw7C75ohJ3PIvPUWfI3XCyLt7lLUilyqnfaPqoKsaLRUY+WyIqlhEV6jkOzYzNAeC6i0j37lDMQU0lxG8U6BMw/8zR+lYQKfF/3eusm0mGxT6/c2BkvcCLfXrPePTU650tELXPtHUJlwdYspIdE1Frgdt8H1+MlNQYCdo4yr2jJ0H+lhYle4XvwAZYJeBR0r4xMQBWDrWANngWVrFENRWoIUY67wT1guAJApEAOTnTvAYAArJ2wq295GJIfM6ICo0+ikwJ1LGA9ozBf2pseqS5YsaHu7vO7JwFEiyEIj2kpFMWENf8GyN6vTqZyvnZDYJ+mcmIzKwjsdlnqq8P6MEv2T2vJbgaSgtuoxIyvAGGk+M6C86FiOyO3Q3rTyPlVgFDh3zYcl1t+PSv+eBsB/CG4r1AKuZy+16YAseidDnlenQoJyeSqxT49x52oz9+eqiLHb4h92zptuvg/311YvbNTEN+xv+iIU0EItw3wAAAABJRU5ErkJggg==", + "lastBackup": null, + "currentDependencies": {}, + "actions": { + "delete-coinstatsindex": { + "name": "Delete Coinstats Index", + "description": "Deletes the Coinstats Index (coinstatsindex) in case it gets corrupted.", + "warning": "The Coinstats Index will be rebuilt once Bitcoin Core is started again, unless you deactivate it in the config settings. Please don't do this unless instructed to by Start9 support staff.", + "visibility": "enabled", + "allowedStatuses": "only-stopped", + "hasInput": true, + "group": null + }, + "delete-peers": { + "name": "Delete Peer List", + "description": "Deletes the Peer List (peers.dat) in case it gets corrupted.", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "only-stopped", + "hasInput": true, + "group": null + }, + "delete-txindex": { + "name": "Delete Transaction Index", + "description": "Deletes the Transaction Index (txindex) in case it gets corrupted.", + "warning": "The Transaction Index will be rebuilt once Bitcoin Core is started again, unless you deactivate it in the config settings. Please don't do this unless instructed to by Start9 support staff.", + "visibility": "enabled", + "allowedStatuses": "only-stopped", + "hasInput": true, + "group": null + }, + "reindex": { + "name": "Reindex Blockchain", + "description": "Rebuilds the block and chainstate databases starting from genesis. If blocks already exist on disk, these are used rather than being redownloaded. For pruned nodes, this means downloading the entire blockchain over again.", + "warning": "Blocks not stored on disk will be redownloaded in order to rebuild the database. If your node is pruned, this action is equivalent to syncing the node from scratch, so this process could take weeks on low-end hardware.", + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": true, + "group": null + }, + "reindex-chainstate": { + "name": "Reindex Chainstate", + "description": "Rebuilds the chainstate database using existing block index data; as the block index is not rebuilt, 'reindex_chainstate' should be strictly faster than 'reindex'. This action should only be used in the case of chainstate corruption; if the blocks stored on disk are corrupted, the 'reindex' action will need to be run instead.", + "warning": "While faster than 'Reindex', 'Reindex Chainstate' can still take several days or more to complete. Pruned nodes do not allow 'reindex-chainstate'; if you are running a pruned node and suspect chainstate corruption the 'reindex' action (requiring redownloading the entire Blockchain) should be run instead.", + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": true, + "group": null + }, + "config": { + "name": "Configure", + "description": "Customize Bitcoin Core", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": true, + "group": null + }, + "properties": { + "name": "Properties", + "description": "Runtime information, credentials, and other values of interest", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": false, + "group": null + } + }, + "requestedActions": {}, + "serviceInterfaces": { + "peer-8333": { + "id": "peer-8333", + "name": "Peer Interface", + "description": "Listens for incoming connections from peers on the bitcoin network", + "hasPrimary": false, + "masked": false, + "addressInfo": { + "username": null, + "hostId": "peer", + "internalPort": 8333, + "scheme": null, + "sslScheme": null, + "suffix": "" + }, + "type": "api" + }, + "rpc-8332": { + "id": "rpc-8332", + "name": "RPC Interface", + "description": "Listens for JSON-RPC commands", + "hasPrimary": false, + "masked": false, + "addressInfo": { + "username": null, + "hostId": "rpc", + "internalPort": 8332, + "scheme": "http", + "sslScheme": "https", + "suffix": "" + }, + "type": "api" + }, + "zmq-28332": { + "id": "zmq-28332", + "name": "ZeroMQ Interface", + "description": "Listens for subscriptions to the ZeroMQ raw block and raw transaction event streams", + "hasPrimary": false, + "masked": false, + "addressInfo": { + "username": null, + "hostId": "zmq", + "internalPort": 28332, + "scheme": null, + "sslScheme": null, + "suffix": "" + }, + "type": "api" + } + }, + "hosts": { + "peer": { + "kind": "multi", + "bindings": { + "8333": { + "enabled": false, + "options": { + "preferredExternalPort": 8333, + "addSsl": null, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": null + } + } + }, + "addresses": [ + { + "kind": "onion", + "address": "m27eb66v4tndzhowirshkysxt6rmt6iubmjxxc2l2leca7lphcbs7sid" + } + ], + "hostnameInfo": {} + }, + "rpc": { + "kind": "multi", + "bindings": { + "8332": { + "enabled": false, + "options": { + "preferredExternalPort": 8332, + "addSsl": { + "preferredExternalPort": 443, + "alpn": { + "specified": [ + "http/1.1" + ] + } + }, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": 49153 + } + } + }, + "addresses": [ + { + "kind": "onion", + "address": "4dc6awce5nc4ldc36b4naibuzzsj5dwgyfvp43uc3mjp2nnvprchniyd" + } + ], + "hostnameInfo": {} + }, + "zmq": { + "kind": "multi", + "bindings": { + "28332": { + "enabled": false, + "options": { + "preferredExternalPort": 28332, + "addSsl": null, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": null + } + }, + "28333": { + "enabled": false, + "options": { + "preferredExternalPort": 28333, + "addSsl": null, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": null + } + } + }, + "addresses": [ + { + "kind": "onion", + "address": "dmdx34vr7mpjgtmxuhj67vmec7xvh7omxaxyunqpg35dvcbs4pvdh7id" + } + ], + "hostnameInfo": {} + } + }, + "storeExposedDependents": [] + }, + "hello-world": { + "stateInfo": { + "state": "installed", + "manifest": { + "id": "hello-world", + "title": "Hello World", + "version": "0.3.6:0", + "satisfies": [], + "releaseNotes": "Revamped for StartOS 0.3.6", + "canMigrateTo": "=0.3.6:0", + "canMigrateFrom": "=0.3.6:0 || <0.3.6:0", + "license": "mit", + "wrapperRepo": "https://github.com/Start9Labs/hello-world-wrapper", + "upstreamRepo": "https://github.com/Start9Labs/hello-world", + "supportSite": "https://docs.start9.com/", + "marketingSite": "https://start9.com/", + "donationUrl": "https://donate.start9.com/", + "description": { + "short": "Bare bones example of a StartOS service", + "long": "Hello World is a template service that provides examples of basic StartOS features." + }, + "images": { + "main": { + "source": "packed", + "arch": [ + "aarch64", + "x86_64" + ], + "emulateMissingAs": "aarch64" + } + }, + "assets": [], + "volumes": [ + "main" + ], + "alerts": { + "install": "Optional alert to display before installing the service", + "uninstall": null, + "restore": null, + "start": null, + "stop": null + }, + "dependencies": {}, + "hardwareRequirements": { + "device": [], + "ram": null, + "arch": null + }, + "gitHash": null, + "osVersion": "0.3.6" + } + }, + "dataVersion": "0.3.6:0", + "status": { + "main": "error", + "debug": "Error { source: \n 0: \u001b[91minvalid type: map, expected a sequence at line 1 column 922\u001b[0m\n\nLocation:\n \u001b[35m/home/rust/src/core/models/src/errors.rs\u001b[0m:\u001b[35m509\u001b[0m\n\n ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n 0: \u001b[91mstartos::s9pk::v2\u001b[0m\u001b[91m::\u001b[0m\u001b[91mdeserialize\u001b[0m\n at \u001b[35mstartos/src/s9pk/v2/mod.rs\u001b[0m:\u001b[35m265\u001b[0m\n 1: \u001b[91mstartos::service\u001b[0m\u001b[91m::\u001b[0m\u001b[91mload\u001b[0m\n at \u001b[35mstartos/src/service/mod.rs\u001b[0m:\u001b[35m271\u001b[0m\n 2: \u001b[91mstartos::service::service_map\u001b[0m\u001b[91m::\u001b[0m\u001b[91mload\u001b[0m\n at \u001b[35mstartos/src/service/service_map.rs\u001b[0m:\u001b[35m80\u001b[0m\n 3: \u001b[91mstartos::service::service_map\u001b[0m\u001b[91m::\u001b[0m\u001b[91minit\u001b[0m\n at \u001b[35mstartos/src/service/service_map.rs\u001b[0m:\u001b[35m60\u001b[0m\n 4: \u001b[91mstartos::context::rpc\u001b[0m\u001b[91m::\u001b[0m\u001b[91mcleanup_and_initialize\u001b[0m\n at \u001b[35mstartos/src/context/rpc.rs\u001b[0m:\u001b[35m300\u001b[0m\n 5: \u001b[91mstartos::context::rpc\u001b[0m\u001b[91m::\u001b[0m\u001b[91minit\u001b[0m\n at \u001b[35mstartos/src/context/rpc.rs\u001b[0m:\u001b[35m118\u001b[0m\n 6: \u001b[91mstartos::bins::start_init\u001b[0m\u001b[91m::\u001b[0m\u001b[91msetup_or_init\u001b[0m\n at \u001b[35mstartos/src/bins/start_init.rs\u001b[0m:\u001b[35m21\u001b[0m\n 7: \u001b[91mstartos::bins::start_init\u001b[0m\u001b[91m::\u001b[0m\u001b[91mmain\u001b[0m\n at \u001b[35mstartos/src/bins/start_init.rs\u001b[0m:\u001b[35m204\u001b[0m\n 8: \u001b[91mstartos::bins::startd\u001b[0m\u001b[91m::\u001b[0m\u001b[91minner_main\u001b[0m\n at \u001b[35mstartos/src/bins/startd.rs\u001b[0m:\u001b[35m21\u001b[0m\n\nBacktrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.\nRun with RUST_BACKTRACE=full to include source snippets., kind: Deserialization, revision: None }", + "message": "Deserialization Error: invalid type: map, expected a sequence at line 1 column 922", + "onRebuild": "start" + }, + "registry": null, + "developerKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAhUw/T99KgSZQYh1mp1FzDaZCOLmSG9qYSMNjw5WCfP4=\n-----END PUBLIC KEY-----\n", + "icon": "data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wgARCAFaAVoDASIAAhEBAxEB/8QAHAAAAwADAQEBAAAAAAAAAAAAAAECBQYHBAgD/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB6CAAAAAAAAAAAAwBghskoJKCSkJUhDBAAAAAAAAAAAAAAAAAAAAMYmwTbJbBFBLYSUElBKoJVBBSJGCAAAAAAAAAAAAAAYDYm2JsE2xDZLYIbJGElBKoJVIkpEqkSqQgAAAAAAAAAAYMYMYMYMYDYhsQ2SUElBKoJVBKpEq0QrRCuSVSEAAAAAAAA2qBjChgxgxgNiYxE4oy5rf4m1GsewzZ+HoJVolUiVSJVIlUiFSJVIQAAAADGFKgYxsYMoTfkPX4+eaWdF1XCbGa2uk5o44u5/scHO6+E4vld8149u48Z859Drj3STLqkTNySqRCpEzckjQAADCkx0mNjGxhS0s93KvNsRgt93jIHi9wxNgigkoIKR+Gm7yjgnh+g+aHr6B887kdTmkSqRCpEKkTNIQANMdKgpUNqhteYwPI/Tmj3dWLGxgxibZLYSUEqkSqRKpGi8u+iecn4dI+e+xGwzckzckzckzSJAG0x0mU1Q2qHzfo/BT8u6886mNjGxgyhNsQwSoJVImbklUiI/RHDa37kx9CrD5glVJM3JM0iU0OpodTQ6TKaownFurcoOx7Rj8gYX89bwx0/B4zUTqGExmtm8bLom5mG3v5x+kCdC3/AOaj6M/TyewxWoaDvpkdt4dv5+e08X7QX8+/RHCDdt85h08maRM1IpqSU0OpodJlNUOpo0/lXYePnfvZg84c+03e/Ka9+PTNEMlrO46UbL0TjXQzk30j8+UfQPzV0fTjt/r4HnTTPoHStKM9lNV66aP0XjXWj08M7n8+m19S510UU1JKaJmpFNSOpodTRTTKqaPNwT6F48bPv/CO6n6VNDqaKc0VUUU5BoQk5EnIpqRS0YHiO66mdU2iLFLRKcimpFNSOpZVTQ6minNFa/sDPnnpWJ00+ia1vYy3LLcspyynIMSHIgQhS0LXvfxM8XRNS7WUnIk5FNSKWiU0Nyy3NFOaKc0VUUePgvX+QHUt7wWcKc0U4ZbhlEsYkNCBHmP3wWpc+PbPp7AGQciTkSciTkU1Ik0DTKc0U5ZblluWc80TOo7NceQrj/m8Zsm48zxh9C+z5uyB9AnDv2O1nEfCd217i6N70utxNH3re8kfj+jkEISEJCEhCloQAADqWVUsqoooWKONbxoHUDa+NLyB2ac2Vh8uzR8R09nIfx7IHHvZ1UOf57YQmkhpIciBCEhBLkJchLQAAADTKcsqoorH+9nAa6zrxpvYrypbhlOQskKEDEikgaSGhAhAiQQglyCEIAAAAAAbllVDLcspyynLKcMpyFOQokKJBiQxIaECECECEEuQQAAAAAAAAA3LKcspyynLKcMpyFEsokKSBiQxIaENJDSQ0kCAAAAAAAAAAAABuWU4ZTllOGU4ZRIUIGJFEgxIaENJDQgQAAAAAAAAAAAAAAAA0FEsollEsokKJCiQpIGSDSBpAAAAAAAAAAAAAAAAAAAAAAAAMQMQUSFEgxAxAAAAAAAAAAAAAAf/xAAvEAABBAIBAgQEBgMBAAAAAAAEAQIDBQAGQBMUEBESFRYzNDUgISMwMVAiMoAk/9oACAEBAAEFAv8Ap572RpJaV0eOva1M+IK7Evq1cZb1r8ingl/oyixhWl7I1MIt7CfHKrl/EPYnD4LscqYFZhF84omAWKx2CaXHuc9zWq5w1HYTZBrLMjoa1mNq65udgDjqyvdklJWPybWhlwnXjo8nhlgdlfcli5XWQxycq4t4gsKImJlhikmkr9cVcFFHFb+zLGyVh+vDyYaESG9qq11Pe+fJvrft8VVVaipmPUEMcOL92WOOWO4oXReFFbqMqfmnFv7LsoV/NaGo7vGtRreBfUyTJmtWfodxC52DDFTyEkUVd3xLURreFs9Z5eFAd3ofD20v1SxMdLJXCsDE4bkRzbgPsTqQvs7DhKqNaVMpBOpDdQvi7WN1QMpCO5rODeydKozWIelU5a2UVclVaw2ElgUwIQC+HLLtrWGud8TiZ8UCYLYRz1kezCvk8H7OK10T0kiywOGBY/aIPMPYAZ35LsQ0cokyEDTxpNC5Fa7T5POHg7Y7yq8rm+gDN0/01cscQnYLIEmr1v73u3zaeCvnX2/XccLGFRCfU+E/zAfocsynGHV2ujuEuwPbzNSJdMBeM6dsGzpiZat9FnqDv/dwdu+2r/Aa+Ymbp/pV181hJYUhQQut/e92+bT1ktkvwsVlinlVCfU+E/zAfoVTzSRqtcBK2YLcZWPsNLYvSuamcm18LlfO21H7jwdmZ66fKKTq1GbYPOQzUxSYCtjikmqKAEyK324YkiVgNozO0uMqoik1v2yxz26zzURS4Dpa2wWX26zygDOit9go5Jp2w2g2CU9gU+vFjCEtgDZLINFaH/GEP6pGnM/U4JcXXFVFRdOI84eLekdtV5q8PSq+FsY3b2dWUoZzVRzeJtZnWLGhcQRExsUXCvwu8BzVbH1N4d5YIAIqqq6mF5cXZq7oStcrXUNh34vBsDIQhzipTCakF55UbGxx8SwVjQM06L0h8C1sxwGHFzGzgizGEV4cQI3F2eXp1OUcXRqf3iJ4h47PYlXHuc91bXzny14UAMHG3GX/AChYsszURqYYVCIOfamFTBbAdBguwgS5ASPOn4ZJGRoTeV0OGbIQ/J5pp5GtVzqvX3vyKOOGPj7HL1bfXIurb4YVCIPZnzHziwSkzlgFi+MRpkWNurNue+2mOu7RcksT5McquXB4JyHBa5O/AQBQk5Hn5JPIss2nRfqGExCQWZ0x84sEpM9TXxV8GE1gBGTa0M7JNZJTHa9YpnsNnia/ZLjNaMXIdZjTB6WthxqNa3lXEnSq8pJIgKKzOlOnFglJnqa+ICD+msoFJAVFRVe5WiwSkzVVfEBD/UWdQOY5utyequAgBj/7X//EABQRAQAAAAAAAAAAAAAAAAAAAID/2gAIAQMBAT8Bcn//xAAUEQEAAAAAAAAAAAAAAAAAAACA/9oACAECAQE/AXJ//8QAQRAAAgECAQcIBgkDBAMAAAAAAQIDABEEEBIhIjFBURMjMkBSYXFyMzRikbHBFEJjc4GSodHhMFCCIICDkzVTov/aAAgBAQAGPwL/AHPXkdU8xtWtjIvwN69K58IzW2b/AK69JIPGOtGLQea4rmpo38rf2O+ImVO7fVsLAW9qTR+lacQUHBNWrsSx7/8AXzWJkA4E3FWxMCuOKaDVopgH7DaD17lJ5Ai/GimEHIp2j0v4os7FmO81mqCTwFXMQiHGQ2/SufxTHuRbVpid/M5rRgofdXqcH5BWnBQflr1fN8rEVzOIlTx1qvEY5h3GxrNmjeM+0LZArNy0fZf965prPvRtvWzFHaSfhuXxoyzyF2oRxIzudwoPjZLfZp+9ZuHhWPw2/wBLMkRXXgwvRbCtyLdk6VrNxEZXgdxoMpII2EUIccdO6X9+snDYU899Zux/NXNZ/o4N78fCsyCPN4nef6xjlQOh2g0ZsFd03x7x4ccgw+JN4Nx7H8VcaR1bk4jz77PZHGrmhiMQCINw7f8AFBVAAGwDqLYnCLaXayD6/wDOQYKdtU+jPDu6q88nRQU88p1mNXf0CdPv7qCqAANg6mcdAv3o+eTXPPR6H7+/qiYNToXWfx3UsaC7MbAUkCbtp4nqhVhcHaKaL6h1k8KRydRtV/DqZZtg0mpJ22u16fEsNEQsPE9W5cDWhN/w35IZCdYDNbxHUsQw2lc335IzvkJfJGZI3fPv0adI4pEzFvrU2IdWZQRoFJhkglVn3m1RiWKR88EjNr1af9K9Wn/Smx6o4QBjmnbopUGGn1iBuykfRp9HhSSDQGUHJnYiS19ijSTWphJSO9gKCSZ8BPb2e/I8Zw811JU7KjnVSodbgGnibY6laKnaNFYiHgwbqQHGQZMOvCNfhkwvi3yqZsRKIwyWF/GpIoMQruSuix41hvE/A1hfK1SfT8SYLWzdNr1/5Nvzj9qnghZmQROQW8Kh86/HLJ5jWH+6X4ZJZ2O06vcN1JJimkMji9lNgtckGLIwzlJpoXNzCbDwrFL9oTUMfZjUfpkxK8JTUy8Yvn1JPvR8DkhP2a/DJhfFvlTpCyKUFznU2IkkiKi3RJrDeJ+BrC+VqkEcipydukK9ah/KaxA4QN8Kh86/HLJ5jWH+6X4VamQ7QSKhljN1KCo41NzHHre+sTJuJUU08Zi5Ns293se+tGTFfeGpD9l8x1KQ9khsmGbgub7smG5CF5LFr5ovbZU5ngkjBjFs5bb6kjiRnfOXQo76w8kmFmRATcle6sMYIJJbK181b2rUwuLXwBFehx3/ANVPFMkvLESWVtteo4j8lep4r8pqVsRBNGpj0Fx305+hYjpH6lep4r8pqB5cNOiAm5YG2ymxWDALNpePv4iiiR4yK+0KGrTC0ana8milw8ewbTxPGsS6YSZlaQ2ITbUKsLERrcfhVzUkvbYmsTL3BepSw9tSKsdtTYUnSpz16tM19ZhmL4nIHO2Vi3U3YDUl1x86jn+qNDeFBlNwdIPVRhkOpDt81RwJ0nNqWNOiosOpnMHOx6yftk+gSnSPRH5dUuDzz6Ix86uTcmmx0g26sfzPVfpcK8051vZNBlJBGkEUS+iWPQ/f39SM0x8q72NNPMdJ2DgOFCMaEGl24CljQZqqLAdVnaRQyiM3ByTTdt7e7qOvrynoxjbRlna53DcKEMK3O87gONCGL/Ju0erOu+RguTDpvK5x/HT/AF+UmkWNeJNGPArb7RvkKLuxZjtJrNiFlHSc7BXJQjzMdrdXw8Hi5pIhtdgtBRsGjIZpmso/Wi/KvEn1URrWrNltiF9rb7653PgPtC499Xhnjk8rf6ryOqD2javTcqeEYvVsLEsQ7TaxrlJpGkbixoKoJJ2AUJMdeNf/AFjafHhQjiQIg2AdYl4JZKh4Jd/dkM0zWUfrXKSaFHQTctLDCuc7Vz8DqO1tHvy83ipl/wAzXrbnxANesD8gr1ojwUVr4yc/5VdiWPfkzYInkPsis7FyCIdldJrmIgDvY6WP49Zudgp5T9diaxE/ABBRmmayj9a5STQo6CdmlhhXOdqzV1pG6b8f4yXkwyX4rqmuanlj8daubxMLeIIrQIW/5K9Cn/YK6EQ/5K15oF95rnsW7eVbV6vyh4yG9ZqgKOA63iHG3Mt79GT6TObB2Ld54VykmhR0V7NLDCuc7VmrrSN034/x/Z5oBtZdHjViLEbaCliVXYL7KWGFc5zWautIem/H+08qCYpd7Df41rYpM3uWs2Iax6THaf8Aex//xAAsEAEAAQEFBwUBAAMBAAAAAAABABEQITFBUWFxgZGhsfAgMEDB0fFQgOFg/9oACAEBAAE/If8AwNP9XaQBqPdLkqNPqTB5R4T9mOb2S7twO4QHkaxNf8FslNN7cYypbeFHJK8PcujpfNv+HV6+oualzrKRRn/JMp+qV0m6UVg8fPhKfNZZaVx2AziSeBjP6jEKQlVhZbwCqwsQ/AXwNFHRdTMeHyXUnfBX3sm7ACdp4nnGVlR0IPqVHwKB/ZsbzYrnTzLzdiJQtHMkf3h8I9kUG1/BsiMDm5GgZEws6HVgGpat/H8SnRZovb3FlPZWKeNIhq+/D7JfOmDfu2PhaqKIyrPMGU+c5iVGp7D7Z6bvrgPoPFIgRVaq5wKqk3hyDPtKKwcW/fvvYaiXUYGYt7X/AFnXfY+17nx8uUQCCFRM/U+6W0k1PxOkSiKt6uLHQxdNfjjCwtQFAPYp7JJB0TaPFY1G+5jXmdHz/Vy9La+otLSLviKauRxlYBXOzQNhGpKUVasj5hDgNQC4NPhkKZp+ePOYMopWodpxd/Sx9gtLUuC98sHAv4yjf7XLKLTTXnb8NhgDUTBIodcZc3+YS4M50z4N8bG19RCHpcChqbCY0i/PSHVTzNoV52HxBPus3fRsSqXQR+RjY2voLCEIQjsKUXipYFFvRzodCEqEqC5dSmu+O+Aai++mUNOo0a3tM4iiEFoLlydkR4VhLqb5/Z/c/p/uJhUUNr2hKCKrnaa2pNK0xy8YTSEjiVK2DazgnBkBTqr9SCBcw0PKw4zGlM5jo/GMaawHlsYCDdVBxKTFBVbyOg4TxKPa1jaxtIQhCEJsH/RY3DBNyXRCef0gpxUC1dyLVKDeU2izV47UlMQNycyuI7LCi3AAq3pynitFmc8TqzyOmMaMig0cELxnGRfTC+bWbUphRi+B1dZUOF8oNoON/wBzx4BYIeFDzrKWUrch+2MY2tpCEIQtFV0kYm6Edgp0Qnn9IBrGtcK0yIgPwlRe0zLNXntSUReTUa1rpulzBqfiCfFaLfE6s8jplY1FIP8ARybS6F4wrrS8hGaJRktVOUKourvBfsmVyjhQC5LtzKMJJIroHBjGxjG0hCELSUSL+vU+7DqFWq3qkIcJ7IyCS6JgsNDSFYsVOQjgX4YOJVYhK/fuN+crGURdgtdS7GFz3dgVCyFEFp1gwYaF/WUIVO9QoX5eplDSvguiWQgA8oNq59lfeyqK3q4tjDG0XEEcQYTJoimQC9n9yVrHcgG4q/UYxjGMbSEIekWfvUuiAaBom2B0iI3PWnOFhCFhD1MbKurG1lIaOgDtWymynAOB2jGMYx9BCEIWFhLuDm7g595ftUombx/eEOAYBma2EPbbWMYwq+ZHNY8i7nCpuZ2bYI1B7sIxjH1EIWEIQhENU4lrxH1YQ5vTMz4coQtPfSGam0fQiJCKq5sYZiq6eBz9bH0HsEVcopm/T3jd3qF46xzIopYaOL4LNmwDgCZeCDDKErFeNGbAxiCyCMYxjGMfQQhCEIWEWUTgG6GEqIZbcP1+Ay+agqi9tdCZdQDB0CVRzenFIQ1c0x1GNr7RaWllNHoOtXtZXIpxJ/0tPcSDui/sPWOFK/6N7FITVVVZfSrzN7smMC34g1fz0trH1HpLahHA7A+5ggg4tJckjRuLMpdDNaG2GwxvADhi7YAIXd5X3BA8UKJRitgy/Mlbd023+HulfBn/AEMJXfKhyOsdbXR/I2NqAqsXViROc/rdDOTQdA9Da2vsHqpAaiHAv6rLxFahwXdaQmUugYvQ2ysTCA3H7ti58IabXQjbsQKvBdZg1MdZgN6EMK8kYTyf6nZY/qH0E0rO02uYVbOKSmUjw88jrKF9c0Da+yepLYRV3TEcR4tZUQdwXvYmH+AGL0NsqkwgNw/dsVPgDTa6EuwZI5DSMqZMWavyuxK07OIPqYQvk4zoyfoswx7eRMIXY/RKM7APqaygoBzHThDQ7gND5b58Dv8A+ll1jMcEGuEqcwBtw/dsUDgDTa6Eu0ZI5DT45+B9COrzqRm5FA5Mrf4rXKsaaRSOBNNroS6JkzkND/Eq0vA1N19zC3qVY2W+LcDZ/lq/7Rf/2gAMAwEAAgADAAAAEPPPPPPPNBCMMBFMPPPPPPPPPPPPIJILMLOIGCIOPPPPPPPPOEMBMCJCDOGDIBEPPPPPPOBHPDEGCMIGGDMMNOPPPPPCHIADECENODMDDDALEPPPIKPAONJEIHBPFBAFKOFNPOJBFFLGNLJEIJIACFIKKGPLIKCDOHABIJMDDACFFEEGPBJEGABFBOMDGAFIOHJIEGFAKJECBCHOGGJGFGEFCOHBPKNEKANDEGPNLAGDKENAKFFKOFIPHAKGCBIJJECFFCLFFBIOGBCJFAOIJHAOGAJJECNDEGGKBIEDCGJAAJEBJJIEFKEFFFDCCNKDAKEAFAKKKGPPIIKEGCAMCEMOJDHAKFFFPPOAKFCFDJBMJEBAIBKFAPPPPCLAMIDJIDDJJMMOAFPPPPPPONOMCIOAEJJMDDDPPPPPPPPCDMCCHMNCIMDAPPPPPPPPPPLGGGBDDECGDPPPPPPPPPPPPPPDPDDLDPPPPPPPPP/EABQRAQAAAAAAAAAAAAAAAAAAAID/2gAIAQMBAT8Qcn//xAAUEQEAAAAAAAAAAAAAAAAAAACA/9oACAECAQE/EHJ//8QAKBABAQEAAQIGAgIDAQEAAAAAAQARITFBEFFhcYGhIJEwsUDB8NFQ/9oACAEBAAE/EP8AByyyyyyyyyz/AOBllllllngyyyyyyyz/ACsggggsssssssssskskkks/xwgggsgsgssssssskkkkkkkks/xAggssggssssssssskkskkkkkkk/wQgggggsggsssssssssskkkskkkkkk/nIIIIIIIIILLLLLLLLLJJJJJJJJJJJJP5SCCCCCCCCCCyCyyyyyyySSSSSSSSSSSST+Mggggggggggggsssssssskkkkkkkkkkkkkn+IIIIIIIIIIIIIIOSeAvYH2JhRHVD+rNJi+d/sJNwT2Ua+Q/0dmBO9v6UIg6nTT/Q7IOBHySSSSSSSSSSSSSSf4AgiCIIIIIIILJxTdr2XV+ptkOD5qcj8pNdgxYeW8nyyJQ6tb50x5WPlfMc3JcHieg4T5sB0Q/tiSvTY/OOZfCSXaEeTyB4+RlDjw+Ukkkkkkkkkkn5kERBBBBBBBADXCet5LlehLFW4APmdfg19SXImvu9V5ZJv4/7YOW587ctns39gje7wJ+1fqIMx3VvxhHhxe+/7MGYZ/8AXaCxn/J0Rjmz3wvsfUX2Cf7dfccA+hv/AKx+Fcnr65PtvD8bf6ZvgEbU/wDoHT0tutTN5pOx9deuSSSSSSTJJM/kEIggggiCJoRPeeizv6efPC4cmi8eUuPQJTY823r6Hq8TxQ5gvs6Z7B97gQ2fs135GMQWWWWWSSRd5wFfhlxLkRd5f+KnpEdvz9afD7cPpDWc1G7icjCqLMuJ2AdPbx5h1hAAhojonnJJJJJMkJ/EggiIRBBBc1SdSA/s/wCueiMXKKp6qvVfOSDBa6jqrq9ej1eLCaDrD59d9uh2CCCCCCyyyySSSSMrmD/Ge/r1IGDLCfurqHl0TpIxjllH7fOe440h7iX0CaInUTvJMzJCSSfwIiIIPAER7Keznsr9eoPPXtLBdQ1HlV7u3W4qEB556geF9h3Yrzzg3QA4CCCCCCyzwZJJJJJJJnJFlBCzzA7enp7uoQBBxExG6j6P1e58z4PHRMSZnwJJ4H8DwBB4CPBkwSw89IfVIHvbuKI6Hp5IMD2nzZB425R5vVe3qSFCPJCMAdgIIIIIIILLLLJJJJJJJIow3R8c8ZPr4eaF0FEeEcRkIeSuoz6xz6HzmZnxEnxIjwEeAi4jiMei5Pk9x8pKAId1gXQnJnKcv7vTyAO0EEEEEEEEFlkkkkkkkkJIMydUYieScRXr1GDhvmtXt6zrS8943D6HwYf8SeBnwMz4H4AiILbYIdg1f0M/Spx7C8PjB8WboVTjJN+H9EHgIIIIIIILLJJJJJJmZtSmkhyuB8P6GTdHo8NyOPF5V2vuC+fEfAz4HwPAfkDMACnmX9VuNwkLht8x/ov78Bj0yzwp3RFakageA087OHEsNIZoO83pZnLmcnSIkUAgYO6PPtHhUCIhOjc1gxzno5sYjgAkF8nMnKb0YNQ82XZLpzKT+lm7E3AgOd+ZIP8At6QOuPb1cDzmmg8OH4DH7lSgDJfJT9gHrGcwzGI8c97WvIMlSz4SwVLNnTc42IAMXyb/AGgSzSfMx+y4yhXyG+wzPiM/gH8ABiHr3qH+kS0eQsR2H3g/b4gGKLHoaTg9owiyiAV0Dg8SmcfcF6lfNGZ+1gawTsXwXY5AZzxx4Pk5fdg4e54ssODq4d3yJ45heHIPkZz7q95oZ85CDqKBNXje0wkWcDarDhRE06iMoNKtd3ygD0w7WQMML0IYAhmyeoD9y46duYmcwXov/abZ6gev/qT+AZ8D/AARKKcbPkLkHq/qY/h3jACDWoArgNOdkLbUNIZidXz8Sj9Xd1KWDPRv9yiOoZ/wTsNQudcZ4f8Az92zk9/Hlgy9xbfuZMhSLqIo/ZYYQnlgInkiInpYAga3eC9eCnqTCBL80X6+y60OSxyBz0PmePIc8jy7XIma0z/SH+oTHG35/CD+AfF0fgCPA2wezwK+ld8biQP01f1DwMD2K4OG503H9R7CKC2gvVzmRXqwwVQPI5mDpHuiavTlD5lQDICXBzpuMo+A9vOm8Nv+D/1GLIi7oQ0u9oAskD/vPBXl7dhlwe7BZgZaMUWj0gGBB0yXTC7YiavHVCSdOMfeV4V7pOeTrlusXJr59r7zVoE6TeuOX7HPmWO6oOc0md17dgDtOMqkB4Rzkh+zTiRInZHiG0B6PYOX6ncd/TqPplAcveqP0P4AD+QERF0bxd7I1+8mSLO7BxP3cJREvXPH2C8A/AHiDDbLPiNhwB7Ny5ZnwH30/wCe6PbT4gDA6dCTqq4dv+tPz+YDP5gPAX6T5FtQ4Ez4tvsI1fA2x9w4eonXnLoxoPREY8QiIhttlll8DP4hyhztcC/0Pcx8Ck+p59ga/FhH7eQA/r8QZmfxB+QN3PRDnJ/5HqTp576lgLJV9f3OT5jTsS8BDKGGGG2WWWWZmZihWrzncz1N9XDzmPAW1R1V7q82JTAh7D9+mMzMz4j4HgIiIiLsRbJxx/x33ovPpo7kGhuME6B7I2EEJ5qaJ24OnZHtkMQxDDDDbbLLLLLPg4tPLJwuC/t6ByxFXlLjfoH26vW4yeOd/ofoD3egwQZr4NgH8IA+B/AAHwGgvfR3gnvkHA9c5nzJF/P/ANb+oYYYYYYbbbZZZZZZSoH4U4v3vL2G2ip0ff7Y+15b7EBBL2D9rwc3FQuRmXn+oOxxKWZTMzM+J4BjwDD4BuKQaef+i+9vd6d7iwE/Vf6D9Qw2noL7EMMMNttttsst1qwGD6HdehrMq+rn/Vv9j0JvTHfnyvK3FVwFz8v6Bz7HMLVwDHrT+hwftVmZZSz4jPieAYiPAQ3ZI56qfUQ1NADzL/aJrFE7Bh9EN0l7Dl/Q+6+urwXolAabVD5y+MLiU1nFPTmfg24b69X+X7CJJHQX/W6fqcdQ9yB2tlh3jl7T4AOTH2ITuRfb78P7gHPwIe6HH6o6Pd6z0Ow9DC0quOvyA5bimD4PBwPQ2OPNpI/9815e8ssspZfAz4H8CGHwEREOF/eQFoN19hZ/ZFdJSeZ2p919dXgmfOeiT/a793thKBLhcAdU6B3Ww7bx7lGn7ZCJo6eYxw8Q6DhPmIAR0xv0qRQCH/M0Mzb1aBYymwUOoL+szFg6vf7d8DT64jE93ofKS/WCuKeS00PBivkLkPQw9JcllllllLL4FmfxGUMMMMM2OId7A1+iY/Wp9b/a6KTz1T9X7LzaNDtT7r66vBMec9E3t6rv3ewEkFMDgHdOx1W44sL4X9B2O/V56bqXQxOz7w4XWd7m5352WSHoS/2+5Be0Bb6BN8R5kf0STmvqf+8xnrn+uM2eftM/QPuS8wDP7V9TnRg5L7s+l0U5KfwYSyyyyyyyyyzLM/kQwwxDDPEmpOyEBnB26R3BwGruX3I/bdeJfzrrvK+q793sBJmTA4B3TsdVuDKC+F/Qdjv1eegwwww2222yyyyyyyyyyyyyymZfzGGIYYbMXab6aA/IHzLI4XiTiJ2R4uS7DnduC5rvkjZeDgHdOx1W4/qL4X9D2Pl5hhhhhtttttllllllllllllllln+AhhhhhhhdDwKvNc19CPnskG85XT2UPuCdhqHLoKcB2HHu8wwwww2222222yyyyyyyyyyyyyyz/CQwwwwwwwwwwwww222+DZZZZZZZZZZZZZZZf4xhhhhhhhhhthttttttttlllllllllllll/lGGGGGGGGGG222222222WWWWWWWWWWWX+cYYYYYYYYbbbbbbbbZZZZZZZZZZZZf8EYYYYYYbbbbbbbbbbZZZZZZZZZZf8QYYYYYYbbbbbbbbZZZZZZZZZf8AG22GGG22222222222WWW2X/K2G22222222222W223/P222222222223/AO7/AP/Z", + "lastBackup": null, + "currentDependencies": {}, + "actions": { + "set-name": { + "name": "Set Name", + "description": "Set your name so Hello World can say hello to you", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": true, + "group": null + }, + "show-secret-phrase": { + "name": "Show Secret Phrase", + "description": "Reveal the secret phrase for Hello World", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "any", + "hasInput": false, + "group": null + }, + "name-to-logs": { + "name": "Print name to Logs", + "description": "Prints \"Hello [Name]\" to the service logs.", + "warning": null, + "visibility": "enabled", + "allowedStatuses": "only-running", + "hasInput": false, + "group": null + } + }, + "requestedActions": {}, + "serviceInterfaces": { + "ui": { + "id": "ui", + "name": "Web UI", + "description": "The web interface of Hello World", + "hasPrimary": false, + "masked": false, + "addressInfo": { + "username": null, + "hostId": "ui-multi", + "internalPort": 80, + "scheme": "http", + "sslScheme": "https", + "suffix": "" + }, + "type": "ui" + } + }, + "hosts": { + "ui-multi": { + "kind": "multi", + "bindings": { + "80": { + "enabled": false, + "options": { + "preferredExternalPort": 80, + "addSsl": { + "preferredExternalPort": 443, + "alpn": { + "specified": [ + "http/1.1" + ] + } + }, + "secure": null + }, + "lan": { + "assignedPort": null, + "assignedSslPort": 49154 + } + } + }, + "addresses": [ + { + "kind": "onion", + "address": "b5vx4e3liq2twdeuqqp5bcuvqvoh2hil3yyci7re4ioeiwz4q3qlg2qd" + } + ], + "hostnameInfo": {} + } + }, + "storeExposedDependents": [] + } + }, + "ui": { + "name": null, + "ack-welcome": "0.3.5.1", + "marketplace": { + "selected-url": "https://registry.start9.com/", + "known-hosts": { + "https://registry.start9.com/": { + "name": "Start9 Registry" + }, + "https://community-registry.start9.com/": { + "name": "Community Registry" + } + } + }, + "dev": {}, + "gaming": { + "snake": { + "high-score": 0 + } + }, + "ack-instructions": {}, + "theme": "Dark", + "widgets": [], + "ackWelcome": "0.3.6-alpha.7" + } + } +} diff --git a/container-runtime/package-lock.json b/container-runtime/package-lock.json index 2fddf23f2..2ff4bb8c6 100644 --- a/container-runtime/package-lock.json +++ b/container-runtime/package-lock.json @@ -20,7 +20,6 @@ "node-fetch": "^3.1.0", "ts-matches": "^5.5.1", "tslib": "^2.5.3", - "tslog": "^4.9.3", "typescript": "^5.1.3", "yaml": "^2.3.1" }, @@ -36,9 +35,36 @@ "typescript": ">5.2" } }, + "../sdk/baseDist": { + "name": "@start9labs/start-sdk-base", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime": "^4.0.3", + "ts-matches": "^5.5.1", + "yaml": "^2.2.2" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/lodash.merge": "^4.6.2", + "jest": "^29.4.3", + "peggy": "^3.0.2", + "prettier": "^3.2.5", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "ts-pegjs": "^4.2.1", + "tsx": "^4.7.1", + "typescript": "^5.0.4" + } + }, "../sdk/dist": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha6", + "version": "0.3.6-alpha8", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -5628,17 +5654,6 @@ "version": "2.6.3", "license": "0BSD" }, - "node_modules/tslog": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz", - "integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/fullstack-build/tslog?sponsor=1" - } - }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -9758,11 +9773,6 @@ "tslib": { "version": "2.6.3" }, - "tslog": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz", - "integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==" - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/container-runtime/readme.md b/container-runtime/readme.md deleted file mode 100644 index 023091463..000000000 --- a/container-runtime/readme.md +++ /dev/null @@ -1,86 +0,0 @@ -## Testing - -So, we are going to - -1. create a fake server -2. pretend socket server os (while the fake server is running) -3. Run a fake effects system (while 1/2 are running) - -In order to simulate that we created a server like the start-os and -a fake server (in this case I am using syncthing-wrapper) - -### TODO - -Undo the packing that I have done earlier, and hijack the embassy.js to use the bundle service + code - -Converting embassy.js -> service.js - -```sequence {theme="hand"} -startOs ->> startInit.js: Rpc Call -startInit.js ->> service.js: Rpc Converted into js code -``` - -### Create a fake server - -```bash -run_test () { - ( - set -e - libs=/home/jh/Projects/start-os/libs/start_init - sockets=/tmp/start9 - service=/home/jh/Projects/syncthing-wrapper - - docker run \ - -v $libs:/libs \ - -v $service:/service \ - -w /libs \ - --rm node:18-alpine \ - sh -c " - npm i && - npm run bundle:esbuild && - npm run bundle:service - " - - - - docker run \ - -v ./libs/start_init/:/libs \ - -w /libs \ - --rm node:18-alpine \ - sh -c " - npm i && - npm run bundle:esbuild - " - - - - rm -rf $sockets || true - mkdir -p $sockets/sockets - cd $service - docker run \ - -v $libs:/start-init \ - -v $sockets:/start9 \ - --rm -it $(docker build -q .) sh -c " - apk add nodejs && - node /start-init/bundleEs.js - " - ) -} -run_test -``` - -### Pretend Socket Server OS - -First we are going to create our fake server client with the bash then send it the json possible data - -```bash -sudo socat - unix-client:/tmp/start9/sockets/rpc.sock -``` - - -```json -{"id":"a","method":"run","params":{"methodName":"/dependencyMounts","methodArgs":[]}} -{"id":"a","method":"run","params":{"methodName":"/actions/test","methodArgs":{"input":{"id": 1}}}} -{"id":"b","method":"run","params":{"methodName":"/actions/test","methodArgs":{"id": 1}}} - -``` diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index 1c2954cb2..0123b0cbc 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -4,7 +4,7 @@ import { object, string, number, literals, some, unknown } from "ts-matches" import { Effects } from "../Models/Effects" import { CallbackHolder } from "../Models/CallbackHolder" -import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk" +import { asError } from "@start9labs/start-sdk/base/lib/util" const matchRpcError = object({ error: object( { @@ -35,12 +35,13 @@ let hostSystemId = 0 export type EffectContext = { procedureId: string | null - callbacks: CallbackHolder | null + callbacks?: CallbackHolder + constRetry: () => void } const rpcRoundFor = (procedureId: string | null) => - ( + ( method: K, params: Record, ) => { @@ -50,7 +51,7 @@ const rpcRoundFor = JSON.stringify({ id, method, - params: { ...params, procedureId }, + params: { ...params, procedureId: procedureId || undefined }, }) + "\n", ) }) @@ -67,7 +68,7 @@ const rpcRoundFor = let message = res.error.message console.error( "Error in host RPC:", - utils.asError({ method, params }), + utils.asError({ method, params, error: res.error }), ) if (string.test(res.error.data)) { message += ": " + res.error.data @@ -100,75 +101,100 @@ const rpcRoundFor = }) } -function makeEffects(context: EffectContext): Effects { +export function makeEffects(context: EffectContext): Effects { const rpcRound = rpcRoundFor(context.procedureId) const self: Effects = { + constRetry: context.constRetry, + clearCallbacks(...[options]: Parameters) { + return rpcRound("clear-callbacks", { + ...options, + }) as ReturnType + }, + action: { + clear(...[options]: Parameters) { + return rpcRound("action.clear", { + ...options, + }) as ReturnType + }, + export(...[options]: Parameters) { + return rpcRound("action.export", { + ...options, + }) as ReturnType + }, + getInput(...[options]: Parameters) { + return rpcRound("action.get-input", { + ...options, + }) as ReturnType + }, + request(...[options]: Parameters) { + return rpcRound("action.request", { + ...options, + }) as ReturnType + }, + run(...[options]: Parameters) { + return rpcRound("action.run", { + ...options, + }) as ReturnType + }, + clearRequests( + ...[options]: Parameters + ) { + return rpcRound("action.clear-requests", { + ...options, + }) as ReturnType + }, + }, bind(...[options]: Parameters) { return rpcRound("bind", { ...options, stack: new Error().stack, }) as ReturnType }, - clearBindings(...[]: Parameters) { - return rpcRound("clearBindings", {}) as ReturnType< + clearBindings(...[options]: Parameters) { + return rpcRound("clear-bindings", { ...options }) as ReturnType< T.Effects["clearBindings"] > }, clearServiceInterfaces( - ...[]: Parameters + ...[options]: Parameters ) { - return rpcRound("clearServiceInterfaces", {}) as ReturnType< + return rpcRound("clear-service-interfaces", { ...options }) as ReturnType< T.Effects["clearServiceInterfaces"] > }, getInstalledPackages(...[]: Parameters) { - return rpcRound("getInstalledPackages", {}) as ReturnType< + return rpcRound("get-installed-packages", {}) as ReturnType< T.Effects["getInstalledPackages"] > }, - createOverlayedImage(options: { - imageId: string - }): Promise<[string, string]> { - return rpcRound("createOverlayedImage", options) as ReturnType< - T.Effects["createOverlayedImage"] - > - }, - destroyOverlayedImage(options: { guid: string }): Promise { - return rpcRound("destroyOverlayedImage", options) as ReturnType< - T.Effects["destroyOverlayedImage"] - > - }, - executeAction(...[options]: Parameters) { - return rpcRound("executeAction", options) as ReturnType< - T.Effects["executeAction"] - > - }, - exportAction(...[options]: Parameters) { - return rpcRound("exportAction", options) as ReturnType< - T.Effects["exportAction"] - > + subcontainer: { + createFs(options: { imageId: string; name: string }) { + return rpcRound("subcontainer.create-fs", options) as ReturnType< + T.Effects["subcontainer"]["createFs"] + > + }, + destroyFs(options: { guid: string }): Promise { + return rpcRound("subcontainer.destroy-fs", options) as ReturnType< + T.Effects["subcontainer"]["destroyFs"] + > + }, }, exportServiceInterface: (( ...[options]: Parameters ) => { - return rpcRound("exportServiceInterface", options) as ReturnType< + return rpcRound("export-service-interface", options) as ReturnType< T.Effects["exportServiceInterface"] > }) as Effects["exportServiceInterface"], exposeForDependents( ...[options]: Parameters ) { - return rpcRound("exposeForDependents", options) as ReturnType< + return rpcRound("expose-for-dependents", options) as ReturnType< T.Effects["exposeForDependents"] > }, - getConfigured(...[]: Parameters) { - return rpcRound("getConfigured", {}) as ReturnType< - T.Effects["getConfigured"] - > - }, getContainerIp(...[]: Parameters) { - return rpcRound("getContainerIp", {}) as ReturnType< + return rpcRound("get-container-ip", {}) as ReturnType< T.Effects["getContainerIp"] > }, @@ -177,21 +203,21 @@ function makeEffects(context: EffectContext): Effects { ...allOptions, callback: context.callbacks?.addCallback(allOptions.callback) || null, } - return rpcRound("getHostInfo", options) as ReturnType< + return rpcRound("get-host-info", options) as ReturnType< T.Effects["getHostInfo"] > as any }) as Effects["getHostInfo"], getServiceInterface( ...[options]: Parameters ) { - return rpcRound("getServiceInterface", { + return rpcRound("get-service-interface", { ...options, callback: context.callbacks?.addCallback(options.callback) || null, }) as ReturnType }, getPrimaryUrl(...[options]: Parameters) { - return rpcRound("getPrimaryUrl", { + return rpcRound("get-primary-url", { ...options, callback: context.callbacks?.addCallback(options.callback) || null, }) as ReturnType @@ -199,22 +225,22 @@ function makeEffects(context: EffectContext): Effects { getServicePortForward( ...[options]: Parameters ) { - return rpcRound("getServicePortForward", options) as ReturnType< + return rpcRound("get-service-port-forward", options) as ReturnType< T.Effects["getServicePortForward"] > }, getSslCertificate(options: Parameters[0]) { - return rpcRound("getSslCertificate", options) as ReturnType< + return rpcRound("get-ssl-certificate", options) as ReturnType< T.Effects["getSslCertificate"] > }, getSslKey(options: Parameters[0]) { - return rpcRound("getSslKey", options) as ReturnType< + return rpcRound("get-ssl-key", options) as ReturnType< T.Effects["getSslKey"] > }, getSystemSmtp(...[options]: Parameters) { - return rpcRound("getSystemSmtp", { + return rpcRound("get-system-smtp", { ...options, callback: context.callbacks?.addCallback(options.callback) || null, }) as ReturnType @@ -222,7 +248,7 @@ function makeEffects(context: EffectContext): Effects { listServiceInterfaces( ...[options]: Parameters ) { - return rpcRound("listServiceInterfaces", { + return rpcRound("list-service-interfaces", { ...options, callback: context.callbacks?.addCallback(options.callback) || null, }) as ReturnType @@ -230,46 +256,41 @@ function makeEffects(context: EffectContext): Effects { mount(...[options]: Parameters) { return rpcRound("mount", options) as ReturnType }, - clearActions(...[]: Parameters) { - return rpcRound("clearActions", {}) as ReturnType< - T.Effects["clearActions"] - > - }, restart(...[]: Parameters) { return rpcRound("restart", {}) as ReturnType }, - setConfigured(...[configured]: Parameters) { - return rpcRound("setConfigured", { configured }) as ReturnType< - T.Effects["setConfigured"] - > - }, setDependencies( dependencies: Parameters[0], ): ReturnType { - return rpcRound("setDependencies", dependencies) as ReturnType< + return rpcRound("set-dependencies", dependencies) as ReturnType< T.Effects["setDependencies"] > }, checkDependencies( options: Parameters[0], ): ReturnType { - return rpcRound("checkDependencies", options) as ReturnType< + return rpcRound("check-dependencies", options) as ReturnType< T.Effects["checkDependencies"] > }, getDependencies(): ReturnType { - return rpcRound("getDependencies", {}) as ReturnType< + return rpcRound("get-dependencies", {}) as ReturnType< T.Effects["getDependencies"] > }, setHealth(...[options]: Parameters) { - return rpcRound("setHealth", options) as ReturnType< + return rpcRound("set-health", options) as ReturnType< T.Effects["setHealth"] > }, - setMainStatus(o: { status: "running" | "stopped" }): Promise { - return rpcRound("setMainStatus", o) as ReturnType + getStatus(...[o]: Parameters) { + return rpcRound("get-status", o) as ReturnType + }, + setMainStatus(o: { status: "running" | "stopped" }): Promise { + return rpcRound("set-main-status", o) as ReturnType< + T.Effects["setHealth"] + > }, shutdown(...[]: Parameters) { @@ -277,38 +298,23 @@ function makeEffects(context: EffectContext): Effects { }, store: { get: async (options: any) => - rpcRound("getStore", { + rpcRound("store.get", { ...options, callback: context.callbacks?.addCallback(options.callback) || null, }) as any, set: async (options: any) => - rpcRound("setStore", options) as ReturnType, + rpcRound("store.set", options) as ReturnType, } as T.Effects["store"], getDataVersion() { - return rpcRound("getDataVersion", {}) as ReturnType< + return rpcRound("get-data-version", {}) as ReturnType< T.Effects["getDataVersion"] > }, setDataVersion(...[options]: Parameters) { - return rpcRound("setDataVersion", options) as ReturnType< + return rpcRound("set-data-version", options) as ReturnType< T.Effects["setDataVersion"] > }, } return self } - -export function makeProcedureEffects(procedureId: string): Effects { - return makeEffects({ procedureId, callbacks: null }) -} - -export function makeMainEffects(): MainEffects { - const rpcRound = rpcRoundFor(null) - return { - _type: "main", - clearCallbacks: () => { - return rpcRound("clearCallbacks", {}) as Promise - }, - ...makeEffects({ procedureId: null, callbacks: new CallbackHolder() }), - } -} diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index 28f578149..3e86e60d1 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -14,17 +14,14 @@ import { anyOf, } from "ts-matches" -import { types as T } from "@start9labs/start-sdk" +import { types as T, utils } from "@start9labs/start-sdk" import * as fs from "fs" import { CallbackHolder } from "../Models/CallbackHolder" import { AllGetDependencies } from "../Interfaces/AllGetDependencies" -import { jsonPath } from "../Models/JsonPath" -import { RunningMain, System } from "../Interfaces/System" -import { - MakeMainEffects, - MakeProcedureEffects, -} from "../Interfaces/MakeEffects" +import { jsonPath, unNestPath } from "../Models/JsonPath" +import { System } from "../Interfaces/System" +import { makeEffects } from "./EffectCreator" type MaybePromise = T | Promise export const matchRpcResult = anyOf( object({ result: any }), @@ -45,6 +42,7 @@ export const matchRpcResult = anyOf( ), }), ) + export type RpcResult = typeof matchRpcResult._TYPE type SocketResponse = ({ jsonrpc: "2.0"; id: IdType } & RpcResult) | null @@ -52,74 +50,99 @@ const SOCKET_PARENT = "/media/startos/rpc" const SOCKET_PATH = "/media/startos/rpc/service.sock" const jsonrpc = "2.0" as const +const isResult = object({ result: any }).test + const idType = some(string, number, literal(null)) -type IdType = null | string | number -const runType = object({ - id: idType, - method: literal("execute"), - params: object( - { - id: string, - procedure: string, - input: any, - timeout: number, - }, - ["timeout", "input"], - ), -}) -const sandboxRunType = object({ - id: idType, - method: literal("sandbox"), - params: object( - { - id: string, - procedure: string, - input: any, - timeout: number, - }, - ["timeout", "input"], - ), -}) +type IdType = null | string | number | undefined +const runType = object( + { + id: idType, + method: literal("execute"), + params: object( + { + id: string, + procedure: string, + input: any, + timeout: number, + }, + ["timeout"], + ), + }, + ["id"], +) +const sandboxRunType = object( + { + id: idType, + method: literal("sandbox"), + params: object( + { + id: string, + procedure: string, + input: any, + timeout: number, + }, + ["timeout"], + ), + }, + ["id"], +) const callbackType = object({ method: literal("callback"), params: object({ - callback: number, + id: number, args: array, }), }) -const initType = object({ - id: idType, - method: literal("init"), -}) -const startType = object({ - id: idType, - method: literal("start"), -}) -const stopType = object({ - id: idType, - method: literal("stop"), -}) -const exitType = object({ - id: idType, - method: literal("exit"), -}) -const evalType = object({ - id: idType, - method: literal("eval"), - params: object({ - script: string, - }), -}) +const initType = object( + { + id: idType, + method: literal("init"), + }, + ["id"], +) +const startType = object( + { + id: idType, + method: literal("start"), + }, + ["id"], +) +const stopType = object( + { + id: idType, + method: literal("stop"), + }, + ["id"], +) +const exitType = object( + { + id: idType, + method: literal("exit"), + }, + ["id"], +) +const evalType = object( + { + id: idType, + method: literal("eval"), + params: object({ + script: string, + }), + }, + ["id"], +) const jsonParse = (x: string) => JSON.parse(x) const handleRpc = (id: IdType, result: Promise) => result - .then((result) => ({ - jsonrpc, - id, - ...result, - })) + .then((result) => { + return { + jsonrpc, + id, + ...result, + } + }) .then((x) => { if ( ("result" in x && x.result === undefined) || @@ -142,8 +165,7 @@ const hasId = object({ id: idType }).test export class RpcListener { unixSocketServer = net.createServer(async (server) => {}) private _system: System | undefined - private _makeProcedureEffects: MakeProcedureEffects | undefined - private _makeMainEffects: MakeMainEffects | undefined + private callbacks: CallbackHolder | undefined constructor(readonly getDependencies: AllGetDependencies) { if (!fs.existsSync(SOCKET_PARENT)) { @@ -196,7 +218,11 @@ export class RpcListener { .then((x) => this.dealWithInput(x)) .catch(mapError) .then(logData("response")) - .then(writeDataToSocket), + .then(writeDataToSocket) + .catch((e) => { + console.error(`Major error in socket handling: ${e}`) + console.debug(`Data in: ${a.toString()}`) + }), ) }) } @@ -206,18 +232,33 @@ export class RpcListener { return this._system } - private get makeProcedureEffects() { - if (!this._makeProcedureEffects) { - this._makeProcedureEffects = this.getDependencies.makeProcedureEffects() + private callbackHolders: Map = new Map() + private removeCallbackHolderFor(procedure: string) { + const prev = this.callbackHolders.get(procedure) + if (prev) { + this.callbackHolders.delete(procedure) + this.callbacks?.removeChild(prev) } - return this._makeProcedureEffects + } + private callbackHolderFor(procedure: string): CallbackHolder { + this.removeCallbackHolderFor(procedure) + const callbackHolder = this.callbacks!.child() + this.callbackHolders.set(procedure, callbackHolder) + return callbackHolder } - private get makeMainEffects() { - if (!this._makeMainEffects) { - this._makeMainEffects = this.getDependencies.makeMainEffects() + callCallback(callback: number, args: any[]): void { + if (this.callbacks) { + this.callbacks + .callCallback(callback, args) + .catch((error) => + console.error(`callback ${callback} failed`, utils.asError(error)), + ) + } else { + console.warn( + `callback ${callback} ignored because system is not initialized`, + ) } - return this._makeMainEffects } private dealWithInput(input: unknown): MaybePromise { @@ -225,42 +266,49 @@ export class RpcListener { .when(runType, async ({ id, params }) => { const system = this.system const procedure = jsonPath.unsafeCast(params.procedure) - const effects = this.getDependencies.makeProcedureEffects()(params.id) - return handleRpc( - id, - system.execute(effects, { - procedure, - input: params.input, - timeout: params.timeout, - }), + const { input, timeout, id: procedureId } = params + const result = this.getResult( + procedure, + system, + procedureId, + timeout, + input, ) + + return handleRpc(id, result) }) .when(sandboxRunType, async ({ id, params }) => { const system = this.system const procedure = jsonPath.unsafeCast(params.procedure) - const effects = this.makeProcedureEffects(params.id) - return handleRpc( - id, - system.sandbox(effects, { - procedure, - input: params.input, - timeout: params.timeout, - }), + const { input, timeout, id: procedureId } = params + const result = this.getResult( + procedure, + system, + procedureId, + timeout, + input, ) + + return handleRpc(id, result) }) - .when(callbackType, async ({ params: { callback, args } }) => { - this.system.callCallback(callback, args) + .when(callbackType, async ({ params: { id, args } }) => { + this.callCallback(id, args) return null }) .when(startType, async ({ id }) => { + const callbacks = this.callbackHolderFor("main") + const effects = makeEffects({ + procedureId: null, + callbacks, + constRetry: () => {}, + }) return handleRpc( id, - this.system - .start(this.makeMainEffects()) - .then((result) => ({ result })), + this.system.start(effects).then((result) => ({ result })), ) }) .when(stopType, async ({ id }) => { + this.removeCallbackHolderFor("main") return handleRpc( id, this.system.stop().then((result) => ({ result })), @@ -280,7 +328,20 @@ export class RpcListener { (async () => { if (!this._system) { const system = await this.getDependencies.system() - await system.init() + this.callbacks = new CallbackHolder( + makeEffects({ + procedureId: null, + constRetry: () => {}, + }), + ) + const callbacks = this.callbackHolderFor("containerInit") + await system.containerInit( + makeEffects({ + procedureId: null, + callbacks, + constRetry: () => {}, + }), + ) this._system = system } })().then((result) => ({ result })), @@ -312,17 +373,20 @@ export class RpcListener { })(), ) }) - .when(shape({ id: idType, method: string }), ({ id, method }) => ({ - jsonrpc, - id, - error: { - code: -32601, - message: `Method not found`, - data: { - details: method, + .when( + shape({ id: idType, method: string }, ["id"]), + ({ id, method }) => ({ + jsonrpc, + id, + error: { + code: -32601, + message: `Method not found`, + data: { + details: method, + }, }, - }, - })) + }), + ) .defaultToLazy(() => { console.warn( @@ -341,4 +405,81 @@ export class RpcListener { } }) } + private getResult( + procedure: typeof jsonPath._TYPE, + system: System, + procedureId: string, + timeout: number | undefined, + input: any, + ) { + const ensureResultTypeShape = ( + result: void | T.ActionInput | T.ActionResult | null, + ): { result: any } => { + return { result } + } + const callbacks = this.callbackHolderFor(procedure) + const effects = makeEffects({ + procedureId, + callbacks, + constRetry: () => {}, + }) + + return (async () => { + switch (procedure) { + case "/backup/create": + return system.createBackup(effects, timeout || null) + case "/backup/restore": + return system.restoreBackup(effects, timeout || null) + case "/packageInit": + return system.packageInit(effects, timeout || null) + case "/packageUninit": + return system.packageUninit( + effects, + string.optional().unsafeCast(input), + timeout || null, + ) + default: + const procedures = unNestPath(procedure) + switch (true) { + case procedures[1] === "actions" && procedures[3] === "getInput": + return system.getActionInput( + effects, + procedures[2], + timeout || null, + ) + case procedures[1] === "actions" && procedures[3] === "run": + return system.runAction( + effects, + procedures[2], + input.input, + timeout || null, + ) + } + } + })().then(ensureResultTypeShape, (error) => + matches(error) + .when( + object( + { + error: string, + code: number, + }, + ["code"], + { code: 0 }, + ), + (error) => ({ + error: { + code: error.code, + message: error.error, + }, + }), + ) + .defaultToLazy(() => ({ + error: { + code: 0, + message: String(error), + }, + })), + ) + } } diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 47325170d..26e4dd8bf 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -1,56 +1,67 @@ import * as fs from "fs/promises" import * as cp from "child_process" -import { Overlay, types as T } from "@start9labs/start-sdk" +import { SubContainer, types as T } from "@start9labs/start-sdk" import { promisify } from "util" import { DockerProcedure, VolumeId } from "../../../Models/DockerProcedure" import { Volume } from "./matchVolume" -import { ExecSpawnable } from "@start9labs/start-sdk/cjs/lib/util/Overlay" +import { + CommandOptions, + ExecOptions, + ExecSpawnable, +} from "@start9labs/start-sdk/package/lib/util/SubContainer" export const exec = promisify(cp.exec) export const execFile = promisify(cp.execFile) export class DockerProcedureContainer { - private constructor(private readonly overlay: ExecSpawnable) {} + private constructor(private readonly subcontainer: ExecSpawnable) {} static async of( effects: T.Effects, packageId: string, data: DockerProcedure, volumes: { [id: VolumeId]: Volume }, - options: { overlay?: ExecSpawnable } = {}, + name: string, + options: { subcontainer?: ExecSpawnable } = {}, ) { - const overlay = - options?.overlay ?? - (await DockerProcedureContainer.createOverlay( + const subcontainer = + options?.subcontainer ?? + (await DockerProcedureContainer.createSubContainer( effects, packageId, data, volumes, + name, )) - return new DockerProcedureContainer(overlay) + return new DockerProcedureContainer(subcontainer) } - static async createOverlay( + static async createSubContainer( effects: T.Effects, packageId: string, data: DockerProcedure, volumes: { [id: VolumeId]: Volume }, + name: string, ) { - const overlay = await Overlay.of(effects, { id: data.image }) + const subcontainer = await SubContainer.of( + effects, + { id: data.image }, + name, + ) if (data.mounts) { const mounts = data.mounts for (const mount in mounts) { const path = mounts[mount].startsWith("/") - ? `${overlay.rootfs}${mounts[mount]}` - : `${overlay.rootfs}/${mounts[mount]}` + ? `${subcontainer.rootfs}${mounts[mount]}` + : `${subcontainer.rootfs}/${mounts[mount]}` await fs.mkdir(path, { recursive: true }) const volumeMount = volumes[mount] if (volumeMount.type === "data") { - await overlay.mount( + await subcontainer.mount( { type: "volume", id: mount, subpath: null, readonly: false }, mounts[mount], ) } else if (volumeMount.type === "assets") { - await overlay.mount( + await subcontainer.mount( { type: "assets", id: mount, subpath: null }, mounts[mount], ) @@ -96,24 +107,35 @@ export class DockerProcedureContainer { }) .catch(console.warn) } else if (volumeMount.type === "backup") { - await overlay.mount({ type: "backup", subpath: null }, mounts[mount]) + await subcontainer.mount( + { type: "backup", subpath: null }, + mounts[mount], + ) } } } - return overlay + return subcontainer } - async exec(commands: string[], {} = {}) { + async exec( + commands: string[], + options?: CommandOptions & ExecOptions, + timeoutMs?: number | null, + ) { try { - return await this.overlay.exec(commands) + return await this.subcontainer.exec(commands, options, timeoutMs) } finally { - await this.overlay.destroy?.() + await this.subcontainer.destroy?.() } } - async execFail(commands: string[], timeoutMs: number | null, {} = {}) { + async execFail( + commands: string[], + timeoutMs: number | null, + options?: CommandOptions & ExecOptions, + ) { try { - const res = await this.overlay.exec(commands, {}, timeoutMs) + const res = await this.subcontainer.exec(commands, options, timeoutMs) if (res.exitCode !== 0) { const codeOrSignal = res.exitCode !== null @@ -125,11 +147,11 @@ export class DockerProcedureContainer { } return res } finally { - await this.overlay.destroy?.() + await this.subcontainer.destroy?.() } } - async spawn(commands: string[]): Promise { - return await this.overlay.spawn(commands) + async spawn(commands: string[]): Promise { + return await this.subcontainer.spawn(commands) } } diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts index 79f197091..b6fe39854 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts @@ -2,10 +2,10 @@ import { polyfillEffects } from "./polyfillEffects" import { DockerProcedureContainer } from "./DockerProcedureContainer" import { SystemForEmbassy } from "." import { T, utils } from "@start9labs/start-sdk" -import { Daemon } from "@start9labs/start-sdk/cjs/lib/mainFn/Daemon" +import { Daemon } from "@start9labs/start-sdk/package/lib/mainFn/Daemon" import { Effects } from "../../../Models/Effects" import { off } from "node:process" -import { CommandController } from "@start9labs/start-sdk/cjs/lib/mainFn/CommandController" +import { CommandController } from "@start9labs/start-sdk/package/lib/mainFn/CommandController" const EMBASSY_HEALTH_INTERVAL = 15 * 1000 const EMBASSY_PROPERTIES_LOOP = 30 * 1000 @@ -15,8 +15,8 @@ const EMBASSY_PROPERTIES_LOOP = 30 * 1000 * Also, this has an ability to clean itself up too if need be. */ export class MainLoop { - get mainOverlay() { - return this.mainEvent?.daemon?.overlay + get mainSubContainerHandle() { + return this.mainEvent?.daemon?.subContainerHandle } private healthLoops?: { name: string @@ -56,19 +56,19 @@ export class MainLoop { throw new Error("Unreachable") } const daemon = new Daemon(async () => { - const overlay = await DockerProcedureContainer.createOverlay( + const subcontainer = await DockerProcedureContainer.createSubContainer( effects, this.system.manifest.id, this.system.manifest.main, this.system.manifest.volumes, + `Main - ${currentCommand.join(" ")}`, ) return CommandController.of()( this.effects, - - { id: this.system.manifest.main.image }, + subcontainer, currentCommand, { - overlay, + runAsInit: true, env: { TINI_SUBREAPER: "true", }, @@ -136,7 +136,7 @@ export class MainLoop { delete this.healthLoops await main?.daemon .stop() - .catch((e) => console.error(`Main loop error`, utils.asError(e))) + .catch((e: unknown) => console.error(`Main loop error`, utils.asError(e))) this.effects.setMainStatus({ status: "stopped" }) if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval)) } @@ -147,32 +147,45 @@ export class MainLoop { const start = Date.now() return Object.entries(manifest["health-checks"]).map( ([healthId, value]) => { + effects + .setHealth({ + id: healthId, + name: value.name, + result: "starting", + message: null, + }) + .catch((e) => console.error(utils.asError(e))) const interval = setInterval(async () => { const actionProcedure = value const timeChanged = Date.now() - start if (actionProcedure.type === "docker") { - const overlay = actionProcedure.inject - ? this.mainOverlay + const subcontainer = actionProcedure.inject + ? this.mainSubContainerHandle : undefined - // prettier-ignore - const container = - await DockerProcedureContainer.of( - effects, - manifest.id, - actionProcedure, - manifest.volumes, - { - overlay, - } - ) - const executed = await container.exec( - [ - actionProcedure.entrypoint, - ...actionProcedure.args, - JSON.stringify(timeChanged), - ], - {}, + const commands = [ + actionProcedure.entrypoint, + ...actionProcedure.args, + ] + const container = await DockerProcedureContainer.of( + effects, + manifest.id, + actionProcedure, + manifest.volumes, + `Health Check - ${commands.join(" ")}`, + { + subcontainer, + }, ) + const env: Record = actionProcedure.inject + ? { + HOME: "/root", + } + : {} + const executed = await container.exec(commands, { + input: JSON.stringify(timeChanged), + env, + }) + if (executed.exitCode === 0) { await effects.setHealth({ id: healthId, @@ -223,6 +236,18 @@ export class MainLoop { }) return } + if (executed.exitCode && executed.exitCode > 0) { + await effects.setHealth({ + id: healthId, + name: value.name, + result: "failure", + message: + executed.stderr.toString() || + executed.stdout.toString() || + `Program exited with code ${executed.exitCode}:`, + }) + return + } await effects.setHealth({ id: healthId, name: value.name, diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap b/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap index 01e2d0763..2c3d4b167 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap @@ -264,7 +264,6 @@ exports[`transformConfigSpec transformConfigSpec(bitcoind) 1`] = ` "disabled": false, "immutable": false, "name": "Pruning Mode", - "required": true, "type": "union", "variants": { "automatic": { @@ -524,7 +523,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Type", - "required": true, "type": "union", "variants": { "index": { @@ -589,7 +587,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Folder Location", - "required": false, "type": "select", "values": { "filebrowser": "filebrowser", @@ -644,7 +641,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Type", - "required": true, "type": "union", "variants": { "redirect": { @@ -705,7 +701,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Folder Location", - "required": false, "type": "select", "values": { "filebrowser": "filebrowser", @@ -758,7 +753,6 @@ exports[`transformConfigSpec transformConfigSpec(nostr2) 1`] = ` "disabled": false, "immutable": false, "name": "Relay Type", - "required": true, "type": "union", "variants": { "private": { diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 131d912e1..531b30cd2 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -2,8 +2,8 @@ import { ExtendedVersion, types as T, utils } from "@start9labs/start-sdk" import * as fs from "fs/promises" import { polyfillEffects } from "./polyfillEffects" -import { Duration, duration, fromDuration } from "../../../Models/Duration" -import { System, Procedure } from "../../../Interfaces/System" +import { fromDuration } from "../../../Models/Duration" +import { System } from "../../../Interfaces/System" import { matchManifest, Manifest } from "./matchManifest" import * as childProcess from "node:child_process" import { DockerProcedureContainer } from "./DockerProcedureContainer" @@ -27,19 +27,12 @@ import { Parser, array, } from "ts-matches" -import { JsonPath, unNestPath } from "../../../Models/JsonPath" -import { RpcResult, matchRpcResult } from "../../RpcListener" -import { CT } from "@start9labs/start-sdk" -import { - AddSslOptions, - BindOptions, -} from "@start9labs/start-sdk/cjs/lib/osBindings" +import { AddSslOptions } from "@start9labs/start-sdk/base/lib/osBindings" import { BindOptionsByProtocol, - Host, MultiHost, -} from "@start9labs/start-sdk/cjs/lib/interfaces/Host" -import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder" +} from "@start9labs/start-sdk/base/lib/interfaces/Host" +import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/base/lib/interfaces/ServiceInterfaceBuilder" import { Effects } from "../../../Models/Effects" import { OldConfigSpec, @@ -48,18 +41,52 @@ import { transformNewConfigToOld, transformOldConfigToNew, } from "./transformConfigSpec" -import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk" -import { StorePath } from "@start9labs/start-sdk/cjs/lib/store/PathBuilder" +import { partialDiff } from "@start9labs/start-sdk/base/lib/util" type Optional = A | undefined | null function todo(): never { throw new Error("Not implemented") } -const execFile = promisify(childProcess.execFile) const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json" export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js" -const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" as StorePath +const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" as utils.StorePath + +const matchResult = object({ + result: any, +}) +const matchError = object({ + error: string, +}) +const matchErrorCode = object<{ + "error-code": [number, string] | readonly [number, string] +}>({ + "error-code": tuple(number, string), +}) + +const assertNever = ( + x: never, + message = "Not expecting to get here: ", +): never => { + throw new Error(message + JSON.stringify(x)) +} +/** + Should be changing the type for specific properties, and this is mostly a transformation for the old return types to the newer one. +*/ +const fromReturnType = (a: U.ResultType): A => { + if (matchResult.test(a)) { + return a.result + } + if (matchError.test(a)) { + console.info({ passedErrorStack: new Error().stack, error: a.error }) + throw { error: a.error } + } + if (matchErrorCode.test(a)) { + const [code, message] = a["error-code"] + throw { error: message, code } + } + return assertNever(a) +} const matchSetResult = object( { @@ -108,6 +135,34 @@ type OldGetConfigRes = { spec: OldConfigSpec } +export type PropertiesValue = + | { + /** The type of this value, either "string" or "object" */ + type: "object" + /** A nested mapping of values. The user will experience this as a nested page with back button */ + value: { [k: string]: PropertiesValue } + /** (optional) A human readable description of the new set of values */ + description: string | null + } + | { + /** The type of this value, either "string" or "object" */ + type: "string" + /** The value to display to the user */ + value: string + /** A human readable description of the value */ + description: string | null + /** Whether or not to mask the value, for example, when displaying a password */ + masked: boolean | null + /** Whether or not to include a button for copying the value to clipboard */ + copyable: boolean | null + /** Whether or not to include a button for displaying the value as a QR code */ + qr: boolean | null + } + +export type PropertiesReturn = { + [key: string]: PropertiesValue +} + export type PackagePropertiesV2 = { [name: string]: PackagePropertyObject | PackagePropertyString } @@ -130,7 +185,7 @@ export type PackagePropertyObject = { const asProperty_ = ( x: PackagePropertyString | PackagePropertyObject, -): T.PropertiesValue => { +): PropertiesValue => { if (x.type === "object") { return { ...x, @@ -150,7 +205,7 @@ const asProperty_ = ( ...x, } } -const asProperty = (x: PackagePropertiesV2): T.PropertiesReturn => +const asProperty = (x: PackagePropertiesV2): PropertiesReturn => Object.fromEntries( Object.entries(x).map(([key, value]) => [key, asProperty_(value)]), ) @@ -187,6 +242,31 @@ const matchProperties = object({ data: matchPackageProperties, }) +function convertProperties( + name: string, + value: PropertiesValue, +): T.ActionResultMember { + if (value.type === "string") { + return { + type: "single", + name, + description: value.description, + copyable: value.copyable || false, + masked: value.masked || false, + qr: value.qr || false, + value: value.value, + } + } + return { + type: "group", + name, + description: value.description, + value: Object.entries(value.value).map(([name, value]) => + convertProperties(name, value), + ), + } +} + const DEFAULT_REGISTRY = "https://registry.start9.com" export class SystemForEmbassy implements System { currentRunning: MainLoop | undefined @@ -206,19 +286,44 @@ export class SystemForEmbassy implements System { moduleCode, ) } + constructor( readonly manifest: Manifest, readonly moduleCode: Partial, ) {} - async init(): Promise {} + async containerInit(effects: Effects): Promise { + for (let depId in this.manifest.dependencies) { + if (this.manifest.dependencies[depId].config) { + await this.dependenciesAutoconfig(effects, depId, null) + } + } + await effects.setMainStatus({ status: "stopped" }) + await this.exportActions(effects) + await this.exportNetwork(effects) + await this.containerSetDependencies(effects) + } + async containerSetDependencies(effects: T.Effects) { + const oldDeps: Record = Object.fromEntries( + await effects + .getDependencies() + .then((x) => + x.flatMap((x) => + x.kind === "running" ? [[x.id, x?.healthChecks || []]] : [], + ), + ) + .catch(() => []), + ) + await this.setDependencies(effects, oldDeps) + } async exit(): Promise { if (this.currentRunning) await this.currentRunning.clean() delete this.currentRunning } - async start(effects: MainEffects): Promise { + async start(effects: T.Effects): Promise { + effects.constRetry = utils.once(() => effects.restart()) if (!!this.currentRunning) return this.currentRunning = await MainLoop.of(this, effects) @@ -235,150 +340,26 @@ export class SystemForEmbassy implements System { } } - async execute( - effects: Effects, - options: { - procedure: JsonPath - input?: unknown - timeout?: number | undefined - }, - ): Promise { - return this._execute(effects, options) - .then((x) => - matches(x) - .when( - object({ - result: any, - }), - (x) => x, - ) - .when( - object({ - error: string, - }), - (x) => ({ - error: { - code: 0, - message: x.error, - }, - }), - ) - .when( - object({ - "error-code": tuple(number, string), - }), - ({ "error-code": [code, message] }) => ({ - error: { - code, - message, - }, - }), - ) - .defaultTo({ result: x }), - ) - .catch((error: unknown) => { - if (error instanceof Error) - return { - error: { - code: 0, - message: error.name, - data: { - details: error.message, - debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`, - }, - }, - } - if (matchRpcResult.test(error)) return error - return { - error: { - code: 0, - message: String(error), - }, - } + async packageInit(effects: Effects, timeoutMs: number | null): Promise { + const previousVersion = await effects.getDataVersion() + if (previousVersion) { + if ( + (await this.migration(effects, previousVersion, timeoutMs)).configured + ) { + await effects.action.clearRequests({ only: ["needs-config"] }) + } + await effects.setDataVersion({ + version: ExtendedVersion.parseEmver(this.manifest.version).toString(), + }) + } else if (this.manifest.config) { + await effects.action.request({ + packageId: this.manifest.id, + actionId: "config", + severity: "critical", + replayId: "needs-config", + reason: "This service must be configured before it can be run", }) - } - async _execute( - effects: Effects, - options: { - procedure: JsonPath - input?: unknown - timeout?: number | undefined - }, - ): Promise { - const input = options.input - switch (options.procedure) { - case "/backup/create": - return this.createBackup(effects, options.timeout || null) - case "/backup/restore": - return this.restoreBackup(effects, options.timeout || null) - case "/config/get": - return this.getConfig(effects, options.timeout || null) - case "/config/set": - return this.setConfig(effects, input, options.timeout || null) - case "/properties": - return this.properties(effects, options.timeout || null) - case "/actions/metadata": - return todo() - case "/init": - return this.initProcedure( - effects, - string.optional().unsafeCast(input), - options.timeout || null, - ) - case "/uninit": - return this.uninit( - effects, - string.optional().unsafeCast(input), - options.timeout || null, - ) - default: - const procedures = unNestPath(options.procedure) - switch (true) { - case procedures[1] === "actions" && procedures[3] === "get": - return this.action( - effects, - procedures[2], - input, - options.timeout || null, - ) - case procedures[1] === "actions" && procedures[3] === "run": - return this.action( - effects, - procedures[2], - input, - options.timeout || null, - ) - case procedures[1] === "dependencies" && procedures[3] === "query": - return null - - case procedures[1] === "dependencies" && procedures[3] === "update": - return this.dependenciesAutoconfig( - effects, - procedures[2], - input, - options.timeout || null, - ) - } } - throw new Error(`Could not find the path for ${options.procedure}`) - } - async sandbox( - effects: Effects, - options: { procedure: Procedure; input: unknown; timeout?: number }, - ): Promise { - return this.execute(effects, options) - } - - private async initProcedure( - effects: Effects, - previousVersion: Optional, - timeoutMs: number | null, - ): Promise { - if (previousVersion) - await this.migration(effects, previousVersion, timeoutMs) - await effects.setMainStatus({ status: "stopped" }) - await this.exportActions(effects) - await this.exportNetwork(effects) } async exportNetwork(effects: Effects) { for (const [id, interfaceValue] of Object.entries( @@ -461,10 +442,75 @@ export class SystemForEmbassy implements System { ) } } + async getActionInput( + effects: Effects, + actionId: string, + timeoutMs: number | null, + ): Promise { + if (actionId === "config") { + const config = await this.getConfig(effects, timeoutMs) + return { spec: config.spec, value: config.config } + } else if (actionId === "properties") { + return null + } else { + const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"] + if (!oldSpec) return null + return { + spec: transformConfigSpec(oldSpec as OldConfigSpec), + value: null, + } + } + } + async runAction( + effects: Effects, + actionId: string, + input: unknown, + timeoutMs: number | null, + ): Promise { + if (actionId === "config") { + await this.setConfig(effects, input, timeoutMs) + return null + } else if (actionId === "properties") { + return { + version: "1", + title: "Properties", + message: null, + result: { + type: "group", + value: Object.entries(await this.properties(effects, timeoutMs)).map( + ([name, value]) => convertProperties(name, value), + ), + }, + } + } else { + return this.action(effects, actionId, input, timeoutMs) + } + } async exportActions(effects: Effects) { const manifest = this.manifest - if (!manifest.actions) return - for (const [actionId, action] of Object.entries(manifest.actions)) { + const actions = { + ...manifest.actions, + } + if (manifest.config) { + actions.config = { + name: "Configure", + description: `Customize ${manifest.title}`, + "allowed-statuses": ["running", "stopped"], + "input-spec": {}, + implementation: { type: "script", args: [] }, + } + } + if (manifest.properties) { + actions.properties = { + name: "Properties", + description: + "Runtime information, credentials, and other values of interest", + "allowed-statuses": ["running", "stopped"], + "input-spec": null, + implementation: { type: "script", args: [] }, + } + } + for (const [actionId, action] of Object.entries(actions)) { const hasRunning = !!action["allowed-statuses"].find( (x) => x === "running", ) @@ -473,23 +519,24 @@ export class SystemForEmbassy implements System { ) // prettier-ignore const allowedStatuses = hasRunning && hasStopped ? "any": - hasRunning ? "onlyRunning" : - "onlyStopped" - await effects.exportAction({ + hasRunning ? "only-running" : + "only-stopped" + await effects.action.export({ id: actionId, metadata: { name: action.name, description: action.description, warning: action.warning || null, - input: action["input-spec"] as CT.InputSpec, - disabled: false, + visibility: "enabled", allowedStatuses, + hasInput: !!action["input-spec"], group: null, }, }) } + await effects.action.clear({ except: Object.keys(actions) }) } - private async uninit( + async packageUninit( effects: Effects, nextVersion: Optional, timeoutMs: number | null, @@ -498,12 +545,13 @@ export class SystemForEmbassy implements System { await effects.setMainStatus({ status: "stopped" }) } - private async createBackup( + async createBackup( effects: Effects, timeoutMs: number | null, ): Promise { const backup = this.manifest.backup.create if (backup.type === "docker") { + const commands = [backup.entrypoint, ...backup.args] const container = await DockerProcedureContainer.of( effects, this.manifest.id, @@ -512,19 +560,21 @@ export class SystemForEmbassy implements System { ...this.manifest.volumes, BACKUP: { type: "backup", readonly: false }, }, + `Backup - ${commands.join(" ")}`, ) - await container.execFail([backup.entrypoint, ...backup.args], timeoutMs) + await container.execFail(commands, timeoutMs) } else { const moduleCode = await this.moduleCode await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest)) } } - private async restoreBackup( + async restoreBackup( effects: Effects, timeoutMs: number | null, ): Promise { const restoreBackup = this.manifest.backup.restore if (restoreBackup.type === "docker") { + const commands = [restoreBackup.entrypoint, ...restoreBackup.args] const container = await DockerProcedureContainer.of( effects, this.manifest.id, @@ -533,20 +583,15 @@ export class SystemForEmbassy implements System { ...this.manifest.volumes, BACKUP: { type: "backup", readonly: true }, }, + `Restore Backup - ${commands.join(" ")}`, ) - await container.execFail( - [restoreBackup.entrypoint, ...restoreBackup.args], - timeoutMs, - ) + await container.execFail(commands, timeoutMs) } else { const moduleCode = await this.moduleCode await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest)) } } - private async getConfig( - effects: Effects, - timeoutMs: number | null, - ): Promise { + async getConfig(effects: Effects, timeoutMs: number | null) { return this.getConfigUncleaned(effects, timeoutMs).then(convertToNewConfig) } private async getConfigUncleaned( @@ -556,20 +601,17 @@ export class SystemForEmbassy implements System { const config = this.manifest.config?.get if (!config) return { spec: {} } if (config.type === "docker") { + const commands = [config.entrypoint, ...config.args] const container = await DockerProcedureContainer.of( effects, this.manifest.id, config, this.manifest.volumes, + `Get Config - ${commands.join(" ")}`, ) // TODO: yaml return JSON.parse( - ( - await container.execFail( - [config.entrypoint, ...config.args], - timeoutMs, - ) - ).stdout.toString(), + (await container.execFail(commands, timeoutMs)).stdout.toString(), ) } else { const moduleCode = await this.moduleCode @@ -584,7 +626,7 @@ export class SystemForEmbassy implements System { )) as any } } - private async setConfig( + async setConfig( effects: Effects, newConfigWithoutPointers: unknown, timeoutMs: number | null, @@ -604,28 +646,25 @@ export class SystemForEmbassy implements System { const setConfigValue = this.manifest.config?.set if (!setConfigValue) return if (setConfigValue.type === "docker") { + const commands = [ + setConfigValue.entrypoint, + ...setConfigValue.args, + JSON.stringify(newConfig), + ] const container = await DockerProcedureContainer.of( effects, this.manifest.id, setConfigValue, this.manifest.volumes, + `Set Config - ${commands.join(" ")}`, ) const answer = matchSetResult.unsafeCast( JSON.parse( - ( - await container.execFail( - [ - setConfigValue.entrypoint, - ...setConfigValue.args, - JSON.stringify(newConfig), - ], - timeoutMs, - ) - ).stdout.toString(), + (await container.execFail(commands, timeoutMs)).stdout.toString(), ), ) const dependsOn = answer["depends-on"] ?? answer.dependsOn ?? {} - await this.setConfigSetConfig(effects, dependsOn) + await this.setDependencies(effects, dependsOn) return } else if (setConfigValue.type === "script") { const moduleCode = await this.moduleCode @@ -648,39 +687,68 @@ export class SystemForEmbassy implements System { }), ) const dependsOn = answer["depends-on"] ?? answer.dependsOn ?? {} - await this.setConfigSetConfig(effects, dependsOn) + await this.setDependencies(effects, dependsOn) return } } - private async setConfigSetConfig( + private async setDependencies( effects: Effects, - dependsOn: { [x: string]: readonly string[] }, + rawDepends: { [x: string]: readonly string[] }, ) { + const dependsOn: Record = { + ...Object.fromEntries( + Object.entries(this.manifest.dependencies || {})?.map((x) => [ + x[0], + null, + ]) || [], + ), + ...rawDepends, + } await effects.setDependencies({ - dependencies: Object.entries(dependsOn).flatMap(([key, value]) => { - const dependency = this.manifest.dependencies?.[key] - if (!dependency) return [] - const versionRange = dependency.version - const registryUrl = DEFAULT_REGISTRY - const kind = "running" - return [ - { - id: key, - versionRange, - registryUrl, - kind, - healthChecks: [...value], - }, - ] - }), + dependencies: Object.entries(dependsOn).flatMap( + ([key, value]): T.Dependencies => { + const dependency = this.manifest.dependencies?.[key] + if (!dependency) return [] + if (value == null) { + const versionRange = dependency.version + if (dependency.requirement.type === "required") { + return [ + { + id: key, + versionRange, + kind: "running", + healthChecks: [], + }, + ] + } + return [ + { + kind: "exists", + id: key, + versionRange, + }, + ] + } + const versionRange = dependency.version + const kind = "running" + return [ + { + id: key, + versionRange, + kind, + healthChecks: [...value], + }, + ] + }, + ), }) } - private async migration( + async migration( effects: Effects, fromVersion: string, timeoutMs: number | null, - ): Promise { + ): Promise<{ configured: boolean }> { const fromEmver = ExtendedVersion.parseEmver(fromVersion) const currentEmver = ExtendedVersion.parseEmver(this.manifest.version) if (!this.manifest.migrations) return { configured: true } @@ -713,23 +781,20 @@ export class SystemForEmbassy implements System { if (migration) { const [version, procedure] = migration if (procedure.type === "docker") { + const commands = [ + procedure.entrypoint, + ...procedure.args, + JSON.stringify(fromVersion), + ] const container = await DockerProcedureContainer.of( effects, this.manifest.id, procedure, this.manifest.volumes, + `Migration - ${commands.join(" ")}`, ) return JSON.parse( - ( - await container.execFail( - [ - procedure.entrypoint, - ...procedure.args, - JSON.stringify(fromVersion), - ], - timeoutMs, - ) - ).stdout.toString(), + (await container.execFail(commands, timeoutMs)).stdout.toString(), ) } else if (procedure.type === "script") { const moduleCode = await this.moduleCode @@ -748,28 +813,25 @@ export class SystemForEmbassy implements System { } return { configured: true } } - private async properties( + async properties( effects: Effects, timeoutMs: number | null, - ): Promise> { + ): Promise { // TODO BLU-J set the properties ever so often const setConfigValue = this.manifest.properties if (!setConfigValue) throw new Error("There is no properties") if (setConfigValue.type === "docker") { + const commands = [setConfigValue.entrypoint, ...setConfigValue.args] const container = await DockerProcedureContainer.of( effects, this.manifest.id, setConfigValue, this.manifest.volumes, + `Properties - ${commands.join(" ")}`, ) const properties = matchProperties.unsafeCast( JSON.parse( - ( - await container.execFail( - [setConfigValue.entrypoint, ...setConfigValue.args], - timeoutMs, - ) - ).stdout.toString(), + (await container.execFail(commands, timeoutMs)).stdout.toString(), ), ) return asProperty(properties.data) @@ -779,84 +841,167 @@ export class SystemForEmbassy implements System { if (!method) throw new Error("Expecting that the method properties exists") const properties = matchProperties.unsafeCast( - await method(polyfillEffects(effects, this.manifest)).then((x) => { - if ("result" in x) return x.result - if ("error" in x) throw new Error("Error getting config: " + x.error) - throw new Error("Error getting config: " + x["error-code"][1]) - }), + await method(polyfillEffects(effects, this.manifest)).then( + fromReturnType, + ), ) return asProperty(properties.data) } throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`) } - private async action( + async action( effects: Effects, actionId: string, formData: unknown, timeoutMs: number | null, ): Promise { const actionProcedure = this.manifest.actions?.[actionId]?.implementation - if (!actionProcedure) return { message: "Action not found", value: null } + const toActionResult = ({ + message, + value, + copyable, + qr, + }: U.ActionResult): T.ActionResult => ({ + version: "0", + message, + value: value ?? null, + copyable, + qr, + }) + if (!actionProcedure) throw Error("Action not found") if (actionProcedure.type === "docker") { - const overlay = actionProcedure.inject - ? this.currentRunning?.mainOverlay + const subcontainer = actionProcedure.inject + ? this.currentRunning?.mainSubContainerHandle : undefined + + const env: Record = actionProcedure.inject + ? { + HOME: "/root", + } + : {} const container = await DockerProcedureContainer.of( effects, this.manifest.id, actionProcedure, this.manifest.volumes, + `Action ${actionId}`, { - overlay, + subcontainer, }, ) - return JSON.parse( - ( - await container.execFail( - [ - actionProcedure.entrypoint, - ...actionProcedure.args, - JSON.stringify(formData), - ], - timeoutMs, - ) - ).stdout.toString(), + return toActionResult( + JSON.parse( + ( + await container.execFail( + [ + actionProcedure.entrypoint, + ...actionProcedure.args, + JSON.stringify(formData), + ], + timeoutMs, + { env }, + ) + ).stdout.toString(), + ), ) } else { const moduleCode = await this.moduleCode const method = moduleCode.action?.[actionId] if (!method) throw new Error("Expecting that the method action exists") - return (await method( + return await method( polyfillEffects(effects, this.manifest), formData as any, + ) + .then(fromReturnType) + .then(toActionResult) + } + } + async dependenciesCheck( + effects: Effects, + id: string, + oldConfig: unknown, + timeoutMs: number | null, + ): Promise { + const actionProcedure = this.manifest.dependencies?.[id]?.config?.check + if (!actionProcedure) return { message: "Action not found", value: null } + if (actionProcedure.type === "docker") { + const commands = [ + actionProcedure.entrypoint, + ...actionProcedure.args, + JSON.stringify(oldConfig), + ] + const container = await DockerProcedureContainer.of( + effects, + this.manifest.id, + actionProcedure, + this.manifest.volumes, + `Dependencies Check - ${commands.join(" ")}`, + ) + return JSON.parse( + (await container.execFail(commands, timeoutMs)).stdout.toString(), + ) + } else if (actionProcedure.type === "script") { + const moduleCode = await this.moduleCode + const method = moduleCode.dependencies?.[id]?.check + if (!method) + throw new Error( + `Expecting that the method dependency check ${id} exists`, + ) + return (await method( + polyfillEffects(effects, this.manifest), + oldConfig as any, ).then((x) => { if ("result" in x) return x.result if ("error" in x) throw new Error("Error getting config: " + x.error) throw new Error("Error getting config: " + x["error-code"][1]) })) as any + } else { + return {} } } - private async dependenciesAutoconfig( + async dependenciesAutoconfig( effects: Effects, id: string, - input: unknown, timeoutMs: number | null, ): Promise { - const oldConfig = object({ remoteConfig: any }).unsafeCast( - input, - ).remoteConfig // TODO: docker + const oldConfig = (await effects.store.get({ + packageId: id, + path: EMBASSY_POINTER_PATH_PREFIX, + callback: () => { + this.dependenciesAutoconfig(effects, id, timeoutMs) + }, + })) as U.Config + if (!oldConfig) return const moduleCode = await this.moduleCode const method = moduleCode.dependencies?.[id]?.autoConfigure if (!method) return - return (await method( + const newConfig = (await method( polyfillEffects(effects, this.manifest), - oldConfig, + JSON.parse(JSON.stringify(oldConfig)), ).then((x) => { if ("result" in x) return x.result if ("error" in x) throw new Error("Error getting config: " + x.error) throw new Error("Error getting config: " + x["error-code"][1]) })) as any + const diff = partialDiff(oldConfig, newConfig) + if (diff) { + await effects.action.request({ + actionId: "config", + packageId: id, + replayId: `${id}/config`, + severity: "important", + reason: `Configure this dependency for the needs of ${this.manifest.title}`, + input: { + kind: "partial", + value: diff.diff, + }, + when: { + condition: "input-not-matches", + once: false, + }, + }) + } } } @@ -1031,9 +1176,7 @@ function extractServiceInterfaceId(manifest: Manifest, specInterface: string) { const serviceInterfaceId = `${specInterface}-${internalPort}` return serviceInterfaceId } -async function convertToNewConfig( - value: OldGetConfigRes, -): Promise { +async function convertToNewConfig(value: OldGetConfigRes) { const valueSpec: OldConfigSpec = matchOldConfigSpec.unsafeCast(value.spec) const spec = transformConfigSpec(valueSpec) if (!value.config) return { spec, config: null } diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/matchManifest.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/matchManifest.ts index bd8856b42..5bda20de0 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/matchManifest.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/matchManifest.ts @@ -42,6 +42,7 @@ const matchAction = object( export const matchManifest = object( { id: string, + title: string, version: string, main: matchDockerProcedure, assets: object( diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts index 03af30c90..7438070ea 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts @@ -105,12 +105,14 @@ export const polyfillEffects = ( args?: string[] | undefined timeoutMillis?: number | undefined }): Promise> { + const commands: [string, ...string[]] = [command, ...(args || [])] return startSdk .runCommand( effects, { id: manifest.main.image }, - [command, ...(args || [])], + commands, {}, + commands.join(" "), ) .then((x: any) => ({ stderr: x.stderr.toString(), @@ -124,20 +126,19 @@ export const polyfillEffects = ( wait(): Promise> term(): Promise } { - const promiseOverlay = DockerProcedureContainer.createOverlay( + const promiseSubcontainer = DockerProcedureContainer.createSubContainer( effects, manifest.id, manifest.main, manifest.volumes, + [input.command, ...(input.args || [])].join(" "), ) - const daemon = promiseOverlay.then((overlay) => + const daemon = promiseSubcontainer.then((subcontainer) => daemons.runCommand()( effects, - { id: manifest.main.image }, + subcontainer, [input.command, ...(input.args || [])], - { - overlay, - }, + {}, ), ) return { @@ -155,11 +156,17 @@ export const polyfillEffects = ( path: string uid: string }): Promise { + const commands: [string, ...string[]] = [ + "chown", + "--recursive", + input.uid, + `/drive/${input.path}`, + ] await startSdk .runCommand( effects, { id: manifest.main.image }, - ["chown", "--recursive", input.uid, `/drive/${input.path}`], + commands, { mounts: [ { @@ -173,6 +180,7 @@ export const polyfillEffects = ( }, ], }, + commands.join(" "), ) .then((x: any) => ({ stderr: x.stderr.toString(), @@ -190,11 +198,17 @@ export const polyfillEffects = ( path: string mode: string }): Promise { + const commands: [string, ...string[]] = [ + "chmod", + "--recursive", + input.mode, + `/drive/${input.path}`, + ] await startSdk .runCommand( effects, { id: manifest.main.image }, - ["chmod", "--recursive", input.mode, `/drive/${input.path}`], + commands, { mounts: [ { @@ -208,6 +222,7 @@ export const polyfillEffects = ( }, ], }, + commands.join(" "), ) .then((x: any) => ({ stderr: x.stderr.toString(), diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts index 5ce601c57..1eb2ea508 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts @@ -1,4 +1,4 @@ -import { CT } from "@start9labs/start-sdk" +import { IST } from "@start9labs/start-sdk" import { dictionary, object, @@ -15,9 +15,9 @@ import { literal, } from "ts-matches" -export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec { +export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec { return Object.entries(oldSpec).reduce((inputSpec, [key, oldVal]) => { - let newVal: CT.ValueSpec + let newVal: IST.ValueSpec if (oldVal.type === "boolean") { newVal = { @@ -43,7 +43,6 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec { }), {}, ), - required: false, disabled: false, immutable: false, } @@ -124,10 +123,9 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec { spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(spec)), }, }), - {} as Record, + {} as Record, ), disabled: false, - required: true, default: oldVal.default, immutable: false, } @@ -141,7 +139,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec { ...inputSpec, [key]: newVal, } - }, {} as CT.InputSpec) + }, {} as IST.InputSpec) } export function transformOldConfigToNew( @@ -233,10 +231,10 @@ export function transformNewConfigToOld( function getListSpec( oldVal: OldValueSpecList, -): CT.ValueSpecMultiselect | CT.ValueSpecList { +): IST.ValueSpecMultiselect | IST.ValueSpecList { const range = Range.from(oldVal.range) - let partial: Omit = { + let partial: Omit = { name: oldVal.name, description: oldVal.description || null, warning: oldVal.warning || null, diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index be7b0fc84..334764a87 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -1,20 +1,12 @@ -import { ExecuteResult, Procedure, System } from "../../Interfaces/System" -import { unNestPath } from "../../Models/JsonPath" -import matches, { any, number, object, string, tuple } from "ts-matches" +import { System } from "../../Interfaces/System" import { Effects } from "../../Models/Effects" -import { RpcResult, matchRpcResult } from "../RpcListener" -import { duration } from "../../Models/Duration" import { T, utils } from "@start9labs/start-sdk" -import { Volume } from "../../Models/Volume" -import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk" -import { CallbackHolder } from "../../Models/CallbackHolder" +import { Optional } from "ts-matches/lib/parsers/interfaces" export const STARTOS_JS_LOCATION = "/usr/lib/startos/package/index.js" type RunningMain = { - effects: MainEffects stop: () => Promise - callbacks: CallbackHolder } export class SystemForStartOs implements System { @@ -24,203 +16,90 @@ export class SystemForStartOs implements System { return new SystemForStartOs(require(STARTOS_JS_LOCATION)) } - constructor(readonly abi: T.ABI) {} - - async init(): Promise {} + constructor(readonly abi: T.ABI) { + this + } + async containerInit(effects: Effects): Promise { + return void (await this.abi.containerInit({ effects })) + } + async packageInit( + effects: Effects, + timeoutMs: number | null = null, + ): Promise { + return void (await this.abi.packageInit({ effects })) + } + async packageUninit( + effects: Effects, + nextVersion: Optional = null, + timeoutMs: number | null = null, + ): Promise { + return void (await this.abi.packageUninit({ effects, nextVersion })) + } + async createBackup( + effects: T.Effects, + timeoutMs: number | null, + ): Promise { + return void (await this.abi.createBackup({ + effects, + })) + } + async restoreBackup( + effects: T.Effects, + timeoutMs: number | null, + ): Promise { + return void (await this.abi.restoreBackup({ + effects, + })) + } + getActionInput( + effects: Effects, + id: string, + timeoutMs: number | null, + ): Promise { + const action = this.abi.actions.get(id) + if (!action) throw new Error(`Action ${id} not found`) + return action.getInput({ effects }) + } + runAction( + effects: Effects, + id: string, + input: unknown, + timeoutMs: number | null, + ): Promise { + const action = this.abi.actions.get(id) + if (!action) throw new Error(`Action ${id} not found`) + return action.run({ effects, input }) + } async exit(): Promise {} - async start(effects: MainEffects): Promise { + async start(effects: Effects): Promise { + effects.constRetry = utils.once(() => effects.restart()) if (this.runningMain) await this.stop() let mainOnTerm: () => Promise | undefined const started = async (onTerm: () => Promise) => { await effects.setMainStatus({ status: "running" }) mainOnTerm = onTerm + return null } const daemons = await ( await this.abi.main({ - effects: effects as MainEffects, + effects, started, }) ).build() this.runningMain = { - effects, stop: async () => { if (mainOnTerm) await mainOnTerm() await daemons.term() }, - callbacks: new CallbackHolder(), - } - } - - callCallback(callback: number, args: any[]): void { - if (this.runningMain) { - this.runningMain.callbacks - .callCallback(callback, args) - .catch((error) => - console.error(`callback ${callback} failed`, utils.asError(error)), - ) - } else { - console.warn(`callback ${callback} ignored because system is not running`) } } async stop(): Promise { if (this.runningMain) { await this.runningMain.stop() - await this.runningMain.effects.clearCallbacks() this.runningMain = undefined } } - - async execute( - effects: Effects, - options: { - procedure: Procedure - input?: unknown - timeout?: number | undefined - }, - ): Promise { - return this._execute(effects, options) - .then((x) => - matches(x) - .when( - object({ - result: any, - }), - (x) => x, - ) - .when( - object({ - error: string, - }), - (x) => ({ - error: { - code: 0, - message: x.error, - }, - }), - ) - .when( - object({ - "error-code": tuple(number, string), - }), - ({ "error-code": [code, message] }) => ({ - error: { - code, - message, - }, - }), - ) - .defaultTo({ result: x }), - ) - .catch((error: unknown) => { - if (error instanceof Error) - return { - error: { - code: 0, - message: error.name, - data: { - details: error.message, - debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`, - }, - }, - } - if (matchRpcResult.test(error)) return error - return { - error: { - code: 0, - message: String(error), - }, - } - }) - } - async _execute( - effects: Effects | MainEffects, - options: { - procedure: Procedure - input?: unknown - timeout?: number | undefined - }, - ): Promise { - switch (options.procedure) { - case "/init": { - return this.abi.init({ effects }) - } - case "/uninit": { - const nextVersion = string.optional().unsafeCast(options.input) || null - return this.abi.uninit({ effects, nextVersion }) - } - // case "/main/start": { - // - // } - // case "/main/stop": { - // if (this.onTerm) await this.onTerm() - // await effects.setMainStatus({ status: "stopped" }) - // delete this.onTerm - // return duration(30, "s") - // } - case "/config/set": { - const input = options.input as any // TODO - return this.abi.setConfig({ effects, input }) - } - case "/config/get": { - return this.abi.getConfig({ effects }) - } - case "/backup/create": - return this.abi.createBackup({ - effects, - pathMaker: ((options) => - new Volume(options.volume, options.path).path) as T.PathMaker, - }) - case "/backup/restore": - return this.abi.restoreBackup({ - effects, - pathMaker: ((options) => - new Volume(options.volume, options.path).path) as T.PathMaker, - }) - case "/actions/metadata": { - return this.abi.actionsMetadata({ effects }) - } - case "/properties": { - throw new Error("TODO") - } - default: - const procedures = unNestPath(options.procedure) - const id = procedures[2] - switch (true) { - case procedures[1] === "actions" && procedures[3] === "get": { - const action = (await this.abi.actions({ effects }))[id] - if (!action) throw new Error(`Action ${id} not found`) - return action.getConfig({ effects }) - } - case procedures[1] === "actions" && procedures[3] === "run": { - const action = (await this.abi.actions({ effects }))[id] - if (!action) throw new Error(`Action ${id} not found`) - return action.run({ effects, input: options.input as any }) // TODO - } - case procedures[1] === "dependencies" && procedures[3] === "query": { - const dependencyConfig = this.abi.dependencyConfig[id] - if (!dependencyConfig) - throw new Error(`dependencyConfig ${id} not found`) - const localConfig = options.input - return dependencyConfig.query({ effects }) - } - case procedures[1] === "dependencies" && procedures[3] === "update": { - const dependencyConfig = this.abi.dependencyConfig[id] - if (!dependencyConfig) - throw new Error(`dependencyConfig ${id} not found`) - return dependencyConfig.update(options.input as any) // TODO - } - } - return - } - } - - async sandbox( - effects: Effects, - options: { procedure: Procedure; input?: unknown; timeout?: number }, - ): Promise { - return this.execute(effects, options) - } } diff --git a/container-runtime/src/Interfaces/AllGetDependencies.ts b/container-runtime/src/Interfaces/AllGetDependencies.ts index ca5c43585..24b68acc5 100644 --- a/container-runtime/src/Interfaces/AllGetDependencies.ts +++ b/container-runtime/src/Interfaces/AllGetDependencies.ts @@ -1,7 +1,4 @@ import { GetDependency } from "./GetDependency" import { System } from "./System" -import { MakeMainEffects, MakeProcedureEffects } from "./MakeEffects" -export type AllGetDependencies = GetDependency<"system", Promise> & - GetDependency<"makeProcedureEffects", MakeProcedureEffects> & - GetDependency<"makeMainEffects", MakeMainEffects> +export type AllGetDependencies = GetDependency<"system", Promise> diff --git a/container-runtime/src/Interfaces/MakeEffects.ts b/container-runtime/src/Interfaces/MakeEffects.ts deleted file mode 100644 index 3b25f8180..000000000 --- a/container-runtime/src/Interfaces/MakeEffects.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Effects } from "../Models/Effects" -import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk" -export type MakeProcedureEffects = (procedureId: string) => Effects -export type MakeMainEffects = () => MainEffects diff --git a/container-runtime/src/Interfaces/System.ts b/container-runtime/src/Interfaces/System.ts index 01fd3c5ff..63781cfbd 100644 --- a/container-runtime/src/Interfaces/System.ts +++ b/container-runtime/src/Interfaces/System.ts @@ -1,49 +1,45 @@ import { types as T } from "@start9labs/start-sdk" -import { RpcResult } from "../Adapters/RpcListener" import { Effects } from "../Models/Effects" import { CallbackHolder } from "../Models/CallbackHolder" -import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk" +import { Optional } from "ts-matches/lib/parsers/interfaces" export type Procedure = - | "/init" - | "/uninit" - | "/config/set" - | "/config/get" + | "/packageInit" + | "/packageUninit" | "/backup/create" | "/backup/restore" - | "/actions/metadata" - | "/properties" - | `/actions/${string}/get` + | `/actions/${string}/getInput` | `/actions/${string}/run` - | `/dependencies/${string}/query` - | `/dependencies/${string}/update` export type ExecuteResult = | { ok: unknown } | { err: { code: number; message: string } } export type System = { - init(): Promise + containerInit(effects: T.Effects): Promise - start(effects: MainEffects): Promise - callCallback(callback: number, args: any[]): void + start(effects: T.Effects): Promise stop(): Promise - execute( + packageInit(effects: Effects, timeoutMs: number | null): Promise + packageUninit( effects: Effects, - options: { - procedure: Procedure - input: unknown - timeout?: number - }, - ): Promise - sandbox( + nextVersion: Optional, + timeoutMs: number | null, + ): Promise + + createBackup(effects: T.Effects, timeoutMs: number | null): Promise + restoreBackup(effects: T.Effects, timeoutMs: number | null): Promise + runAction( + effects: Effects, + actionId: string, + input: unknown, + timeoutMs: number | null, + ): Promise + getActionInput( effects: Effects, - options: { - procedure: Procedure - input: unknown - timeout?: number - }, - ): Promise + actionId: string, + timeoutMs: number | null, + ): Promise exit(): Promise } diff --git a/container-runtime/src/Models/CallbackHolder.ts b/container-runtime/src/Models/CallbackHolder.ts index b51af0bee..ce474268a 100644 --- a/container-runtime/src/Models/CallbackHolder.ts +++ b/container-runtime/src/Models/CallbackHolder.ts @@ -1,22 +1,62 @@ +import { T } from "@start9labs/start-sdk" + +const CallbackIdCell = { inc: 1 } + +const callbackRegistry = new FinalizationRegistry( + async (options: { cbs: Map; effects: T.Effects }) => { + await options.effects.clearCallbacks({ + only: Array.from(options.cbs.keys()), + }) + }, +) + export class CallbackHolder { - constructor() {} - private inc = 0 + constructor(private effects?: T.Effects) {} + private callbacks = new Map() + private children: WeakRef[] = [] private newId() { - return this.inc++ + return CallbackIdCell.inc++ } addCallback(callback?: Function) { if (!callback) { return } const id = this.newId() + console.error("adding callback", id) this.callbacks.set(id, callback) + if (this.effects) + callbackRegistry.register(this, { + cbs: this.callbacks, + effects: this.effects, + }) return id } + child(): CallbackHolder { + const child = new CallbackHolder() + this.children.push(new WeakRef(child)) + return child + } + removeChild(child: CallbackHolder) { + this.children = this.children.filter((c) => { + const ref = c.deref() + return ref && ref !== child + }) + } + private getCallback(index: number): Function | undefined { + let callback = this.callbacks.get(index) + if (callback) this.callbacks.delete(index) + else { + for (let i = 0; i < this.children.length; i++) { + callback = this.children[i].deref()?.getCallback(index) + if (callback) return callback + } + } + return callback + } callCallback(index: number, args: any[]): Promise { - const callback = this.callbacks.get(index) - if (!callback) throw new Error(`Callback ${index} does not exist`) - this.callbacks.delete(index) + const callback = this.getCallback(index) + if (!callback) return Promise.resolve() return Promise.resolve().then(() => callback(...args)) } } diff --git a/container-runtime/src/Models/JsonPath.ts b/container-runtime/src/Models/JsonPath.ts index 95a2b3a00..d101836da 100644 --- a/container-runtime/src/Models/JsonPath.ts +++ b/container-runtime/src/Models/JsonPath.ts @@ -1,9 +1,7 @@ import { literals, some, string } from "ts-matches" type NestedPath = `/${A}/${string}/${B}` -type NestedPaths = - | NestedPath<"actions", "run" | "get"> - | NestedPath<"dependencies", "query" | "update"> +type NestedPaths = NestedPath<"actions", "run" | "getInput"> // prettier-ignore type UnNestPaths = A extends `${infer A}/${infer B}` ? [...UnNestPaths, ... UnNestPaths] : @@ -15,25 +13,16 @@ export function unNestPath(a: A): UnNestPaths { function isNestedPath(path: string): path is NestedPaths { const paths = path.split("/") if (paths.length !== 4) return false - if (paths[1] === "actions" && (paths[3] === "run" || paths[3] === "get")) - return true - if ( - paths[1] === "dependencies" && - (paths[3] === "query" || paths[3] === "update") - ) + if (paths[1] === "actions" && (paths[3] === "run" || paths[3] === "getInput")) return true return false } export const jsonPath = some( literals( - "/init", - "/uninit", - "/config/set", - "/config/get", + "/packageInit", + "/packageUninit", "/backup/create", "/backup/restore", - "/actions/metadata", - "/properties", ), string.refine(isNestedPath, "isNestedPath"), ) diff --git a/container-runtime/src/index.ts b/container-runtime/src/index.ts index 5454bee3d..ec6a998f4 100644 --- a/container-runtime/src/index.ts +++ b/container-runtime/src/index.ts @@ -1,13 +1,10 @@ import { RpcListener } from "./Adapters/RpcListener" import { SystemForEmbassy } from "./Adapters/Systems/SystemForEmbassy" -import { makeMainEffects, makeProcedureEffects } from "./Adapters/EffectCreator" import { AllGetDependencies } from "./Interfaces/AllGetDependencies" import { getSystem } from "./Adapters/Systems" const getDependencies: AllGetDependencies = { system: getSystem, - makeProcedureEffects: () => makeProcedureEffects, - makeMainEffects: () => makeMainEffects, } new RpcListener(getDependencies) diff --git a/core/Cargo.lock b/core/Cargo.lock index f31bc21b1..498e5903e 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -23,6 +23,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aes" version = "0.7.5" @@ -86,9 +92,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -107,9 +113,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -122,49 +128,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -174,9 +180,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ascii-canvas" @@ -187,6 +193,67 @@ dependencies = [ "term", ] +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "async-acme" +version = "0.5.0" +source = "git+https://github.com/dr-bonez/async-acme.git#b9ff31ad900adc9086c0d1437ce51661d30856d2" +dependencies = [ + "async-trait", + "base64 0.22.1", + "futures-util", + "generic-async-http-client", + "log", + "pem", + "rcgen", + "ring", + "rustls 0.23.17", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "thiserror", + "tokio", + "x509-parser", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -200,9 +267,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.12" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" dependencies = [ "brotli", "flate2", @@ -214,9 +281,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -225,24 +292,24 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -262,15 +329,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.8.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" +checksum = "fe7c2840b66236045acd2607d5866e274380afd87ef99d6226e961e2cb47df45" dependencies = [ "aws-lc-sys", "mirai-annotations", @@ -280,9 +347,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.20.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e89b6941c2d1a7045538884d6e760ccfffdf8e1ffc2613d8efa74305e1f3752" +checksum = "ad3a619a9de81e1d7de1f1186dcba4506ed661a0e483d84410fdef0ee87b2f96" dependencies = [ "bindgen", "cc", @@ -306,7 +373,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "itoa", "matchit", "memchr", @@ -316,26 +383,26 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 0.1.2", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] [[package]] name = "axum" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core 0.4.3", - "base64 0.21.7", + "axum-core 0.4.5", + "base64 0.22.1", "bytes", "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "itoa", "matchit", @@ -351,8 +418,8 @@ dependencies = [ "sha1", "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite 0.21.0", - "tower", + "tokio-tungstenite 0.24.0", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -377,9 +444,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -390,7 +457,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", "tracing", @@ -407,11 +474,11 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "pin-project-lite", "tokio", - "tower", + "tower 0.4.13", "tower-service", ] @@ -441,7 +508,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -515,9 +582,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.4" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ "bitflags 2.6.0", "cexpr", @@ -532,7 +599,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.87", "which", ] @@ -603,18 +670,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "constant_time_eq", ] [[package]] name = "blake3" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ec96fe9a81b5e365f9db71fe00edc4fe4ca2cc7dcb7861f0603012a7caa210" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cc", "cfg-if", "constant_time_eq", @@ -642,9 +709,9 @@ dependencies = [ [[package]] name = "brotli" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -667,17 +734,29 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cache-padded" @@ -687,12 +766,13 @@ checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] name = "cc" -version = "1.1.6" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -799,9 +879,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.11" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -809,9 +889,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.11" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -821,27 +901,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.11" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cmake" -version = "0.1.50" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" dependencies = [ "cc", ] @@ -875,9 +955,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "concurrent-queue" @@ -906,7 +986,7 @@ dependencies = [ "encode_unicode 0.3.6", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.12", "windows-sys 0.52.0", ] @@ -976,9 +1056,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "convert_case" @@ -1008,12 +1088,13 @@ dependencies = [ [[package]] name = "cookie_store" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" dependencies = [ "cookie", - "idna 0.5.0", + "document-features", + "idna 1.0.3", "log", "publicsuffix", "serde", @@ -1035,15 +1116,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -1117,16 +1198,16 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", "futures-core", - "libc", - "mio 0.8.11", + "mio", "parking_lot", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -1171,9 +1252,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -1236,7 +1317,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1260,7 +1341,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1271,7 +1352,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1302,7 +1383,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1317,6 +1398,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.3", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "der_derive" version = "0.7.3" @@ -1325,7 +1420,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1348,7 +1443,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1393,12 +1488,32 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "divrem" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82" +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1416,9 +1531,9 @@ dependencies = [ [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clone" @@ -1554,23 +1669,23 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -1579,6 +1694,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + [[package]] name = "errno" version = "0.3.9" @@ -1589,6 +1715,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "etcetera" version = "0.8.0" @@ -1636,9 +1772,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fd-lock-rs" @@ -1667,14 +1803,14 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -1685,19 +1821,19 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -1763,9 +1899,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1778,9 +1914,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1788,15 +1924,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1816,38 +1952,49 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", +] + +[[package]] +name = "futures-rustls" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d8a2499f0fecc0492eb3e47eab4e92da7875e1028ad2528f214ac3346ca04e" +dependencies = [ + "futures-io", + "rustls 0.22.4", + "rustls-pki-types", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1872,6 +2019,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "generic-async-http-client" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cec8bb4d3d32542cfcb9517f78366b52c17931e30d7ee1682c13686c19cee7" +dependencies = [ + "futures", + "futures-rustls", + "hyper 1.5.1", + "log", + "serde", + "serde_json", + "serde_qs", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.25.0", + "webpki-roots 0.26.6", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1941,7 +2107,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.6", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1950,9 +2116,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1960,7 +2126,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.6", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -2008,6 +2174,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + [[package]] name = "hashlink" version = "0.8.4" @@ -2068,6 +2240,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -2076,9 +2254,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hifijson" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18ae468bcb4dfecf0e4949ee28abbc99076b6a0077f51ddbc94dbfff8e6a870c" +checksum = "9958ab3ce3170c061a27679916bd9b969eceeb5e8b120438e6751d0987655c42" [[package]] name = "hkdf" @@ -2165,9 +2343,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2183,9 +2361,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -2207,14 +2385,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -2228,18 +2406,18 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.12", + "rustls 0.23.17", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", ] @@ -2249,7 +2427,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.30", + "hyper 0.14.31", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -2263,7 +2441,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -2273,29 +2451,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2314,6 +2491,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "id-pool" version = "0.2.2" @@ -2331,9 +2626,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2341,22 +2636,34 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "idna" -version = "0.5.0" +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", ] [[package]] @@ -2385,7 +2692,7 @@ dependencies = [ [[package]] name = "imbl-value" version = "0.1.0" -source = "git+https://github.com/Start9Labs/imbl-value.git#48dc39a762a3b4f9300d3b9f850cbd394e777ae0" +source = "git+https://github.com/Start9Labs/imbl-value.git#3ce01b17ae5e756fc829ee5e3513a1b19b2a03fc" dependencies = [ "imbl", "serde", @@ -2432,27 +2739,27 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", "serde", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", "tokio", - "unicode-width", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -2464,20 +2771,11 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "integer-encoding" -version = "4.0.0" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924df4f0e24e2e7f9cdd90babb0b96f93b20f3ecfa949ea9e6613756b8c8e1bf" +checksum = "0d762194228a2f1c11063e46e32e5acb96e66e906382b9eb5441f2e0504bbd5a" dependencies = [ "async-trait", "tokio", @@ -2485,9 +2783,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" dependencies = [ "serde", ] @@ -2504,11 +2802,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -2647,9 +2945,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2708,7 +3006,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", @@ -2722,7 +3020,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.7", + "regex-automata 0.4.9", ] [[package]] @@ -2771,9 +3069,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libloading" @@ -2787,9 +3085,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -2799,6 +3097,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.7", ] [[package]] @@ -2824,6 +3123,18 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -2897,9 +3208,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -2944,25 +3255,23 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.8.11" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "adler2", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2977,7 +3286,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" name = "models" version = "0.1.0" dependencies = [ - "axum 0.7.5", + "axum 0.7.9", "base64 0.21.7", "color-eyre", "ed25519-dalek 2.1.1", @@ -3029,9 +3338,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "new_mime_guess" -version = "4.0.1" +version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d684d1b59e0dc07b37e2203ef576987473288f530082512aff850585c61b1f" +checksum = "02a2dfb3559d53e90b709376af1c379462f7fb3085a0177deb73e6ea0d99eff4" dependencies = [ "mime", "unicase", @@ -3219,29 +3528,29 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -3259,11 +3568,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -3286,9 +3604,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -3307,7 +3625,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -3318,18 +3636,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -3406,7 +3724,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -3468,6 +3786,16 @@ dependencies = [ "hmac", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3485,9 +3813,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -3496,9 +3824,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -3506,22 +3834,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -3535,7 +3863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.6.0", ] [[package]] @@ -3555,29 +3883,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -3608,15 +3936,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -3626,9 +3954,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "precomputed-hash" @@ -3638,12 +3969,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -3657,7 +3988,7 @@ dependencies = [ "is-terminal", "lazy_static", "term", - "unicode-width", + "unicode-width 0.1.12", ] [[package]] @@ -3671,22 +4002,48 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +dependencies = [ + "bitflags 2.6.0", + "chrono", + "flate2", + "hex", + "lazy_static", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.6.0", + "chrono", + "hex", +] + [[package]] name = "proptest" version = "1.5.0" @@ -3701,7 +4058,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", @@ -3715,7 +4072,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -3738,7 +4095,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -3758,14 +4115,23 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "publicsuffix" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "idna 0.3.0", + "idna 1.0.3", "psl-types", ] +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" +dependencies = [ + "image", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3774,9 +4140,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -3892,6 +4258,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" +dependencies = [ + "pem", + "ring", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -3909,27 +4287,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", @@ -3938,14 +4307,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -3959,13 +4328,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -3976,15 +4345,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -3993,11 +4362,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.5", + "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-rustls", "hyper-tls", "hyper-util", @@ -4009,7 +4378,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.2", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", @@ -4025,7 +4394,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -4079,11 +4448,11 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/no-dyn-ctx#f608480034942f1f521ab95949ab33fbc51d99a9" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor%2Fno-dyn-ctx#21e35d85fb8f5de0e046c7ab5266236c2b639a4b" dependencies = [ "async-stream", "async-trait", - "axum 0.7.5", + "axum 0.7.9", "clap", "futures", "http 1.1.0", @@ -4160,21 +4529,30 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", - "errno", + "errno 0.3.9", "libc", "linux-raw-sys", "windows-sys 0.52.0", @@ -4193,15 +4571,30 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -4217,19 +4610,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -4243,9 +4635,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "aws-lc-rs", "ring", @@ -4255,9 +4647,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rusty-fork" @@ -4273,9 +4665,9 @@ dependencies = [ [[package]] name = "rustyline-async" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6eb06391513b2184f0a5405c11a4a0a5302e8be442f4c5c35267187c2b37d5" +checksum = "bc9396d834c31f9fddd716e7c279e7cb70207092a1e59767918610f5c560c6eb" dependencies = [ "crossterm", "futures-channel", @@ -4284,7 +4676,7 @@ dependencies = [ "thingbuf", "thiserror", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.12", ] [[package]] @@ -4304,11 +4696,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4356,9 +4748,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -4375,9 +4767,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -4401,23 +4793,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "itoa", + "memchr", "ryu", "serde", ] @@ -4432,11 +4825,22 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -4455,15 +4859,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -4473,14 +4877,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -4489,7 +4893,7 @@ version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "itoa", "libyml", "log", @@ -4578,12 +4982,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 0.8.11", + "mio", "signal-hook", ] @@ -4681,9 +5085,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom 7.1.3", "unicode_categories", @@ -4724,7 +5128,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.2.6", + "indexmap 2.6.0", "log", "memchr", "once_cell", @@ -4742,7 +5146,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -4913,8 +5317,8 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.72", - "unicode-width", + "syn 2.0.87", + "unicode-width 0.1.12", ] [[package]] @@ -4940,9 +5344,9 @@ dependencies = [ [[package]] name = "ssh-key" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" dependencies = [ "ed25519-dalek 2.1.1", "p256", @@ -4959,15 +5363,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "start-os" -version = "0.3.6-alpha.4" +version = "0.3.6-alpha.8" dependencies = [ "aes", + "async-acme", "async-compression", "async-stream", "async-trait", - "axum 0.7.5", + "axum 0.7.9", "axum-server", "backhand", "barrage", @@ -5006,7 +5417,7 @@ dependencies = [ "imbl", "imbl-value", "include_dir", - "indexmap 2.2.6", + "indexmap 2.6.0", "indicatif", "integer-encoding", "ipnet", @@ -5039,8 +5450,10 @@ dependencies = [ "pin-project", "pkcs8", "prettytable-rs", + "procfs", "proptest", "proptest-derive", + "qrcode", "rand 0.8.5", "regex", "reqwest", @@ -5057,6 +5470,7 @@ dependencies = [ "serde_yml", "sha2 0.10.8", "shell-words", + "signal-hook", "simple-logging", "socket2", "sqlx", @@ -5066,13 +5480,13 @@ dependencies = [ "textwrap", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-socks", "tokio-stream", "tokio-tar", "tokio-tungstenite 0.23.1", "tokio-util", - "toml 0.8.16", + "toml 0.8.19", "torut", "tower-service", "tracing", @@ -5082,7 +5496,9 @@ dependencies = [ "tracing-subscriber", "trust-dns-server", "ts-rs", + "tty-spawn", "typed-builder", + "unix-named-pipe", "url", "urlencoding", "uuid", @@ -5150,9 +5566,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -5170,23 +5586,37 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -5200,9 +5630,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.41" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -5211,14 +5641,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5249,7 +5680,7 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.1.12", ] [[package]] @@ -5264,22 +5695,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -5343,6 +5774,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -5360,14 +5801,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.1" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.1", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -5395,7 +5836,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -5408,22 +5849,33 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.17", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-socks" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", @@ -5433,9 +5885,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -5459,35 +5911,35 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", "log", + "native-tls", "tokio", - "tungstenite 0.21.0", + "tokio-native-tls", + "tungstenite 0.23.0", ] [[package]] name = "tokio-tungstenite" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", - "native-tls", "tokio", - "tokio-native-tls", - "tungstenite 0.23.0", + "tungstenite 0.24.0", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -5510,21 +5962,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.17", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -5535,7 +5987,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -5544,26 +5996,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" -dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.16", + "winnow 0.6.20", ] [[package]] @@ -5580,14 +6021,14 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-timeout", "percent-encoding", "pin-project", "prost", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5596,7 +6037,7 @@ dependencies = [ [[package]] name = "torut" version = "0.2.1" -source = "git+https://github.com/Start9Labs/torut.git?branch=update/dependencies#cc7a1425a01214465e106975e6690794d8551bdb" +source = "git+https://github.com/Start9Labs/torut.git?branch=update%2Fdependencies#cc7a1425a01214465e106975e6690794d8551bdb" dependencies = [ "base32 0.4.0", "base64 0.21.7", @@ -5632,17 +6073,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -5664,7 +6121,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -5802,7 +6259,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" version = "8.1.0" -source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature/top-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" +source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature%2Ftop-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" dependencies = [ "thiserror", "ts-rs-macros", @@ -5811,20 +6268,31 @@ dependencies = [ [[package]] name = "ts-rs-macros" version = "8.1.0" -source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature/top-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" +source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature%2Ftop-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", "termcolor", ] +[[package]] +name = "tty-spawn" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb91489cf2611235ae8d755d66ab028437980ee573e2230c05af41b136236ad1" +dependencies = [ + "anyhow", + "nix 0.29.0", + "signal-hook", +] + [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" dependencies = [ "byteorder", "bytes", @@ -5832,6 +6300,7 @@ dependencies = [ "http 1.1.0", "httparse", "log", + "native-tls", "rand 0.8.5", "sha1", "thiserror", @@ -5841,9 +6310,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", "bytes", @@ -5851,11 +6320,9 @@ dependencies = [ "http 1.1.0", "httparse", "log", - "native-tls", "rand 0.8.5", "sha1", "thiserror", - "url", "utf-8", ] @@ -5876,7 +6343,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", ] [[package]] @@ -5887,9 +6354,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unarray" @@ -5899,24 +6366,21 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -5926,24 +6390,24 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" @@ -5951,11 +6415,17 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -5963,6 +6433,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unix-named-pipe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad653da8f36ac5825ba06642b5a3cce14a4e52c6a5fab4a8928d53f4426dae2" +dependencies = [ + "errno 0.2.8", + "libc", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -5971,12 +6451,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.3", "percent-encoding", "serde", ] @@ -5993,6 +6473,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -6001,9 +6493,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom 0.2.15", ] @@ -6074,34 +6566,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -6111,9 +6604,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6121,28 +6614,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -6153,9 +6646,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -6167,6 +6670,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -6181,11 +6693,11 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall 0.5.7", "wasite", ] @@ -6207,11 +6719,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6229,6 +6741,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -6247,6 +6789,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6379,22 +6930,24 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.16" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.52.0" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wyz" @@ -6411,6 +6964,23 @@ dependencies = [ "tap", ] +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom 7.1.3", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xattr" version = "0.2.3" @@ -6464,12 +7034,46 @@ dependencies = [ "serde", ] +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -6481,7 +7085,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", ] [[package]] @@ -6501,7 +7126,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.87", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -6515,18 +7162,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.0" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/core/build-cli.sh b/core/build-cli.sh new file mode 100755 index 000000000..8e069a690 --- /dev/null +++ b/core/build-cli.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +cd "$(dirname "${BASH_SOURCE[0]}")" + +set -ea +shopt -s expand_aliases + +if [ -z "$ARCH" ]; then + ARCH=$(uname -m) +fi +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + +if [ -z "$KERNEL_NAME" ]; then + KERNEL_NAME=$(uname -s) +fi + +if [ -z "$TARGET" ]; then + if [ "$KERNEL_NAME" = "Linux" ]; then + TARGET="$ARCH-unknown-linux-musl" + elif [ "$KERNEL_NAME" = "Darwin" ]; then + TARGET="$ARCH-apple-darwin" + else + >&2 echo "unknown kernel $KERNEL_NAME" + exit 1 + fi +fi + +USE_TTY= +if tty -s; then + USE_TTY="-it" +fi + +cd .. +FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')" +RUSTFLAGS="" + +if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then + RUSTFLAGS="--cfg tokio_unstable" +fi + +alias 'rust-zig-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/cargo-zigbuild' + +echo "FEATURES=\"$FEATURES\"" +echo "RUSTFLAGS=\"$RUSTFLAGS\"" +rust-zig-builder sh -c "cd core && cargo zigbuild --release --no-default-features --features cli,daemon,$FEATURES --locked --bin start-cli --target=$TARGET" +if [ "$(ls -nd core/target/$TARGET/release/start-cli | awk '{ print $3 }')" != "$UID" ]; then + rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" +fi \ No newline at end of file diff --git a/core/build-containerbox.sh b/core/build-containerbox.sh index e4a8f6e7a..e81efcc97 100755 --- a/core/build-containerbox.sh +++ b/core/build-containerbox.sh @@ -2,13 +2,17 @@ cd "$(dirname "${BASH_SOURCE[0]}")" -set -e +set -ea shopt -s expand_aliases if [ -z "$ARCH" ]; then ARCH=$(uname -m) fi +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + USE_TTY= if tty -s; then USE_TTY="-it" @@ -24,16 +28,9 @@ fi alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' -set +e -fail= echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -if ! rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"; then - fail=true -fi -set -e -cd core - -if [ -n "$fail" ]; then - exit 1 -fi +rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl" +if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then + rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" +fi \ No newline at end of file diff --git a/core/build-registrybox.sh b/core/build-registrybox.sh index 9db57dd80..3659b372a 100755 --- a/core/build-registrybox.sh +++ b/core/build-registrybox.sh @@ -2,13 +2,17 @@ cd "$(dirname "${BASH_SOURCE[0]}")" -set -e +set -ea shopt -s expand_aliases if [ -z "$ARCH" ]; then ARCH=$(uname -m) fi +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + USE_TTY= if tty -s; then USE_TTY="-it" @@ -24,16 +28,9 @@ fi alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' -set +e -fail= echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -if ! rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"; then - fail=true -fi -set -e -cd core - -if [ -n "$fail" ]; then - exit 1 +rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl" +if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then + rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" fi diff --git a/core/build-startbox.sh b/core/build-startbox.sh index 55a455f09..9fad6fa3d 100755 --- a/core/build-startbox.sh +++ b/core/build-startbox.sh @@ -2,13 +2,17 @@ cd "$(dirname "${BASH_SOURCE[0]}")" -set -e +set -ea shopt -s expand_aliases if [ -z "$ARCH" ]; then ARCH=$(uname -m) fi +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + USE_TTY= if tty -s; then USE_TTY="-it" @@ -24,16 +28,9 @@ fi alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' -set +e -fail= echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -if ! rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"; then - fail=true -fi -set -e -cd core - -if [ -n "$fail" ]; then - exit 1 -fi +rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl" +if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/startbox | awk '{ print $3 }')" != "$UID" ]; then + rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" +fi \ No newline at end of file diff --git a/core/build-ts.sh b/core/build-ts.sh new file mode 100755 index 000000000..c9890bfe7 --- /dev/null +++ b/core/build-ts.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +cd "$(dirname "${BASH_SOURCE[0]}")" + +set -ea +shopt -s expand_aliases + +if [ -z "$ARCH" ]; then + ARCH=$(uname -m) +fi + +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + +USE_TTY= +if tty -s; then + USE_TTY="-it" +fi + +cd .. +FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')" +RUSTFLAGS="" + +if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then + RUSTFLAGS="--cfg tokio_unstable" +fi + +alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' + +echo "FEATURES=\"$FEATURES\"" +echo "RUSTFLAGS=\"$RUSTFLAGS\"" +rust-musl-builder sh -c "cd core && cargo test --release --features=test,$FEATURES 'export_bindings_' && chown \$UID:\$UID startos/bindings" +if [ "$(ls -nd core/startos/bindings | awk '{ print $3 }')" != "$UID" ]; then + rust-musl-builder sh -c "cd core && chown -R $UID:$UID startos/bindings && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" +fi \ No newline at end of file diff --git a/core/helpers/src/lib.rs b/core/helpers/src/lib.rs index 80631fea2..a9df58ece 100644 --- a/core/helpers/src/lib.rs +++ b/core/helpers/src/lib.rs @@ -6,6 +6,7 @@ use std::time::Duration; use color_eyre::eyre::{eyre, Context, Error}; use futures::future::BoxFuture; use futures::FutureExt; +use models::ResultExt; use tokio::fs::File; use tokio::sync::oneshot; use tokio::task::{JoinError, JoinHandle, LocalSet}; @@ -176,7 +177,7 @@ impl Drop for AtomicFile { if let Some(file) = self.file.take() { drop(file); let path = std::mem::take(&mut self.tmp_path); - tokio::spawn(async move { tokio::fs::remove_file(path).await.unwrap() }); + tokio::spawn(async move { tokio::fs::remove_file(path).await.log_err() }); } } } diff --git a/core/install-cli.sh b/core/install-cli.sh index 620600d92..b278947a3 100755 --- a/core/install-cli.sh +++ b/core/install-cli.sh @@ -2,14 +2,18 @@ cd "$(dirname "${BASH_SOURCE[0]}")" -set -e +set -ea shopt -s expand_aliases web="../web/dist/static" [ -d "$web" ] || mkdir -p "$web" if [ -z "$PLATFORM" ]; then - export PLATFORM=$(uname -m) + PLATFORM=$(uname -m) +fi + +if [ "$PLATFORM" = "arm64" ]; then + PLATFORM="aarch64" fi cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked diff --git a/core/startos/src/util/clap.rs b/core/models/src/clap.rs similarity index 91% rename from core/startos/src/util/clap.rs rename to core/models/src/clap.rs index 7c3b5a0bc..122e8cf9d 100644 --- a/core/startos/src/util/clap.rs +++ b/core/models/src/clap.rs @@ -1,9 +1,8 @@ use std::marker::PhantomData; use std::str::FromStr; -use clap::builder::TypedValueParser; - -use crate::prelude::*; +use rpc_toolkit::clap; +use rpc_toolkit::clap::builder::TypedValueParser; pub struct FromStrParser(PhantomData); impl FromStrParser { diff --git a/core/models/src/errors.rs b/core/models/src/errors.rs index ee6b0ae12..2077a8bbd 100644 --- a/core/models/src/errors.rs +++ b/core/models/src/errors.rs @@ -322,6 +322,11 @@ impl From for Error { Error::new(e, kind) } } +impl From for Error { + fn from(e: torut::onion::OnionAddressParseError) -> Self { + Error::new(e, ErrorKind::Tor) + } +} impl From for Error { fn from(value: patch_db::value::Error) -> Self { match value.kind { @@ -351,6 +356,14 @@ impl Debug for ErrorData { } } impl std::error::Error for ErrorData {} +impl From for ErrorData { + fn from(value: Error) -> Self { + Self { + details: value.to_string(), + debug: format!("{:?}", value), + } + } +} impl From<&RpcError> for ErrorData { fn from(value: &RpcError) -> Self { Self { diff --git a/core/models/src/id/mod.rs b/core/models/src/id/mod.rs index 85c9d8255..90dbc978c 100644 --- a/core/models/src/id/mod.rs +++ b/core/models/src/id/mod.rs @@ -11,6 +11,7 @@ mod host; mod image; mod invalid_id; mod package; +mod replay; mod service_interface; mod volume; @@ -20,6 +21,7 @@ pub use host::HostId; pub use image::ImageId; pub use invalid_id::InvalidId; pub use package::{PackageId, SYSTEM_PACKAGE_ID}; +pub use replay::ReplayId; pub use service_interface::ServiceInterfaceId; pub use volume::VolumeId; diff --git a/core/models/src/id/replay.rs b/core/models/src/id/replay.rs new file mode 100644 index 000000000..299b6160a --- /dev/null +++ b/core/models/src/id/replay.rs @@ -0,0 +1,45 @@ +use std::convert::Infallible; +use std::path::Path; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use yasi::InternedString; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)] +#[ts(type = "string")] +pub struct ReplayId(InternedString); +impl FromStr for ReplayId { + type Err = Infallible; + fn from_str(s: &str) -> Result { + Ok(ReplayId(InternedString::intern(s))) + } +} +impl AsRef for ReplayId { + fn as_ref(&self) -> &ReplayId { + self + } +} +impl std::fmt::Display for ReplayId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} +impl AsRef for ReplayId { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl AsRef for ReplayId { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} +impl<'de> Deserialize<'de> for ReplayId { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + Ok(ReplayId(serde::Deserialize::deserialize(deserializer)?)) + } +} diff --git a/core/models/src/id/service_interface.rs b/core/models/src/id/service_interface.rs index 25aec0aba..f08d89cd5 100644 --- a/core/models/src/id/service_interface.rs +++ b/core/models/src/id/service_interface.rs @@ -1,9 +1,11 @@ use std::path::Path; +use std::str::FromStr; +use rpc_toolkit::clap::builder::ValueParserFactory; use serde::{Deserialize, Deserializer, Serialize}; use ts_rs::TS; -use crate::Id; +use crate::{FromStrParser, Id}; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)] #[ts(export, type = "string")] @@ -59,3 +61,15 @@ impl sqlx::Type for ServiceInterfaceId { <&str as sqlx::Type>::compatible(ty) } } +impl FromStr for ServiceInterfaceId { + type Err = ::Err; + fn from_str(s: &str) -> Result { + Id::from_str(s).map(Self) + } +} +impl ValueParserFactory for ServiceInterfaceId { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + FromStrParser::new() + } +} diff --git a/core/models/src/lib.rs b/core/models/src/lib.rs index ad9055f24..304ac87c5 100644 --- a/core/models/src/lib.rs +++ b/core/models/src/lib.rs @@ -1,3 +1,4 @@ +mod clap; mod data_url; mod errors; mod id; @@ -5,6 +6,7 @@ mod mime; mod procedure_name; mod version; +pub use clap::*; pub use data_url::*; pub use errors::*; pub use id::*; diff --git a/core/models/src/procedure_name.rs b/core/models/src/procedure_name.rs index 466835818..4a4a682e6 100644 --- a/core/models/src/procedure_name.rs +++ b/core/models/src/procedure_name.rs @@ -1,38 +1,30 @@ use serde::{Deserialize, Serialize}; -use crate::{ActionId, PackageId}; +use crate::ActionId; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ProcedureName { GetConfig, SetConfig, CreateBackup, - Properties, RestoreBackup, - ActionMetadata, + GetActionInput(ActionId), RunAction(ActionId), - GetAction(ActionId), - QueryDependency(PackageId), - UpdateDependency(PackageId), - Init, - Uninit, + PackageInit, + PackageUninit, } impl ProcedureName { pub fn js_function_name(&self) -> String { match self { - ProcedureName::Init => "/init".to_string(), - ProcedureName::Uninit => "/uninit".to_string(), + ProcedureName::PackageInit => "/packageInit".to_string(), + ProcedureName::PackageUninit => "/packageUninit".to_string(), ProcedureName::SetConfig => "/config/set".to_string(), ProcedureName::GetConfig => "/config/get".to_string(), ProcedureName::CreateBackup => "/backup/create".to_string(), - ProcedureName::Properties => "/properties".to_string(), ProcedureName::RestoreBackup => "/backup/restore".to_string(), - ProcedureName::ActionMetadata => "/actions/metadata".to_string(), ProcedureName::RunAction(id) => format!("/actions/{}/run", id), - ProcedureName::GetAction(id) => format!("/actions/{}/get", id), - ProcedureName::QueryDependency(id) => format!("/dependencies/{}/query", id), - ProcedureName::UpdateDependency(id) => format!("/dependencies/{}/update", id), + ProcedureName::GetActionInput(id) => format!("/actions/{}/getInput", id), } } } diff --git a/core/run-tests.sh b/core/run-tests.sh new file mode 100755 index 000000000..02ec34d55 --- /dev/null +++ b/core/run-tests.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +cd "$(dirname "${BASH_SOURCE[0]}")" + +set -ea +shopt -s expand_aliases + +if [ -z "$ARCH" ]; then + ARCH=$(uname -m) +fi + +if [ "$ARCH" = "arm64" ]; then + ARCH="aarch64" +fi + +USE_TTY= +if tty -s; then + USE_TTY="-it" +fi + +cd .. +FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')" +RUSTFLAGS="" + +if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then + RUSTFLAGS="--cfg tokio_unstable" +fi + +alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' + +echo "FEATURES=\"$FEATURES\"" +echo "RUSTFLAGS=\"$RUSTFLAGS\"" +rust-musl-builder sh -c "apt-get update && apt-get install -y rsync && cd core && cargo test --release --features=test,$FEATURES --workspace --locked --target=$ARCH-unknown-linux-musl -- --skip export_bindings_ && chown \$UID:\$UID target" +if [ "$(ls -nd core/target | awk '{ print $3 }')" != "$UID" ]; then + rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo" +fi \ No newline at end of file diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 3bab5c6f3..e2a20a0a1 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -14,7 +14,7 @@ keywords = [ name = "start-os" readme = "README.md" repository = "https://github.com/Start9Labs/start-os" -version = "0.3.6-alpha.4" +version = "0.3.6-alpha.8" license = "MIT" [lib] @@ -39,10 +39,10 @@ path = "src/main.rs" [features] cli = [] -container-runtime = [] +container-runtime = ["procfs", "tty-spawn"] daemon = [] registry = [] -default = ["cli", "daemon"] +default = ["cli", "daemon", "registry", "container-runtime"] dev = [] unstable = ["console-subscriber", "tokio/tracing"] docker = [] @@ -50,6 +50,10 @@ test = [] [dependencies] aes = { version = "0.7.5", features = ["ctr"] } +async-acme = { version = "0.5.0", git = "https://github.com/dr-bonez/async-acme.git", features = [ + "use_rustls", + "use_tokio", +] } async-compression = { version = "0.4.4", features = [ "gzip", "brotli", @@ -130,7 +134,14 @@ log = "0.4.20" mbrman = "0.5.2" models = { version = "*", path = "../models" } new_mime_guess = "4" -nix = { version = "0.29.0", features = ["user", "process", "signal", "fs"] } +nix = { version = "0.29.0", features = [ + "fs", + "mount", + "process", + "sched", + "signal", + "user", +] } nom = "7.1.3" num = "0.4.1" num_enum = "0.7.0" @@ -146,8 +157,10 @@ pbkdf2 = "0.12.2" pin-project = "1.1.3" pkcs8 = { version = "0.10.2", features = ["std"] } prettytable-rs = "0.10.0" +procfs = { version = "0.16.0", optional = true } proptest = "1.3.1" proptest-derive = "0.5.0" +qrcode = "0.14.1" rand = { version = "0.8.5", features = ["std"] } regex = "1.10.2" reqwest = { version = "0.12.4", features = ["stream", "json", "socks"] } @@ -166,6 +179,7 @@ serde_with = { version = "3.4.0", features = ["macros", "json"] } serde_yaml = { package = "serde_yml", version = "0.0.10" } sha2 = "0.10.2" shell-words = "1" +signal-hook = "0.3.17" simple-logging = "2.0.2" socket2 = "0.5.7" sqlx = { version = "0.7.2", features = [ @@ -188,7 +202,7 @@ tokio-util = { version = "0.7.9", features = ["io"] } torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies", features = [ "serialize", ] } -tower-service = "0.3.2" +tower-service = "0.3.3" tracing = "0.1.39" tracing-error = "0.2.0" tracing-futures = "0.2.5" @@ -196,7 +210,9 @@ tracing-journald = "0.3.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } trust-dns-server = "0.23.1" ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0" +tty-spawn = { version = "0.4.0", optional = true } typed-builder = "0.18.0" +unix-named-pipe = "0.2.0" url = { version = "2.4.1", features = ["serde"] } urlencoding = "2.1.3" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/core/startos/registry.service b/core/startos/registry.service new file mode 100644 index 000000000..63941a25e --- /dev/null +++ b/core/startos/registry.service @@ -0,0 +1,13 @@ +[Unit] +Description=StartOS Registry + +[Service] +Type=simple +Environment=RUST_LOG=startos=debug,patch_db=warn +ExecStart=/usr/local/bin/registry +Restart=always +RestartSec=3 +ManagedOOMPreference=avoid + +[Install] +WantedBy=multi-user.target diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index 7c4492adc..d0748265b 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -1,87 +1,302 @@ -use clap::Parser; +use std::collections::BTreeMap; +use std::fmt; + +use clap::{CommandFactory, FromArgMatches, Parser}; pub use models::ActionId; use models::PackageId; +use qrcode::QrCode; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; -use crate::config::Config; -use crate::context::RpcContext; +use crate::context::{CliContext, RpcContext}; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::util::serde::{display_serializable, StdinDeserializable, WithIoFormat}; +use crate::util::serde::{ + display_serializable, HandlerExtSerde, StdinDeserializable, WithIoFormat, +}; + +pub fn action_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "get-input", + from_fn_async(get_action_input) + .with_display_serializable() + .with_about("Get action input spec") + .with_call_remote::(), + ) + .subcommand( + "run", + from_fn_async(run_action) + .with_display_serializable() + .with_custom_display_fn(|_, res| { + if let Some(res) = res { + println!("{res}") + } + Ok(()) + }) + .with_about("Run service action") + .with_call_remote::(), + ) +} -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ActionInput { + #[ts(type = "Record")] + pub spec: Value, + #[ts(type = "Record | null")] + pub value: Option, +} + +#[derive(Deserialize, Serialize, TS, Parser)] +#[serde(rename_all = "camelCase")] +pub struct GetActionInputParams { + pub package_id: PackageId, + pub action_id: ActionId, +} + +#[instrument(skip_all)] +pub async fn get_action_input( + ctx: RpcContext, + GetActionInputParams { + package_id, + action_id, + }: GetActionInputParams, +) -> Result, Error> { + ctx.services + .get(&package_id) + .await + .as_ref() + .or_not_found(lazy_format!("Manager for {}", package_id))? + .get_action_input(Guid::new(), action_id) + .await +} + +#[derive(Debug, Serialize, Deserialize, TS)] #[serde(tag = "version")] +#[ts(export)] pub enum ActionResult { #[serde(rename = "0")] V0(ActionResultV0), + #[serde(rename = "1")] + V1(ActionResultV1), +} +impl fmt::Display for ActionResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::V0(res) => res.fmt(f), + Self::V1(res) => res.fmt(f), + } + } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, TS)] pub struct ActionResultV0 { pub message: String, pub value: Option, pub copyable: bool, pub qr: bool, } +impl fmt::Display for ActionResultV0 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message)?; + if let Some(value) = &self.value { + write!(f, ":\n{value}")?; + if self.qr { + use qrcode::render::unicode; + write!( + f, + "\n{}", + QrCode::new(value.as_bytes()) + .unwrap() + .render::() + .build() + )?; + } + } + Ok(()) + } +} -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] -pub enum DockerStatus { - Running, - Stopped, +pub struct ActionResultV1 { + pub title: String, + pub message: Option, + pub result: Option, } -pub fn display_action_result(params: WithIoFormat, result: ActionResult) { - if let Some(format) = params.format { - return display_serializable(format, result); +#[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +pub struct ActionResultMember { + pub name: String, + pub description: Option, + #[serde(flatten)] + #[ts(flatten)] + pub value: ActionResultValue, +} + +#[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[serde(rename_all_fields = "camelCase")] +#[serde(tag = "type")] +pub enum ActionResultValue { + Single { + value: String, + copyable: bool, + qr: bool, + masked: bool, + }, + Group { + value: Vec, + }, +} +impl ActionResultValue { + fn fmt_rec(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + match self { + Self::Single { value, qr, .. } => { + for _ in 0..indent { + write!(f, " ")?; + } + write!(f, "{value}")?; + if *qr { + use qrcode::render::unicode; + writeln!(f)?; + for _ in 0..indent { + write!(f, " ")?; + } + write!( + f, + "{}", + QrCode::new(value.as_bytes()) + .unwrap() + .render::() + .build() + )?; + } + } + Self::Group { value } => { + for ActionResultMember { + name, + description, + value, + } in value + { + for _ in 0..indent { + write!(f, " ")?; + } + write!(f, "{name}")?; + if let Some(description) = description { + write!(f, ": {description}")?; + } + writeln!(f, ":")?; + value.fmt_rec(f, indent + 1)?; + } + } + } + Ok(()) } - match result { - ActionResult::V0(ar) => { - println!( - "{}: {}", - ar.message, - serde_json::to_string(&ar.value).unwrap() - ); +} +impl fmt::Display for ActionResultV1 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{}:", self.title)?; + if let Some(message) = &self.message { + writeln!(f, "{message}")?; + } + if let Some(result) = &self.result { + result.fmt_rec(f, 1)?; } + Ok(()) } } -#[derive(Deserialize, Serialize, Parser, TS)] +pub fn display_action_result(params: WithIoFormat, result: Option) { + let Some(result) = result else { + return; + }; + if let Some(format) = params.format { + return display_serializable(format, result); + } + println!("{result}") +} + +#[derive(Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct ActionParams { - #[arg(id = "id")] - #[serde(rename = "id")] +pub struct RunActionParams { + pub package_id: PackageId, + pub action_id: ActionId, + #[ts(optional, type = "any")] + pub input: Option, +} + +#[derive(Parser)] +struct CliRunActionParams { pub package_id: PackageId, pub action_id: ActionId, #[command(flatten)] - #[ts(type = "{ [key: string]: any } | null")] - #[serde(default)] - pub input: StdinDeserializable>, + pub input: StdinDeserializable>, +} +impl From for RunActionParams { + fn from( + CliRunActionParams { + package_id, + action_id, + input, + }: CliRunActionParams, + ) -> Self { + Self { + package_id, + action_id, + input: input.0, + } + } +} +impl CommandFactory for RunActionParams { + fn command() -> clap::Command { + CliRunActionParams::command() + } + fn command_for_update() -> clap::Command { + CliRunActionParams::command_for_update() + } +} +impl FromArgMatches for RunActionParams { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + CliRunActionParams::from_arg_matches(matches).map(Self::from) + } + fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result { + CliRunActionParams::from_arg_matches_mut(matches).map(Self::from) + } + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + *self = CliRunActionParams::from_arg_matches(matches).map(Self::from)?; + Ok(()) + } + fn update_from_arg_matches_mut( + &mut self, + matches: &mut clap::ArgMatches, + ) -> Result<(), clap::Error> { + *self = CliRunActionParams::from_arg_matches_mut(matches).map(Self::from)?; + Ok(()) + } } -// impl C // #[command(about = "Executes an action", display(display_action_result))] #[instrument(skip_all)] -pub async fn action( +pub async fn run_action( ctx: RpcContext, - ActionParams { + RunActionParams { package_id, action_id, - input: StdinDeserializable(input), - }: ActionParams, -) -> Result { + input, + }: RunActionParams, +) -> Result, Error> { ctx.services .get(&package_id) .await .as_ref() .or_not_found(lazy_format!("Manager for {}", package_id))? - .action( - Guid::new(), - action_id, - input.map(|c| to_value(&c)).transpose()?.unwrap_or_default(), - ) + .run_action(Guid::new(), action_id, input.unwrap_or_default()) .await } diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index d998e9897..a6b624b70 100644 --- a/core/startos/src/auth.rs +++ b/core/startos/src/auth.rs @@ -91,28 +91,40 @@ pub fn auth() -> ParentHandler { .with_metadata("login", Value::Bool(true)) .no_cli(), ) - .subcommand("login", from_fn_async(cli_login).no_display()) + .subcommand( + "login", + from_fn_async(cli_login) + .no_display() + .with_about("Log in to StartOS server"), + ) .subcommand( "logout", from_fn_async(logout) .with_metadata("get_session", Value::Bool(true)) .no_display() + .with_about("Log out of StartOS server") .with_call_remote::(), ) - .subcommand("session", session::()) + .subcommand( + "session", + session::().with_about("List or kill StartOS sessions"), + ) .subcommand( "reset-password", from_fn_async(reset_password_impl).no_cli(), ) .subcommand( "reset-password", - from_fn_async(cli_reset_password).no_display(), + from_fn_async(cli_reset_password) + .no_display() + .with_about("Reset StartOS password"), ) .subcommand( "get-pubkey", from_fn_async(get_pubkey) .with_metadata("authenticated", Value::Bool(false)) .no_display() + .with_about("Get public key derived from server private key") .with_call_remote::(), ) } @@ -275,8 +287,8 @@ pub struct Session { #[serde(rename_all = "camelCase")] #[ts(export)] pub struct SessionList { - #[ts(type = "string")] - current: InternedString, + #[ts(type = "string | null")] + current: Option, sessions: Sessions, } @@ -290,12 +302,14 @@ pub fn session() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_sessions(handle.params, result)) }) + .with_about("Display all server sessions") .with_call_remote::(), ) .subcommand( "kill", from_fn_async(kill) .no_display() + .with_about("Terminate existing server session(s)") .with_call_remote::(), ) } @@ -323,7 +337,7 @@ fn display_sessions(params: WithIoFormat, arg: SessionList) { session.user_agent.as_deref().unwrap_or("N/A"), &format!("{}", session.metadata), ]; - if id == arg.current { + if Some(id) == arg.current { row.iter_mut() .map(|c| c.style(Attr::ForegroundColor(color::GREEN))) .collect::<()>() @@ -340,7 +354,7 @@ pub struct ListParams { #[arg(skip)] #[ts(skip)] #[serde(rename = "__auth_session")] // from Auth middleware - session: InternedString, + session: Option, } // #[command(display(display_sessions))] diff --git a/core/startos/src/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index b4419e88e..136595b67 100644 --- a/core/startos/src/backup/backup_bulk.rs +++ b/core/startos/src/backup/backup_bulk.rs @@ -141,7 +141,7 @@ impl Drop for BackupStatusGuard { .ser(&None) }) .await - .unwrap() + .log_err() }); } } @@ -332,10 +332,10 @@ async fn perform_backup( let timestamp = Utc::now(); - backup_guard.unencrypted_metadata.version = crate::version::Current::new().semver().into(); + backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into(); backup_guard.unencrypted_metadata.hostname = ctx.account.read().await.hostname.clone(); backup_guard.unencrypted_metadata.timestamp = timestamp.clone(); - backup_guard.metadata.version = crate::version::Current::new().semver().into(); + backup_guard.metadata.version = crate::version::Current::default().semver().into(); backup_guard.metadata.timestamp = Some(timestamp); backup_guard.metadata.package_backups = package_backups; diff --git a/core/startos/src/backup/mod.rs b/core/startos/src/backup/mod.rs index 8afafaa33..110a918b6 100644 --- a/core/startos/src/backup/mod.rs +++ b/core/startos/src/backup/mod.rs @@ -40,9 +40,13 @@ pub fn backup() -> ParentHandler { "create", from_fn_async(backup_bulk::backup_all) .no_display() + .with_about("Create backup for all packages") .with_call_remote::(), ) - .subcommand("target", target::target::()) + .subcommand( + "target", + target::target::().with_about("Commands related to a backup target"), + ) } pub fn package_backup() -> ParentHandler { @@ -50,6 +54,7 @@ pub fn package_backup() -> ParentHandler { "restore", from_fn_async(restore::restore_packages_rpc) .no_display() + .with_about("Restore package(s) from backup") .with_call_remote::(), ) } diff --git a/core/startos/src/backup/target/cifs.rs b/core/startos/src/backup/target/cifs.rs index e83f4e981..71cbe267e 100644 --- a/core/startos/src/backup/target/cifs.rs +++ b/core/startos/src/backup/target/cifs.rs @@ -52,18 +52,21 @@ pub fn cifs() -> ParentHandler { "add", from_fn_async(add) .no_display() + .with_about("Add a new backup target") .with_call_remote::(), ) .subcommand( "update", from_fn_async(update) .no_display() + .with_about("Update an existing backup target") .with_call_remote::(), ) .subcommand( "remove", from_fn_async(remove) .no_display() + .with_about("Remove an existing backup target") .with_call_remote::(), ) } diff --git a/core/startos/src/backup/target/mod.rs b/core/startos/src/backup/target/mod.rs index 032f70848..eb3fc29bc 100644 --- a/core/startos/src/backup/target/mod.rs +++ b/core/startos/src/backup/target/mod.rs @@ -9,7 +9,7 @@ use digest::generic_array::GenericArray; use digest::OutputSizeUser; use exver::Version; use imbl_value::InternedString; -use models::PackageId; +use models::{FromStrParser, PackageId}; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use sha2::Sha256; @@ -27,7 +27,6 @@ use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite}; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; use crate::disk::util::PartitionInfo; use crate::prelude::*; -use crate::util::clap::FromStrParser; use crate::util::serde::{ deserialize_from_str, display_serializable, serialize_display, HandlerExtSerde, WithIoFormat, }; @@ -142,11 +141,15 @@ impl FileSystem for BackupTargetFS { // #[command(subcommands(cifs::cifs, list, info, mount, umount))] pub fn target() -> ParentHandler { ParentHandler::new() - .subcommand("cifs", cifs::cifs::()) + .subcommand( + "cifs", + cifs::cifs::().with_about("Add, remove, or update a backup target"), + ) .subcommand( "list", from_fn_async(list) .with_display_serializable() + .with_about("List existing backup targets") .with_call_remote::(), ) .subcommand( @@ -156,16 +159,20 @@ pub fn target() -> ParentHandler { .with_custom_display_fn::(|params, info| { Ok(display_backup_info(params.params, info)) }) + .with_about("Display package backup information") .with_call_remote::(), ) .subcommand( "mount", - from_fn_async(mount).with_call_remote::(), + from_fn_async(mount) + .with_about("Mount backup target") + .with_call_remote::(), ) .subcommand( "umount", from_fn_async(umount) .no_display() + .with_about("Unmount backup target") .with_call_remote::(), ) } diff --git a/core/startos/src/bins/container_cli.rs b/core/startos/src/bins/container_cli.rs index db7cbd36a..b0da1cb00 100644 --- a/core/startos/src/bins/container_cli.rs +++ b/core/startos/src/bins/container_cli.rs @@ -8,7 +8,7 @@ use crate::util::logger::EmbassyLogger; use crate::version::{Current, VersionT}; lazy_static::lazy_static! { - static ref VERSION_STRING: String = Current::new().semver().to_string(); + static ref VERSION_STRING: String = Current::default().semver().to_string(); } pub fn main(args: impl IntoIterator) { diff --git a/core/startos/src/bins/mod.rs b/core/startos/src/bins/mod.rs index 4a4670a5b..6ffecfce9 100644 --- a/core/startos/src/bins/mod.rs +++ b/core/startos/src/bins/mod.rs @@ -28,6 +28,16 @@ fn select_executable(name: &str) -> Option)> { "embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")), "embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")), "embassy-init" => Some(|_| deprecated::removed("embassy-init")), + "contents" => Some(|_| { + #[cfg(feature = "cli")] + println!("start-cli"); + #[cfg(feature = "container-runtime")] + println!("start-cli (container)"); + #[cfg(feature = "daemon")] + println!("startd"); + #[cfg(feature = "registry")] + println!("registry"); + }), _ => None, } } diff --git a/core/startos/src/bins/start_cli.rs b/core/startos/src/bins/start_cli.rs index 17cc095a3..2e92e0cc0 100644 --- a/core/startos/src/bins/start_cli.rs +++ b/core/startos/src/bins/start_cli.rs @@ -9,7 +9,7 @@ use crate::util::logger::EmbassyLogger; use crate::version::{Current, VersionT}; lazy_static::lazy_static! { - static ref VERSION_STRING: String = Current::new().semver().to_string(); + static ref VERSION_STRING: String = Current::default().semver().to_string(); } pub fn main(args: impl IntoIterator) { diff --git a/core/startos/src/config/action.rs b/core/startos/src/config/action.rs deleted file mode 100644 index ef26571a7..000000000 --- a/core/startos/src/config/action.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use models::PackageId; -use serde::{Deserialize, Serialize}; - -use super::{Config, ConfigSpec}; -#[allow(unused_imports)] -use crate::prelude::*; -use crate::status::health_check::HealthCheckId; - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ConfigRes { - pub config: Option, - pub spec: ConfigSpec, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SetResult { - pub depends_on: BTreeMap>, -} diff --git a/core/startos/src/config/mod.rs b/core/startos/src/config/mod.rs deleted file mode 100644 index 22edd98f7..000000000 --- a/core/startos/src/config/mod.rs +++ /dev/null @@ -1,281 +0,0 @@ -use std::collections::BTreeSet; -use std::sync::Arc; -use std::time::Duration; - -use clap::Parser; -use color_eyre::eyre::eyre; -use indexmap::{IndexMap, IndexSet}; -use itertools::Itertools; -use models::{ErrorKind, OptionExt, PackageId}; -use patch_db::value::InternedString; -use patch_db::Value; -use regex::Regex; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; -use serde::{Deserialize, Serialize}; -use tracing::instrument; -use ts_rs::TS; - -use crate::context::{CliContext, RpcContext}; -use crate::prelude::*; -use crate::rpc_continuations::Guid; -use crate::util::serde::{HandlerExtSerde, StdinDeserializable}; - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct ConfigSpec(pub IndexMap); - -pub mod action; -pub mod util; - -use util::NumRange; - -use self::action::ConfigRes; - -pub type Config = patch_db::value::InOMap; -pub trait TypeOf { - fn type_of(&self) -> &'static str; -} -impl TypeOf for Value { - fn type_of(&self) -> &'static str { - match self { - Value::Array(_) => "list", - Value::Bool(_) => "boolean", - Value::Null => "null", - Value::Number(_) => "number", - Value::Object(_) => "object", - Value::String(_) => "string", - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum ConfigurationError { - #[error("Timeout Error")] - TimeoutError(#[from] TimeoutError), - #[error("No Match: {0}")] - NoMatch(#[from] NoMatchWithPath), - #[error("System Error: {0}")] - SystemError(Error), -} -impl From for Error { - fn from(err: ConfigurationError) -> Self { - let kind = match &err { - ConfigurationError::SystemError(e) => e.kind, - _ => crate::ErrorKind::ConfigGen, - }; - crate::Error::new(err, kind) - } -} - -#[derive(Clone, Copy, Debug, thiserror::Error)] -#[error("Timeout Error")] -pub struct TimeoutError; - -#[derive(Clone, Debug, thiserror::Error)] -pub struct NoMatchWithPath { - pub path: Vec, - pub error: MatchError, -} -impl NoMatchWithPath { - pub fn new(error: MatchError) -> Self { - NoMatchWithPath { - path: Vec::new(), - error, - } - } - pub fn prepend(mut self, seg: InternedString) -> Self { - self.path.push(seg); - self - } -} -impl std::fmt::Display for NoMatchWithPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}: {}", self.path.iter().rev().join("."), self.error) - } -} -impl From for Error { - fn from(e: NoMatchWithPath) -> Self { - ConfigurationError::from(e).into() - } -} - -#[derive(Clone, Debug, thiserror::Error)] -pub enum MatchError { - #[error("String {0:?} Does Not Match Pattern {1}")] - Pattern(Arc, Regex), - #[error("String {0:?} Is Not In Enum {1:?}")] - Enum(Arc, IndexSet), - #[error("Field Is Not Nullable")] - NotNullable, - #[error("Length Mismatch: expected {0}, actual: {1}")] - LengthMismatch(NumRange, usize), - #[error("Invalid Type: expected {0}, actual: {1}")] - InvalidType(&'static str, &'static str), - #[error("Number Out Of Range: expected {0}, actual: {1}")] - OutOfRange(NumRange, f64), - #[error("Number Is Not Integral: {0}")] - NonIntegral(f64), - #[error("Variant {0:?} Is Not In Union {1:?}")] - Union(Arc, IndexSet), - #[error("Variant Is Missing Tag {0:?}")] - MissingTag(InternedString), - #[error("Property {0:?} Of Variant {1:?} Conflicts With Union Tag")] - PropertyMatchesUnionTag(InternedString, String), - #[error("Name of Property {0:?} Conflicts With Map Tag Name")] - PropertyNameMatchesMapTag(String), - #[error("Object Key Is Invalid: {0}")] - InvalidKey(String), - #[error("Value In List Is Not Unique")] - ListUniquenessViolation, -} - -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct ConfigParams { - pub id: PackageId, -} - -// #[command(subcommands(get, set))] -pub fn config() -> ParentHandler { - ParentHandler::new() - .subcommand( - "get", - from_fn_async(get) - .with_inherited(|ConfigParams { id }, _| id) - .with_display_serializable() - .with_call_remote::(), - ) - .subcommand( - "set", - set::().with_inherited(|ConfigParams { id }, _| id), - ) -} - -#[instrument(skip_all)] -pub async fn get(ctx: RpcContext, _: Empty, id: PackageId) -> Result { - ctx.services - .get(&id) - .await - .as_ref() - .or_not_found(lazy_format!("Manager for {id}"))? - .get_config(Guid::new()) - .await -} - -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -pub struct SetParams { - #[arg(long = "timeout")] - pub timeout: Option, - #[command(flatten)] - #[ts(type = "{ [key: string]: any } | null")] - pub config: StdinDeserializable>, -} - -// #[command( -// subcommands(self(set_impl(async, context(RpcContext))), set_dry), -// display(display_none), -// metadata(sync_db = true) -// )] -#[instrument(skip_all)] -pub fn set() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(set_impl) - .with_metadata("sync_db", Value::Bool(true)) - .with_inherited(|set_params, id| (id, set_params)) - .no_display() - .with_call_remote::(), - ) - .subcommand( - "dry", - from_fn_async(set_dry) - .with_inherited(|set_params, id| (id, set_params)) - .no_display() - .with_call_remote::(), - ) -} - -pub async fn set_dry( - ctx: RpcContext, - _: Empty, - ( - id, - SetParams { - timeout, - config: StdinDeserializable(config), - }, - ): (PackageId, SetParams), -) -> Result, Error> { - let mut breakages = BTreeSet::new(); - - let procedure_id = Guid::new(); - - let db = ctx.db.peek().await; - for dep in db - .as_public() - .as_package_data() - .as_entries()? - .into_iter() - .filter_map( - |(k, v)| match v.as_current_dependencies().contains_key(&id) { - Ok(true) => Some(Ok(k)), - Ok(false) => None, - Err(e) => Some(Err(e)), - }, - ) - { - let dep_id = dep?; - - let Some(dependent) = &*ctx.services.get(&dep_id).await else { - continue; - }; - - if dependent - .dependency_config(procedure_id.clone(), id.clone(), config.clone()) - .await? - .is_some() - { - breakages.insert(dep_id); - } - } - - Ok(breakages) -} - -#[derive(Default)] -pub struct ConfigureContext { - pub timeout: Option, - pub config: Option, -} - -#[instrument(skip_all)] -pub async fn set_impl( - ctx: RpcContext, - _: Empty, - ( - id, - SetParams { - timeout, - config: StdinDeserializable(config), - }, - ): (PackageId, SetParams), -) -> Result<(), Error> { - let configure_context = ConfigureContext { - timeout: timeout.map(|t| *t), - config, - }; - ctx.services - .get(&id) - .await - .as_ref() - .ok_or_else(|| { - Error::new( - eyre!("There is no manager running for {id}"), - ErrorKind::Unknown, - ) - })? - .configure(Guid::new(), configure_context) - .await?; - Ok(()) -} diff --git a/core/startos/src/config/util.rs b/core/startos/src/config/util.rs deleted file mode 100644 index 359c24476..000000000 --- a/core/startos/src/config/util.rs +++ /dev/null @@ -1,406 +0,0 @@ -use std::borrow::Cow; -use std::ops::{Bound, RangeBounds, RangeInclusive}; - -use patch_db::Value; -use rand::distributions::Distribution; -use rand::Rng; - -use super::Config; - -pub const STATIC_NULL: Value = Value::Null; - -#[derive(Clone, Debug)] -pub struct CharSet(pub Vec<(RangeInclusive, usize)>, usize); -impl CharSet { - pub fn contains(&self, c: &char) -> bool { - self.0.iter().any(|r| r.0.contains(c)) - } - pub fn gen(&self, rng: &mut R) -> char { - let mut idx = rng.gen_range(0..self.1); - for r in &self.0 { - if idx < r.1 { - return std::convert::TryFrom::try_from( - rand::distributions::Uniform::new_inclusive( - u32::from(*r.0.start()), - u32::from(*r.0.end()), - ) - .sample(rng), - ) - .unwrap(); - } else { - idx -= r.1; - } - } - unreachable!() - } -} -impl Default for CharSet { - fn default() -> Self { - CharSet(vec![('!'..='~', 94)], 94) - } -} -impl<'de> serde::de::Deserialize<'de> for CharSet { - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let mut res = Vec::new(); - let mut len = 0; - let mut a: Option = None; - let mut b: Option = None; - let mut in_range = false; - for c in s.chars() { - match c { - ',' => match (a, b, in_range) { - (Some(start), Some(end), _) => { - if !end.is_ascii() { - return Err(serde::de::Error::custom("Invalid Character")); - } - if start >= end { - return Err(serde::de::Error::custom("Invalid Bounds")); - } - let l = u32::from(end) - u32::from(start) + 1; - res.push((start..=end, l as usize)); - len += l as usize; - a = None; - b = None; - in_range = false; - } - (Some(start), None, false) => { - len += 1; - res.push((start..=start, 1)); - a = None; - } - (Some(_), None, true) => { - b = Some(','); - } - (None, None, false) => { - a = Some(','); - } - _ => { - return Err(serde::de::Error::custom("Syntax Error")); - } - }, - '-' => { - if a.is_none() { - a = Some('-'); - } else if !in_range { - in_range = true; - } else if b.is_none() { - b = Some('-') - } else { - return Err(serde::de::Error::custom("Syntax Error")); - } - } - _ => { - if a.is_none() { - a = Some(c); - } else if in_range && b.is_none() { - b = Some(c); - } else { - return Err(serde::de::Error::custom("Syntax Error")); - } - } - } - } - match (a, b) { - (Some(start), Some(end)) => { - if !end.is_ascii() { - return Err(serde::de::Error::custom("Invalid Character")); - } - if start >= end { - return Err(serde::de::Error::custom("Invalid Bounds")); - } - let l = u32::from(end) - u32::from(start) + 1; - res.push((start..=end, l as usize)); - len += l as usize; - } - (Some(c), None) => { - len += 1; - res.push((c..=c, 1)); - } - _ => (), - } - - Ok(CharSet(res, len)) - } -} -impl serde::ser::Serialize for CharSet { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - <&str>::serialize( - &self - .0 - .iter() - .map(|r| match r.1 { - 1 => format!("{}", r.0.start()), - _ => format!("{}-{}", r.0.start(), r.0.end()), - }) - .collect::>() - .join(",") - .as_str(), - serializer, - ) - } -} - -pub trait MergeWith { - fn merge_with(&mut self, other: &serde_json::Value); -} - -impl MergeWith for serde_json::Value { - fn merge_with(&mut self, other: &serde_json::Value) { - use serde_json::Value::Object; - if let (Object(orig), Object(ref other)) = (self, other) { - for (key, val) in other.into_iter() { - match (orig.get_mut(key), val) { - (Some(new_orig @ Object(_)), other @ Object(_)) => { - new_orig.merge_with(other); - } - (None, _) => { - orig.insert(key.clone(), val.clone()); - } - _ => (), - } - } - } - } -} - -#[test] -fn merge_with_tests() { - use serde_json::json; - - let mut a = json!( - {"a": 1, "c": {"d": "123"}, "i": [1,2,3], "j": {}, "k":[1,2,3], "l": "test"} - ); - a.merge_with( - &json!({"a":"a", "b": "b", "c":{"d":"d", "e":"e"}, "f":{"g":"g"}, "h": [1,2,3], "i":"i", "j":[1,2,3], "k":{}}), - ); - assert_eq!( - a, - json!({"a": 1, "c": {"d": "123", "e":"e"}, "b":"b", "f": {"g":"g"}, "h":[1,2,3], "i":[1,2,3], "j": {}, "k":[1,2,3], "l": "test"}) - ) -} -pub mod serde_regex { - use regex::Regex; - use serde::*; - - pub fn serialize(regex: &Regex, serializer: S) -> Result - where - S: Serializer, - { - <&str>::serialize(®ex.as_str(), serializer) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Regex::new(&s).map_err(|e| de::Error::custom(e)) - } -} - -#[derive(Clone, Debug)] -pub struct NumRange( - pub (Bound, Bound), -); -impl std::ops::Deref for NumRange -where - T: std::str::FromStr + std::fmt::Display + std::cmp::PartialOrd, -{ - type Target = (Bound, Bound); - - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl<'de, T> serde::de::Deserialize<'de> for NumRange -where - T: std::str::FromStr + std::fmt::Display + std::cmp::PartialOrd, - ::Err: std::fmt::Display, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let mut split = s.split(","); - let start = split - .next() - .map(|s| match s.get(..1) { - Some("(") => match s.get(1..2) { - Some("*") => Ok(Bound::Unbounded), - _ => s[1..] - .trim() - .parse() - .map(Bound::Excluded) - .map_err(|e| serde::de::Error::custom(e)), - }, - Some("[") => s[1..] - .trim() - .parse() - .map(Bound::Included) - .map_err(|e| serde::de::Error::custom(e)), - _ => Err(serde::de::Error::custom(format!( - "Could not parse left bound: {}", - s - ))), - }) - .transpose()? - .unwrap(); - let end = split - .next() - .map(|s| match s.get(s.len() - 1..) { - Some(")") => match s.get(s.len() - 2..s.len() - 1) { - Some("*") => Ok(Bound::Unbounded), - _ => s[..s.len() - 1] - .trim() - .parse() - .map(Bound::Excluded) - .map_err(|e| serde::de::Error::custom(e)), - }, - Some("]") => s[..s.len() - 1] - .trim() - .parse() - .map(Bound::Included) - .map_err(|e| serde::de::Error::custom(e)), - _ => Err(serde::de::Error::custom(format!( - "Could not parse right bound: {}", - s - ))), - }) - .transpose()? - .unwrap_or(Bound::Unbounded); - - Ok(NumRange((start, end))) - } -} -impl std::fmt::Display for NumRange -where - T: std::str::FromStr + std::fmt::Display + std::cmp::PartialOrd, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.start_bound() { - Bound::Excluded(n) => write!(f, "({},", n)?, - Bound::Included(n) => write!(f, "[{},", n)?, - Bound::Unbounded => write!(f, "(*,")?, - }; - match self.end_bound() { - Bound::Excluded(n) => write!(f, "{})", n), - Bound::Included(n) => write!(f, "{}]", n), - Bound::Unbounded => write!(f, "*)"), - } - } -} -impl serde::ser::Serialize for NumRange -where - T: std::str::FromStr + std::fmt::Display + std::cmp::PartialOrd, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - <&str>::serialize(&format!("{}", self).as_str(), serializer) - } -} - -#[derive(Clone, Debug)] -pub enum UniqueBy { - Any(Vec), - All(Vec), - Exactly(String), - NotUnique, -} -impl UniqueBy { - pub fn eq(&self, lhs: &Config, rhs: &Config) -> bool { - match self { - UniqueBy::Any(any) => any.iter().any(|u| u.eq(lhs, rhs)), - UniqueBy::All(all) => all.iter().all(|u| u.eq(lhs, rhs)), - UniqueBy::Exactly(key) => lhs.get(&**key) == rhs.get(&**key), - UniqueBy::NotUnique => false, - } - } -} -impl Default for UniqueBy { - fn default() -> Self { - UniqueBy::NotUnique - } -} -impl<'de> serde::de::Deserialize<'de> for UniqueBy { - fn deserialize>(deserializer: D) -> Result { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = UniqueBy; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "a key, an \"any\" object, or an \"all\" object") - } - fn visit_str(self, v: &str) -> Result { - Ok(UniqueBy::Exactly(v.to_owned())) - } - fn visit_string(self, v: String) -> Result { - Ok(UniqueBy::Exactly(v)) - } - fn visit_map>( - self, - mut map: A, - ) -> Result { - let mut variant = None; - while let Some(key) = map.next_key::>()? { - match key.as_ref() { - "any" => { - return Ok(UniqueBy::Any(map.next_value()?)); - } - "all" => { - return Ok(UniqueBy::All(map.next_value()?)); - } - _ => { - variant = Some(key); - } - } - } - Err(serde::de::Error::unknown_variant( - variant.unwrap_or_default().as_ref(), - &["any", "all"], - )) - } - fn visit_unit(self) -> Result { - Ok(UniqueBy::NotUnique) - } - fn visit_none(self) -> Result { - Ok(UniqueBy::NotUnique) - } - } - deserializer.deserialize_any(Visitor) - } -} - -impl serde::ser::Serialize for UniqueBy { - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - - match self { - UniqueBy::Any(any) => { - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_key("any")?; - map.serialize_value(any)?; - map.end() - } - UniqueBy::All(all) => { - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_key("all")?; - map.serialize_value(all)?; - map.end() - } - UniqueBy::Exactly(key) => serializer.serialize_str(key), - UniqueBy::NotUnique => serializer.serialize_unit(), - } - } -} diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index 0db681d3b..73d103adc 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -1,4 +1,5 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; +use std::future::Future; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::ops::Deref; use std::path::PathBuf; @@ -6,8 +7,13 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; +use chrono::{TimeDelta, Utc}; +use helpers::NonDetachingJoinHandle; +use imbl::OrdMap; use imbl_value::InternedString; +use itertools::Itertools; use josekit::jwk::Jwk; +use models::{ActionId, PackageId}; use reqwest::{Client, Proxy}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{CallRemote, Context, Empty}; @@ -20,7 +26,6 @@ use crate::account::AccountInfo; use crate::auth::Sessions; use crate::context::config::ServerConfig; use crate::db::model::Database; -use crate::dependencies::compute_dependency_config_errs; use crate::disk::OsPartitionInfo; use crate::init::check_time_is_synchronized; use crate::lxc::{ContainerId, LxcContainer, LxcManager}; @@ -29,12 +34,12 @@ use crate::net::utils::{find_eth_iface, find_wifi_iface}; use crate::net::wifi::WpaCli; use crate::prelude::*; use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle}; -use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations}; +use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations}; +use crate::service::action::update_requested_actions; use crate::service::effects::callbacks::ServiceCallbacks; use crate::service::ServiceMap; use crate::shutdown::Shutdown; -use crate::system::get_mem_info; -use crate::util::lshw::{lshw, LshwDevice}; +use crate::util::lshw::LshwDevice; use crate::util::sync::SyncMutex; pub struct RpcContextSeed { @@ -55,15 +60,15 @@ pub struct RpcContextSeed { pub shutdown: broadcast::Sender>, pub tor_socks: SocketAddr, pub lxc_manager: Arc, - pub open_authed_continuations: OpenAuthedContinuations, + pub open_authed_continuations: OpenAuthedContinuations>, pub rpc_continuations: RpcContinuations, pub callbacks: ServiceCallbacks, pub wifi_manager: Option>>, pub current_secret: Arc, pub client: Client, - pub hardware: Hardware, pub start_time: Instant, - #[cfg(feature = "dev")] + pub crons: SyncMutex>>, + // #[cfg(feature = "dev")] pub dev: Dev, } @@ -79,29 +84,30 @@ pub struct Hardware { pub struct InitRpcContextPhases { load_db: PhaseProgressTrackerHandle, init_net_ctrl: PhaseProgressTrackerHandle, - read_device_info: PhaseProgressTrackerHandle, cleanup_init: CleanupInitPhases, + // TODO: migrations } impl InitRpcContextPhases { pub fn new(handle: &FullProgressTracker) -> Self { Self { load_db: handle.add_phase("Loading database".into(), Some(5)), init_net_ctrl: handle.add_phase("Initializing network".into(), Some(1)), - read_device_info: handle.add_phase("Reading device information".into(), Some(1)), cleanup_init: CleanupInitPhases::new(handle), } } } pub struct CleanupInitPhases { + cleanup_sessions: PhaseProgressTrackerHandle, init_services: PhaseProgressTrackerHandle, - check_dependencies: PhaseProgressTrackerHandle, + check_requested_actions: PhaseProgressTrackerHandle, } impl CleanupInitPhases { pub fn new(handle: &FullProgressTracker) -> Self { Self { + cleanup_sessions: handle.add_phase("Cleaning up sessions".into(), Some(1)), init_services: handle.add_phase("Initializing services".into(), Some(10)), - check_dependencies: handle.add_phase("Checking dependencies".into(), Some(1)), + check_requested_actions: handle.add_phase("Checking action requests".into(), Some(1)), } } } @@ -117,7 +123,6 @@ impl RpcContext { InitRpcContextPhases { mut load_db, mut init_net_ctrl, - mut read_device_info, cleanup_init, }: InitRpcContextPhases, ) -> Result { @@ -169,10 +174,7 @@ impl RpcContext { let metrics_cache = RwLock::>::new(None); let tor_proxy_url = format!("socks5h://{tor_proxy}"); - read_device_info.start(); - let devices = lshw().await?; - let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024; - read_device_info.complete(); + let crons = SyncMutex::new(BTreeMap::new()); if !db .peek() @@ -183,18 +185,24 @@ impl RpcContext { .de()? { let db = db.clone(); - tokio::spawn(async move { - while !check_time_is_synchronized().await.unwrap() { - tokio::time::sleep(Duration::from_secs(30)).await; - } - db.mutate(|v| { - v.as_public_mut() - .as_server_info_mut() - .as_ntp_synced_mut() - .ser(&true) - }) - .await - .unwrap() + crons.mutate(|c| { + c.insert( + Guid::new(), + tokio::spawn(async move { + while !check_time_is_synchronized().await.unwrap() { + tokio::time::sleep(Duration::from_secs(30)).await; + } + db.mutate(|v| { + v.as_public_mut() + .as_server_info_mut() + .as_ntp_synced_mut() + .ser(&true) + }) + .await + .unwrap() + }) + .into(), + ) }); } @@ -257,9 +265,9 @@ impl RpcContext { })) .build() .with_kind(crate::ErrorKind::ParseUrl)?, - hardware: Hardware { devices, ram }, start_time: Instant::now(), - #[cfg(feature = "dev")] + crons, + // #[cfg(feature = "dev")] dev: Dev { lxc: Mutex::new(BTreeMap::new()), }, @@ -268,11 +276,13 @@ impl RpcContext { let res = Self(seed.clone()); res.cleanup_and_initialize(cleanup_init).await?; tracing::info!("Cleaned up transient states"); + crate::version::post_init(&res).await?; Ok(res) } #[instrument(skip_all)] pub async fn shutdown(self) -> Result<(), Error> { + self.crons.mutate(|c| std::mem::take(c)); self.services.shutdown_all().await?; self.is_closed.store(true, Ordering::SeqCst); tracing::info!("RPC Context is shutdown"); @@ -280,44 +290,138 @@ impl RpcContext { Ok(()) } + pub fn add_cron + Send + 'static>(&self, fut: F) -> Guid { + let guid = Guid::new(); + self.crons + .mutate(|c| c.insert(guid.clone(), tokio::spawn(fut).into())); + guid + } + #[instrument(skip_all)] pub async fn cleanup_and_initialize( &self, CleanupInitPhases { + mut cleanup_sessions, init_services, - mut check_dependencies, + mut check_requested_actions, }: CleanupInitPhases, ) -> Result<(), Error> { + cleanup_sessions.start(); + self.db + .mutate(|db| { + if db.as_public().as_server_info().as_ntp_synced().de()? { + for id in db.as_private().as_sessions().keys()? { + if Utc::now() + - db.as_private() + .as_sessions() + .as_idx(&id) + .unwrap() + .de()? + .last_active + > TimeDelta::days(30) + { + db.as_private_mut().as_sessions_mut().remove(&id)?; + } + } + } + Ok(()) + }) + .await?; + let db = self.db.clone(); + self.add_cron(async move { + loop { + tokio::time::sleep(Duration::from_secs(86400)).await; + if let Err(e) = db + .mutate(|db| { + if db.as_public().as_server_info().as_ntp_synced().de()? { + for id in db.as_private().as_sessions().keys()? { + if Utc::now() + - db.as_private() + .as_sessions() + .as_idx(&id) + .unwrap() + .de()? + .last_active + > TimeDelta::days(30) + { + db.as_private_mut().as_sessions_mut().remove(&id)?; + } + } + } + Ok(()) + }) + .await + { + tracing::error!("Error in session cleanup cron: {e}"); + tracing::debug!("{e:?}"); + } + } + }); + cleanup_sessions.complete(); + self.services.init(&self, init_services).await?; - tracing::info!("Initialized Package Managers"); + tracing::info!("Initialized Services"); - check_dependencies.start(); - let mut updated_current_dependents = BTreeMap::new(); + // TODO + check_requested_actions.start(); let peek = self.db.peek().await; - for (package_id, package) in peek.as_public().as_package_data().as_entries()?.into_iter() { - let package = package.clone(); - let mut current_dependencies = package.as_current_dependencies().de()?; - compute_dependency_config_errs(self, &package_id, &mut current_dependencies) - .await - .log_err(); - updated_current_dependents.insert(package_id.clone(), current_dependencies); + let mut action_input: OrdMap> = OrdMap::new(); + let requested_actions: BTreeSet<_> = peek + .as_public() + .as_package_data() + .as_entries()? + .into_iter() + .map(|(_, pde)| { + Ok(pde + .as_requested_actions() + .as_entries()? + .into_iter() + .map(|(_, r)| { + Ok::<_, Error>(( + r.as_request().as_package_id().de()?, + r.as_request().as_action_id().de()?, + )) + })) + }) + .flatten_ok() + .map(|a| a.and_then(|a| a)) + .try_collect()?; + let procedure_id = Guid::new(); + for (package_id, action_id) in requested_actions { + if let Some(service) = self.services.get(&package_id).await.as_ref() { + if let Some(input) = service + .get_action_input(procedure_id.clone(), action_id.clone()) + .await? + .and_then(|i| i.value) + { + action_input + .entry(package_id) + .or_default() + .insert(action_id, input); + } + } } self.db - .mutate(|v| { - for (package_id, deps) in updated_current_dependents { - if let Some(model) = v - .as_public_mut() - .as_package_data_mut() - .as_idx_mut(&package_id) - .map(|i| i.as_current_dependencies_mut()) - { - model.ser(&deps)?; + .mutate(|db| { + for (package_id, action_input) in &action_input { + for (action_id, input) in action_input { + for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { + pde.as_requested_actions_mut().mutate(|requested_actions| { + Ok(update_requested_actions( + requested_actions, + package_id, + action_id, + input, + false, + )) + })?; + } } } Ok(()) }) .await?; - check_dependencies.complete(); + check_requested_actions.complete(); Ok(()) } @@ -354,8 +458,8 @@ impl AsRef for RpcContext { &self.rpc_continuations } } -impl AsRef> for RpcContext { - fn as_ref(&self) -> &OpenAuthedContinuations { +impl AsRef>> for RpcContext { + fn as_ref(&self) -> &OpenAuthedContinuations> { &self.open_authed_continuations } } diff --git a/core/startos/src/context/setup.rs b/core/startos/src/context/setup.rs index 999154977..96ec07700 100644 --- a/core/startos/src/context/setup.rs +++ b/core/startos/src/context/setup.rs @@ -21,6 +21,7 @@ use crate::account::AccountInfo; use crate::context::config::ServerConfig; use crate::context::RpcContext; use crate::disk::OsPartitionInfo; +use crate::hostname::Hostname; use crate::init::init_postgres; use crate::prelude::*; use crate::progress::FullProgressTracker; @@ -42,6 +43,8 @@ lazy_static::lazy_static! { pub struct SetupResult { pub tor_address: String, #[ts(type = "string")] + pub hostname: Hostname, + #[ts(type = "string")] pub lan_address: InternedString, pub root_ca: String, } @@ -50,6 +53,7 @@ impl TryFrom<&AccountInfo> for SetupResult { fn try_from(value: &AccountInfo) -> Result { Ok(Self { tor_address: format!("https://{}", value.tor_key.public().get_onion_address()), + hostname: value.hostname.clone(), lan_address: value.hostname.lan_address(), root_ca: String::from_utf8(value.root_ca_cert.to_pem()?)?, }) diff --git a/core/startos/src/db/mod.rs b/core/startos/src/db/mod.rs index ef35bd30d..c7e38e81c 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -31,7 +31,12 @@ lazy_static::lazy_static! { pub fn db() -> ParentHandler { ParentHandler::new() - .subcommand("dump", from_fn_async(cli_dump).with_display_serializable()) + .subcommand( + "dump", + from_fn_async(cli_dump) + .with_display_serializable() + .with_about("Filter/query db to display tables and records"), + ) .subcommand("dump", from_fn_async(dump).no_cli()) .subcommand( "subscribe", @@ -39,8 +44,16 @@ pub fn db() -> ParentHandler { .with_metadata("get_session", Value::Bool(true)) .no_cli(), ) - .subcommand("put", put::()) - .subcommand("apply", from_fn_async(cli_apply).no_display()) + .subcommand( + "put", + put::().with_about("Command for adding UI record to db"), + ) + .subcommand( + "apply", + from_fn_async(cli_apply) + .no_display() + .with_about("Update a db record"), + ) .subcommand("apply", from_fn_async(apply).no_cli()) } @@ -115,7 +128,7 @@ pub struct SubscribeParams { pointer: Option, #[ts(skip)] #[serde(rename = "__auth_session")] - session: InternedString, + session: Option, } #[derive(Deserialize, Serialize, TS)] @@ -215,6 +228,8 @@ pub async fn subscribe( #[serde(rename_all = "camelCase")] #[command(rename_all = "kebab-case")] pub struct CliApplyParams { + #[arg(long)] + allow_model_mismatch: bool, expr: String, path: Option, } @@ -225,7 +240,12 @@ async fn cli_apply( context, parent_method, method, - params: CliApplyParams { expr, path }, + params: + CliApplyParams { + allow_model_mismatch, + expr, + path, + }, .. }: HandlerArgs, ) -> Result<(), RpcError> { @@ -240,7 +260,14 @@ async fn cli_apply( &expr, )?; - Ok::<_, Error>(( + let value = if allow_model_mismatch { + serde_json::from_value::(res.clone().into()).with_ctx(|_| { + ( + crate::ErrorKind::Deserialization, + "result does not match database model", + ) + })? + } else { to_value( &serde_json::from_value::(res.clone().into()).with_ctx( |_| { @@ -250,9 +277,9 @@ async fn cli_apply( ) }, )?, - )?, - (), - )) + )? + }; + Ok::<_, Error>((value, ())) }) .await?; } else { @@ -299,6 +326,7 @@ pub fn put() -> ParentHandler { "ui", from_fn_async(ui) .with_display_serializable() + .with_about("Add path and value to db") .with_call_remote::(), ) } diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 957e42c54..df5773cc3 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -4,7 +4,8 @@ use chrono::{DateTime, Utc}; use exver::VersionRange; use imbl_value::InternedString; use models::{ - ActionId, DataUrl, HealthCheckId, HostId, PackageId, ServiceInterfaceId, VersionString, + ActionId, DataUrl, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId, + VersionString, }; use patch_db::json_ptr::JsonPointer; use patch_db::HasModel; @@ -17,8 +18,8 @@ use crate::net::service_interface::ServiceInterface; use crate::prelude::*; use crate::progress::FullProgress; use crate::s9pk::manifest::Manifest; -use crate::status::Status; -use crate::util::serde::Pem; +use crate::status::MainStatus; +use crate::util::serde::{is_partial_of, Pem}; #[derive(Debug, Default, Deserialize, Serialize, TS)] #[ts(export)] @@ -310,9 +311,9 @@ pub struct InstallingInfo { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[ts(export)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] pub enum AllowedStatuses { - OnlyRunning, // onlyRunning + OnlyRunning, OnlyStopped, Any, } @@ -324,13 +325,28 @@ pub struct ActionMetadata { pub name: String, pub description: String, pub warning: Option, - #[ts(type = "any")] - pub input: Value, - pub disabled: bool, + #[serde(default)] + pub visibility: ActionVisibility, pub allowed_statuses: AllowedStatuses, + pub has_input: bool, pub group: Option, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "kebab-case")] +#[serde(rename_all_fields = "camelCase")] +pub enum ActionVisibility { + Hidden, + Disabled(String), + Enabled, +} +impl Default for ActionVisibility { + fn default() -> Self { + Self::Enabled + } +} + #[derive(Debug, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] #[model = "Model"] @@ -338,7 +354,7 @@ pub struct ActionMetadata { pub struct PackageDataEntry { pub state_info: PackageState, pub data_version: Option, - pub status: Status, + pub status: MainStatus, #[ts(type = "string | null")] pub registry: Option, #[ts(type = "string")] @@ -348,6 +364,8 @@ pub struct PackageDataEntry { pub last_backup: Option>, pub current_dependencies: CurrentDependencies, pub actions: BTreeMap, + #[ts(as = "BTreeMap::")] + pub requested_actions: BTreeMap, pub service_interfaces: BTreeMap, pub hosts: Hosts, #[ts(type = "string[]")] @@ -384,8 +402,9 @@ impl Map for CurrentDependencies { } } -#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Deserialize, Serialize, TS, HasModel)] #[serde(rename_all = "camelCase")] +#[model = "Model"] pub struct CurrentDependencyInfo { #[ts(type = "string | null")] pub title: Option, @@ -394,11 +413,10 @@ pub struct CurrentDependencyInfo { pub kind: CurrentDependencyKind, #[ts(type = "string")] pub version_range: VersionRange, - pub config_satisfied: bool, } #[derive(Clone, Debug, Deserialize, Serialize, TS)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] #[serde(tag = "kind")] pub enum CurrentDependencyKind { Exists, @@ -410,6 +428,81 @@ pub enum CurrentDependencyKind { }, } +#[derive(Clone, Debug, Deserialize, Serialize, TS, HasModel)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[model = "Model"] +pub struct ActionRequestEntry { + pub request: ActionRequest, + pub active: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS, HasModel)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[model = "Model"] +pub struct ActionRequest { + pub package_id: PackageId, + pub action_id: ActionId, + #[serde(default)] + pub severity: ActionSeverity, + #[ts(optional)] + pub reason: Option, + #[ts(optional)] + pub when: Option, + #[ts(optional)] + pub input: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "kebab-case")] +#[ts(export)] +pub enum ActionSeverity { + Critical, + Important, +} +impl Default for ActionSeverity { + fn default() -> Self { + ActionSeverity::Important + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct ActionRequestTrigger { + #[serde(default)] + pub once: bool, + pub condition: ActionRequestCondition, +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "kebab-case")] +#[ts(export)] +pub enum ActionRequestCondition { + InputNotMatches, +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "kebab-case")] +#[serde(tag = "kind")] +pub enum ActionRequestInput { + Partial { + #[ts(type = "Record")] + value: Value, + }, +} +impl ActionRequestInput { + pub fn matches(&self, input: Option<&Value>) -> bool { + match self { + Self::Partial { value } => match input { + None => false, + Some(full) => is_partial_of(value, full), + }, + } + } +} + #[derive(Debug, Default, Deserialize, Serialize)] pub struct InterfaceAddressMap(pub BTreeMap); impl Map for InterfaceAddressMap { diff --git a/core/startos/src/db/model/private.rs b/core/startos/src/db/model/private.rs index c57364fc3..2675b36d0 100644 --- a/core/startos/src/db/model/private.rs +++ b/core/startos/src/db/model/private.rs @@ -31,6 +31,6 @@ pub struct Private { pub package_stores: BTreeMap, } -fn generate_compat_key() -> Pem { +pub fn generate_compat_key() -> Pem { Pem(ed25519_dalek::SigningKey::generate(&mut rand::thread_rng())) } diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index b20693a90..85978134d 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -22,6 +22,7 @@ use crate::prelude::*; use crate::progress::FullProgress; use crate::system::SmtpValue; use crate::util::cpupower::Governor; +use crate::util::lshw::LshwDevice; use crate::version::{Current, VersionT}; use crate::{ARCH, PLATFORM}; @@ -43,16 +44,18 @@ impl Public { arch: get_arch(), platform: get_platform(), id: account.server_id.clone(), - version: Current::new().semver(), + version: Current::default().semver(), hostname: account.hostname.no_dot_host_name(), last_backup: None, - eos_version_compat: Current::new().compat().clone(), + package_version_compat: Current::default().compat().clone(), + post_init_migration_todos: BTreeSet::new(), lan_address, onion_address: account.tor_key.public().get_onion_address(), tor_address: format!("https://{}", account.tor_key.public().get_onion_address()) .parse() .unwrap(), ip_info: BTreeMap::new(), + acme: None, status_info: ServerStatus { backup_progress: None, updated: false, @@ -77,6 +80,8 @@ impl Public { zram: true, governor: None, smtp: None, + ram: 0, + devices: Vec::new(), }, package_data: AllPackageData::default(), ui: serde_json::from_str(include_str!(concat!( @@ -112,11 +117,13 @@ pub struct ServerInfo { pub hostname: InternedString, #[ts(type = "string")] pub version: Version, + #[ts(type = "string")] + pub package_version_compat: VersionRange, + #[ts(type = "string[]")] + pub post_init_migration_todos: BTreeSet, #[ts(type = "string | null")] pub last_backup: Option>, #[ts(type = "string")] - pub eos_version_compat: VersionRange, - #[ts(type = "string")] pub lan_address: Url, #[ts(type = "string")] pub onion_address: OnionAddressV3, @@ -124,6 +131,7 @@ pub struct ServerInfo { #[ts(type = "string")] pub tor_address: Url, pub ip_info: BTreeMap, + pub acme: Option, #[serde(default)] pub status_info: ServerStatus, pub wifi: WifiInfo, @@ -138,6 +146,9 @@ pub struct ServerInfo { pub zram: bool, pub governor: Option, pub smtp: Option, + #[ts(type = "number")] + pub ram: u64, + pub devices: Vec, } #[derive(Debug, Deserialize, Serialize, HasModel, TS)] @@ -165,6 +176,20 @@ impl IpInfo { } } +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct AcmeSettings { + #[ts(type = "string")] + pub provider: Url, + /// email addresses for letsencrypt + pub contact: Vec, + #[ts(type = "string[]")] + /// domains to get letsencrypt certs for + pub domains: BTreeSet, +} + #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[model = "Model"] #[ts(export)] diff --git a/core/startos/src/dependencies.rs b/core/startos/src/dependencies.rs index 013648980..3b55c8fc3 100644 --- a/core/startos/src/dependencies.rs +++ b/core/startos/src/dependencies.rs @@ -1,28 +1,14 @@ use std::collections::BTreeMap; -use std::time::Duration; -use clap::Parser; use imbl_value::InternedString; use models::PackageId; -use patch_db::json_patch::merge; -use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; -use tracing::instrument; use ts_rs::TS; -use crate::config::{Config, ConfigSpec, ConfigureContext}; -use crate::context::{CliContext, RpcContext}; -use crate::db::model::package::CurrentDependencies; use crate::prelude::*; -use crate::rpc_continuations::Guid; -use crate::util::serde::HandlerExtSerde; use crate::util::PathOrUrl; use crate::Error; -pub fn dependency() -> ParentHandler { - ParentHandler::new().subcommand("configure", configure::()) -} - #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)] #[model = "Model"] #[ts(export)] @@ -56,129 +42,3 @@ pub struct DependencyMetadata { #[ts(type = "string")] pub title: InternedString, } - -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct ConfigureParams { - dependent_id: PackageId, - dependency_id: PackageId, -} -pub fn configure() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(configure_impl) - .with_inherited(|params, _| params) - .no_display() - .with_call_remote::(), - ) - .subcommand( - "dry", - from_fn_async(configure_dry) - .with_inherited(|params, _| params) - .with_display_serializable() - .with_call_remote::(), - ) -} - -pub async fn configure_impl( - ctx: RpcContext, - _: Empty, - ConfigureParams { - dependent_id, - dependency_id, - }: ConfigureParams, -) -> Result<(), Error> { - let ConfigDryRes { - old_config: _, - new_config, - spec: _, - } = configure_logic(ctx.clone(), (dependent_id, dependency_id.clone())).await?; - - let configure_context = ConfigureContext { - timeout: Some(Duration::from_secs(3).into()), - config: Some(new_config), - }; - ctx.services - .get(&dependency_id) - .await - .as_ref() - .ok_or_else(|| { - Error::new( - eyre!("There is no manager running for {dependency_id}"), - ErrorKind::Unknown, - ) - })? - .configure(Guid::new(), configure_context) - .await?; - Ok(()) -} - -pub async fn configure_dry( - ctx: RpcContext, - _: Empty, - ConfigureParams { - dependent_id, - dependency_id, - }: ConfigureParams, -) -> Result { - configure_logic(ctx.clone(), (dependent_id, dependency_id.clone())).await -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ConfigDryRes { - pub old_config: Config, - pub new_config: Config, - pub spec: ConfigSpec, -} - -pub async fn configure_logic( - ctx: RpcContext, - (dependent_id, dependency_id): (PackageId, PackageId), -) -> Result { - let procedure_id = Guid::new(); - let dependency_guard = ctx.services.get(&dependency_id).await; - let dependency = dependency_guard.as_ref().or_not_found(&dependency_id)?; - let dependent_guard = ctx.services.get(&dependent_id).await; - let dependent = dependent_guard.as_ref().or_not_found(&dependent_id)?; - let config_res = dependency.get_config(procedure_id.clone()).await?; - let diff = Value::Object( - dependent - .dependency_config(procedure_id, dependency_id, config_res.config.clone()) - .await? - .unwrap_or_default(), - ); - let mut new_config = Value::Object(config_res.config.clone().unwrap_or_default()); - merge(&mut new_config, &diff); - Ok(ConfigDryRes { - old_config: config_res.config.unwrap_or_default(), - new_config: new_config.as_object().cloned().unwrap_or_default(), - spec: config_res.spec, - }) -} - -#[instrument(skip_all)] -pub async fn compute_dependency_config_errs( - ctx: &RpcContext, - id: &PackageId, - current_dependencies: &mut CurrentDependencies, -) -> Result<(), Error> { - let procedure_id = Guid::new(); - let service_guard = ctx.services.get(id).await; - let service = service_guard.as_ref().or_not_found(id)?; - for (dep_id, dep_info) in current_dependencies.0.iter_mut() { - // check if config passes dependency check - let Some(dependency) = &*ctx.services.get(dep_id).await else { - continue; - }; - - let dep_config = dependency.get_config(procedure_id.clone()).await?.config; - - dep_info.config_satisfied = service - .dependency_config(procedure_id.clone(), dep_id.clone(), dep_config) - .await? - .is_none(); - } - Ok(()) -} diff --git a/core/startos/src/diagnostic.rs b/core/startos/src/diagnostic.rs index 5e99580e9..f0c142706 100644 --- a/core/startos/src/diagnostic.rs +++ b/core/startos/src/diagnostic.rs @@ -9,35 +9,53 @@ use rpc_toolkit::{ use crate::context::{CliContext, DiagnosticContext, RpcContext}; use crate::init::SYSTEM_REBUILD_PATH; use crate::shutdown::Shutdown; +use crate::util::io::delete_file; use crate::Error; pub fn diagnostic() -> ParentHandler { ParentHandler::new() - .subcommand("error", from_fn(error).with_call_remote::()) - .subcommand("logs", crate::system::logs::()) + .subcommand( + "error", + from_fn(error) + .with_about("Display diagnostic error") + .with_call_remote::(), + ) + .subcommand( + "logs", + crate::system::logs::().with_about("Display OS logs"), + ) .subcommand( "logs", - from_fn_async(crate::logs::cli_logs::).no_display(), + from_fn_async(crate::logs::cli_logs::) + .no_display() + .with_about("Display OS logs"), ) .subcommand( "kernel-logs", - crate::system::kernel_logs::(), + crate::system::kernel_logs::().with_about("Display kernel logs"), ) .subcommand( "kernel-logs", - from_fn_async(crate::logs::cli_logs::).no_display(), + from_fn_async(crate::logs::cli_logs::) + .no_display() + .with_about("Display kernal logs"), ) .subcommand( "restart", from_fn(restart) .no_display() + .with_about("Restart the server") .with_call_remote::(), ) - .subcommand("disk", disk::()) + .subcommand( + "disk", + disk::().with_about("Command to remove disk from filesystem"), + ) .subcommand( "rebuild", from_fn_async(rebuild) .no_display() + .with_about("Teardown and rebuild service containers") .with_call_remote::(), ) } @@ -72,14 +90,13 @@ pub fn disk() -> ParentHandler { CallRemoteHandler::::new( from_fn_async(forget_disk::).no_display(), ) - .no_display(), + .no_display() + .with_about("Remove disk from filesystem"), ) } pub async fn forget_disk(_: C) -> Result<(), Error> { - let disk_guid = Path::new("/media/startos/config/disk.guid"); - if tokio::fs::metadata(disk_guid).await.is_ok() { - tokio::fs::remove_file(disk_guid).await?; - } + delete_file("/media/startos/config/overlay/etc/hostname").await?; + delete_file("/media/startos/config/disk.guid").await?; Ok(()) } diff --git a/core/startos/src/disk/mod.rs b/core/startos/src/disk/mod.rs index c0a701fc9..d1fbe282f 100644 --- a/core/startos/src/disk/mod.rs +++ b/core/startos/src/disk/mod.rs @@ -51,13 +51,16 @@ pub fn disk() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_disk_info(handle.params, result)) }) + .with_about("List disk info") .with_call_remote::(), ) .subcommand("repair", from_fn_async(|_: C| repair()).no_cli()) .subcommand( "repair", CallRemoteHandler::::new( - from_fn_async(|_: RpcContext| repair()).no_display(), + from_fn_async(|_: RpcContext| repair()) + .no_display() + .with_about("Repair disk in the event of corruption"), ), ) } diff --git a/core/startos/src/disk/mount/backup.rs b/core/startos/src/disk/mount/backup.rs index 8f45b0d4f..2c322c284 100644 --- a/core/startos/src/disk/mount/backup.rs +++ b/core/startos/src/disk/mount/backup.rs @@ -219,10 +219,10 @@ impl Drop for BackupMountGuard { let second = self.backup_disk_mount_guard.take(); tokio::spawn(async move { if let Some(guard) = first { - guard.unmount().await.unwrap(); + guard.unmount().await.log_err(); } if let Some(guard) = second { - guard.unmount().await.unwrap(); + guard.unmount().await.log_err(); } }); } diff --git a/core/startos/src/disk/mount/filesystem/overlayfs.rs b/core/startos/src/disk/mount/filesystem/overlayfs.rs index 5e40a21a1..e8d1f0b34 100644 --- a/core/startos/src/disk/mount/filesystem/overlayfs.rs +++ b/core/startos/src/disk/mount/filesystem/overlayfs.rs @@ -151,12 +151,12 @@ impl Drop for OverlayGuard { let guard = self.inner_guard.take(); if lower.is_some() || upper.is_some() || guard.mounted { tokio::spawn(async move { - guard.unmount(false).await.unwrap(); + guard.unmount(false).await.log_err(); if let Some(lower) = lower { - lower.unmount().await.unwrap(); + lower.unmount().await.log_err(); } if let Some(upper) = upper { - upper.delete().await.unwrap(); + upper.delete().await.log_err(); } }); } diff --git a/core/startos/src/disk/mount/guard.rs b/core/startos/src/disk/mount/guard.rs index a2d577226..6e1cdc35e 100644 --- a/core/startos/src/disk/mount/guard.rs +++ b/core/startos/src/disk/mount/guard.rs @@ -96,7 +96,7 @@ impl Drop for MountGuard { fn drop(&mut self) { if self.mounted { let mountpoint = std::mem::take(&mut self.mountpoint); - tokio::spawn(async move { unmount(mountpoint, true).await.unwrap() }); + tokio::spawn(async move { unmount(mountpoint, true).await.log_err() }); } } } diff --git a/core/startos/src/disk/mount/util.rs b/core/startos/src/disk/mount/util.rs index 674f33304..61368e67a 100644 --- a/core/startos/src/disk/mount/util.rs +++ b/core/startos/src/disk/mount/util.rs @@ -5,6 +5,16 @@ use tracing::instrument; use crate::util::Invoke; use crate::Error; +pub async fn is_mountpoint(path: impl AsRef) -> Result { + let is_mountpoint = tokio::process::Command::new("mountpoint") + .arg(path.as_ref()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .await?; + Ok(is_mountpoint.success()) +} + #[instrument(skip_all)] pub async fn bind, P1: AsRef>( src: P0, @@ -16,13 +26,7 @@ pub async fn bind, P1: AsRef>( src.as_ref().display(), dst.as_ref().display() ); - let is_mountpoint = tokio::process::Command::new("mountpoint") - .arg(dst.as_ref()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() - .await?; - if is_mountpoint.success() { + if is_mountpoint(&dst).await? { unmount(dst.as_ref(), true).await?; } tokio::fs::create_dir_all(&src).await?; diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index e6b7be598..a81e7e336 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -32,7 +32,9 @@ use crate::progress::{ use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL}; use crate::ssh::SSH_AUTHORIZED_KEYS_FILE; +use crate::system::get_mem_info; use crate::util::io::{create_file, IOHook}; +use crate::util::lshw::lshw; use crate::util::net::WebSocketExt; use crate::util::{cpupower, Invoke}; use crate::Error; @@ -323,7 +325,9 @@ pub async fn init( local_auth.complete(); load_database.start(); - let db = TypedPatchDb::::load_unchecked(cfg.db().await?); + let db = cfg.db().await?; + crate::version::Current::default().pre_init(&db).await?; + let db = TypedPatchDb::::load_unchecked(db); let peek = db.peek().await; load_database.complete(); tracing::info!("Opened PatchDB"); @@ -506,6 +510,8 @@ pub async fn init( update_server_info.start(); server_info.ip_info = crate::net::dhcp::init_ips().await?; + server_info.ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024; + server_info.devices = lshw().await?; server_info.status_info = ServerStatus { updated: false, update_progress: None, @@ -528,8 +534,6 @@ pub async fn init( .await?; launch_service_network.complete(); - crate::version::init(&db, run_migrations).await?; - validate_db.start(); db.mutate(|d| { let model = d.de()?; @@ -549,18 +553,33 @@ pub async fn init( pub fn init_api() -> ParentHandler { ParentHandler::new() - .subcommand("logs", crate::system::logs::()) .subcommand( "logs", - from_fn_async(crate::logs::cli_logs::).no_display(), + crate::system::logs::().with_about("Disply OS logs"), + ) + .subcommand( + "logs", + from_fn_async(crate::logs::cli_logs::) + .no_display() + .with_about("Display OS logs"), ) - .subcommand("kernel-logs", crate::system::kernel_logs::()) .subcommand( "kernel-logs", - from_fn_async(crate::logs::cli_logs::).no_display(), + crate::system::kernel_logs::().with_about("Display kernel logs"), + ) + .subcommand( + "kernel-logs", + from_fn_async(crate::logs::cli_logs::) + .no_display() + .with_about("Display kernel logs"), ) .subcommand("subscribe", from_fn_async(init_progress).no_cli()) - .subcommand("subscribe", from_fn_async(cli_init_progress).no_display()) + .subcommand( + "subscribe", + from_fn_async(cli_init_progress) + .no_display() + .with_about("Get initialization progress"), + ) } #[derive(Debug, Deserialize, Serialize, TS)] diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index 7a545a3aa..f153c88ad 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -9,7 +9,7 @@ use exver::VersionRange; use futures::{AsyncWriteExt, StreamExt}; use imbl_value::{json, InternedString}; use itertools::Itertools; -use models::VersionString; +use models::{FromStrParser, VersionString}; use reqwest::header::{HeaderMap, CONTENT_LENGTH}; use reqwest::Url; use rpc_toolkit::yajrc::{GenericRpcMethod, RpcError}; @@ -17,6 +17,7 @@ use rpc_toolkit::HandlerArgs; use rustyline_async::ReadlineEvent; use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; +use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; use tracing::instrument; use ts_rs::TS; @@ -29,7 +30,6 @@ use crate::registry::package::get::GetPackageResponse; use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::manifest::PackageId; use crate::upload::upload; -use crate::util::clap::FromStrParser; use crate::util::io::open_file; use crate::util::net::WebSocketExt; use crate::util::Never; @@ -172,7 +172,7 @@ pub async fn install( pub struct SideloadParams { #[ts(skip)] #[serde(rename = "__auth_session")] - session: InternedString, + session: Option, } #[derive(Deserialize, Serialize, TS)] @@ -188,7 +188,7 @@ pub async fn sideload( SideloadParams { session }: SideloadParams, ) -> Result { let (upload, file) = upload(&ctx, session.clone()).await?; - let (err_send, err_recv) = oneshot::channel(); + let (err_send, err_recv) = oneshot::channel::(); let progress = Guid::new(); let progress_tracker = FullProgressTracker::new(); let mut progress_listener = progress_tracker.stream(Some(Duration::from_millis(200))); @@ -202,12 +202,14 @@ pub async fn sideload( use axum::extract::ws::Message; async move { if let Err(e) = async { - type RpcResponse = rpc_toolkit::yajrc::RpcResponse::>; + type RpcResponse = rpc_toolkit::yajrc::RpcResponse< + GenericRpcMethod<&'static str, (), FullProgress>, + >; tokio::select! { res = async { while let Some(progress) = progress_listener.next().await { ws.send(Message::Text( - serde_json::to_string(&RpcResponse::from_result::(Ok(progress))) + serde_json::to_string(&progress) .with_kind(ErrorKind::Serialization)?, )) .await @@ -217,12 +219,8 @@ pub async fn sideload( } => res?, err = err_recv => { if let Ok(e) = err { - ws.send(Message::Text( - serde_json::to_string(&RpcResponse::from_result::(Err(e))) - .with_kind(ErrorKind::Serialization)?, - )) - .await - .with_kind(ErrorKind::Network)?; + ws.close_result(Err::<&str, _>(e.clone_output())).await?; + return Err(e) } } } @@ -260,7 +258,7 @@ pub async fn sideload( } .await { - let _ = err_send.send(RpcError::from(e.clone_output())); + let _ = err_send.send(e.clone_output()); tracing::error!("Error sideloading package: {e}"); tracing::debug!("{e:?}"); } @@ -407,19 +405,21 @@ pub async fn cli_install( let mut progress = FullProgress::new(); - type RpcResponse = rpc_toolkit::yajrc::RpcResponse< - GenericRpcMethod<&'static str, (), FullProgress>, - >; - loop { tokio::select! { msg = ws.next() => { if let Some(msg) = msg { - if let Message::Text(t) = msg.with_kind(ErrorKind::Network)? { - progress = - serde_json::from_str::(&t) - .with_kind(ErrorKind::Deserialization)?.result?; - bar.update(&progress); + match msg.with_kind(ErrorKind::Network)? { + Message::Text(t) => { + progress = + serde_json::from_str::(&t) + .with_kind(ErrorKind::Deserialization)?; + bar.update(&progress); + } + Message::Close(Some(c)) if c.code != CloseCode::Normal => { + return Err(Error::new(eyre!("{}", c.reason), ErrorKind::Network)) + } + _ => (), } } else { break; diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index feeb5a647..3c5875e36 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -29,7 +29,6 @@ pub mod action; pub mod auth; pub mod backup; pub mod bins; -pub mod config; pub mod context; pub mod control; pub mod db; @@ -50,7 +49,6 @@ pub mod notifications; pub mod os_install; pub mod prelude; pub mod progress; -pub mod properties; pub mod registry; pub mod rpc_continuations; pub mod s9pk; @@ -70,7 +68,6 @@ pub mod volume; use std::time::SystemTime; use clap::Parser; -pub use config::Config; pub use error::{Error, ErrorKind, ResultExt}; use imbl_value::Value; use rpc_toolkit::yajrc::RpcError; @@ -116,29 +113,70 @@ impl std::fmt::Display for ApiState { pub fn main_api() -> ParentHandler { let api = ParentHandler::new() - .subcommand::("git-info", from_fn(version::git_info)) + .subcommand( + "git-info", + from_fn(|_: C| version::git_info()).with_about("Display the githash of StartOS CLI"), + ) .subcommand( "echo", from_fn(echo::) .with_metadata("authenticated", Value::Bool(false)) + .with_about("Echo a message") .with_call_remote::(), ) .subcommand( "state", from_fn(|_: RpcContext| Ok::<_, Error>(ApiState::Running)) .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the API that is currently serving") .with_call_remote::(), ) - .subcommand("server", server::()) - .subcommand("package", package::()) - .subcommand("net", net::net::()) - .subcommand("auth", auth::auth::()) - .subcommand("db", db::db::()) - .subcommand("ssh", ssh::ssh::()) - .subcommand("wifi", net::wifi::wifi::()) - .subcommand("disk", disk::disk::()) - .subcommand("notification", notifications::notification::()) - .subcommand("backup", backup::backup::()) + .subcommand( + "server", + server::() + .with_about("Commands related to the server i.e. restart, update, and shutdown"), + ) + .subcommand( + "package", + package::().with_about("Commands related to packages"), + ) + .subcommand( + "net", + net::net::().with_about("Network commands related to tor and dhcp"), + ) + .subcommand( + "auth", + auth::auth::().with_about( + "Commands related to Authentication i.e. login, logout, reset-password", + ), + ) + .subcommand( + "db", + db::db::().with_about("Commands to interact with the db i.e. dump, put, apply"), + ) + .subcommand( + "ssh", + ssh::ssh::() + .with_about("Commands for interacting with ssh keys i.e. add, delete, list"), + ) + .subcommand( + "wifi", + net::wifi::wifi::() + .with_about("Commands related to wifi networks i.e. add, connect, delete"), + ) + .subcommand( + "disk", + disk::disk::().with_about("Commands for listing disk info and repairing"), + ) + .subcommand( + "notification", + notifications::notification::().with_about("Create, delete, or list notifications"), + ) + .subcommand( + "backup", + backup::backup::() + .with_about("Commands related to backup creation and backup targets"), + ) .subcommand( "registry", CallRemoteHandler::::new( @@ -146,10 +184,20 @@ pub fn main_api() -> ParentHandler { ) .no_cli(), ) - .subcommand("s9pk", s9pk::rpc::s9pk()) - .subcommand("util", util::rpc::util::()); + .subcommand( + "s9pk", + s9pk::rpc::s9pk().with_about("Commands for interacting with s9pk files"), + ) + .subcommand( + "util", + util::rpc::util::().with_about("Command for calculating the blake3 hash of a file"), + ); #[cfg(feature = "dev")] - let api = api.subcommand("lxc", lxc::dev::lxc::()); + let api = api.subcommand( + "lxc", + lxc::dev::lxc::() + .with_about("Commands related to lxc containers i.e. create, list, remove, connect"), + ); api } @@ -162,42 +210,57 @@ pub fn server() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(system::display_time(handle.params, result)) }) - .with_call_remote::(), + .with_about("Display current time and server uptime") + .with_call_remote::() + ) + .subcommand( + "experimental", + system::experimental::() + .with_about("Commands related to configuring experimental options such as zram and cpu governor"), + ) + .subcommand( + "logs", + system::logs::().with_about("Display OS logs"), ) - .subcommand("experimental", system::experimental::()) - .subcommand("logs", system::logs::()) .subcommand( "logs", - from_fn_async(logs::cli_logs::).no_display(), + from_fn_async(logs::cli_logs::).no_display().with_about("Display OS logs"), ) - .subcommand("kernel-logs", system::kernel_logs::()) .subcommand( "kernel-logs", - from_fn_async(logs::cli_logs::).no_display(), + system::kernel_logs::().with_about("Display Kernel logs"), + ) + .subcommand( + "kernel-logs", + from_fn_async(logs::cli_logs::).no_display().with_about("Display Kernel logs"), ) .subcommand( "metrics", from_fn_async(system::metrics) .with_display_serializable() - .with_call_remote::(), + .with_about("Display information about the server i.e. temperature, RAM, CPU, and disk usage") + .with_call_remote::() ) .subcommand( "shutdown", from_fn_async(shutdown::shutdown) .no_display() - .with_call_remote::(), + .with_about("Shutdown the server") + .with_call_remote::() ) .subcommand( "restart", from_fn_async(shutdown::restart) .no_display() - .with_call_remote::(), + .with_about("Restart the server") + .with_call_remote::() ) .subcommand( "rebuild", from_fn_async(shutdown::rebuild) .no_display() - .with_call_remote::(), + .with_about("Teardown and rebuild service containers") + .with_call_remote::() ) .subcommand( "update", @@ -207,7 +270,7 @@ pub fn server() -> ParentHandler { ) .subcommand( "update", - from_fn_async(update::cli_update_system).no_display(), + from_fn_async(update::cli_update_system).no_display().with_about("Check a given registry for StartOS updates and update if available"), ) .subcommand( "update-firmware", @@ -222,19 +285,22 @@ pub fn server() -> ParentHandler { .with_custom_display_fn(|_handle, result| { Ok(firmware::display_firmware_update_result(result)) }) - .with_call_remote::(), + .with_about("Update the mainboard's firmware to the latest firmware available in this version of StartOS if available. Note: This command does not reach out to the Internet") + .with_call_remote::() ) .subcommand( "set-smtp", from_fn_async(system::set_system_smtp) .no_display() - .with_call_remote::(), + .with_about("Set system smtp server and credentials") + .with_call_remote::() ) .subcommand( "clear-smtp", from_fn_async(system::clear_system_smtp) .no_display() - .with_call_remote::(), + .with_about("Remove system smtp server and credentials") + .with_call_remote::() ) } @@ -242,12 +308,7 @@ pub fn package() -> ParentHandler { ParentHandler::new() .subcommand( "action", - from_fn_async(action::action) - .with_display_serializable() - .with_custom_display_fn(|handle, result| { - Ok(action::display_action_result(handle.params, result)) - }) - .with_call_remote::(), + action::action_api::().with_about("Commands to get action input or run an action"), ) .subcommand( "install", @@ -261,32 +322,40 @@ pub fn package() -> ParentHandler { .with_metadata("get_session", Value::Bool(true)) .no_cli(), ) - .subcommand("install", from_fn_async(install::cli_install).no_display()) + .subcommand( + "install", + from_fn_async(install::cli_install) + .no_display() + .with_about("Install a package from a marketplace or via sideloading"), + ) .subcommand( "uninstall", from_fn_async(install::uninstall) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Remove a package") .with_call_remote::(), ) .subcommand( "list", from_fn_async(install::list) .with_display_serializable() + .with_about("List installed packages") .with_call_remote::(), ) .subcommand( "installed-version", from_fn_async(install::installed_version) .with_display_serializable() + .with_about("Display installed version for a PackageId") .with_call_remote::(), ) - .subcommand("config", config::config::()) .subcommand( "start", from_fn_async(control::start) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Start a service") .with_call_remote::(), ) .subcommand( @@ -294,6 +363,7 @@ pub fn package() -> ParentHandler { from_fn_async(control::stop) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Stop a service") .with_call_remote::(), ) .subcommand( @@ -301,100 +371,174 @@ pub fn package() -> ParentHandler { from_fn_async(control::restart) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Restart a service") + .with_call_remote::(), + ) + .subcommand( + "rebuild", + from_fn_async(service::rebuild) + .with_metadata("sync_db", Value::Bool(true)) + .no_display() + .with_about("Rebuild service container") .with_call_remote::(), ) .subcommand("logs", logs::package_logs()) .subcommand( "logs", - from_fn_async(logs::cli_logs::).no_display(), + logs::package_logs().with_about("Display package logs"), ) .subcommand( - "properties", - from_fn_async(properties::properties) - .with_custom_display_fn(|_handle, result| { - Ok(properties::display_properties(result)) - }) - .with_call_remote::(), + "logs", + from_fn_async(logs::cli_logs::) + .no_display() + .with_about("Display package logs"), + ) + .subcommand( + "backup", + backup::package_backup::() + .with_about("Commands for restoring package(s) from backup"), ) - .subcommand("dependency", dependencies::dependency::()) - .subcommand("backup", backup::package_backup::()) .subcommand("connect", from_fn_async(service::connect_rpc).no_cli()) .subcommand( "connect", - from_fn_async(service::connect_rpc_cli).no_display(), + from_fn_async(service::connect_rpc_cli) + .no_display() + .with_about("Connect to a LXC container"), + ) + .subcommand( + "attach", + from_fn_async(service::attach) + .with_metadata("get_session", Value::Bool(true)) + .with_about("Execute commands within a service container") + .no_cli(), + ) + .subcommand("attach", from_fn_async(service::cli_attach).no_display()) + .subcommand( + "host", + net::host::host::().with_about("Manage network hosts for a package"), ) } pub fn diagnostic_api() -> ParentHandler { ParentHandler::new() - .subcommand::( + .subcommand( "git-info", - from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), + from_fn(|_: DiagnosticContext| version::git_info()) + .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the githash of StartOS CLI"), ) .subcommand( "echo", - from_fn(echo::).with_call_remote::(), + from_fn(echo::) + .with_about("Echo a message") + .with_call_remote::(), ) .subcommand( "state", from_fn(|_: DiagnosticContext| Ok::<_, Error>(ApiState::Error)) .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the API that is currently serving") .with_call_remote::(), ) - .subcommand("diagnostic", diagnostic::diagnostic::()) + .subcommand( + "diagnostic", + diagnostic::diagnostic::() + .with_about("Diagnostic commands i.e. logs, restart, rebuild"), + ) } pub fn init_api() -> ParentHandler { ParentHandler::new() - .subcommand::( + .subcommand( "git-info", - from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), + from_fn(|_: InitContext| version::git_info()) + .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the githash of StartOS CLI"), ) .subcommand( "echo", - from_fn(echo::).with_call_remote::(), + from_fn(echo::) + .with_about("Echo a message") + .with_call_remote::(), ) .subcommand( "state", from_fn(|_: InitContext| Ok::<_, Error>(ApiState::Initializing)) .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the API that is currently serving") .with_call_remote::(), ) - .subcommand("init", init::init_api::()) + .subcommand( + "init", + init::init_api::() + .with_about("Commands to get logs or initialization progress"), + ) } pub fn setup_api() -> ParentHandler { ParentHandler::new() - .subcommand::( + .subcommand( "git-info", - from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), + from_fn(|_: SetupContext| version::git_info()) + .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the githash of StartOS CLI"), ) .subcommand( "echo", - from_fn(echo::).with_call_remote::(), + from_fn(echo::) + .with_about("Echo a message") + .with_call_remote::(), ) .subcommand("setup", setup::setup::()) } pub fn install_api() -> ParentHandler { ParentHandler::new() - .subcommand::( + .subcommand( "git-info", - from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), + from_fn(|_: InstallContext| version::git_info()) + .with_metadata("authenticated", Value::Bool(false)) + .with_about("Display the githash of StartOS CLI"), ) .subcommand( "echo", - from_fn(echo::).with_call_remote::(), + from_fn(echo::) + .with_about("Echo a message") + .with_call_remote::(), + ) + .subcommand( + "install", + os_install::install::() + .with_about("Commands to list disk info, install StartOS, and reboot"), ) - .subcommand("install", os_install::install::()) } pub fn expanded_api() -> ParentHandler { main_api() - .subcommand("init", from_fn_blocking(developer::init).no_display()) - .subcommand("pubkey", from_fn_blocking(developer::pubkey)) - .subcommand("diagnostic", diagnostic::diagnostic::()) + .subcommand( + "init", + from_fn_blocking(developer::init) + .no_display() + .with_about("Create developer key if it doesn't exist"), + ) + .subcommand( + "pubkey", + from_fn_blocking(developer::pubkey) + .with_about("Get public key for developer private key"), + ) + .subcommand( + "diagnostic", + diagnostic::diagnostic::() + .with_about("Commands to display logs, restart the server, etc"), + ) .subcommand("setup", setup::setup::()) - .subcommand("install", os_install::install::()) - .subcommand("registry", registry::registry_api::()) + .subcommand( + "install", + os_install::install::() + .with_about("Commands to list disk info, install StartOS, and reboot"), + ) + .subcommand( + "registry", + registry::registry_api::().with_about("Commands related to the registry"), + ) } diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index 9cf234f5f..1cb3327b6 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -12,7 +12,7 @@ use color_eyre::eyre::eyre; use futures::stream::BoxStream; use futures::{Future, FutureExt, Stream, StreamExt, TryStreamExt}; use itertools::Itertools; -use models::PackageId; +use models::{FromStrParser, PackageId}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{ from_fn_async, CallRemote, Context, Empty, HandlerArgs, HandlerExt, HandlerFor, ParentHandler, @@ -30,7 +30,6 @@ use crate::error::ResultExt; use crate::lxc::ContainerId; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; -use crate::util::clap::FromStrParser; use crate::util::serde::Reversible; use crate::util::Invoke; @@ -114,7 +113,7 @@ async fn ws_handler( #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct LogResponse { - entries: Reversible, + pub entries: Reversible, start_cursor: Option, end_cursor: Option, } @@ -361,11 +360,7 @@ pub fn logs< source: impl for<'a> LogSourceFn<'a, C, Extra>, ) -> ParentHandler> { ParentHandler::new() - .root_handler( - logs_nofollow::(source.clone()) - .with_inherited(|params, _| params) - .no_cli(), - ) + .root_handler(logs_nofollow::(source.clone()).no_cli()) .subcommand( "follow", logs_follow::(source) @@ -437,7 +432,7 @@ where fn logs_nofollow( f: impl for<'a> LogSourceFn<'a, C, Extra>, -) -> impl HandlerFor, Ok = LogResponse, Err = Error> +) -> impl HandlerFor, InheritedParams = Empty, Ok = LogResponse, Err = Error> where C: Context, Extra: FromArgMatches + Args + Send + Sync + 'static, @@ -445,7 +440,7 @@ where from_fn_async( move |HandlerArgs { context, - inherited_params: + params: LogsParams { extra, limit, @@ -454,7 +449,7 @@ where before, }, .. - }: HandlerArgs>| { + }: HandlerArgs>| { let f = f.clone(); async move { fetch_logs( @@ -487,14 +482,18 @@ fn logs_follow< context, inherited_params: LogsParams { - extra, limit, boot, .. + extra, + cursor, + limit, + boot, + .. }, .. }: HandlerArgs>| { let f = f.clone(); async move { let src = f.call(&context, extra).await?; - follow_logs(context, src, limit, boot.map(String::from)).await + follow_logs(context, src, cursor, limit, boot.map(String::from)).await } }, ) @@ -525,7 +524,7 @@ pub fn package_logs() -> ParentHandler> pub async fn journalctl( id: LogSource, - limit: usize, + limit: Option, cursor: Option<&str>, boot: Option<&str>, before: bool, @@ -533,11 +532,12 @@ pub async fn journalctl( ) -> Result { let mut cmd = gen_journalctl_command(&id); - cmd.arg(format!("--lines={}", limit)); + if let Some(limit) = limit { + cmd.arg(format!("--lines={}", limit)); + } - let cursor_formatted = format!("--after-cursor={}", cursor.unwrap_or("")); - if cursor.is_some() { - cmd.arg(&cursor_formatted); + if let Some(cursor) = cursor { + cmd.arg(&format!("--after-cursor={}", cursor)); if before { cmd.arg("--reverse"); } @@ -638,8 +638,15 @@ pub async fn fetch_logs( before: bool, ) -> Result { let limit = limit.unwrap_or(50); - let mut stream = - journalctl(id, limit, cursor.as_deref(), boot.as_deref(), before, false).await?; + let mut stream = journalctl( + id, + Some(limit), + cursor.as_deref(), + boot.as_deref(), + before, + false, + ) + .await?; let mut entries = Vec::with_capacity(limit); let mut start_cursor = None; @@ -682,11 +689,16 @@ pub async fn fetch_logs( pub async fn follow_logs>( ctx: Context, id: LogSource, + cursor: Option, limit: Option, boot: Option, ) -> Result { - let limit = limit.unwrap_or(50); - let mut stream = journalctl(id, limit, None, boot.as_deref(), false, true).await?; + let limit = if cursor.is_some() { + None + } else { + Some(limit.unwrap_or(50)) + }; + let mut stream = journalctl(id, limit, cursor.as_deref(), boot.as_deref(), false, true).await?; let mut start_cursor = None; let mut first_entry = None; diff --git a/core/startos/src/lxc/dev.rs b/core/startos/src/lxc/dev.rs index 61dd8e598..248546d88 100644 --- a/core/startos/src/lxc/dev.rs +++ b/core/startos/src/lxc/dev.rs @@ -8,16 +8,21 @@ use rpc_toolkit::{ use serde::{Deserialize, Serialize}; use ts_rs::TS; -use crate::context::{CliContext, RpcContext}; use crate::lxc::{ContainerId, LxcConfig}; use crate::prelude::*; use crate::rpc_continuations::Guid; +use crate::{ + context::{CliContext, RpcContext}, + service::ServiceStats, +}; pub fn lxc() -> ParentHandler { ParentHandler::new() .subcommand( "create", - from_fn_async(create).with_call_remote::(), + from_fn_async(create) + .with_about("Create lxc container") + .with_call_remote::(), ) .subcommand( "list", @@ -31,16 +36,59 @@ pub fn lxc() -> ParentHandler { table.printstd(); Ok(()) }) + .with_about("List lxc containers") + .with_call_remote::(), + ) + .subcommand( + "stats", + from_fn_async(stats) + .with_custom_display_fn(|_, res| { + use prettytable::*; + let mut table = table!([ + "Container ID", + "Name", + "Memory Usage", + "Memory Limit", + "Memory %" + ]); + for ServiceStats { + container_id, + package_id, + memory_usage, + memory_limit, + } in res + { + table.add_row(row![ + &*container_id, + &*package_id, + memory_usage, + memory_limit, + format!( + "{:.2}", + memory_usage.0 as f64 / memory_limit.0 as f64 * 100.0 + ) + ]); + } + table.printstd(); + Ok(()) + }) + .with_about("List information related to the lxc containers i.e. CPU, Memory, Disk") .with_call_remote::(), ) .subcommand( "remove", from_fn_async(remove) .no_display() + .with_about("Remove lxc container") .with_call_remote::(), ) .subcommand("connect", from_fn_async(connect_rpc).no_cli()) - .subcommand("connect", from_fn_async(connect_rpc_cli).no_display()) + .subcommand( + "connect", + from_fn_async(connect_rpc_cli) + .no_display() + .with_about("Connect to a lxc container"), + ) } pub async fn create(ctx: RpcContext) -> Result { @@ -54,6 +102,22 @@ pub async fn list(ctx: RpcContext) -> Result, Error> { Ok(ctx.dev.lxc.lock().await.keys().cloned().collect()) } +pub async fn stats(ctx: RpcContext) -> Result, Error> { + let ids = ctx.db.peek().await.as_public().as_package_data().keys()?; + let guids: Vec<_> = ctx.dev.lxc.lock().await.keys().cloned().collect(); + + let mut stats = Vec::with_capacity(guids.len()); + for id in ids { + let service: tokio::sync::OwnedRwLockReadGuard> = + ctx.services.get(&id).await; + + let service_ref = service.as_ref().or_not_found(&id)?; + + stats.push(service_ref.stats().await?); + } + Ok(stats) +} + #[derive(Deserialize, Serialize, Parser, TS)] pub struct RemoveParams { #[ts(type = "string")] diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index b8ce9c703..c0fb6eaba 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -1,13 +1,13 @@ -use std::collections::BTreeSet; use std::net::Ipv4Addr; use std::path::Path; use std::sync::{Arc, Weak}; use std::time::Duration; +use std::{collections::BTreeSet, ffi::OsString}; use clap::builder::ValueParserFactory; use futures::{AsyncWriteExt, StreamExt}; use imbl_value::{InOMap, InternedString}; -use models::InvalidId; +use models::{FromStrParser, InvalidId}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse}; use rustyline_async::{ReadlineEvent, SharedWriter}; @@ -28,12 +28,11 @@ use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; use crate::disk::mount::util::unmount; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation}; -use crate::util::clap::FromStrParser; use crate::util::io::open_file; use crate::util::rpc_client::UnixRpcClient; use crate::util::{new_guid, Invoke}; -#[cfg(feature = "dev")] +// #[cfg(feature = "dev")] pub mod dev; const LXC_CONTAINER_DIR: &str = "/var/lib/lxc"; @@ -127,7 +126,8 @@ impl LxcManager { Path::new(LXC_CONTAINER_DIR).join(container).join("rootfs"), true, ) - .await?; + .await + .log_err(); if tokio_stream::wrappers::ReadDirStream::new( tokio::fs::read_dir(&rootfs_path).await?, ) @@ -268,9 +268,10 @@ impl LxcContainer { .invoke(ErrorKind::Docker) .await?, )?; - let out_str = output.trim(); - if !out_str.is_empty() { - return Ok(out_str.parse()?); + for line in output.lines() { + if let Ok(ip) = line.trim().parse() { + return Ok(ip); + } } if start.elapsed() > CONTAINER_DHCP_TIMEOUT { return Err(Error::new( @@ -286,6 +287,30 @@ impl LxcContainer { self.rpc_bind.path() } + pub async fn command(&self, commands: &[&str]) -> Result { + let mut cmd = Command::new("lxc-attach"); + cmd.kill_on_drop(true); + + let output = cmd + .arg(&**self.guid) + .arg("--") + .args(commands) + .output() + .await?; + + if !output.status.success() { + return Err(Error::new( + eyre!( + "Command failed with exit code: {:?} \n Message: {:?}", + output.status.code(), + String::from_utf8(output.stderr) + ), + ErrorKind::Docker, + )); + } + Ok(String::from_utf8(output.stdout)?) + } + #[instrument(skip_all)] pub async fn exit(mut self) -> Result<(), Error> { Command::new("lxc-stop") @@ -364,7 +389,7 @@ impl Drop for LxcContainer { tracing::error!("Error reading logs from crashed container: {e}"); tracing::debug!("{e:?}") } - rootfs.unmount(true).await.unwrap(); + rootfs.unmount(true).await.log_err(); drop(guid); if let Err(e) = manager.gc().await { tracing::error!("Error cleaning up dangling LXC containers: {e}"); diff --git a/core/startos/src/middleware/auth.rs b/core/startos/src/middleware/auth.rs index 9b04afb38..7c5eaa4c2 100644 --- a/core/startos/src/middleware/auth.rs +++ b/core/startos/src/middleware/auth.rs @@ -49,7 +49,7 @@ impl HasLoggedOutSessions { .map(|s| s.as_logout_session_id()) .collect(); for sid in &to_log_out { - ctx.open_authed_continuations.kill(sid) + ctx.open_authed_continuations.kill(&Some(sid.clone())) } ctx.ephemeral_sessions.mutate(|s| { for sid in &to_log_out { diff --git a/core/startos/src/net/acme.rs b/core/startos/src/net/acme.rs new file mode 100644 index 000000000..95f9d4adb --- /dev/null +++ b/core/startos/src/net/acme.rs @@ -0,0 +1,324 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::str::FromStr; + +use clap::builder::ValueParserFactory; +use clap::Parser; +use imbl_value::InternedString; +use itertools::Itertools; +use models::{ErrorData, FromStrParser}; +use openssl::pkey::{PKey, Private}; +use openssl::x509::X509; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use url::Url; + +use crate::context::{CliContext, RpcContext}; +use crate::db::model::public::AcmeSettings; +use crate::db::model::Database; +use crate::prelude::*; +use crate::util::serde::{Pem, Pkcs8Doc}; + +#[derive(Debug, Default, Deserialize, Serialize, HasModel)] +#[model = "Model"] +pub struct AcmeCertStore { + pub accounts: BTreeMap>, Pem>, + pub certs: BTreeMap>, AcmeCert>>, +} +impl AcmeCertStore { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AcmeCert { + pub key: Pem>, + pub fullchain: Vec>, +} + +pub struct AcmeCertCache<'a>(pub &'a TypedPatchDb); +#[async_trait::async_trait] +impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> { + type Error = ErrorData; + + async fn read_account(&self, contacts: &[&str]) -> Result>, Self::Error> { + let contacts = JsonKey::new(contacts.into_iter().map(|s| (*s).to_owned()).collect_vec()); + let Some(account) = self + .0 + .peek() + .await + .into_private() + .into_key_store() + .into_acme() + .into_accounts() + .into_idx(&contacts) + else { + return Ok(None); + }; + Ok(Some(account.de()?.0.document.into_vec())) + } + + async fn write_account(&self, contacts: &[&str], contents: &[u8]) -> Result<(), Self::Error> { + let contacts = JsonKey::new(contacts.into_iter().map(|s| (*s).to_owned()).collect_vec()); + let key = Pkcs8Doc { + tag: "EC PRIVATE KEY".into(), + document: pkcs8::Document::try_from(contents).with_kind(ErrorKind::Pem)?, + }; + self.0 + .mutate(|db| { + db.as_private_mut() + .as_key_store_mut() + .as_acme_mut() + .as_accounts_mut() + .insert(&contacts, &Pem::new(key)) + }) + .await?; + Ok(()) + } + + async fn read_certificate( + &self, + domains: &[String], + directory_url: &str, + ) -> Result, Self::Error> { + let domains = JsonKey::new(domains.into_iter().map(InternedString::intern).collect()); + let directory_url = directory_url + .parse::() + .with_kind(ErrorKind::ParseUrl)?; + let Some(cert) = self + .0 + .peek() + .await + .into_private() + .into_key_store() + .into_acme() + .into_certs() + .into_idx(&directory_url) + .and_then(|a| a.into_idx(&domains)) + else { + return Ok(None); + }; + let cert = cert.de()?; + Ok(Some(( + String::from_utf8( + cert.key + .0 + .private_key_to_pem_pkcs8() + .with_kind(ErrorKind::OpenSsl)?, + ) + .with_kind(ErrorKind::Utf8)?, + cert.fullchain + .into_iter() + .map(|cert| { + String::from_utf8(cert.0.to_pem().with_kind(ErrorKind::OpenSsl)?) + .with_kind(ErrorKind::Utf8) + }) + .collect::, _>>()? + .join("\n"), + ))) + } + + async fn write_certificate( + &self, + domains: &[String], + directory_url: &str, + key_pem: &str, + certificate_pem: &str, + ) -> Result<(), Self::Error> { + tracing::info!("Saving new certificate for {domains:?}"); + let domains = JsonKey::new(domains.into_iter().map(InternedString::intern).collect()); + let directory_url = directory_url + .parse::() + .with_kind(ErrorKind::ParseUrl)?; + let cert = AcmeCert { + key: Pem(PKey::::private_key_from_pem(key_pem.as_bytes()) + .with_kind(ErrorKind::OpenSsl)?), + fullchain: X509::stack_from_pem(certificate_pem.as_bytes()) + .with_kind(ErrorKind::OpenSsl)? + .into_iter() + .map(Pem) + .collect(), + }; + self.0 + .mutate(|db| { + db.as_private_mut() + .as_key_store_mut() + .as_acme_mut() + .as_certs_mut() + .upsert(&directory_url, || Ok(BTreeMap::new()))? + .insert(&domains, &cert) + }) + .await?; + + Ok(()) + } +} + +pub fn acme() -> ParentHandler { + ParentHandler::new() + .subcommand( + "init", + from_fn_async(init) + .no_display() + .with_about("Setup ACME certificate acquisition") + .with_call_remote::(), + ) + .subcommand( + "domain", + domain::() + .with_about("Add, remove, or view domains for which to acquire ACME certificates"), + ) +} + +#[derive(Clone, Deserialize, Serialize)] +pub struct AcmeProvider(pub Url); +impl FromStr for AcmeProvider { + type Err = ::Err; + fn from_str(s: &str) -> Result { + match s { + "letsencrypt" => async_acme::acme::LETS_ENCRYPT_PRODUCTION_DIRECTORY.parse(), + "letsencrypt-staging" => async_acme::acme::LETS_ENCRYPT_STAGING_DIRECTORY.parse(), + s => s.parse(), + } + .map(Self) + } +} +impl ValueParserFactory for AcmeProvider { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + Self::Parser::new() + } +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct InitAcmeParams { + #[arg(long)] + pub provider: AcmeProvider, + #[arg(long)] + pub contact: Vec, +} + +pub async fn init( + ctx: RpcContext, + InitAcmeParams { + provider: AcmeProvider(provider), + contact, + }: InitAcmeParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_acme_mut() + .map_mutate(|acme| { + Ok(Some(AcmeSettings { + provider, + contact, + domains: acme.map(|acme| acme.domains).unwrap_or_default(), + })) + }) + }) + .await?; + Ok(()) +} + +pub fn domain() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_domain) + .no_display() + .with_about("Add a domain for which to acquire ACME certificates") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_domain) + .no_display() + .with_about("Remove a domain for which to acquire ACME certificates") + .with_call_remote::(), + ) + .subcommand( + "list", + from_fn_async(list_domains) + .with_custom_display_fn(|_, res| { + for domain in res { + println!("{domain}") + } + Ok(()) + }) + .with_about("List domains for which to acquire ACME certificates") + .with_call_remote::(), + ) +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct DomainParams { + pub domain: InternedString, +} + +pub async fn add_domain( + ctx: RpcContext, + DomainParams { domain }: DomainParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_acme_mut() + .transpose_mut() + .ok_or_else(|| { + Error::new( + eyre!("Please call `start-cli net acme init` before adding a domain"), + ErrorKind::InvalidRequest, + ) + })? + .as_domains_mut() + .mutate(|domains| { + domains.insert(domain); + Ok(()) + }) + }) + .await?; + Ok(()) +} + +pub async fn remove_domain( + ctx: RpcContext, + DomainParams { domain }: DomainParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + if let Some(acme) = db + .as_public_mut() + .as_server_info_mut() + .as_acme_mut() + .transpose_mut() + { + acme.as_domains_mut().mutate(|domains| { + domains.remove(&domain); + Ok(()) + }) + } else { + Ok(()) + } + }) + .await?; + Ok(()) +} + +pub async fn list_domains(ctx: RpcContext) -> Result, Error> { + if let Some(acme) = ctx + .db + .peek() + .await + .into_public() + .into_server_info() + .into_acme() + .transpose() + { + acme.into_domains().de() + } else { + Ok(BTreeSet::new()) + } +} diff --git a/core/startos/src/net/dhcp.rs b/core/startos/src/net/dhcp.rs index ffcb9774b..e323ba371 100644 --- a/core/startos/src/net/dhcp.rs +++ b/core/startos/src/net/dhcp.rs @@ -58,6 +58,7 @@ pub fn dhcp() -> ParentHandler { "update", from_fn_async::<_, _, (), Error, (RpcContext, UpdateParams)>(update) .no_display() + .with_about("Update IP assigned by dhcp") .with_call_remote::(), ) } diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index 090e845b0..016e5de9f 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -98,16 +98,8 @@ impl RequestHandler for Resolver { ) .await } - a => { - if a != RecordType::AAAA { - tracing::warn!( - "Non A-Record requested for {}: {:?}", - query.name(), - query.query_type() - ); - } - let mut res = Header::response_from_request(request.header()); - res.set_response_code(ResponseCode::NXDomain); + _ => { + let res = Header::response_from_request(request.header()); response_handle .send_response( MessageResponseBuilder::from_message_request(&*request).build( diff --git a/core/startos/src/net/host/address.rs b/core/startos/src/net/host/address.rs index 9b16441ce..05942ffa9 100644 --- a/core/startos/src/net/host/address.rs +++ b/core/startos/src/net/host/address.rs @@ -1,7 +1,9 @@ use std::fmt; use std::str::FromStr; +use clap::builder::ValueParserFactory; use imbl_value::InternedString; +use models::FromStrParser; use serde::{Deserialize, Serialize}; use torut::onion::OnionAddressV3; use ts_rs::TS; @@ -46,3 +48,10 @@ impl fmt::Display for HostAddress { } } } + +impl ValueParserFactory for HostAddress { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + Self::Parser::new() + } +} diff --git a/core/startos/src/net/host/binding.rs b/core/startos/src/net/host/binding.rs index 76dd04059..174f0330f 100644 --- a/core/startos/src/net/host/binding.rs +++ b/core/startos/src/net/host/binding.rs @@ -1,3 +1,7 @@ +use std::str::FromStr; + +use clap::builder::ValueParserFactory; +use models::{FromStrParser, HostId}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -5,10 +9,37 @@ use crate::net::forward::AvailablePorts; use crate::net::vhost::AlpnInfo; use crate::prelude::*; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct BindId { + pub id: HostId, + pub internal_port: u16, +} +impl ValueParserFactory for BindId { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + FromStrParser::new() + } +} +impl FromStr for BindId { + type Err = Error; + fn from_str(s: &str) -> Result { + let (id, port) = s + .split_once(":") + .ok_or_else(|| Error::new(eyre!("expected :"), ErrorKind::ParseUrl))?; + Ok(Self { + id: id.parse()?, + internal_port: port.parse()?, + }) + } +} + #[derive(Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct BindInfo { + pub enabled: bool, pub options: BindOptions, pub lan: LanInfo, } @@ -30,6 +61,7 @@ impl BindInfo { assigned_ssl_port = Some(available_ports.alloc()?); } Ok(Self { + enabled: true, options, lan: LanInfo { assigned_port, @@ -69,7 +101,14 @@ impl BindInfo { available_ports.free([port]); } } - Ok(Self { options, lan }) + Ok(Self { + enabled: true, + options, + lan, + }) + } + pub fn disable(&mut self) { + self.enabled = false; } } diff --git a/core/startos/src/net/host/mod.rs b/core/startos/src/net/host/mod.rs index 175fe3e83..be5db0f2d 100644 --- a/core/startos/src/net/host/mod.rs +++ b/core/startos/src/net/host/mod.rs @@ -1,10 +1,13 @@ use std::collections::{BTreeMap, BTreeSet}; +use clap::Parser; use imbl_value::InternedString; use models::{HostId, PackageId}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::net::forward::AvailablePorts; use crate::net::host::address::HostAddress; @@ -134,3 +137,163 @@ impl Model { }) } } + +#[derive(Deserialize, Serialize, Parser)] +pub struct HostParams { + package: PackageId, +} + +pub fn host() -> ParentHandler { + ParentHandler::::new() + .subcommand( + "list", + from_fn_async(list_hosts) + .with_inherited(|HostParams { package }, _| package) + .with_custom_display_fn(|_, ids| { + for id in ids { + println!("{id}") + } + Ok(()) + }) + .with_about("List host IDs available for this service"), + ) + .subcommand( + "address", + address::().with_inherited(|HostParams { package }, _| package), + ) +} + +pub async fn list_hosts( + ctx: RpcContext, + _: Empty, + package: PackageId, +) -> Result, Error> { + ctx.db + .peek() + .await + .into_public() + .into_package_data() + .into_idx(&package) + .or_not_found(&package)? + .into_hosts() + .keys() +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct AddressApiParams { + host: HostId, +} + +pub fn address() -> ParentHandler { + ParentHandler::::new() + .subcommand( + "add", + from_fn_async(add_address) + .with_inherited(|AddressApiParams { host }, package| (package, host)) + .no_display() + .with_about("Add an address to this host") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_address) + .with_inherited(|AddressApiParams { host }, package| (package, host)) + .no_display() + .with_about("Remove an address from this host") + .with_call_remote::(), + ) + .subcommand( + "list", + from_fn_async(list_addresses) + .with_inherited(|AddressApiParams { host }, package| (package, host)) + .with_custom_display_fn(|_, res| { + for address in res { + println!("{address}") + } + Ok(()) + }) + .with_about("List addresses for this host") + .with_call_remote::(), + ) +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct AddressParams { + pub address: HostAddress, +} + +pub async fn add_address( + ctx: RpcContext, + AddressParams { address }: AddressParams, + (package, host): (PackageId, HostId), +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + if let HostAddress::Onion { address } = address { + db.as_private() + .as_key_store() + .as_onion() + .get_key(&address)?; + } + + db.as_public_mut() + .as_package_data_mut() + .as_idx_mut(&package) + .or_not_found(&package)? + .as_hosts_mut() + .as_idx_mut(&host) + .or_not_found(&host)? + .as_addresses_mut() + .mutate(|a| Ok(a.insert(address))) + }) + .await?; + let service = ctx.services.get(&package).await; + let service_ref = service.as_ref().or_not_found(&package)?; + service_ref.update_host(host).await?; + + Ok(()) +} + +pub async fn remove_address( + ctx: RpcContext, + AddressParams { address }: AddressParams, + (package, host): (PackageId, HostId), +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_public_mut() + .as_package_data_mut() + .as_idx_mut(&package) + .or_not_found(&package)? + .as_hosts_mut() + .as_idx_mut(&host) + .or_not_found(&host)? + .as_addresses_mut() + .mutate(|a| Ok(a.remove(&address))) + }) + .await?; + let service = ctx.services.get(&package).await; + let service_ref = service.as_ref().or_not_found(&package)?; + service_ref.update_host(host).await?; + + Ok(()) +} + +pub async fn list_addresses( + ctx: RpcContext, + _: Empty, + (package, host): (PackageId, HostId), +) -> Result, Error> { + ctx.db + .peek() + .await + .into_public() + .into_package_data() + .into_idx(&package) + .or_not_found(&package)? + .into_hosts() + .into_idx(&host) + .or_not_found(&host)? + .into_addresses() + .de() +} diff --git a/core/startos/src/net/keys.rs b/core/startos/src/net/keys.rs index 02ec17329..866b2ca06 100644 --- a/core/startos/src/net/keys.rs +++ b/core/startos/src/net/keys.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::account::AccountInfo; +use crate::net::acme::AcmeCertStore; use crate::net::ssl::CertStore; use crate::net::tor::OnionStore; use crate::prelude::*; @@ -10,13 +11,15 @@ use crate::prelude::*; pub struct KeyStore { pub onion: OnionStore, pub local_certs: CertStore, - // pub letsencrypt_certs: BTreeMap, CertData> + #[serde(default)] + pub acme: AcmeCertStore, } impl KeyStore { pub fn new(account: &AccountInfo) -> Result { let mut res = Self { onion: OnionStore::new(), local_certs: CertStore::new(account)?, + acme: AcmeCertStore::new(), }; res.onion.insert(account.tor_key.clone()); Ok(res) diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index e55da4206..53b94454d 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -1,5 +1,6 @@ -use rpc_toolkit::{Context, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler}; +pub mod acme; pub mod dhcp; pub mod dns; pub mod forward; @@ -20,6 +21,16 @@ pub const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl"; pub fn net() -> ParentHandler { ParentHandler::new() - .subcommand("tor", tor::tor::()) - .subcommand("dhcp", dhcp::dhcp::()) + .subcommand( + "tor", + tor::tor::().with_about("Tor commands such as list-services, logs, and reset"), + ) + .subcommand( + "dhcp", + dhcp::dhcp::().with_about("Command to update IP assigned from dhcp"), + ) + .subcommand( + "acme", + acme::acme::().with_about("Setup automatic clearnet certificate acquisition"), + ) } diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index b7a8022b4..a8beaf55f 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -15,8 +15,8 @@ use crate::hostname::Hostname; use crate::net::dns::DnsController; use crate::net::forward::LanPortForwardController; use crate::net::host::address::HostAddress; -use crate::net::host::binding::{AddSslOptions, BindOptions, LanInfo}; -use crate::net::host::{host_for, Host, HostKind}; +use crate::net::host::binding::{AddSslOptions, BindId, BindOptions, LanInfo}; +use crate::net::host::{host_for, Host, HostKind, Hosts}; use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname}; use crate::net::tor::TorController; use crate::net::vhost::{AlpnInfo, VHostController}; @@ -154,14 +154,16 @@ impl NetController { ) -> Result { let dns = self.dns.add(Some(package.clone()), ip).await?; - Ok(NetService { + let mut res = NetService { shutdown: false, id: package, ip, dns, controller: Arc::downgrade(self), binds: BTreeMap::new(), - }) + }; + res.clear_bindings(Default::default()).await?; + Ok(res) } } @@ -221,35 +223,45 @@ impl NetService { self.update(id, host).await } - pub async fn clear_bindings(&mut self) -> Result<(), Error> { - let ctrl = self.net_controller()?; - let mut errors = ErrorCollection::new(); - for (_, binds) in std::mem::take(&mut self.binds) { - for (_, (lan, _, hostnames, rc)) in binds.lan { - drop(rc); - if let Some(external) = lan.assigned_ssl_port { - for hostname in ctrl.server_hostnames.iter().cloned() { - ctrl.vhost.gc(hostname, external).await?; - } - for hostname in hostnames { - ctrl.vhost.gc(Some(hostname), external).await?; - } - } - if let Some(external) = lan.assigned_port { - ctrl.forward.gc(external).await?; + pub async fn clear_bindings(&mut self, except: BTreeSet) -> Result<(), Error> { + let pkg_id = &self.id; + let hosts = self + .net_controller()? + .db + .mutate(|db| { + let mut res = Hosts::default(); + for (host_id, host) in db + .as_public_mut() + .as_package_data_mut() + .as_idx_mut(pkg_id) + .or_not_found(pkg_id)? + .as_hosts_mut() + .as_entries_mut()? + { + host.as_bindings_mut().mutate(|b| { + for (internal_port, info) in b { + if !except.contains(&BindId { + id: host_id.clone(), + internal_port: *internal_port, + }) { + info.disable(); + } + } + Ok(()) + })?; + res.0.insert(host_id, host.de()?); } - } - for (addr, (_, rcs)) in binds.tor { - drop(rcs); - errors.handle(ctrl.tor.gc(Some(addr), None).await); - } + Ok(res) + }) + .await?; + let mut errors = ErrorCollection::new(); + for (id, host) in hosts.0 { + errors.handle(self.update(id, host).await); } - std::mem::take(&mut self.dns); - errors.handle(ctrl.dns.gc(Some(self.id.clone()), self.ip).await); errors.into_result() } - async fn update(&mut self, id: HostId, host: Host) -> Result<(), Error> { + pub async fn update(&mut self, id: HostId, host: Host) -> Result<(), Error> { let ctrl = self.net_controller()?; let mut hostname_info = BTreeMap::new(); let binds = self.binds.entry(id.clone()).or_default(); @@ -261,6 +273,9 @@ impl NetService { let ip_info = server_info.as_ip_info().de()?; let hostname = server_info.as_hostname().de()?; for (port, bind) in &host.bindings { + if !bind.enabled { + continue; + } let old_lan_bind = binds.lan.remove(port); let lan_bind = old_lan_bind .as_ref() @@ -315,16 +330,29 @@ impl NetService { } HostAddress::Domain { address } => { if hostnames.insert(address.clone()) { + let address = Some(address.clone()); rcs.push( ctrl.vhost .add( - Some(address.clone()), + address.clone(), external, target, connect_ssl.clone(), ) .await?, ); + if ssl.preferred_external_port == 443 { + rcs.push( + ctrl.vhost + .add( + address.clone(), + 5443, + target, + connect_ssl.clone(), + ) + .await?, + ); + } } } } @@ -348,11 +376,32 @@ impl NetService { network_interface_id: interface.clone(), public: false, hostname: IpHostname::Local { - value: format!("{hostname}.local"), + value: InternedString::from_display(&{ + let hostname = &hostname; + lazy_format!("{hostname}.local") + }), port: new_lan_bind.0.assigned_port, ssl_port: new_lan_bind.0.assigned_ssl_port, }, }); + for address in host.addresses() { + if let HostAddress::Domain { address } = address { + if let Some(ssl) = &new_lan_bind.1 { + if ssl.preferred_external_port == 443 { + bind_hostname_info.push(HostnameInfo::Ip { + network_interface_id: interface.clone(), + public: false, + hostname: IpHostname::Domain { + domain: address.clone(), + subdomain: None, + port: None, + ssl_port: Some(443), + }, + }); + } + } + } + } if let Some(ipv4) = ip_info.ipv4 { bind_hostname_info.push(HostnameInfo::Ip { network_interface_id: interface.clone(), @@ -395,7 +444,7 @@ impl NetService { } let mut removed = BTreeSet::new(); binds.lan.retain(|internal, (external, _, hostnames, _)| { - if host.bindings.contains_key(internal) { + if host.bindings.get(internal).map_or(false, |b| b.enabled) { true } else { removed.insert((*external, std::mem::take(hostnames))); @@ -424,6 +473,9 @@ impl NetService { let mut tor_hostname_ports = BTreeMap::::new(); let mut tor_binds = OrdMap::::new(); for (internal, info) in &host.bindings { + if !info.enabled { + continue; + } tor_binds.insert( info.options.preferred_external_port, SocketAddr::from((self.ip, *internal)), @@ -497,6 +549,7 @@ impl NetService { ctrl.tor.gc(Some(addr.clone()), None).await?; } } + self.net_controller()? .db .mutate(|db| { @@ -511,7 +564,7 @@ impl NetService { pub async fn remove_all(mut self) -> Result<(), Error> { self.shutdown = true; if let Some(ctrl) = Weak::upgrade(&self.controller) { - self.clear_bindings().await?; + self.clear_bindings(Default::default()).await?; drop(ctrl); Ok(()) } else { @@ -566,7 +619,7 @@ impl Drop for NetService { binds: BTreeMap::new(), }, ); - tokio::spawn(async move { svc.remove_all().await.unwrap() }); + tokio::spawn(async move { svc.remove_all().await.log_err() }); } } } diff --git a/core/startos/src/net/service_interface.rs b/core/startos/src/net/service_interface.rs index b1824140b..ade10d959 100644 --- a/core/startos/src/net/service_interface.rs +++ b/core/startos/src/net/service_interface.rs @@ -47,13 +47,16 @@ pub enum IpHostname { ssl_port: Option, }, Local { - value: String, + #[ts(type = "string")] + value: InternedString, port: Option, ssl_port: Option, }, Domain { - domain: String, - subdomain: Option, + #[ts(type = "string")] + domain: InternedString, + #[ts(type = "string | null")] + subdomain: Option, port: Option, ssl_port: Option, }, diff --git a/core/startos/src/net/static_server.rs b/core/startos/src/net/static_server.rs index f1da91851..c070d7920 100644 --- a/core/startos/src/net/static_server.rs +++ b/core/startos/src/net/static_server.rs @@ -84,7 +84,7 @@ pub fn rpc_router>( server: HttpServer, ) -> Router { Router::new() - .route("/rpc/*path", post(server)) + .route("/rpc/*path", any(server)) .route( "/ws/rpc/:guid", get({ diff --git a/core/startos/src/net/tor.rs b/core/startos/src/net/tor.rs index 24b8ddb02..bba50c371 100644 --- a/core/startos/src/net/tor.rs +++ b/core/startos/src/net/tor.rs @@ -26,7 +26,7 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::logs::{journalctl, LogSource, LogsParams}; use crate::prelude::*; -use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::util::serde::{display_serializable, Base64, HandlerExtSerde, WithIoFormat}; use crate::util::Invoke; pub const SYSTEMD_UNIT: &str = "tor@default"; @@ -59,7 +59,9 @@ impl Model { self.insert(&key.public().get_onion_address(), &key) } pub fn get_key(&self, address: &OnionAddressV3) -> Result { - self.as_idx(address).or_not_found(address)?.de() + self.as_idx(address) + .or_not_found(lazy_format!("private key for {address}"))? + .de() } } @@ -91,20 +93,102 @@ pub fn tor() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_services(handle.params, result)) }) + .with_about("Display Tor V3 Onion Addresses") .with_call_remote::(), ) - .subcommand("logs", logs()) + .subcommand("logs", logs().with_about("Display Tor logs")) .subcommand( "logs", - from_fn_async(crate::logs::cli_logs::).no_display(), + from_fn_async(crate::logs::cli_logs::) + .no_display() + .with_about("Display Tor logs"), ) .subcommand( "reset", from_fn_async(reset) .no_display() + .with_about("Reset Tor daemon") .with_call_remote::(), ) + .subcommand( + "key", + key::().with_about("Manage the onion service key store"), + ) } + +pub fn key() -> ParentHandler { + ParentHandler::new() + .subcommand( + "generate", + from_fn_async(generate_key) + .with_about("Generate an onion service key and add it to the key store") + .with_call_remote::(), + ) + .subcommand( + "add", + from_fn_async(add_key) + .with_about("Add an onion service key to the key store") + .with_call_remote::(), + ) + .subcommand( + "list", + from_fn_async(list_keys) + .with_custom_display_fn(|_, res| { + for addr in res { + println!("{addr}"); + } + Ok(()) + }) + .with_about("List onion services with keys in the key store") + .with_call_remote::(), + ) +} + +pub async fn generate_key(ctx: RpcContext) -> Result { + ctx.db + .mutate(|db| { + Ok(db + .as_private_mut() + .as_key_store_mut() + .as_onion_mut() + .new_key()? + .public() + .get_onion_address()) + }) + .await +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct AddKeyParams { + pub key: Base64<[u8; 64]>, +} + +pub async fn add_key( + ctx: RpcContext, + AddKeyParams { key }: AddKeyParams, +) -> Result { + let key = TorSecretKeyV3::from(key.0); + ctx.db + .mutate(|db| { + db.as_private_mut() + .as_key_store_mut() + .as_onion_mut() + .insert_key(&key) + }) + .await?; + Ok(key.public().get_onion_address()) +} + +pub async fn list_keys(ctx: RpcContext) -> Result, Error> { + ctx.db + .peek() + .await + .into_private() + .into_key_store() + .into_onion() + .keys() +} + #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] #[command(rename_all = "kebab-case")] @@ -307,7 +391,7 @@ async fn torctl( let logs = journalctl( LogSource::Unit(SYSTEMD_UNIT), - 0, + Some(0), None, Some("0"), false, diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 9fc7c8384..7d48b1469 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use std::sync::{Arc, Weak}; use std::time::Duration; +use async_acme::acme::ACME_TLS_ALPN_NAME; use axum::body::Body; use axum::extract::Request; use axum::response::Response; @@ -15,31 +16,47 @@ use models::ResultExt; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::{watch, Mutex, RwLock}; +use tokio_rustls::rustls::crypto::CryptoProvider; use tokio_rustls::rustls::pki_types::{ CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, }; -use tokio_rustls::rustls::server::Acceptor; +use tokio_rustls::rustls::server::{Acceptor, ResolvesServerCert}; +use tokio_rustls::rustls::sign::CertifiedKey; use tokio_rustls::rustls::{RootCertStore, ServerConfig}; use tokio_rustls::{LazyConfigAcceptor, TlsConnector}; +use tokio_stream::wrappers::WatchStream; +use tokio_stream::StreamExt; use tracing::instrument; use ts_rs::TS; use crate::db::model::Database; +use crate::net::acme::AcmeCertCache; use crate::net::static_server::server_error; use crate::prelude::*; use crate::util::io::BackTrackingIO; +use crate::util::sync::SyncMutex; use crate::util::serde::MaybeUtf8String; +#[derive(Debug)] +struct SingleCertResolver(Arc); +impl ResolvesServerCert for SingleCertResolver { + fn resolve(&self, _: tokio_rustls::rustls::server::ClientHello) -> Option> { + Some(self.0.clone()) + } +} + // not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353 pub struct VHostController { + crypto_provider: Arc, db: TypedPatchDb, servers: Mutex>, } impl VHostController { pub fn new(db: TypedPatchDb) -> Self { Self { + crypto_provider: Arc::new(tokio_rustls::rustls::crypto::ring::default_provider()), db, servers: Mutex::new(BTreeMap::new()), } @@ -56,7 +73,8 @@ impl VHostController { let server = if let Some(server) = writable.remove(&external) { server } else { - VHostServer::new(external, self.db.clone()).await? + tracing::info!("Listening on {external}"); + VHostServer::new(external, self.db.clone(), self.crypto_provider.clone()).await? }; let rc = server .add( @@ -108,7 +126,11 @@ struct VHostServer { } impl VHostServer { #[instrument(skip_all)] - async fn new(port: u16, db: TypedPatchDb) -> Result { + async fn new(port: u16, db: TypedPatchDb, crypto_provider: Arc) -> Result { + let acme_tls_alpn_cache = Arc::new(SyncMutex::new(BTreeMap::< + InternedString, + watch::Receiver>>, + >::new())); // check if port allowed let listener = TcpListener::bind(SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port)) .await @@ -133,9 +155,11 @@ impl VHostServer { let mut stream = BackTrackingIO::new(stream); let mapping = mapping.clone(); let db = db.clone(); + let acme_tls_alpn_cache = acme_tls_alpn_cache.clone(); + let crypto_provider = crypto_provider.clone(); tokio::spawn(async move { if let Err(e) = async { - let mid = match LazyConfigAcceptor::new( + let mid: tokio_rustls::StartHandshake<&mut BackTrackingIO> = match LazyConfigAcceptor::new( Acceptor::default(), &mut stream, ) @@ -206,38 +230,102 @@ impl VHostServer { .map(|(target, _)| target.clone()) }; if let Some(target) = target { - let mut tcp_stream = - TcpStream::connect(target.addr).await?; - let hostnames = target_name - .into_iter() - .chain( - db.peek() - .await - .into_public() - .into_server_info() - .into_ip_info() - .into_entries()? - .into_iter() - .flat_map(|(_, ips)| [ - ips.as_ipv4().de().map(|ip| ip.map(IpAddr::V4)), - ips.as_ipv6().de().map(|ip| ip.map(IpAddr::V6)) - ]) - .filter_map(|a| a.transpose()) - .map(|a| a.map(|ip| InternedString::from_display(&ip))) - .collect::, _>>()?, - ) - .collect(); - let key = db - .mutate(|v| { - v.as_private_mut() - .as_key_store_mut() - .as_local_certs_mut() - .cert_for(&hostnames) - }) - .await?; - let cfg = ServerConfig::builder() - .with_no_client_auth(); - let mut cfg = + let peek = db.peek().await; + let root = peek.as_private().as_key_store().as_local_certs().as_root_cert().de()?; + let mut cfg = match async { + if let Some(acme_settings) = peek.as_public().as_server_info().as_acme().de()? { + if let Some(domain) = target_name.as_ref().filter(|target_name| acme_settings.domains.contains(*target_name)) { + if mid + .client_hello() + .alpn() + .into_iter() + .flatten() + .any(|alpn| alpn == ACME_TLS_ALPN_NAME) + { + let cert = WatchStream::new( + acme_tls_alpn_cache.peek(|c| c.get(&**domain).cloned()) + .ok_or_else(|| { + Error::new( + eyre!("No challenge recv available for {domain}"), + ErrorKind::OpenSsl + ) + })?, + ); + tracing::info!("Waiting for verification cert for {domain}"); + let cert = cert + .filter(|c| c.is_some()) + .next() + .await + .flatten() + .ok_or_else(|| { + Error::new(eyre!("No challenge available for {domain}"), ErrorKind::OpenSsl) + })?; + tracing::info!("Verification cert received for {domain}"); + let mut cfg = ServerConfig::builder_with_provider(crypto_provider.clone()) + .with_safe_default_protocol_versions() + .with_kind(crate::ErrorKind::OpenSsl)? + .with_no_client_auth() + .with_cert_resolver(Arc::new(SingleCertResolver(cert))); + + cfg.alpn_protocols = vec![ACME_TLS_ALPN_NAME.to_vec()]; + return Ok(Err(cfg)); + } else { + let domains = [domain.to_string()]; + let (send, recv) = watch::channel(None); + acme_tls_alpn_cache.mutate(|c| c.insert(domain.clone(), recv)); + let cert = + async_acme::rustls_helper::order( + |_, cert| { + send.send_replace(Some(Arc::new(cert))); + Ok(()) + }, + acme_settings.provider.as_str(), + &domains, + Some(&AcmeCertCache(&db)), + &acme_settings.contact, + ) + .await + .with_kind(ErrorKind::OpenSsl)?; + return Ok(Ok( + ServerConfig::builder_with_provider(crypto_provider.clone()) + .with_safe_default_protocol_versions() + .with_kind(crate::ErrorKind::OpenSsl)? + .with_no_client_auth() + .with_cert_resolver(Arc::new(SingleCertResolver(Arc::new(cert)))) + )); + } + } + } + let hostnames = target_name + .into_iter() + .chain( + peek + .as_public() + .as_server_info() + .as_ip_info() + .as_entries()? + .into_iter() + .flat_map(|(_, ips)| [ + ips.as_ipv4().de().map(|ip| ip.map(IpAddr::V4)), + ips.as_ipv6().de().map(|ip| ip.map(IpAddr::V6)) + ]) + .filter_map(|a| a.transpose()) + .map(|a| a.map(|ip| InternedString::from_display(&ip))) + .collect::, _>>()?, + ) + .collect(); + let key = db + .mutate(|v| { + v.as_private_mut() + .as_key_store_mut() + .as_local_certs_mut() + .cert_for(&hostnames) + }) + .await?; + let cfg = ServerConfig::builder_with_provider(crypto_provider.clone()) + .with_safe_default_protocol_versions() + .with_kind(crate::ErrorKind::OpenSsl)? + .with_no_client_auth(); if mid.client_hello().signature_schemes().contains( &tokio_rustls::rustls::SignatureScheme::ED25519, ) { @@ -275,16 +363,34 @@ impl VHostServer { )), ) } - .with_kind(crate::ErrorKind::OpenSsl)?; + .with_kind(crate::ErrorKind::OpenSsl) + .map(Ok) + }.await? { + Ok(a) => a, + Err(cfg) => { + tracing::info!("performing ACME auth challenge"); + let mut accept = mid.into_stream(Arc::new(cfg)); + let io = accept.get_mut().unwrap(); + let buffered = io.stop_buffering(); + io.write_all(&buffered).await?; + accept.await?; + tracing::info!("ACME auth challenge completed"); + return Ok(()); + } + }; + let mut tcp_stream = + TcpStream::connect(target.addr).await?; match target.connect_ssl { Ok(()) => { let mut client_cfg = - tokio_rustls::rustls::ClientConfig::builder() + tokio_rustls::rustls::ClientConfig::builder_with_provider(crypto_provider) + .with_safe_default_protocol_versions() + .with_kind(crate::ErrorKind::OpenSsl)? .with_root_certificates({ let mut store = RootCertStore::empty(); store.add( CertificateDer::from( - key.root.to_der()?, + root.to_der()?, ), ).with_kind(crate::ErrorKind::OpenSsl)?; store diff --git a/core/startos/src/net/web_server.rs b/core/startos/src/net/web_server.rs index a9cfdf046..d1ad64d01 100644 --- a/core/startos/src/net/web_server.rs +++ b/core/startos/src/net/web_server.rs @@ -7,7 +7,7 @@ use axum::extract::Request; use axum::Router; use axum_server::Handle; use bytes::Bytes; -use futures::future::ready; +use futures::future::{ready, BoxFuture}; use futures::FutureExt; use helpers::NonDetachingJoinHandle; use tokio::sync::{oneshot, watch}; @@ -30,8 +30,39 @@ impl SwappableRouter { } } -#[derive(Clone)] -pub struct SwappableRouterService(watch::Receiver); +pub struct SwappableRouterService { + router: watch::Receiver, + changed: Option>, +} +impl SwappableRouterService { + fn router(&self) -> Router { + self.router.borrow().clone() + } + fn changed(&mut self, cx: &mut std::task::Context<'_>) -> Poll<()> { + let mut changed = if let Some(changed) = self.changed.take() { + changed + } else { + let mut router = self.router.clone(); + async move { + router.changed().await; + } + .boxed() + }; + if changed.poll_unpin(cx).is_ready() { + return Poll::Ready(()); + } + self.changed = Some(changed); + Poll::Pending + } +} +impl Clone for SwappableRouterService { + fn clone(&self) -> Self { + Self { + router: self.router.clone(), + changed: None, + } + } +} impl tower_service::Service> for SwappableRouterService where B: axum::body::HttpBody + Send + 'static, @@ -42,15 +73,13 @@ where type Future = >>::Future; #[inline] fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - let mut changed = self.0.changed().boxed(); - if changed.poll_unpin(cx).is_ready() { + if self.changed(cx).is_ready() { return Poll::Ready(Ok(())); } - drop(changed); - tower_service::Service::>::poll_ready(&mut self.0.borrow().clone(), cx) + tower_service::Service::>::poll_ready(&mut self.router(), cx) } fn call(&mut self, req: Request) -> Self::Future { - self.0.borrow().clone().call(req) + self.router().call(req) } } @@ -66,7 +95,10 @@ impl tower_service::Service for SwappableRouter { Poll::Ready(Ok(())) } fn call(&mut self, _: T) -> Self::Future { - ready(Ok(SwappableRouterService(self.0.subscribe()))) + ready(Ok(SwappableRouterService { + router: self.0.subscribe(), + changed: None, + })) } } diff --git a/core/startos/src/net/wifi.rs b/core/startos/src/net/wifi.rs index 298fad71f..056a403de 100644 --- a/core/startos/src/net/wifi.rs +++ b/core/startos/src/net/wifi.rs @@ -43,18 +43,21 @@ pub fn wifi() -> ParentHandler { "add", from_fn_async(add) .no_display() + .with_about("Add wifi ssid and password") .with_call_remote::(), ) .subcommand( "connect", from_fn_async(connect) .no_display() + .with_about("Connect to wifi network") .with_call_remote::(), ) .subcommand( "delete", from_fn_async(delete) .no_display() + .with_about("Remove a wifi network") .with_call_remote::(), ) .subcommand( @@ -64,10 +67,17 @@ pub fn wifi() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_wifi_info(handle.params, result)) }) + .with_about("List wifi info") .with_call_remote::(), ) - .subcommand("country", country::()) - .subcommand("available", available::()) + .subcommand( + "country", + country::().with_about("Command to set country"), + ) + .subcommand( + "available", + available::().with_about("Command to list available wifi networks"), + ) } pub fn available() -> ParentHandler { @@ -76,6 +86,7 @@ pub fn available() -> ParentHandler { from_fn_async(get_available) .with_display_serializable() .with_custom_display_fn(|handle, result| Ok(display_wifi_list(handle.params, result))) + .with_about("List available wifi networks") .with_call_remote::(), ) } @@ -85,6 +96,7 @@ pub fn country() -> ParentHandler { "set", from_fn_async(set_country) .no_display() + .with_about("Set Country") .with_call_remote::(), ) } diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index c99ffb356..b310220b5 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -7,7 +7,7 @@ use clap::builder::ValueParserFactory; use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::InternedString; -use models::PackageId; +use models::{FromStrParser, PackageId}; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -17,7 +17,6 @@ use crate::backup::BackupReport; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::prelude::*; -use crate::util::clap::FromStrParser; use crate::util::serde::HandlerExtSerde; // #[command(subcommands(list, delete, delete_before, create))] @@ -27,24 +26,28 @@ pub fn notification() -> ParentHandler { "list", from_fn_async(list) .with_display_serializable() + .with_about("List notifications") .with_call_remote::(), ) .subcommand( "delete", from_fn_async(delete) .no_display() + .with_about("Delete notification for a given id") .with_call_remote::(), ) .subcommand( "delete-before", from_fn_async(delete_before) .no_display() + .with_about("Delete notifications preceding a given id") .with_call_remote::(), ) .subcommand( "create", from_fn_async(create) .no_display() + .with_about("Persist a newly created notification") .with_call_remote::(), ) } @@ -253,13 +256,13 @@ impl Map for Notifications { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Notification { - package_id: Option, - created_at: DateTime, - code: u32, - level: NotificationLevel, - title: String, - message: String, - data: Value, + pub package_id: Option, + pub created_at: DateTime, + pub code: u32, + pub level: NotificationLevel, + pub title: String, + pub message: String, + pub data: Value, } #[derive(Debug, Serialize, Deserialize)] diff --git a/core/startos/src/os_install/mod.rs b/core/startos/src/os_install/mod.rs index 3d80f6cbd..4cd8aac2d 100644 --- a/core/startos/src/os_install/mod.rs +++ b/core/startos/src/os_install/mod.rs @@ -21,7 +21,7 @@ use crate::disk::OsPartitionInfo; use crate::net::utils::find_eth_iface; use crate::prelude::*; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; -use crate::util::io::{open_file, TmpDir}; +use crate::util::io::{delete_file, open_file, TmpDir}; use crate::util::serde::IoFormat; use crate::util::Invoke; use crate::ARCH; @@ -31,17 +31,19 @@ mod mbr; pub fn install() -> ParentHandler { ParentHandler::new() - .subcommand("disk", disk::()) + .subcommand("disk", disk::().with_about("Command to list disk info")) .subcommand( "execute", from_fn_async(execute::) .no_display() + .with_about("Install StartOS over existing version") .with_call_remote::(), ) .subcommand( "reboot", from_fn_async(reboot) .no_display() + .with_about("Restart the server") .with_call_remote::(), ) } @@ -51,6 +53,7 @@ pub fn disk() -> ParentHandler { "list", from_fn_async(list) .no_display() + .with_about("List disk info") .with_call_remote::(), ) } @@ -147,23 +150,6 @@ pub async fn execute( overwrite |= disk.guid.is_none() && disk.partitions.iter().all(|p| p.guid.is_none()); - if !overwrite - && (disk - .guid - .as_ref() - .map_or(false, |g| g.starts_with("EMBASSY_")) - || disk - .partitions - .iter() - .flat_map(|p| p.guid.as_ref()) - .any(|g| g.starts_with("EMBASSY_"))) - { - return Err(Error::new( - eyre!("installing over versions before 0.3.6 is unsupported"), - ErrorKind::InvalidRequest, - )); - } - let part_info = partition(&mut disk, overwrite).await?; if let Some(efi) = &part_info.efi { @@ -194,18 +180,9 @@ pub async fn execute( { if let Err(e) = async { // cp -r ${guard}/config /tmp/config - if tokio::fs::metadata(guard.path().join("config/upgrade")) - .await - .is_ok() - { - tokio::fs::remove_file(guard.path().join("config/upgrade")).await?; - } - if tokio::fs::metadata(guard.path().join("config/disk.guid")) - .await - .is_ok() - { - tokio::fs::remove_file(guard.path().join("config/disk.guid")).await?; - } + delete_file(guard.path().join("config/upgrade")).await?; + delete_file(guard.path().join("config/overlay/etc/hostname")).await?; + delete_file(guard.path().join("config/disk.guid")).await?; Command::new("cp") .arg("-r") .arg(guard.path().join("config")) diff --git a/core/startos/src/properties.rs b/core/startos/src/properties.rs deleted file mode 100644 index e24b14965..000000000 --- a/core/startos/src/properties.rs +++ /dev/null @@ -1,35 +0,0 @@ -use clap::Parser; -use imbl_value::{json, Value}; -use models::PackageId; -use serde::{Deserialize, Serialize}; - -use crate::context::RpcContext; -use crate::prelude::*; -use crate::Error; - -pub fn display_properties(response: Value) { - println!("{}", response); -} - -#[derive(Deserialize, Serialize, Parser)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct PropertiesParam { - id: PackageId, -} -// #[command(display(display_properties))] -pub async fn properties( - ctx: RpcContext, - PropertiesParam { id }: PropertiesParam, -) -> Result { - match &*ctx.services.get(&id).await { - Some(service) => Ok(json!({ - "version": 2, - "data": service.properties().await? - })), - None => Err(Error::new( - eyre!("Could not find a service with id {id}"), - ErrorKind::NotFound, - )), - } -} diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs index 8125580a4..f3cac9f7e 100644 --- a/core/startos/src/registry/admin.rs +++ b/core/startos/src/registry/admin.rs @@ -18,14 +18,23 @@ use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; pub fn admin_api() -> ParentHandler { ParentHandler::new() - .subcommand("signer", signers_api::()) + .subcommand( + "signer", + signers_api::().with_about("Commands to add or list signers"), + ) .subcommand("add", from_fn_async(add_admin).no_cli()) - .subcommand("add", from_fn_async(cli_add_admin).no_display()) + .subcommand( + "add", + from_fn_async(cli_add_admin) + .no_display() + .with_about("Add admin signer"), + ) .subcommand( "list", from_fn_async(list_admins) .with_display_serializable() .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_about("List admin signers") .with_call_remote::(), ) } @@ -38,6 +47,7 @@ fn signers_api() -> ParentHandler { .with_metadata("admin", Value::Bool(true)) .with_display_serializable() .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_about("List signers") .with_call_remote::(), ) .subcommand( @@ -46,7 +56,17 @@ fn signers_api() -> ParentHandler { .with_metadata("admin", Value::Bool(true)) .no_cli(), ) - .subcommand("add", from_fn_async(cli_add_signer)) + .subcommand( + "add", + from_fn_async(cli_add_signer).with_about("Add signer"), + ) + .subcommand( + "edit", + from_fn_async(edit_signer) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_call_remote::(), + ) } impl Model> { @@ -130,6 +150,64 @@ pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result, + #[arg(short = 'c', long)] + pub add_contact: Vec, + #[arg(short = 'k', long)] + pub add_key: Vec, + #[arg(short = 'C', long)] + pub remove_contact: Vec, + #[arg(short = 'K', long)] + pub remove_key: Vec, +} + +pub async fn edit_signer( + ctx: RegistryContext, + EditSignerParams { + id, + set_name, + add_contact, + add_key, + remove_contact, + remove_key, + }: EditSignerParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_index_mut() + .as_signers_mut() + .as_idx_mut(&id) + .or_not_found(&id)? + .mutate(|s| { + if let Some(name) = set_name { + s.name = name; + } + s.contact.extend(add_contact); + for rm in remove_contact { + let Some((idx, _)) = s.contact.iter().enumerate().find(|(_, c)| *c == &rm) + else { + continue; + }; + s.contact.remove(idx); + } + + s.keys.extend(add_key); + for rm in remove_key { + s.keys.remove(&rm); + } + Ok(()) + }) + }) + .await +} + #[derive(Debug, Deserialize, Serialize, Parser)] #[command(rename_all = "kebab-case")] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/registry/context.rs b/core/startos/src/registry/context.rs index d3eaf3691..16c6465ed 100644 --- a/core/startos/src/registry/context.rs +++ b/core/startos/src/registry/context.rs @@ -255,7 +255,7 @@ impl CallRemote for RpcContext { .header(CONTENT_TYPE, "application/json") .header(ACCEPT, "application/json") .header(CONTENT_LENGTH, body.len()) - .header(DEVICE_INFO_HEADER, DeviceInfo::from(self).to_header_value()) + // .header(DEVICE_INFO_HEADER, DeviceInfo::from(self).to_header_value()) .body(body) .send() .await?; diff --git a/core/startos/src/registry/db.rs b/core/startos/src/registry/db.rs index df39604f1..8de9f8743 100644 --- a/core/startos/src/registry/db.rs +++ b/core/startos/src/registry/db.rs @@ -18,14 +18,24 @@ use crate::util::serde::{apply_expr, HandlerExtSerde}; pub fn db_api() -> ParentHandler { ParentHandler::new() - .subcommand("dump", from_fn_async(cli_dump).with_display_serializable()) + .subcommand( + "dump", + from_fn_async(cli_dump) + .with_display_serializable() + .with_about("Filter/query db to display tables and records"), + ) .subcommand( "dump", from_fn_async(dump) .with_metadata("admin", Value::Bool(true)) .no_cli(), ) - .subcommand("apply", from_fn_async(cli_apply).no_display()) + .subcommand( + "apply", + from_fn_async(cli_apply) + .no_display() + .with_about("Update a db record"), + ) .subcommand( "apply", from_fn_async(apply) diff --git a/core/startos/src/registry/device_info.rs b/core/startos/src/registry/device_info.rs index 172348a10..410e45f8f 100644 --- a/core/startos/src/registry/device_info.rs +++ b/core/startos/src/registry/device_info.rs @@ -15,6 +15,7 @@ use url::Url; use crate::context::RpcContext; use crate::prelude::*; use crate::registry::context::RegistryContext; +use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor}; use crate::util::VersionString; use crate::version::VersionT; @@ -26,12 +27,12 @@ pub struct DeviceInfo { pub os: OsInfo, pub hardware: HardwareInfo, } -impl From<&RpcContext> for DeviceInfo { - fn from(value: &RpcContext) -> Self { - Self { - os: OsInfo::from(value), - hardware: HardwareInfo::from(value), - } +impl DeviceInfo { + pub async fn load(ctx: &RpcContext) -> Result { + Ok(Self { + os: OsInfo::from(ctx), + hardware: HardwareInfo::load(ctx).await?, + }) } } impl DeviceInfo { @@ -44,11 +45,11 @@ impl DeviceInfo { .append_pair("hardware.arch", &*self.hardware.arch) .append_pair("hardware.ram", &self.hardware.ram.to_string()); - for (class, products) in &self.hardware.devices { - for product in products { - url.query_pairs_mut() - .append_pair(&format!("hardware.device.{}", class), product); - } + for device in &self.hardware.devices { + url.query_pairs_mut().append_pair( + &format!("hardware.device.{}", device.class()), + device.product(), + ); } HeaderValue::from_str(url.query().unwrap_or_default()).unwrap() @@ -80,16 +81,20 @@ impl DeviceInfo { devices: identity(query) .split_off("hardware.device.") .into_iter() - .filter_map(|(k, v)| { - k.strip_prefix("hardware.device.") - .map(|k| (k.into(), v.into_owned())) + .filter_map(|(k, v)| match k.strip_prefix("hardware.device.") { + Some("processor") => Some(LshwDevice::Processor(LshwProcessor { + product: v.into_owned(), + })), + Some("display") => Some(LshwDevice::Display(LshwDisplay { + product: v.into_owned(), + })), + Some(class) => { + tracing::warn!("unknown device class: {class}"); + None + } + _ => None, }) - .fold(BTreeMap::new(), |mut acc, (k, v)| { - let mut devs = acc.remove(&k).unwrap_or_default(); - devs.push(v); - acc.insert(k, devs); - acc - }), + .collect(), }, }) } @@ -108,8 +113,8 @@ pub struct OsInfo { impl From<&RpcContext> for OsInfo { fn from(_: &RpcContext) -> Self { Self { - version: crate::version::Current::new().semver(), - compat: crate::version::Current::new().compat().clone(), + version: crate::version::Current::default().semver(), + compat: crate::version::Current::default().compat().clone(), platform: InternedString::intern(&*crate::PLATFORM), } } @@ -122,26 +127,16 @@ pub struct HardwareInfo { pub arch: InternedString, #[ts(type = "number")] pub ram: u64, - #[ts(as = "BTreeMap::>")] - pub devices: BTreeMap>, + pub devices: Vec, } - -impl From<&RpcContext> for HardwareInfo { - fn from(value: &RpcContext) -> Self { - Self { - arch: InternedString::intern(crate::ARCH), - ram: value.hardware.ram, - devices: value - .hardware - .devices - .iter() - .fold(BTreeMap::new(), |mut acc, dev| { - let mut devs = acc.remove(dev.class()).unwrap_or_default(); - devs.push(dev.product().to_owned()); - acc.insert(dev.class().into(), devs); - acc - }), - } +impl HardwareInfo { + pub async fn load(ctx: &RpcContext) -> Result { + let s = ctx.db.peek().await.into_public().into_server_info(); + Ok(Self { + arch: s.as_arch().de()?, + ram: s.as_ram().de()?, + devices: s.as_devices().de()?, + }) } } diff --git a/core/startos/src/registry/info.rs b/core/startos/src/registry/info.rs new file mode 100644 index 000000000..402f0891a --- /dev/null +++ b/core/startos/src/registry/info.rs @@ -0,0 +1,126 @@ +use std::collections::BTreeMap; +use std::path::PathBuf; + +use clap::Parser; +use imbl_value::InternedString; +use itertools::Itertools; +use models::DataUrl; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::registry::context::RegistryContext; +use crate::registry::package::index::Category; +use crate::util::serde::{HandlerExtSerde, WithIoFormat}; + +pub fn info_api() -> ParentHandler> { + ParentHandler::>::new() + .root_handler( + from_fn_async(get_info) + .with_display_serializable() + .with_about("Display registry name, icon, and package categories") + .with_call_remote::(), + ) + .subcommand( + "set-name", + from_fn_async(set_name) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Set the name for the registry") + .with_call_remote::(), + ) + .subcommand( + "set-icon", + from_fn_async(set_icon) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "set-icon", + from_fn_async(cli_set_icon) + .no_display() + .with_about("Set the icon for the registry"), + ) +} + +#[derive(Debug, Default, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RegistryInfo { + pub name: Option, + pub icon: Option>, + #[ts(as = "BTreeMap::")] + pub categories: BTreeMap, +} + +pub async fn get_info(ctx: RegistryContext) -> Result { + let peek = ctx.db.peek().await.into_index(); + Ok(RegistryInfo { + name: peek.as_name().de()?, + icon: peek.as_icon().de()?, + categories: peek.as_package().as_categories().de()?, + }) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct SetNameParams { + pub name: String, +} + +pub async fn set_name( + ctx: RegistryContext, + SetNameParams { name }: SetNameParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| db.as_index_mut().as_name_mut().ser(&Some(name))) + .await +} + +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct SetIconParams { + pub icon: DataUrl<'static>, +} + +pub async fn set_icon( + ctx: RegistryContext, + SetIconParams { icon }: SetIconParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| db.as_index_mut().as_icon_mut().ser(&Some(icon))) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct CliSetIconParams { + pub icon: PathBuf, +} + +pub async fn cli_set_icon( + HandlerArgs { + context: ctx, + parent_method, + method, + params: CliSetIconParams { icon }, + .. + }: HandlerArgs, +) -> Result<(), Error> { + let data_url = DataUrl::from_path(icon).await?; + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), + imbl_value::json!({ + "icon": data_url, + }), + ) + .await?; + Ok(()) +} diff --git a/core/startos/src/registry/mod.rs b/core/startos/src/registry/mod.rs index d34ebb841..0cbbce4e0 100644 --- a/core/startos/src/registry/mod.rs +++ b/core/startos/src/registry/mod.rs @@ -28,6 +28,7 @@ pub mod auth; pub mod context; pub mod db; pub mod device_info; +pub mod info; pub mod os; pub mod package; pub mod signer; @@ -57,52 +58,42 @@ pub async fn get_full_index(ctx: RegistryContext) -> Result { ctx.db.peek().await.into_index().de() } -#[derive(Debug, Default, Deserialize, Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export)] -pub struct RegistryInfo { - pub name: Option, - pub icon: Option>, - #[ts(as = "BTreeMap::")] - pub categories: BTreeMap, -} - -pub async fn get_info(ctx: RegistryContext) -> Result { - let peek = ctx.db.peek().await.into_index(); - Ok(RegistryInfo { - name: peek.as_name().de()?, - icon: peek.as_icon().de()?, - categories: peek.as_package().as_categories().de()?, - }) -} - pub fn registry_api() -> ParentHandler { ParentHandler::new() .subcommand( "index", from_fn_async(get_full_index) .with_display_serializable() + .with_about("List info including registry name and packages") .with_call_remote::(), ) + .subcommand("info", info::info_api::()) + // set info and categories .subcommand( - "info", - from_fn_async(get_info) - .with_display_serializable() - .with_call_remote::(), + "os", + os::os_api::().with_about("Commands related to OS assets and versions"), + ) + .subcommand( + "package", + package::package_api::().with_about("Commands to index, add, or get packages"), + ) + .subcommand( + "admin", + admin::admin_api::().with_about("Commands to add or list admins or signers"), + ) + .subcommand( + "db", + db::db_api::().with_about("Commands to interact with the db i.e. dump and apply"), ) - .subcommand("os", os::os_api::()) - .subcommand("package", package::package_api::()) - .subcommand("admin", admin::admin_api::()) - .subcommand("db", db::db_api::()) } pub fn registry_router(ctx: RegistryContext) -> Router { use axum::extract as x; - use axum::routing::{any, get, post}; + use axum::routing::{any, get}; Router::new() .route("/rpc/*path", { let ctx = ctx.clone(); - post( + any( Server::new(move || ready(Ok(ctx.clone())), registry_api()) .middleware(Cors::new()) .middleware(Auth::new()) diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs index ad0010dca..a3da7047c 100644 --- a/core/startos/src/registry/os/asset/get.rs +++ b/core/startos/src/registry/os/asset/get.rs @@ -26,11 +26,26 @@ use crate::util::io::open_file; pub fn get_api() -> ParentHandler { ParentHandler::new() .subcommand("iso", from_fn_async(get_iso).no_cli()) - .subcommand("iso", from_fn_async(cli_get_os_asset).no_display()) + .subcommand( + "iso", + from_fn_async(cli_get_os_asset) + .no_display() + .with_about("Download iso"), + ) .subcommand("img", from_fn_async(get_img).no_cli()) - .subcommand("img", from_fn_async(cli_get_os_asset).no_display()) + .subcommand( + "img", + from_fn_async(cli_get_os_asset) + .no_display() + .with_about("Download img"), + ) .subcommand("squashfs", from_fn_async(get_squashfs).no_cli()) - .subcommand("squashfs", from_fn_async(cli_get_os_asset).no_display()) + .subcommand( + "squashfs", + from_fn_async(cli_get_os_asset) + .no_display() + .with_about("Download squashfs"), + ) } #[derive(Debug, Deserialize, Serialize, TS)] @@ -94,7 +109,11 @@ pub async fn get_squashfs( pub struct CliGetOsAssetParams { pub version: Version, pub platform: InternedString, - #[arg(long = "download", short = 'd')] + #[arg( + long = "download", + short = 'd', + help = "The path of the directory to download to" + )] pub download: Option, #[arg( long = "reverify", @@ -119,9 +138,15 @@ async fn cli_get_os_asset( .. }: HandlerArgs, ) -> Result, Error> { + let ext = method + .iter() + .last() + .or_else(|| parent_method.iter().last()) + .unwrap_or(&"bin"); + let res = from_value::>( ctx.call_remote::( - &parent_method.into_iter().chain(method).join("."), + &parent_method.iter().chain(&method).join("."), json!({ "version": version, "platform": platform, @@ -133,6 +158,7 @@ async fn cli_get_os_asset( res.validate(SIG_CONTEXT, res.all_signers())?; if let Some(download) = download { + let download = download.join(format!("startos-{version}_{platform}.{ext}")); let mut file = AtomicFile::new(&download, None::<&Path>) .await .with_kind(ErrorKind::Filesystem)?; diff --git a/core/startos/src/registry/os/asset/mod.rs b/core/startos/src/registry/os/asset/mod.rs index ec9d6cae7..52c12341a 100644 --- a/core/startos/src/registry/os/asset/mod.rs +++ b/core/startos/src/registry/os/asset/mod.rs @@ -7,8 +7,21 @@ pub mod sign; pub fn asset_api() -> ParentHandler { ParentHandler::new() .subcommand("add", add::add_api::()) - .subcommand("add", from_fn_async(add::cli_add_asset).no_display()) + .subcommand( + "add", + from_fn_async(add::cli_add_asset) + .no_display() + .with_about("Add asset to registry"), + ) .subcommand("sign", sign::sign_api::()) - .subcommand("sign", from_fn_async(sign::cli_sign_asset).no_display()) - .subcommand("get", get::get_api::()) + .subcommand( + "sign", + from_fn_async(sign::cli_sign_asset) + .no_display() + .with_about("Sign file and add to registry index"), + ) + .subcommand( + "get", + get::get_api::().with_about("Commands to download image, iso, or squashfs files"), + ) } diff --git a/core/startos/src/registry/os/mod.rs b/core/startos/src/registry/os/mod.rs index 64ce44eaf..a1d18cb03 100644 --- a/core/startos/src/registry/os/mod.rs +++ b/core/startos/src/registry/os/mod.rs @@ -15,8 +15,16 @@ pub fn os_api() -> ParentHandler { "index", from_fn_async(index::get_os_index) .with_display_serializable() + .with_about("List index of OS versions") .with_call_remote::(), ) - .subcommand("asset", asset::asset_api::()) - .subcommand("version", version::version_api::()) + .subcommand( + "asset", + asset::asset_api::().with_about("Commands to add, sign, or get registry assets"), + ) + .subcommand( + "version", + version::version_api::() + .with_about("Commands to add, remove, or list versions or version signers"), + ) } diff --git a/core/startos/src/registry/os/version/mod.rs b/core/startos/src/registry/os/version/mod.rs index 4c0568a80..8e4349ed9 100644 --- a/core/startos/src/registry/os/version/mod.rs +++ b/core/startos/src/registry/os/version/mod.rs @@ -26,6 +26,7 @@ pub fn version_api() -> ParentHandler { .with_metadata("admin", Value::Bool(true)) .with_metadata("get_signer", Value::Bool(true)) .no_display() + .with_about("Add OS version") .with_call_remote::(), ) .subcommand( @@ -33,9 +34,13 @@ pub fn version_api() -> ParentHandler { from_fn_async(remove_version) .with_metadata("admin", Value::Bool(true)) .no_display() + .with_about("Remove OS version") .with_call_remote::(), ) - .subcommand("signer", signer::signer_api::()) + .subcommand( + "signer", + signer::signer_api::().with_about("Add, remove, and list version signers"), + ) .subcommand( "get", from_fn_async(get_version) @@ -43,6 +48,7 @@ pub fn version_api() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_version_info(handle.params, result)) }) + .with_about("Get OS versions and related version info") .with_call_remote::(), ) } diff --git a/core/startos/src/registry/os/version/signer.rs b/core/startos/src/registry/os/version/signer.rs index 51f7c6719..c72bb5ef4 100644 --- a/core/startos/src/registry/os/version/signer.rs +++ b/core/startos/src/registry/os/version/signer.rs @@ -21,6 +21,7 @@ pub fn signer_api() -> ParentHandler { from_fn_async(add_version_signer) .with_metadata("admin", Value::Bool(true)) .no_display() + .with_about("Add version signer") .with_call_remote::(), ) .subcommand( @@ -28,6 +29,7 @@ pub fn signer_api() -> ParentHandler { from_fn_async(remove_version_signer) .with_metadata("admin", Value::Bool(true)) .no_display() + .with_about("Remove version signer") .with_call_remote::(), ) .subcommand( @@ -35,6 +37,7 @@ pub fn signer_api() -> ParentHandler { from_fn_async(list_version_signers) .with_display_serializable() .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_about("List version signers and related signer info") .with_call_remote::(), ) } diff --git a/core/startos/src/registry/package/category.rs b/core/startos/src/registry/package/category.rs new file mode 100644 index 000000000..97b0fb227 --- /dev/null +++ b/core/startos/src/registry/package/category.rs @@ -0,0 +1,147 @@ +use std::collections::BTreeMap; + +use clap::Parser; +use imbl_value::InternedString; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::registry::context::RegistryContext; +use crate::registry::package::index::Category; +use crate::s9pk::manifest::Description; +use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; + +pub fn category_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_category) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Add a category to the registry") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_category) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Remove a category from the registry") + .with_call_remote::(), + ) + .subcommand( + "list", + from_fn_async(list_categories) + .with_display_serializable() + .with_custom_display_fn(|params, categories| { + Ok(display_categories(params.params, categories)) + }) + .with_call_remote::(), + ) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct AddCategoryParams { + #[ts(type = "string")] + pub id: InternedString, + pub name: String, + #[arg(short, long, help = "Short description for the category")] + pub short: String, + #[arg(short, long, help = "Long description for the category")] + pub long: String, +} + +pub async fn add_category( + ctx: RegistryContext, + AddCategoryParams { + id, + name, + short, + long, + }: AddCategoryParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_index_mut() + .as_package_mut() + .as_categories_mut() + .insert( + &id, + &Category { + name, + description: Description { short, long }, + }, + ) + }) + .await?; + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemoveCategoryParams { + #[ts(type = "string")] + pub id: InternedString, +} + +pub async fn remove_category( + ctx: RegistryContext, + RemoveCategoryParams { id }: RemoveCategoryParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_index_mut() + .as_package_mut() + .as_categories_mut() + .remove(&id) + }) + .await?; + Ok(()) +} + +pub async fn list_categories( + ctx: RegistryContext, +) -> Result, Error> { + ctx.db + .peek() + .await + .into_index() + .into_package() + .into_categories() + .de() +} + +pub fn display_categories( + params: WithIoFormat, + categories: BTreeMap, +) { + use prettytable::*; + + if let Some(format) = params.format { + return display_serializable(format, categories); + } + + let mut table = Table::new(); + table.add_row(row![bc => + "ID", + "NAME", + "SHORT DESCRIPTION", + "LONG DESCRIPTION", + ]); + for (id, info) in categories { + table.add_row(row![ + &*id, + &info.name, + &info.description.short, + &info.description.long, + ]); + } + table.print_tty(false).unwrap(); +} diff --git a/core/startos/src/registry/package/index.rs b/core/startos/src/registry/package/index.rs index 12a17f634..428200165 100644 --- a/core/startos/src/registry/package/index.rs +++ b/core/startos/src/registry/package/index.rs @@ -180,14 +180,13 @@ impl Model { return Ok(false); } } - for (class, regex) in hw.device { + for device_filter in hw.device { if !device_info .hardware .devices - .get(&*class) - .unwrap_or(&Vec::new()) .iter() - .any(|product| regex.as_ref().is_match(product)) + .filter(|d| d.class() == &*device_filter.class) + .any(|d| device_filter.pattern.as_ref().is_match(d.product())) { return Ok(false); } diff --git a/core/startos/src/registry/package/mod.rs b/core/startos/src/registry/package/mod.rs index cb2d317f9..74d244deb 100644 --- a/core/startos/src/registry/package/mod.rs +++ b/core/startos/src/registry/package/mod.rs @@ -5,8 +5,10 @@ use crate::prelude::*; use crate::util::serde::HandlerExtSerde; pub mod add; +pub mod category; pub mod get; pub mod index; +pub mod signer; pub fn package_api() -> ParentHandler { ParentHandler::new() @@ -14,6 +16,7 @@ pub fn package_api() -> ParentHandler { "index", from_fn_async(index::get_package_index) .with_display_serializable() + .with_about("List packages and categories") .with_call_remote::(), ) .subcommand( @@ -22,7 +25,16 @@ pub fn package_api() -> ParentHandler { .with_metadata("get_signer", Value::Bool(true)) .no_cli(), ) - .subcommand("add", from_fn_async(add::cli_add_package).no_display()) + .subcommand( + "add", + from_fn_async(add::cli_add_package) + .no_display() + .with_about("Add package to registry index"), + ) + .subcommand( + "signer", + signer::signer_api::().with_about("Add, remove, and list package signers"), + ) .subcommand( "get", from_fn_async(get::get_package) @@ -31,6 +43,12 @@ pub fn package_api() -> ParentHandler { .with_custom_display_fn(|handle, result| { get::display_package_info(handle.params, result) }) + .with_about("List installation candidate package(s)") .with_call_remote::(), ) + .subcommand( + "category", + category::category_api::() + .with_about("Update the categories for packages on the registry"), + ) } diff --git a/core/startos/src/registry/package/signer.rs b/core/startos/src/registry/package/signer.rs new file mode 100644 index 000000000..56bfc9b1c --- /dev/null +++ b/core/startos/src/registry/package/signer.rs @@ -0,0 +1,133 @@ +use std::collections::BTreeMap; + +use clap::Parser; +use models::PackageId; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::registry::admin::display_signers; +use crate::registry::context::RegistryContext; +use crate::registry::signer::SignerInfo; +use crate::rpc_continuations::Guid; +use crate::util::serde::HandlerExtSerde; + +pub fn signer_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_package_signer) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Add package signer") + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_package_signer) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Remove package signer") + .with_call_remote::(), + ) + .subcommand( + "list", + from_fn_async(list_package_signers) + .with_display_serializable() + .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_about("List package signers and related signer info") + .with_call_remote::(), + ) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct PackageSignerParams { + pub id: PackageId, + pub signer: Guid, +} + +pub async fn add_package_signer( + ctx: RegistryContext, + PackageSignerParams { id, signer }: PackageSignerParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + ensure_code!( + db.as_index().as_signers().contains_key(&signer)?, + ErrorKind::InvalidRequest, + "unknown signer {signer}" + ); + + db.as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&id) + .or_not_found(&id)? + .as_authorized_mut() + .mutate(|s| Ok(s.insert(signer)))?; + + Ok(()) + }) + .await +} + +pub async fn remove_package_signer( + ctx: RegistryContext, + PackageSignerParams { id, signer }: PackageSignerParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + if !db + .as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&id) + .or_not_found(&id)? + .as_authorized_mut() + .mutate(|s| Ok(s.remove(&signer)))? + { + return Err(Error::new( + eyre!("signer {signer} is not authorized to sign for {id}"), + ErrorKind::NotFound, + )); + } + + Ok(()) + }) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct ListPackageSignersParams { + pub id: PackageId, +} + +pub async fn list_package_signers( + ctx: RegistryContext, + ListPackageSignersParams { id }: ListPackageSignersParams, +) -> Result, Error> { + let db = ctx.db.peek().await; + db.as_index() + .as_package() + .as_packages() + .as_idx(&id) + .or_not_found(&id)? + .as_authorized() + .de()? + .into_iter() + .filter_map(|guid| { + db.as_index() + .as_signers() + .as_idx(&guid) + .map(|s| s.de().map(|s| (guid, s))) + }) + .collect() +} diff --git a/core/startos/src/registry/signer/mod.rs b/core/startos/src/registry/signer/mod.rs index 99b23b88e..137c40f0f 100644 --- a/core/startos/src/registry/signer/mod.rs +++ b/core/startos/src/registry/signer/mod.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; use itertools::Itertools; +use models::FromStrParser; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; @@ -10,7 +11,6 @@ use url::Url; use crate::prelude::*; use crate::registry::signer::commitment::Digestable; use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; -use crate::util::clap::FromStrParser; pub mod commitment; pub mod sign; @@ -25,7 +25,7 @@ pub struct SignerInfo { pub keys: HashSet, } -#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[ts(export)] // TODO: better types diff --git a/core/startos/src/registry/signer/sign/mod.rs b/core/startos/src/registry/signer/sign/mod.rs index a29109864..6a95a2490 100644 --- a/core/startos/src/registry/signer/sign/mod.rs +++ b/core/startos/src/registry/signer/sign/mod.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use ::ed25519::pkcs8::BitStringRef; use clap::builder::ValueParserFactory; use der::referenced::OwnedToRef; +use models::FromStrParser; use pkcs8::der::AnyRef; use pkcs8::{PrivateKeyInfo, SubjectPublicKeyInfo}; use serde::{Deserialize, Serialize}; @@ -13,7 +14,6 @@ use ts_rs::TS; use crate::prelude::*; use crate::registry::signer::commitment::Digestable; use crate::registry::signer::sign::ed25519::Ed25519; -use crate::util::clap::FromStrParser; use crate::util::serde::{deserialize_from_str, serialize_display}; pub mod ed25519; diff --git a/core/startos/src/rpc_continuations.rs b/core/startos/src/rpc_continuations.rs index 043130b69..4614f8fa6 100644 --- a/core/startos/src/rpc_continuations.rs +++ b/core/startos/src/rpc_continuations.rs @@ -13,12 +13,12 @@ use futures::future::BoxFuture; use futures::{Future, FutureExt}; use helpers::TimedResource; use imbl_value::InternedString; +use models::FromStrParser; use tokio::sync::{broadcast, Mutex as AsyncMutex}; use ts_rs::TS; #[allow(unused_imports)] use crate::prelude::*; -use crate::util::clap::FromStrParser; use crate::util::new_guid; #[derive( diff --git a/core/startos/src/s9pk/rpc.rs b/core/startos/src/s9pk/rpc.rs index 92f952077..98b46ac77 100644 --- a/core/startos/src/s9pk/rpc.rs +++ b/core/startos/src/s9pk/rpc.rs @@ -21,11 +21,16 @@ pub const SKIP_ENV: &[&str] = &["TERM", "container", "HOME", "HOSTNAME"]; pub fn s9pk() -> ParentHandler { ParentHandler::new() - .subcommand("pack", from_fn_async(super::v2::pack::pack).no_display()) + .subcommand( + "pack", + from_fn_async(super::v2::pack::pack) + .no_display() + .with_about("Package s9pk input files into valid s9pk"), + ) .subcommand( "list-ingredients", - from_fn_async(super::v2::pack::list_ingredients).with_custom_display_fn( - |_, ingredients| { + from_fn_async(super::v2::pack::list_ingredients) + .with_custom_display_fn(|_, ingredients| { ingredients .into_iter() .map(Some) @@ -39,12 +44,23 @@ pub fn s9pk() -> ParentHandler { }); println!(); Ok(()) - }, - ), + }) + .with_about("List paths of package ingredients"), + ) + .subcommand( + "edit", + edit().with_about("Commands to add an image to an s9pk or edit the manifest"), + ) + .subcommand( + "inspect", + inspect().with_about("Commands to display file paths, file contents, or manifest"), + ) + .subcommand( + "convert", + from_fn_async(convert) + .no_display() + .with_about("Convert s9pk from v1 to v2"), ) - .subcommand("edit", edit()) - .subcommand("inspect", inspect()) - .subcommand("convert", from_fn_async(convert).no_display()) } #[derive(Deserialize, Serialize, Parser)] @@ -59,13 +75,15 @@ fn edit() -> ParentHandler { "add-image", from_fn_async(add_image) .with_inherited(only_parent) - .no_display(), + .no_display() + .with_about("Add image to s9pk"), ) .subcommand( "manifest", from_fn_async(edit_manifest) .with_inherited(only_parent) - .with_display_serializable(), + .with_display_serializable() + .with_about("Edit s9pk manifest"), ) } @@ -76,17 +94,22 @@ fn inspect() -> ParentHandler { "file-tree", from_fn_async(file_tree) .with_inherited(only_parent) - .with_display_serializable(), + .with_display_serializable() + .with_about("Display list of paths"), ) .subcommand( "cat", - from_fn_async(cat).with_inherited(only_parent).no_display(), + from_fn_async(cat) + .with_inherited(only_parent) + .no_display() + .with_about("Display file contents"), ) .subcommand( "manifest", from_fn_async(inspect_manifest) .with_inherited(only_parent) - .with_display_serializable(), + .with_display_serializable() + .with_about("Display s9pk manifest"), ) } diff --git a/core/startos/src/s9pk/v1/manifest.rs b/core/startos/src/s9pk/v1/manifest.rs index 4a9956f9f..31821ad68 100644 --- a/core/startos/src/s9pk/v1/manifest.rs +++ b/core/startos/src/s9pk/v1/manifest.rs @@ -1,7 +1,8 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; use exver::{Version, VersionRange}; +use imbl_value::InternedString; use indexmap::IndexMap; pub use models::PackageId; use models::{ActionId, HealthCheckId, ImageId, VolumeId}; @@ -10,8 +11,8 @@ use url::Url; use crate::prelude::*; use crate::s9pk::git_hash::GitHash; -use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements}; -use crate::util::serde::{Duration, IoFormat}; +use crate::s9pk::manifest::{Alerts, Description}; +use crate::util::serde::{Duration, IoFormat, Regex}; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] @@ -21,7 +22,7 @@ pub struct Manifest { #[serde(default)] pub git_hash: Option, pub title: String, - pub version: exver::emver::Version, + pub version: String, pub description: Description, #[serde(default)] pub assets: Assets, @@ -192,6 +193,15 @@ impl DependencyRequirement { } } +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HardwareRequirements { + #[serde(default)] + pub device: BTreeMap, + pub ram: Option, + pub arch: Option>, +} + #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct Assets { diff --git a/core/startos/src/s9pk/v2/compat.rs b/core/startos/src/s9pk/v2/compat.rs index 970cefb0c..db8cbc414 100644 --- a/core/startos/src/s9pk/v2/compat.rs +++ b/core/startos/src/s9pk/v2/compat.rs @@ -1,5 +1,6 @@ use std::collections::{BTreeMap, BTreeSet}; use std::path::Path; +use std::str::FromStr; use std::sync::Arc; use exver::{ExtendedVersion, VersionRange}; @@ -9,7 +10,7 @@ use tokio::process::Command; use crate::dependencies::{DepInfo, Dependencies}; use crate::prelude::*; -use crate::s9pk::manifest::Manifest; +use crate::s9pk::manifest::{DeviceFilter, Manifest}; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::source::TmpSource; use crate::s9pk::merkle_archive::{Entry, MerkleArchive}; @@ -44,9 +45,9 @@ impl S9pk> { // manifest.json let manifest_raw = reader.manifest().await?; let manifest = from_value::(manifest_raw.clone())?; - let mut new_manifest = Manifest::from(manifest.clone()); + let mut new_manifest = Manifest::try_from(manifest.clone())?; - let images: BTreeMap = manifest + let images: BTreeSet<(ImageId, bool)> = manifest .package_procedures() .filter_map(|p| { if let PackageProcedure::Docker(p) = p { @@ -89,8 +90,6 @@ impl S9pk> { // images for arch in reader.docker_arches().await? { - let images_dir = tmp_dir.join("images").join(&arch); - tokio::fs::create_dir_all(&images_dir).await?; Command::new(CONTAINER_TOOL) .arg("load") .input(Some(&mut reader.docker_images(&arch).await?)) @@ -194,13 +193,18 @@ impl S9pk> { } } -impl From for Manifest { - fn from(value: ManifestV1) -> Self { +impl TryFrom for Manifest { + type Error = Error; + fn try_from(value: ManifestV1) -> Result { let default_url = value.upstream_repo.clone(); - Self { + Ok(Self { id: value.id, title: value.title.into(), - version: ExtendedVersion::from(value.version).into(), + version: ExtendedVersion::from( + exver::emver::Version::from_str(&value.version) + .with_kind(ErrorKind::Deserialization)?, + ) + .into(), satisfies: BTreeSet::new(), release_notes: value.release_notes, can_migrate_from: VersionRange::any(), @@ -242,10 +246,25 @@ impl From for Manifest { }) .collect(), ), - hardware_requirements: value.hardware_requirements, + hardware_requirements: super::manifest::HardwareRequirements { + arch: value.hardware_requirements.arch, + ram: value.hardware_requirements.ram, + device: value + .hardware_requirements + .device + .into_iter() + .map(|(class, product)| DeviceFilter { + pattern_description: format!( + "a {class} device matching the expression {}", + product.as_ref() + ), + class, + pattern: product, + }) + .collect(), + }, git_hash: value.git_hash, os_version: value.eos_version, - has_config: value.config.is_some(), - } + }) } } diff --git a/core/startos/src/s9pk/v2/manifest.rs b/core/startos/src/s9pk/v2/manifest.rs index 1f24a0b73..85f3cd796 100644 --- a/core/startos/src/s9pk/v2/manifest.rs +++ b/core/startos/src/s9pk/v2/manifest.rs @@ -22,7 +22,7 @@ use crate::util::VersionString; use crate::version::{Current, VersionT}; fn current_version() -> Version { - Current::new().semver() + Current::default().semver() } #[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] @@ -68,8 +68,6 @@ pub struct Manifest { #[serde(default = "current_version")] #[ts(type = "string")] pub os_version: Version, - #[serde(default = "const_true")] - pub has_config: bool, } impl Manifest { pub fn validate_for<'a, T: Clone>( @@ -163,14 +161,24 @@ impl Manifest { #[ts(export)] pub struct HardwareRequirements { #[serde(default)] - #[ts(type = "{ display?: string, processor?: string }")] - pub device: BTreeMap, // TODO: array + pub device: Vec, #[ts(type = "number | null")] pub ram: Option, #[ts(type = "string[] | null")] pub arch: Option>, } +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct DeviceFilter { + #[ts(type = "\"processor\" | \"display\"")] + pub class: InternedString, + #[ts(type = "string")] + pub pattern: Regex, + pub pattern_description: String, +} + #[derive(Clone, Debug, Deserialize, Serialize, TS)] #[ts(export)] pub struct Description { diff --git a/core/startos/src/s9pk/v2/mod.rs b/core/startos/src/s9pk/v2/mod.rs index e012480af..7a94c0d79 100644 --- a/core/startos/src/s9pk/v2/mod.rs +++ b/core/startos/src/s9pk/v2/mod.rs @@ -102,6 +102,19 @@ impl S9pk { }) } + pub fn new_with_manifest( + archive: MerkleArchive, + size: Option, + manifest: Manifest, + ) -> Self { + Self { + manifest, + manifest_dirty: true, + archive, + size, + } + } + pub fn validate_and_filter(&mut self, arch: Option<&str>) -> Result<(), Error> { let filter = self.manifest.validate_for(arch, self.archive.contents())?; filter.keep_checked(self.archive.contents_mut()) @@ -263,10 +276,10 @@ impl> + FileSource + Clone> S9pk { impl S9pk> { #[instrument(skip_all)] - pub async fn deserialize( + pub async fn archive( source: &S, commitment: Option<&MerkleArchiveCommitment>, - ) -> Result { + ) -> Result>, Error> { use tokio::io::AsyncReadExt; let mut header = source @@ -283,9 +296,14 @@ impl S9pk> { ErrorKind::ParseS9pk, "Invalid Magic or Unexpected Version" ); - - let mut archive = - MerkleArchive::deserialize(source, SIG_CONTEXT, &mut header, commitment).await?; + MerkleArchive::deserialize(source, SIG_CONTEXT, &mut header, commitment).await + } + #[instrument(skip_all)] + pub async fn deserialize( + source: &S, + commitment: Option<&MerkleArchiveCommitment>, + ) -> Result { + let mut archive = Self::archive(source, commitment).await?; archive.sort_by(|a, b| match (priority(a), priority(b)) { (Some(a), Some(b)) => a.cmp(&b), diff --git a/core/startos/src/s9pk/v2/pack.rs b/core/startos/src/s9pk/v2/pack.rs index 06a47b9d0..67be558f1 100644 --- a/core/startos/src/s9pk/v2/pack.rs +++ b/core/startos/src/s9pk/v2/pack.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -60,14 +60,20 @@ impl SqfsDir { .get_or_try_init(|| async move { let guid = Guid::new(); let path = self.tmpdir.join(guid.as_ref()).with_extension("squashfs"); - let mut cmd = Command::new("mksquashfs"); if self.path.extension().and_then(|s| s.to_str()) == Some("tar") { - cmd.arg("-tar"); + tar2sqfs(&self.path)? + .input(Some(&mut open_file(&self.path).await?)) + .invoke(ErrorKind::Filesystem) + .await?; + } else { + Command::new("mksquashfs") + .arg(&self.path) + .arg(&path) + .arg("-quiet") + .invoke(ErrorKind::Filesystem) + .await?; } - cmd.arg(&self.path) - .arg(&path) - .invoke(ErrorKind::Filesystem) - .await?; + Ok(MultiCursorFile::from( open_file(&path) .await @@ -288,6 +294,7 @@ impl TryFrom for ImageConfig { ImageSource::DockerBuild { dockerfile: value.dockerfile, workdir: value.workdir, + build_args: None, } } else if let Some(tag) = value.docker_tag { ImageSource::DockerTag(tag) @@ -331,6 +338,15 @@ impl clap::FromArgMatches for ImageConfig { } } +#[derive(Debug, Clone, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +#[ts(export)] +pub enum BuildArg { + String(String), + EnvVar { env: String }, +} + #[derive(Debug, Clone, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] @@ -340,6 +356,9 @@ pub enum ImageSource { DockerBuild { workdir: Option, dockerfile: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[ts(optional)] + build_args: Option>, }, DockerTag(String), } @@ -378,6 +397,7 @@ impl ImageSource { ImageSource::DockerBuild { workdir, dockerfile, + build_args, } => { let workdir = workdir.as_deref().unwrap_or(Path::new(".")); let dockerfile = dockerfile @@ -392,7 +412,8 @@ impl ImageSource { }; // docker buildx build ${path} -o type=image,name=start9/${id} let tag = format!("start9/{id}/{image_id}:{}", new_guid()); - Command::new(CONTAINER_TOOL) + let mut command = Command::new(CONTAINER_TOOL); + command .arg("build") .arg(workdir) .arg("-f") @@ -400,6 +421,29 @@ impl ImageSource { .arg("-t") .arg(&tag) .arg(&docker_platform) + .arg("--build-arg") + .arg(format!("ARCH={}", arch)); + + // add build arguments + if let Some(build_args) = build_args { + for (key, value) in build_args { + let build_arg_value = match value { + BuildArg::String(val) => val.to_string(), + BuildArg::EnvVar { env } => { + match std::env::var(&env) { + Ok(val) => val, + Err(_) => continue, // skip if env var not set or invalid + } + } + }; + + command + .arg("--build-arg") + .arg(format!("{}={}", key, build_arg_value)); + } + } + + command .arg("-o") .arg("type=docker,dest=-") .capture(false) @@ -507,7 +551,7 @@ impl ImageSource { Command::new(CONTAINER_TOOL) .arg("export") .arg(container.trim()) - .pipe(Command::new("mksquashfs").arg("-").arg(&dest).arg("-tar")) + .pipe(&mut tar2sqfs(&dest)?) .capture(false) .invoke(ErrorKind::Docker) .await?; @@ -529,6 +573,38 @@ impl ImageSource { } } +fn tar2sqfs(dest: impl AsRef) -> Result { + let dest = dest.as_ref(); + + Ok({ + #[cfg(target_os = "linux")] + { + let mut command = Command::new("tar2sqfs"); + command.arg("-q").arg(&dest); + command + } + #[cfg(target_os = "macos")] + { + let directory = dest + .parent() + .unwrap_or_else(|| Path::new("/")) + .to_path_buf(); + let mut command = Command::new(CONTAINER_TOOL); + command + .arg("run") + .arg("-i") + .arg("--rm") + .arg("-v") + .arg(format!("{}:/data:rw", directory.display())) + .arg("ghcr.io/start9labs/sdk/utils:latest") + .arg("tar2sqfs") + .arg("-q") + .arg(Path::new("/data").join(&dest.file_name().unwrap_or_default())); + command + } + }) +} + #[derive(Debug, Clone, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] diff --git a/core/startos/src/service/action.rs b/core/startos/src/service/action.rs index 6c5ac4eab..4068d5ad8 100644 --- a/core/startos/src/service/action.rs +++ b/core/startos/src/service/action.rs @@ -1,60 +1,179 @@ +use std::collections::BTreeMap; use std::time::Duration; -use models::{ActionId, ProcedureName}; +use imbl_value::json; +use models::{ActionId, PackageId, ProcedureName, ReplayId}; -use crate::action::ActionResult; +use crate::action::{ActionInput, ActionResult}; +use crate::db::model::package::{ActionRequestCondition, ActionRequestEntry, ActionRequestInput}; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::service::config::GetConfig; -use crate::service::dependencies::DependencyConfig; use crate::service::{Service, ServiceActor}; use crate::util::actor::background::BackgroundJobQueue; use crate::util::actor::{ConflictBuilder, Handler}; +use crate::util::serde::is_partial_of; -pub(super) struct Action { +pub(super) struct GetActionInput { + id: ActionId, +} +impl Handler for ServiceActor { + type Response = Result, Error>; + fn conflicts_with(_: &GetActionInput) -> ConflictBuilder { + ConflictBuilder::nothing() + } + async fn handle( + &mut self, + id: Guid, + GetActionInput { id: action_id }: GetActionInput, + _: &BackgroundJobQueue, + ) -> Self::Response { + let container = &self.0.persistent_container; + container + .execute::>( + id, + ProcedureName::GetActionInput(action_id), + Value::Null, + Some(Duration::from_secs(30)), + ) + .await + .with_kind(ErrorKind::Action) + } +} + +impl Service { + pub async fn get_action_input( + &self, + id: Guid, + action_id: ActionId, + ) -> Result, Error> { + if !self + .seed + .ctx + .db + .peek() + .await + .as_public() + .as_package_data() + .as_idx(&self.seed.id) + .or_not_found(&self.seed.id)? + .as_actions() + .as_idx(&action_id) + .or_not_found(&action_id)? + .as_has_input() + .de()? + { + return Ok(None); + } + self.actor + .send(id, GetActionInput { id: action_id }) + .await? + } +} + +pub fn update_requested_actions( + requested_actions: &mut BTreeMap, + package_id: &PackageId, + action_id: &ActionId, + input: &Value, + was_run: bool, +) { + requested_actions.retain(|_, v| { + if &v.request.package_id != package_id || &v.request.action_id != action_id { + return true; + } + if let Some(when) = &v.request.when { + match &when.condition { + ActionRequestCondition::InputNotMatches => match &v.request.input { + Some(ActionRequestInput::Partial { value }) => { + if is_partial_of(value, input) { + if when.once { + return !was_run; + } else { + v.active = false; + } + } else { + v.active = true; + } + } + None => { + tracing::error!( + "action request exists in an invalid state {:?}", + v.request + ); + } + }, + } + true + } else { + !was_run + } + }) +} + +pub(super) struct RunAction { id: ActionId, input: Value, } -impl Handler for ServiceActor { - type Response = Result; - fn conflicts_with(_: &Action) -> ConflictBuilder { - ConflictBuilder::everything() - .except::() - .except::() +impl Handler for ServiceActor { + type Response = Result, Error>; + fn conflicts_with(_: &RunAction) -> ConflictBuilder { + ConflictBuilder::everything().except::() } async fn handle( &mut self, id: Guid, - Action { + RunAction { id: action_id, input, - }: Action, + }: RunAction, _: &BackgroundJobQueue, ) -> Self::Response { let container = &self.0.persistent_container; - container - .execute::( + let result = container + .execute::>( id, - ProcedureName::RunAction(action_id), - input, + ProcedureName::RunAction(action_id.clone()), + json!({ + "input": input, + }), Some(Duration::from_secs(30)), ) .await - .with_kind(ErrorKind::Action) + .with_kind(ErrorKind::Action)?; + let package_id = &self.0.id; + self.0 + .ctx + .db + .mutate(|db| { + for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { + pde.as_requested_actions_mut().mutate(|requested_actions| { + Ok(update_requested_actions( + requested_actions, + package_id, + &action_id, + &input, + true, + )) + })?; + } + Ok(()) + }) + .await?; + Ok(result) } } impl Service { - pub async fn action( + pub async fn run_action( &self, id: Guid, action_id: ActionId, input: Value, - ) -> Result { + ) -> Result, Error> { self.actor .send( id, - Action { + RunAction { id: action_id, input, }, diff --git a/core/startos/src/service/config.rs b/core/startos/src/service/config.rs deleted file mode 100644 index faa70fc41..000000000 --- a/core/startos/src/service/config.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::time::Duration; - -use models::ProcedureName; - -use crate::config::action::ConfigRes; -use crate::config::ConfigureContext; -use crate::prelude::*; -use crate::rpc_continuations::Guid; -use crate::service::dependencies::DependencyConfig; -use crate::service::{Service, ServiceActor}; -use crate::util::actor::background::BackgroundJobQueue; -use crate::util::actor::{ConflictBuilder, Handler}; -use crate::util::serde::NoOutput; - -pub(super) struct Configure(ConfigureContext); -impl Handler for ServiceActor { - type Response = Result<(), Error>; - fn conflicts_with(_: &Configure) -> ConflictBuilder { - ConflictBuilder::everything().except::() - } - async fn handle( - &mut self, - id: Guid, - Configure(ConfigureContext { timeout, config }): Configure, - _: &BackgroundJobQueue, - ) -> Self::Response { - let container = &self.0.persistent_container; - let package_id = &self.0.id; - - container - .execute::(id, ProcedureName::SetConfig, to_value(&config)?, timeout) - .await - .with_kind(ErrorKind::ConfigRulesViolation)?; - self.0 - .ctx - .db - .mutate(move |db| { - db.as_public_mut() - .as_package_data_mut() - .as_idx_mut(package_id) - .or_not_found(package_id)? - .as_status_mut() - .as_configured_mut() - .ser(&true) - }) - .await?; - Ok(()) - } -} - -pub(super) struct GetConfig; -impl Handler for ServiceActor { - type Response = Result; - fn conflicts_with(_: &GetConfig) -> ConflictBuilder { - ConflictBuilder::nothing().except::() - } - async fn handle(&mut self, id: Guid, _: GetConfig, _: &BackgroundJobQueue) -> Self::Response { - let container = &self.0.persistent_container; - container - .execute::( - id, - ProcedureName::GetConfig, - Value::Null, - Some(Duration::from_secs(30)), // TODO timeout - ) - .await - .with_kind(ErrorKind::ConfigRulesViolation) - } -} - -impl Service { - pub async fn configure(&self, id: Guid, ctx: ConfigureContext) -> Result<(), Error> { - self.actor.send(id, Configure(ctx)).await? - } - pub async fn get_config(&self, id: Guid) -> Result { - self.actor.send(id, GetConfig).await? - } -} diff --git a/core/startos/src/service/control.rs b/core/startos/src/service/control.rs index 7c4bdf815..8b920bb32 100644 --- a/core/startos/src/service/control.rs +++ b/core/startos/src/service/control.rs @@ -1,7 +1,6 @@ use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::service::config::GetConfig; -use crate::service::dependencies::DependencyConfig; +use crate::service::action::RunAction; use crate::service::start_stop::StartStop; use crate::service::transition::TransitionKind; use crate::service::{Service, ServiceActor}; @@ -12,9 +11,7 @@ pub(super) struct Start; impl Handler for ServiceActor { type Response = (); fn conflicts_with(_: &Start) -> ConflictBuilder { - ConflictBuilder::everything() - .except::() - .except::() + ConflictBuilder::everything().except::() } async fn handle(&mut self, _: Guid, _: Start, _: &BackgroundJobQueue) -> Self::Response { self.0.persistent_container.state.send_modify(|x| { @@ -33,9 +30,7 @@ struct Stop; impl Handler for ServiceActor { type Response = (); fn conflicts_with(_: &Stop) -> ConflictBuilder { - ConflictBuilder::everything() - .except::() - .except::() + ConflictBuilder::everything().except::() } async fn handle(&mut self, _: Guid, _: Stop, _: &BackgroundJobQueue) -> Self::Response { let mut transition_state = None; diff --git a/core/startos/src/service/dependencies.rs b/core/startos/src/service/dependencies.rs deleted file mode 100644 index e8c6f07c4..000000000 --- a/core/startos/src/service/dependencies.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::time::Duration; - -use imbl_value::json; -use models::{PackageId, ProcedureName}; - -use crate::prelude::*; -use crate::rpc_continuations::Guid; -use crate::service::{Service, ServiceActor, ServiceActorSeed}; -use crate::util::actor::background::BackgroundJobQueue; -use crate::util::actor::{ConflictBuilder, Handler}; -use crate::Config; - -impl ServiceActorSeed { - async fn dependency_config( - &self, - id: Guid, - dependency_id: PackageId, - remote_config: Option, - ) -> Result, Error> { - let container = &self.persistent_container; - container - .sanboxed::>( - id.clone(), - ProcedureName::UpdateDependency(dependency_id.clone()), - json!({ - "queryResults": container - .execute::( - id, - ProcedureName::QueryDependency(dependency_id), - Value::Null, - Some(Duration::from_secs(30)), - ) - .await - .with_kind(ErrorKind::Dependency)?, - "remoteConfig": remote_config, - }), - Some(Duration::from_secs(30)), - ) - .await - .with_kind(ErrorKind::Dependency) - .map(|res| res.filter(|c| !c.is_empty() && Some(c) != remote_config.as_ref())) - } -} - -pub(super) struct DependencyConfig { - dependency_id: PackageId, - remote_config: Option, -} -impl Handler for ServiceActor { - type Response = Result, Error>; - fn conflicts_with(_: &DependencyConfig) -> ConflictBuilder { - ConflictBuilder::nothing() - } - async fn handle( - &mut self, - id: Guid, - DependencyConfig { - dependency_id, - remote_config, - }: DependencyConfig, - _: &BackgroundJobQueue, - ) -> Self::Response { - self.0 - .dependency_config(id, dependency_id, remote_config) - .await - } -} - -impl Service { - pub async fn dependency_config( - &self, - id: Guid, - dependency_id: PackageId, - remote_config: Option, - ) -> Result, Error> { - self.actor - .send( - id, - DependencyConfig { - dependency_id, - remote_config, - }, - ) - .await? - } -} diff --git a/core/startos/src/service/effects/action.rs b/core/startos/src/service/effects/action.rs index 4719c6d3d..5e3605679 100644 --- a/core/startos/src/service/effects/action.rs +++ b/core/startos/src/service/effects/action.rs @@ -1,22 +1,59 @@ -use std::collections::BTreeMap; +use std::collections::BTreeSet; -use models::{ActionId, PackageId}; +use models::{ActionId, PackageId, ReplayId}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; -use crate::action::ActionResult; -use crate::db::model::package::ActionMetadata; +use crate::action::{display_action_result, ActionInput, ActionResult}; +use crate::db::model::package::{ + ActionMetadata, ActionRequest, ActionRequestCondition, ActionRequestEntry, ActionRequestTrigger, +}; use crate::rpc_continuations::Guid; +use crate::service::cli::ContainerCliContext; use crate::service::effects::prelude::*; +use crate::util::serde::HandlerExtSerde; + +pub fn action_api() -> ParentHandler { + ParentHandler::new() + .subcommand("export", from_fn_async(export_action).no_cli()) + .subcommand( + "clear", + from_fn_async(clear_actions) + .no_display() + .with_call_remote::(), + ) + .subcommand( + "get-input", + from_fn_async(get_action_input) + .with_display_serializable() + .with_call_remote::(), + ) + .subcommand( + "run", + from_fn_async(run_action) + .with_display_serializable() + .with_custom_display_fn(|args, res| Ok(display_action_result(args.params, res))) + .with_call_remote::(), + ) + .subcommand("request", from_fn_async(request_action).no_cli()) + .subcommand( + "clear-requests", + from_fn_async(clear_action_requests) + .no_display() + .with_call_remote::(), + ) +} #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct ExportActionParams { - #[ts(optional)] - package_id: Option, id: ActionId, metadata: ActionMetadata, } -pub async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<(), Error> { +pub async fn export_action( + context: EffectContext, + ExportActionParams { id, metadata }: ExportActionParams, +) -> Result<(), Error> { let context = context.deref()?; let package_id = context.seed.id.clone(); context @@ -31,17 +68,26 @@ pub async fn export_action(context: EffectContext, data: ExportActionParams) -> .or_not_found(&package_id)? .as_actions_mut(); let mut value = model.de()?; - value - .insert(data.id, data.metadata) - .map(|_| ()) - .unwrap_or_default(); + value.insert(id, metadata); model.ser(&value) }) .await?; Ok(()) } -pub async fn clear_actions(context: EffectContext) -> Result<(), Error> { +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ClearActionsParams { + #[arg(long)] + pub except: Vec, +} + +async fn clear_actions( + context: EffectContext, + ClearActionsParams { except }: ClearActionsParams, +) -> Result<(), Error> { + let except: BTreeSet<_> = except.into_iter().collect(); let context = context.deref()?; let package_id = context.seed.id.clone(); context @@ -54,18 +100,57 @@ pub async fn clear_actions(context: EffectContext) -> Result<(), Error> { .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_actions_mut() - .ser(&BTreeMap::new()) + .mutate(|a| Ok(a.retain(|e, _| except.contains(e)))) }) .await?; Ok(()) } -#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct GetActionInputParams { + #[serde(default)] + #[ts(skip)] + #[arg(skip)] + procedure_id: Guid, + #[ts(optional)] + package_id: Option, + action_id: ActionId, +} +async fn get_action_input( + context: EffectContext, + GetActionInputParams { + procedure_id, + package_id, + action_id, + }: GetActionInputParams, +) -> Result, Error> { + let context = context.deref()?; + + if let Some(package_id) = package_id { + context + .seed + .ctx + .services + .get(&package_id) + .await + .as_ref() + .or_not_found(&package_id)? + .get_action_input(procedure_id, action_id) + .await + } else { + context.get_action_input(procedure_id, action_id).await + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] #[serde(rename_all = "camelCase")] #[ts(export)] -pub struct ExecuteAction { +pub struct RunActionParams { #[serde(default)] #[ts(skip)] + #[arg(skip)] procedure_id: Guid, #[ts(optional)] package_id: Option, @@ -73,18 +158,24 @@ pub struct ExecuteAction { #[ts(type = "any")] input: Value, } -pub async fn execute_action( +async fn run_action( context: EffectContext, - ExecuteAction { + RunActionParams { procedure_id, package_id, action_id, input, - }: ExecuteAction, -) -> Result { + }: RunActionParams, +) -> Result, Error> { let context = context.deref()?; - if let Some(package_id) = package_id { + let package_id = package_id.as_ref().unwrap_or(&context.seed.id); + + if package_id != &context.seed.id { + return Err(Error::new( + eyre!("calling actions on other packages is unsupported at this time"), + ErrorKind::InvalidRequest, + )); context .seed .ctx @@ -93,9 +184,132 @@ pub async fn execute_action( .await .as_ref() .or_not_found(&package_id)? - .action(procedure_id, action_id, input) + .run_action(procedure_id, action_id, input) .await } else { - context.action(procedure_id, action_id, input).await + context.run_action(procedure_id, action_id, input).await } } + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RequestActionParams { + #[serde(default)] + #[ts(skip)] + procedure_id: Guid, + replay_id: ReplayId, + #[serde(flatten)] + request: ActionRequest, +} +async fn request_action( + context: EffectContext, + RequestActionParams { + procedure_id, + replay_id, + request, + }: RequestActionParams, +) -> Result<(), Error> { + let context = context.deref()?; + + let src_id = &context.seed.id; + let active = match &request.when { + Some(ActionRequestTrigger { once, condition }) => match condition { + ActionRequestCondition::InputNotMatches => { + let Some(input) = request.input.as_ref() else { + return Err(Error::new( + eyre!("input-not-matches trigger requires input to be specified"), + ErrorKind::InvalidRequest, + )); + }; + if let Some(service) = context + .seed + .ctx + .services + .get(&request.package_id) + .await + .as_ref() + { + let Some(prev) = service + .get_action_input(procedure_id, request.action_id.clone()) + .await? + else { + return Err(Error::new( + eyre!( + "action {} of {} has no input", + request.action_id, + request.package_id + ), + ErrorKind::InvalidRequest, + )); + }; + if input.matches(prev.value.as_ref()) { + if *once { + return Ok(()); + } else { + false + } + } else { + true + } + } else { + true // update when service is installed + } + } + }, + None => true, + }; + context + .seed + .ctx + .db + .mutate(|db| { + db.as_public_mut() + .as_package_data_mut() + .as_idx_mut(src_id) + .or_not_found(src_id)? + .as_requested_actions_mut() + .insert(&replay_id, &ActionRequestEntry { active, request }) + }) + .await?; + Ok(()) +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[ts(type = "{ only: string[] } | { except: string[] }")] +#[ts(export)] +pub struct ClearActionRequestsParams { + #[arg(long, conflicts_with = "except")] + pub only: Option>, + #[arg(long, conflicts_with = "only")] + pub except: Option>, +} + +async fn clear_action_requests( + context: EffectContext, + ClearActionRequestsParams { only, except }: ClearActionRequestsParams, +) -> Result<(), Error> { + let context = context.deref()?; + let package_id = context.seed.id.clone(); + let only = only.map(|only| only.into_iter().collect::>()); + let except = except.map(|except| except.into_iter().collect::>()); + context + .seed + .ctx + .db + .mutate(|db| { + db.as_public_mut() + .as_package_data_mut() + .as_idx_mut(&package_id) + .or_not_found(&package_id)? + .as_requested_actions_mut() + .mutate(|a| { + Ok(a.retain(|e, _| { + only.as_ref().map_or(true, |only| !only.contains(e)) + && except.as_ref().map_or(true, |except| except.contains(e)) + })) + }) + }) + .await?; + Ok(()) +} diff --git a/core/startos/src/service/effects/callbacks.rs b/core/startos/src/service/effects/callbacks.rs index 1a9250aa8..65eb707d8 100644 --- a/core/startos/src/service/effects/callbacks.rs +++ b/core/startos/src/service/effects/callbacks.rs @@ -3,19 +3,22 @@ use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, Mutex, Weak}; use std::time::{Duration, SystemTime}; +use clap::Parser; use futures::future::join_all; use helpers::NonDetachingJoinHandle; use imbl::{vector, Vector}; use imbl_value::InternedString; use models::{HostId, PackageId, ServiceInterfaceId}; use patch_db::json_ptr::JsonPointer; +use serde::{Deserialize, Serialize}; use tracing::warn; +use ts_rs::TS; use crate::net::ssl::FullchainCertData; use crate::prelude::*; use crate::service::effects::context::EffectContext; use crate::service::effects::net::ssl::Algorithm; -use crate::service::rpc::CallbackHandle; +use crate::service::rpc::{CallbackHandle, CallbackId}; use crate::service::{Service, ServiceActorSeed}; use crate::util::collections::EqMap; @@ -33,6 +36,7 @@ struct ServiceCallbackMap { (NonDetachingJoinHandle<()>, Vec), >, get_store: BTreeMap>>, + get_status: BTreeMap>, } impl ServiceCallbacks { @@ -68,6 +72,10 @@ impl ServiceCallbacks { }); !v.is_empty() }); + this.get_status.retain(|_, v| { + v.retain(|h| h.handle.is_active() && h.seed.strong_count() > 0); + !v.is_empty() + }); }) } @@ -217,6 +225,20 @@ impl ServiceCallbacks { .push(handler); }) } + pub(super) fn add_get_status(&self, package_id: PackageId, handler: CallbackHandler) { + self.mutate(|this| this.get_status.entry(package_id).or_default().push(handler)) + } + #[must_use] + pub fn get_status(&self, package_id: &PackageId) -> Option { + self.mutate(|this| { + if let Some(watched) = this.get_status.remove(package_id) { + Some(CallbackHandlers(watched)) + } else { + None + } + .filter(|cb| !cb.0.is_empty()) + }) + } pub(super) fn add_get_store( &self, @@ -272,6 +294,7 @@ impl CallbackHandler { } } pub async fn call(mut self, args: Vector) -> Result<(), Error> { + dbg!(eyre!("callback fired: {}", self.handle.is_active())); if let Some(seed) = self.seed.upgrade() { seed.persistent_container .callback(self.handle.take(), args) @@ -299,13 +322,29 @@ impl CallbackHandlers { } } -pub(super) fn clear_callbacks(context: EffectContext) -> Result<(), Error> { +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[ts(type = "{ only: number[] } | { except: number[] }")] +#[ts(export)] +pub struct ClearCallbacksParams { + #[arg(long, conflicts_with = "except")] + pub only: Option>, + #[arg(long, conflicts_with = "only")] + pub except: Option>, +} + +pub(super) fn clear_callbacks( + context: EffectContext, + ClearCallbacksParams { only, except }: ClearCallbacksParams, +) -> Result<(), Error> { let context = context.deref()?; - context - .seed - .persistent_container - .state - .send_if_modified(|s| !std::mem::take(&mut s.callbacks).is_empty()); + let only = only.map(|only| only.into_iter().collect::>()); + let except = except.map(|except| except.into_iter().collect::>()); + context.seed.persistent_container.state.send_modify(|s| { + s.callbacks.retain(|cb| { + only.as_ref().map_or(true, |only| !only.contains(cb)) + && except.as_ref().map_or(true, |except| except.contains(cb)) + }) + }); context.seed.ctx.callbacks.gc(); Ok(()) } diff --git a/core/startos/src/service/effects/config.rs b/core/startos/src/service/effects/config.rs deleted file mode 100644 index 647d3e272..000000000 --- a/core/startos/src/service/effects/config.rs +++ /dev/null @@ -1,53 +0,0 @@ -use models::PackageId; - -use crate::service::effects::prelude::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export)] -pub struct GetConfiguredParams { - #[ts(optional)] - package_id: Option, -} -pub async fn get_configured(context: EffectContext) -> Result { - let context = context.deref()?; - let peeked = context.seed.ctx.db.peek().await; - let package_id = &context.seed.id; - peeked - .as_public() - .as_package_data() - .as_idx(package_id) - .or_not_found(package_id)? - .as_status() - .as_configured() - .de() -} - -#[derive(Debug, Clone, Serialize, Deserialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export)] -pub struct SetConfigured { - configured: bool, -} -pub async fn set_configured( - context: EffectContext, - SetConfigured { configured }: SetConfigured, -) -> Result<(), Error> { - let context = context.deref()?; - let package_id = &context.seed.id; - context - .seed - .ctx - .db - .mutate(|db| { - db.as_public_mut() - .as_package_data_mut() - .as_idx_mut(package_id) - .or_not_found(package_id)? - .as_status_mut() - .as_configured_mut() - .ser(&configured) - }) - .await?; - Ok(()) -} diff --git a/core/startos/src/service/effects/control.rs b/core/startos/src/service/effects/control.rs index 6b3c6f8a0..4b9817b77 100644 --- a/core/startos/src/service/effects/control.rs +++ b/core/startos/src/service/effects/control.rs @@ -1,9 +1,11 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; +use models::{FromStrParser, PackageId}; use crate::service::effects::prelude::*; -use crate::util::clap::FromStrParser; +use crate::service::rpc::CallbackId; +use crate::status::MainStatus; pub async fn restart( context: EffectContext, @@ -23,6 +25,46 @@ pub async fn shutdown( Ok(()) } +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct GetStatusParams { + #[ts(optional)] + pub package_id: Option, + #[ts(optional)] + #[arg(skip)] + pub callback: Option, +} + +pub async fn get_status( + context: EffectContext, + GetStatusParams { + package_id, + callback, + }: GetStatusParams, +) -> Result { + let context = context.deref()?; + let id = package_id.unwrap_or_else(|| context.seed.id.clone()); + let db = context.seed.ctx.db.peek().await; + let status = db + .as_public() + .as_package_data() + .as_idx(&id) + .or_not_found(&id)? + .as_status() + .de()?; + + if let Some(callback) = callback { + let callback = callback.register(&context.seed.persistent_container); + context.seed.ctx.callbacks.add_get_status( + id, + super::callbacks::CallbackHandler::new(&context, callback), + ); + } + + Ok(status) +} + #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] diff --git a/core/startos/src/service/effects/dependency.rs b/core/startos/src/service/effects/dependency.rs index 26582d061..ed43b7a9c 100644 --- a/core/startos/src/service/effects/dependency.rs +++ b/core/startos/src/service/effects/dependency.rs @@ -6,13 +6,13 @@ use clap::builder::ValueParserFactory; use exver::VersionRange; use imbl::OrdMap; use imbl_value::InternedString; -use itertools::Itertools; -use models::{HealthCheckId, PackageId, VersionString, VolumeId}; +use models::{FromStrParser, HealthCheckId, PackageId, ReplayId, VersionString, VolumeId}; use patch_db::json_ptr::JsonPointer; use tokio::process::Command; use crate::db::model::package::{ - CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference, + ActionRequestEntry, CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, + ManifestPreference, }; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::idmapped::IdMapped; @@ -20,7 +20,6 @@ use crate::disk::mount::filesystem::{FileSystem, MountType}; use crate::rpc_continuations::Guid; use crate::service::effects::prelude::*; use crate::status::health_check::NamedHealthCheckResult; -use crate::util::clap::FromStrParser; use crate::util::Invoke; use crate::volume::data_dir; @@ -113,6 +112,7 @@ pub async fn expose_for_dependents( context: EffectContext, ExposeForDependentsParams { paths }: ExposeForDependentsParams, ) -> Result<(), Error> { + // TODO Ok(()) } @@ -192,16 +192,11 @@ impl ValueParserFactory for DependencyRequirement { #[command(rename_all = "camelCase")] #[ts(export)] pub struct SetDependenciesParams { - #[serde(default)] - procedure_id: Guid, dependencies: Vec, } pub async fn set_dependencies( context: EffectContext, - SetDependenciesParams { - procedure_id, - dependencies, - }: SetDependenciesParams, + SetDependenciesParams { dependencies }: SetDependenciesParams, ) -> Result<(), Error> { let context = context.deref()?; let id = &context.seed.id; @@ -222,19 +217,6 @@ pub async fn set_dependencies( version_range, ), }; - let config_satisfied = - if let Some(dep_service) = &*context.seed.ctx.services.get(&dep_id).await { - context - .dependency_config( - procedure_id.clone(), - dep_id.clone(), - dep_service.get_config(procedure_id.clone()).await?.config, - ) - .await? - .is_none() - } else { - true - }; let info = CurrentDependencyInfo { title: context .seed @@ -251,7 +233,6 @@ pub async fn set_dependencies( .await?, kind, version_range, - config_satisfied, }; deps.insert(dep_id, info); } @@ -282,7 +263,8 @@ pub async fn get_dependencies(context: EffectContext) -> Result Result(match kind { + match kind { CurrentDependencyKind::Exists => { DependencyRequirement::Exists { id, version_range } } @@ -301,9 +283,9 @@ pub async fn get_dependencies(context: EffectContext) -> Result, - #[ts(type = "string | null")] - installed_version: Option, - #[ts(type = "string[]")] + installed_version: Option, satisfies: BTreeSet, is_running: bool, - config_satisfied: bool, + requested_actions: BTreeMap, #[ts(as = "BTreeMap::")] health_checks: OrdMap, } @@ -335,14 +315,14 @@ pub async fn check_dependencies( ) -> Result, Error> { let context = context.deref()?; let db = context.seed.ctx.db.peek().await; - let current_dependencies = db + let pde = db .as_public() .as_package_data() .as_idx(&context.seed.id) - .or_not_found(&context.seed.id)? - .as_current_dependencies() - .de()?; - let package_ids: Vec<_> = package_ids + .or_not_found(&context.seed.id)?; + let current_dependencies = pde.as_current_dependencies().de()?; + let requested_actions = pde.as_requested_actions().de()?; + let package_dependency_info: Vec<_> = package_ids .unwrap_or_else(|| current_dependencies.0.keys().cloned().collect()) .into_iter() .filter_map(|x| { @@ -350,18 +330,23 @@ pub async fn check_dependencies( Some((x, info)) }) .collect(); - let mut results = Vec::with_capacity(package_ids.len()); + let mut results = Vec::with_capacity(package_dependency_info.len()); - for (package_id, dependency_info) in package_ids { + for (package_id, dependency_info) in package_dependency_info { let title = dependency_info.title.clone(); let Some(package) = db.as_public().as_package_data().as_idx(&package_id) else { + let requested_actions = requested_actions + .iter() + .filter(|(_, v)| v.request.package_id == package_id) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); results.push(CheckDependenciesResult { package_id, title, installed_version: None, satisfies: BTreeSet::new(), is_running: false, - config_satisfied: false, + requested_actions, health_checks: Default::default(), }); continue; @@ -369,22 +354,27 @@ pub async fn check_dependencies( let manifest = package.as_state_info().as_manifest(ManifestPreference::New); let installed_version = manifest.as_version().de()?.into_version(); let satisfies = manifest.as_satisfies().de()?; - let installed_version = Some(installed_version.clone()); + let installed_version = Some(installed_version.clone().into()); let is_installed = true; - let status = package.as_status().as_main().de()?; + let status = package.as_status().de()?; let is_running = if is_installed { status.running() } else { false }; let health_checks = status.health().cloned().unwrap_or_default(); + let requested_actions = requested_actions + .iter() + .filter(|(_, v)| v.request.package_id == package_id) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); results.push(CheckDependenciesResult { package_id, title, installed_version, satisfies, is_running, - config_satisfied: dependency_info.config_satisfied, + requested_actions, health_checks, }); } diff --git a/core/startos/src/service/effects/health.rs b/core/startos/src/service/effects/health.rs index aad06a004..c95dea946 100644 --- a/core/startos/src/service/effects/health.rs +++ b/core/startos/src/service/effects/health.rs @@ -29,11 +29,10 @@ pub async fn set_health( .as_idx_mut(package_id) .or_not_found(package_id)? .as_status_mut() - .as_main_mut() .mutate(|main| { match main { - &mut MainStatus::Running { ref mut health, .. } - | &mut MainStatus::BackingUp { ref mut health, .. } => { + MainStatus::Running { ref mut health, .. } + | MainStatus::Starting { ref mut health } => { health.insert(id, result); } _ => (), diff --git a/core/startos/src/service/effects/image.rs b/core/startos/src/service/effects/image.rs deleted file mode 100644 index 4b5293506..000000000 --- a/core/startos/src/service/effects/image.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::ffi::OsString; -use std::os::unix::process::CommandExt; -use std::path::{Path, PathBuf}; - -use models::ImageId; -use rpc_toolkit::Context; -use tokio::process::Command; - -use crate::disk::mount::filesystem::overlayfs::OverlayGuard; -use crate::rpc_continuations::Guid; -use crate::service::effects::prelude::*; -use crate::util::Invoke; - -#[derive(Debug, Clone, Serialize, Deserialize, Parser)] -pub struct ChrootParams { - #[arg(short = 'e', long = "env")] - env: Option, - #[arg(short = 'w', long = "workdir")] - workdir: Option, - #[arg(short = 'u', long = "user")] - user: Option, - path: PathBuf, - command: OsString, - args: Vec, -} -pub fn chroot( - _: C, - ChrootParams { - env, - workdir, - user, - path, - command, - args, - }: ChrootParams, -) -> Result<(), Error> { - let mut cmd: std::process::Command = std::process::Command::new(command); - if let Some(env) = env { - for (k, v) in std::fs::read_to_string(env)? - .lines() - .map(|l| l.trim()) - .filter_map(|l| l.split_once("=")) - { - cmd.env(k, v); - } - } - nix::unistd::setsid().ok(); // https://stackoverflow.com/questions/25701333/os-setsid-operation-not-permitted - std::os::unix::fs::chroot(path)?; - if let Some(uid) = user.as_deref().and_then(|u| u.parse::().ok()) { - cmd.uid(uid); - } else if let Some(user) = user { - let (uid, gid) = std::fs::read_to_string("/etc/passwd")? - .lines() - .find_map(|l| { - let mut split = l.trim().split(":"); - if user != split.next()? { - return None; - } - split.next(); // throw away x - Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?)) - // uid gid - }) - .or_not_found(lazy_format!("{user} in /etc/passwd"))?; - cmd.uid(uid); - cmd.gid(gid); - }; - if let Some(workdir) = workdir { - cmd.current_dir(workdir); - } - cmd.args(args); - Err(cmd.exec().into()) -} - -#[derive(Debug, Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export)] -pub struct DestroyOverlayedImageParams { - guid: Guid, -} -#[instrument(skip_all)] -pub async fn destroy_overlayed_image( - context: EffectContext, - DestroyOverlayedImageParams { guid }: DestroyOverlayedImageParams, -) -> Result<(), Error> { - let context = context.deref()?; - if let Some(overlay) = context - .seed - .persistent_container - .overlays - .lock() - .await - .remove(&guid) - { - overlay.unmount(true).await?; - } else { - tracing::warn!("Could not find a guard to remove on the destroy overlayed image; assumming that it already is removed and will be skipping"); - } - Ok(()) -} - -#[derive(Debug, Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export)] -pub struct CreateOverlayedImageParams { - image_id: ImageId, -} -#[instrument(skip_all)] -pub async fn create_overlayed_image( - context: EffectContext, - CreateOverlayedImageParams { image_id }: CreateOverlayedImageParams, -) -> Result<(PathBuf, Guid), Error> { - let context = context.deref()?; - if let Some(image) = context - .seed - .persistent_container - .images - .get(&image_id) - .cloned() - { - let guid = Guid::new(); - let rootfs_dir = context - .seed - .persistent_container - .lxc_container - .get() - .ok_or_else(|| { - Error::new( - eyre!("PersistentContainer has been destroyed"), - ErrorKind::Incoherent, - ) - })? - .rootfs_dir(); - let mountpoint = rootfs_dir - .join("media/startos/overlays") - .join(guid.as_ref()); - tokio::fs::create_dir_all(&mountpoint).await?; - let container_mountpoint = Path::new("/").join( - mountpoint - .strip_prefix(rootfs_dir) - .with_kind(ErrorKind::Incoherent)?, - ); - tracing::info!("Mounting overlay {guid} for {image_id}"); - let guard = OverlayGuard::mount(image, &mountpoint).await?; - Command::new("chown") - .arg("100000:100000") - .arg(&mountpoint) - .invoke(ErrorKind::Filesystem) - .await?; - tracing::info!("Mounted overlay {guid} for {image_id}"); - context - .seed - .persistent_container - .overlays - .lock() - .await - .insert(guid.clone(), guard); - Ok((container_mountpoint, guid)) - } else { - Err(Error::new( - eyre!("image {image_id} not found in s9pk"), - ErrorKind::NotFound, - )) - } -} diff --git a/core/startos/src/service/effects/mod.rs b/core/startos/src/service/effects/mod.rs index a7ee6fb4d..f68985268 100644 --- a/core/startos/src/service/effects/mod.rs +++ b/core/startos/src/service/effects/mod.rs @@ -1,4 +1,4 @@ -use rpc_toolkit::{from_fn, from_fn_async, Context, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn, from_fn_async, from_fn_blocking, Context, HandlerExt, ParentHandler}; use crate::echo; use crate::prelude::*; @@ -7,53 +7,30 @@ use crate::service::effects::context::EffectContext; mod action; pub mod callbacks; -mod config; pub mod context; mod control; mod dependency; mod health; -mod image; mod net; mod prelude; mod store; +mod subcontainer; mod system; pub fn handler() -> ParentHandler { ParentHandler::new() - .subcommand("gitInfo", from_fn(|_: C| crate::version::git_info())) + .subcommand("git-info", from_fn(|_: C| crate::version::git_info())) .subcommand( "echo", from_fn(echo::).with_call_remote::(), ) // action - .subcommand( - "executeAction", - from_fn_async(action::execute_action).no_cli(), - ) - .subcommand( - "exportAction", - from_fn_async(action::export_action).no_cli(), - ) - .subcommand( - "clearActions", - from_fn_async(action::clear_actions).no_cli(), - ) + .subcommand("action", action::action_api::()) // callbacks .subcommand( - "clearCallbacks", + "clear-callbacks", from_fn(callbacks::clear_callbacks).no_cli(), ) - // config - .subcommand( - "getConfigured", - from_fn_async(config::get_configured).no_cli(), - ) - .subcommand( - "setConfigured", - from_fn_async(config::set_configured) - .no_display() - .with_call_remote::(), - ) // control .subcommand( "restart", @@ -68,110 +45,135 @@ pub fn handler() -> ParentHandler { .with_call_remote::(), ) .subcommand( - "setMainStatus", + "set-main-status", from_fn_async(control::set_main_status) .no_display() .with_call_remote::(), ) + .subcommand( + "get-status", + from_fn_async(control::get_status) + .no_display() + .with_call_remote::(), + ) // dependency .subcommand( - "setDependencies", + "set-dependencies", from_fn_async(dependency::set_dependencies) .no_display() .with_call_remote::(), ) .subcommand( - "getDependencies", + "get-dependencies", from_fn_async(dependency::get_dependencies) .no_display() .with_call_remote::(), ) .subcommand( - "checkDependencies", + "check-dependencies", from_fn_async(dependency::check_dependencies) .no_display() .with_call_remote::(), ) .subcommand("mount", from_fn_async(dependency::mount).no_cli()) .subcommand( - "getInstalledPackages", + "get-installed-packages", from_fn_async(dependency::get_installed_packages).no_cli(), ) .subcommand( - "exposeForDependents", + "expose-for-dependents", from_fn_async(dependency::expose_for_dependents).no_cli(), ) // health - .subcommand("setHealth", from_fn_async(health::set_health).no_cli()) - // image - .subcommand( - "chroot", - from_fn(image::chroot::).no_display(), - ) - .subcommand( - "createOverlayedImage", - from_fn_async(image::create_overlayed_image) - .with_custom_display_fn(|_, (path, _)| Ok(println!("{}", path.display()))) - .with_call_remote::(), - ) - .subcommand( - "destroyOverlayedImage", - from_fn_async(image::destroy_overlayed_image).no_cli(), + .subcommand("set-health", from_fn_async(health::set_health).no_cli()) + // subcontainer + .subcommand( + "subcontainer", + ParentHandler::::new() + .subcommand( + "launch", + from_fn_blocking(subcontainer::launch).no_display(), + ) + .subcommand( + "launch-init", + from_fn_blocking(subcontainer::launch_init).no_display(), + ) + .subcommand("exec", from_fn_blocking(subcontainer::exec).no_display()) + .subcommand( + "exec-command", + from_fn_blocking(subcontainer::exec_command).no_display(), + ) + .subcommand( + "create-fs", + from_fn_async(subcontainer::create_subcontainer_fs) + .with_custom_display_fn(|_, (path, _)| Ok(println!("{}", path.display()))) + .with_call_remote::(), + ) + .subcommand( + "destroy-fs", + from_fn_async(subcontainer::destroy_subcontainer_fs) + .no_display() + .with_call_remote::(), + ), ) // net .subcommand("bind", from_fn_async(net::bind::bind).no_cli()) .subcommand( - "getServicePortForward", + "get-service-port-forward", from_fn_async(net::bind::get_service_port_forward).no_cli(), ) .subcommand( - "clearBindings", + "clear-bindings", from_fn_async(net::bind::clear_bindings).no_cli(), ) .subcommand( - "getHostInfo", + "get-host-info", from_fn_async(net::host::get_host_info).no_cli(), ) .subcommand( - "getPrimaryUrl", + "get-primary-url", from_fn_async(net::host::get_primary_url).no_cli(), ) .subcommand( - "getContainerIp", + "get-container-ip", from_fn_async(net::info::get_container_ip).no_cli(), ) .subcommand( - "exportServiceInterface", + "export-service-interface", from_fn_async(net::interface::export_service_interface).no_cli(), ) .subcommand( - "getServiceInterface", + "get-service-interface", from_fn_async(net::interface::get_service_interface).no_cli(), ) .subcommand( - "listServiceInterfaces", + "list-service-interfaces", from_fn_async(net::interface::list_service_interfaces).no_cli(), ) .subcommand( - "clearServiceInterfaces", + "clear-service-interfaces", from_fn_async(net::interface::clear_service_interfaces).no_cli(), ) .subcommand( - "getSslCertificate", + "get-ssl-certificate", from_fn_async(net::ssl::get_ssl_certificate).no_cli(), ) - .subcommand("getSslKey", from_fn_async(net::ssl::get_ssl_key).no_cli()) + .subcommand("get-ssl-key", from_fn_async(net::ssl::get_ssl_key).no_cli()) // store - .subcommand("getStore", from_fn_async(store::get_store).no_cli()) - .subcommand("setStore", from_fn_async(store::set_store).no_cli()) .subcommand( - "setDataVersion", + "store", + ParentHandler::::new() + .subcommand("get", from_fn_async(store::get_store).no_cli()) + .subcommand("set", from_fn_async(store::set_store).no_cli()), + ) + .subcommand( + "set-data-version", from_fn_async(store::set_data_version) .no_display() .with_call_remote::(), ) .subcommand( - "getDataVersion", + "get-data-version", from_fn_async(store::get_data_version) .with_custom_display_fn(|_, v| { if let Some(v) = v { @@ -185,7 +187,7 @@ pub fn handler() -> ParentHandler { ) // system .subcommand( - "getSystemSmtp", + "get-system-smtp", from_fn_async(system::get_system_smtp).no_cli(), ) diff --git a/core/startos/src/service/effects/net/bind.rs b/core/startos/src/service/effects/net/bind.rs index ba273323a..5619375eb 100644 --- a/core/startos/src/service/effects/net/bind.rs +++ b/core/startos/src/service/effects/net/bind.rs @@ -1,6 +1,6 @@ use models::{HostId, PackageId}; -use crate::net::host::binding::{BindOptions, LanInfo}; +use crate::net::host::binding::{BindId, BindOptions, LanInfo}; use crate::net::host::HostKind; use crate::service::effects::prelude::*; @@ -28,10 +28,21 @@ pub async fn bind( svc.bind(kind, id, internal_port, options).await } -pub async fn clear_bindings(context: EffectContext) -> Result<(), Error> { +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ClearBindingsParams { + #[serde(default)] + pub except: Vec, +} + +pub async fn clear_bindings( + context: EffectContext, + ClearBindingsParams { except }: ClearBindingsParams, +) -> Result<(), Error> { let context = context.deref()?; let mut svc = context.seed.persistent_container.net_service.lock().await; - svc.clear_bindings().await?; + svc.clear_bindings(except.into_iter().collect()).await?; Ok(()) } diff --git a/core/startos/src/service/effects/net/interface.rs b/core/startos/src/service/effects/net/interface.rs index 6cd4cd4c9..44258c36a 100644 --- a/core/startos/src/service/effects/net/interface.rs +++ b/core/startos/src/service/effects/net/interface.rs @@ -165,7 +165,17 @@ pub async fn list_service_interfaces( Ok(res) } -pub async fn clear_service_interfaces(context: EffectContext) -> Result<(), Error> { +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct ClearServiceInterfacesParams { + pub except: Vec, +} + +pub async fn clear_service_interfaces( + context: EffectContext, + ClearServiceInterfacesParams { except }: ClearServiceInterfacesParams, +) -> Result<(), Error> { let context = context.deref()?; let package_id = context.seed.id.clone(); @@ -179,7 +189,7 @@ pub async fn clear_service_interfaces(context: EffectContext) -> Result<(), Erro .as_idx_mut(&package_id) .or_not_found(&package_id)? .as_service_interfaces_mut() - .ser(&Default::default()) + .mutate(|s| Ok(s.retain(|id, _| except.contains(id)))) }) .await } diff --git a/core/startos/src/service/effects/store.rs b/core/startos/src/service/effects/store.rs index 6c12b425e..1d4a07086 100644 --- a/core/startos/src/service/effects/store.rs +++ b/core/startos/src/service/effects/store.rs @@ -26,6 +26,7 @@ pub async fn get_store( callback, }: GetStoreParams, ) -> Result { + dbg!(&callback); let context = context.deref()?; let peeked = context.seed.ctx.db.peek().await; let package_id = package_id.unwrap_or(context.seed.id.clone()); @@ -33,8 +34,9 @@ pub async fn get_store( .as_private() .as_package_stores() .as_idx(&package_id) - .or_not_found(&package_id)? - .de()?; + .map(|s| s.de()) + .transpose()? + .unwrap_or_default(); if let Some(callback) = callback { let callback = callback.register(&context.seed.persistent_container); @@ -45,10 +47,7 @@ pub async fn get_store( ); } - Ok(path - .get(&value) - .ok_or_else(|| Error::new(eyre!("Did not find value at path"), ErrorKind::NotFound))? - .clone()) + Ok(path.get(&value).cloned().unwrap_or_default()) } #[derive(Debug, Clone, Serialize, Deserialize, TS)] diff --git a/core/startos/src/service/effects/subcontainer/mod.rs b/core/startos/src/service/effects/subcontainer/mod.rs new file mode 100644 index 000000000..65fcbd387 --- /dev/null +++ b/core/startos/src/service/effects/subcontainer/mod.rs @@ -0,0 +1,122 @@ +use std::path::{Path, PathBuf}; + +use imbl_value::InternedString; +use models::ImageId; +use tokio::process::Command; + +use crate::rpc_continuations::Guid; +use crate::service::effects::prelude::*; +use crate::util::Invoke; +use crate::{ + disk::mount::filesystem::overlayfs::OverlayGuard, service::persistent_container::Subcontainer, +}; + +#[cfg(feature = "container-runtime")] +mod sync; + +#[cfg(not(feature = "container-runtime"))] +mod sync_dummy; + +pub use sync::*; +#[cfg(not(feature = "container-runtime"))] +use sync_dummy as sync; + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct DestroySubcontainerFsParams { + guid: Guid, +} +#[instrument(skip_all)] +pub async fn destroy_subcontainer_fs( + context: EffectContext, + DestroySubcontainerFsParams { guid }: DestroySubcontainerFsParams, +) -> Result<(), Error> { + let context = context.deref()?; + if let Some(overlay) = context + .seed + .persistent_container + .subcontainers + .lock() + .await + .remove(&guid) + { + overlay.overlay.unmount(true).await?; + } else { + tracing::warn!("Could not find a subcontainer fs to destroy; assumming that it already is destroyed and will be skipping"); + } + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct CreateSubcontainerFsParams { + image_id: ImageId, + #[ts(type = "string | null")] + name: Option, +} +#[instrument(skip_all)] +pub async fn create_subcontainer_fs( + context: EffectContext, + CreateSubcontainerFsParams { image_id, name }: CreateSubcontainerFsParams, +) -> Result<(PathBuf, Guid), Error> { + let context = context.deref()?; + if let Some(image) = context + .seed + .persistent_container + .images + .get(&image_id) + .cloned() + { + let guid = Guid::new(); + let rootfs_dir = context + .seed + .persistent_container + .lxc_container + .get() + .ok_or_else(|| { + Error::new( + eyre!("PersistentContainer has been destroyed"), + ErrorKind::Incoherent, + ) + })? + .rootfs_dir(); + let mountpoint = rootfs_dir + .join("media/startos/subcontainers") + .join(guid.as_ref()); + tokio::fs::create_dir_all(&mountpoint).await?; + let container_mountpoint = Path::new("/").join( + mountpoint + .strip_prefix(rootfs_dir) + .with_kind(ErrorKind::Incoherent)?, + ); + tracing::info!("Mounting overlay {guid} for {image_id}"); + let subcontainer_wrapper = Subcontainer { + overlay: OverlayGuard::mount(image, &mountpoint).await?, + name: name + .unwrap_or_else(|| InternedString::intern(format!("subcontainer-{}", image_id))), + image_id: image_id.clone(), + }; + + Command::new("chown") + .arg("100000:100000") + .arg(&mountpoint) + .invoke(ErrorKind::Filesystem) + .await?; + tracing::info!("Mounted overlay {guid} for {image_id}"); + context + .seed + .persistent_container + .subcontainers + .lock() + .await + .insert(guid.clone(), subcontainer_wrapper); + Ok((container_mountpoint, guid)) + } else { + Err(Error::new( + eyre!("image {image_id} not found in s9pk"), + ErrorKind::NotFound, + )) + } +} diff --git a/core/startos/src/service/effects/subcontainer/sync.rs b/core/startos/src/service/effects/subcontainer/sync.rs new file mode 100644 index 000000000..702f34bbe --- /dev/null +++ b/core/startos/src/service/effects/subcontainer/sync.rs @@ -0,0 +1,452 @@ +use std::collections::BTreeMap; +use std::ffi::{c_int, OsStr, OsString}; +use std::fs::File; +use std::io::IsTerminal; +use std::os::unix::process::CommandExt; +use std::path::{Path, PathBuf}; +use std::process::{Command as StdCommand, Stdio}; + +use nix::sched::CloneFlags; +use nix::unistd::Pid; +use signal_hook::consts::signal::*; +use tokio::sync::oneshot; +use tty_spawn::TtySpawn; + +use crate::service::effects::prelude::*; +use crate::service::effects::ContainerCliContext; + +const FWD_SIGNALS: &[c_int] = &[ + SIGABRT, SIGALRM, SIGCONT, SIGHUP, SIGINT, SIGIO, SIGPIPE, SIGPROF, SIGQUIT, SIGTERM, SIGTRAP, + SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, +]; + +struct NSPid(Vec); +impl procfs::FromBufRead for NSPid { + fn from_buf_read(r: R) -> procfs::ProcResult { + for line in r.lines() { + let line = line?; + if let Some(row) = line.trim().strip_prefix("NSpid") { + return Ok(Self( + row.split_ascii_whitespace() + .map(|pid| pid.parse::()) + .collect::, _>>()?, + )); + } + } + Err(procfs::ProcError::Incomplete(None)) + } +} + +fn open_file_read(path: impl AsRef) -> Result { + File::open(&path).with_ctx(|_| { + ( + ErrorKind::Filesystem, + lazy_format!("open r {}", path.as_ref().display()), + ) + }) +} + +#[derive(Debug, Clone, Serialize, Deserialize, Parser)] +pub struct ExecParams { + #[arg(long)] + force_tty: bool, + #[arg(short, long)] + env: Option, + #[arg(short, long)] + workdir: Option, + #[arg(short, long)] + user: Option, + chroot: PathBuf, + #[arg(trailing_var_arg = true)] + command: Vec, +} +impl ExecParams { + fn exec(&self) -> Result<(), Error> { + let ExecParams { + env, + workdir, + user, + chroot, + command, + .. + } = self; + let Some(([command], args)) = command.split_at_checked(1) else { + return Err(Error::new( + eyre!("command cannot be empty"), + ErrorKind::InvalidRequest, + )); + }; + let env_string = if let Some(env) = &env { + std::fs::read_to_string(env) + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("read {env:?}")))? + } else { + Default::default() + }; + let env = env_string + .lines() + .map(|l| l.trim()) + .filter_map(|l| l.split_once("=")) + .collect::>(); + std::os::unix::fs::chroot(chroot) + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("chroot {chroot:?}")))?; + let mut cmd = StdCommand::new(command); + cmd.args(args); + for (k, v) in env { + cmd.env(k, v); + } + + if let Some(uid) = user.as_deref().and_then(|u| u.parse::().ok()) { + cmd.uid(uid); + } else if let Some(user) = user { + let (uid, gid) = std::fs::read_to_string("/etc/passwd") + .with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"))? + .lines() + .find_map(|l| { + let mut split = l.trim().split(":"); + if user != split.next()? { + return None; + } + split.next(); // throw away x + Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?)) + // uid gid + }) + .or_not_found(lazy_format!("{user} in /etc/passwd"))?; + cmd.uid(uid); + cmd.gid(gid); + }; + if let Some(workdir) = workdir { + cmd.current_dir(workdir); + } else { + cmd.current_dir("/"); + } + Err(cmd.exec().into()) + } +} + +pub fn launch( + _: ContainerCliContext, + ExecParams { + force_tty, + env, + workdir, + user, + chroot, + command, + }: ExecParams, +) -> Result<(), Error> { + if chroot.join("proc/1").exists() { + let ns_id = procfs::process::Process::new_with_root(chroot.join("proc/1")) + .with_ctx(|_| (ErrorKind::Filesystem, "open subcontainer procfs"))? + .namespaces() + .with_ctx(|_| (ErrorKind::Filesystem, "read subcontainer pid 1 ns"))? + .0 + .get(OsStr::new("pid")) + .or_not_found("pid namespace")? + .identifier; + for proc in + procfs::process::all_processes().with_ctx(|_| (ErrorKind::Filesystem, "open procfs"))? + { + let proc = proc.with_ctx(|_| (ErrorKind::Filesystem, "read single process details"))?; + let pid = proc.pid(); + if proc + .namespaces() + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("read pid {} ns", pid)))? + .0 + .get(OsStr::new("pid")) + .map_or(false, |ns| ns.identifier == ns_id) + { + let pids = proc.read::("status").with_ctx(|_| { + ( + ErrorKind::Filesystem, + lazy_format!("read pid {} NSpid", pid), + ) + })?; + if pids.0.len() == 2 && pids.0[1] == 1 { + nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL) + .with_ctx(|_| { + ( + ErrorKind::Filesystem, + lazy_format!( + "kill pid {} (determined to be pid 1 in subcontainer)", + pid + ), + ) + })?; + } + } + } + nix::mount::umount(&chroot.join("proc")) + .with_ctx(|_| (ErrorKind::Filesystem, "unmounting subcontainer procfs"))?; + } + + if (std::io::stdin().is_terminal() + && std::io::stdout().is_terminal() + && std::io::stderr().is_terminal()) + || force_tty + { + let mut cmd = TtySpawn::new("/usr/bin/start-cli"); + cmd.arg("subcontainer").arg("launch-init"); + if let Some(env) = env { + cmd.arg("--env").arg(env); + } + if let Some(workdir) = workdir { + cmd.arg("--workdir").arg(workdir); + } + if let Some(user) = user { + cmd.arg("--user").arg(user); + } + cmd.arg(&chroot); + cmd.args(command.iter()); + nix::sched::unshare(CloneFlags::CLONE_NEWPID) + .with_ctx(|_| (ErrorKind::Filesystem, "unshare pid ns"))?; + nix::sched::unshare(CloneFlags::CLONE_NEWCGROUP) + .with_ctx(|_| (ErrorKind::Filesystem, "unshare cgroup ns"))?; + nix::sched::unshare(CloneFlags::CLONE_NEWIPC) + .with_ctx(|_| (ErrorKind::Filesystem, "unshare ipc ns"))?; + std::process::exit(cmd.spawn().with_kind(ErrorKind::Filesystem)?); + } + + let mut sig = signal_hook::iterator::Signals::new(FWD_SIGNALS)?; + let (send_pid, recv_pid) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(pid) = recv_pid.blocking_recv() { + for sig in sig.forever() { + nix::sys::signal::kill( + Pid::from_raw(pid), + Some(nix::sys::signal::Signal::try_from(sig).unwrap()), + ) + .unwrap(); + } + } + }); + let mut cmd = StdCommand::new("/usr/bin/start-cli"); + cmd.arg("subcontainer").arg("launch-init"); + if let Some(env) = env { + cmd.arg("--env").arg(env); + } + if let Some(workdir) = workdir { + cmd.arg("--workdir").arg(workdir); + } + if let Some(user) = user { + cmd.arg("--user").arg(user); + } + cmd.arg(&chroot); + cmd.args(&command); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + let (stdin_send, stdin_recv) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(mut stdin) = stdin_recv.blocking_recv() { + std::io::copy(&mut std::io::stdin(), &mut stdin).unwrap(); + } + }); + let (stdout_send, stdout_recv) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(mut stdout) = stdout_recv.blocking_recv() { + std::io::copy(&mut stdout, &mut std::io::stdout()).unwrap(); + } + }); + let (stderr_send, stderr_recv) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(mut stderr) = stderr_recv.blocking_recv() { + std::io::copy(&mut stderr, &mut std::io::stderr()).unwrap(); + } + }); + nix::sched::unshare(CloneFlags::CLONE_NEWPID) + .with_ctx(|_| (ErrorKind::Filesystem, "unshare pid ns"))?; + nix::sched::unshare(CloneFlags::CLONE_NEWCGROUP) + .with_ctx(|_| (ErrorKind::Filesystem, "unshare cgroup ns"))?; + nix::sched::unshare(CloneFlags::CLONE_NEWIPC) + .with_ctx(|_| (ErrorKind::Filesystem, "unshare ipc ns"))?; + let mut child = cmd + .spawn() + .map_err(color_eyre::eyre::Report::msg) + .with_ctx(|_| (ErrorKind::Filesystem, "spawning child process"))?; + send_pid.send(child.id() as i32).unwrap_or_default(); + stdin_send + .send(child.stdin.take().unwrap()) + .unwrap_or_default(); + stdout_send + .send(child.stdout.take().unwrap()) + .unwrap_or_default(); + stderr_send + .send(child.stderr.take().unwrap()) + .unwrap_or_default(); + // TODO: subreaping, signal handling + let exit = child + .wait() + .with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?; + if let Some(code) = exit.code() { + nix::mount::umount(&chroot.join("proc")) + .with_ctx(|_| (ErrorKind::Filesystem, "umount procfs"))?; + std::process::exit(code); + } else if exit.success() { + Ok(()) + } else { + Err(Error::new( + color_eyre::eyre::Report::msg(exit), + ErrorKind::Unknown, + )) + } +} + +pub fn launch_init(_: ContainerCliContext, params: ExecParams) -> Result<(), Error> { + nix::mount::mount( + Some("proc"), + ¶ms.chroot.join("proc"), + Some("proc"), + nix::mount::MsFlags::empty(), + None::<&str>, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "mount procfs"))?; + if params.command.is_empty() { + signal_hook::iterator::Signals::new(signal_hook::consts::TERM_SIGNALS)? + .forever() + .next(); + std::process::exit(0) + } else { + params.exec() + } +} + +pub fn exec( + _: ContainerCliContext, + ExecParams { + force_tty, + env, + workdir, + user, + chroot, + command, + }: ExecParams, +) -> Result<(), Error> { + if (std::io::stdin().is_terminal() + && std::io::stdout().is_terminal() + && std::io::stderr().is_terminal()) + || force_tty + { + let mut cmd = TtySpawn::new("/usr/bin/start-cli"); + cmd.arg("subcontainer").arg("exec-command"); + if let Some(env) = env { + cmd.arg("--env").arg(env); + } + if let Some(workdir) = workdir { + cmd.arg("--workdir").arg(workdir); + } + if let Some(user) = user { + cmd.arg("--user").arg(user); + } + cmd.arg(&chroot); + cmd.args(command.iter()); + nix::sched::setns( + open_file_read(chroot.join("proc/1/ns/pid"))?, + CloneFlags::CLONE_NEWPID, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "set pid ns"))?; + nix::sched::setns( + open_file_read(chroot.join("proc/1/ns/cgroup"))?, + CloneFlags::CLONE_NEWCGROUP, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "set cgroup ns"))?; + nix::sched::setns( + open_file_read(chroot.join("proc/1/ns/ipc"))?, + CloneFlags::CLONE_NEWIPC, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "set ipc ns"))?; + std::process::exit(cmd.spawn().with_kind(ErrorKind::Filesystem)?); + } + let mut sig = signal_hook::iterator::Signals::new(FWD_SIGNALS)?; + let (send_pid, recv_pid) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(pid) = recv_pid.blocking_recv() { + for sig in sig.forever() { + nix::sys::signal::kill( + Pid::from_raw(pid), + Some(nix::sys::signal::Signal::try_from(sig).unwrap()), + ) + .unwrap(); + } + } + }); + let mut cmd = StdCommand::new("/usr/bin/start-cli"); + cmd.arg("subcontainer").arg("exec-command"); + if let Some(env) = env { + cmd.arg("--env").arg(env); + } + if let Some(workdir) = workdir { + cmd.arg("--workdir").arg(workdir); + } + if let Some(user) = user { + cmd.arg("--user").arg(user); + } + cmd.arg(&chroot); + cmd.args(&command); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + let (stdin_send, stdin_recv) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(mut stdin) = stdin_recv.blocking_recv() { + std::io::copy(&mut std::io::stdin(), &mut stdin).unwrap(); + } + }); + let (stdout_send, stdout_recv) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(mut stdout) = stdout_recv.blocking_recv() { + std::io::copy(&mut stdout, &mut std::io::stdout()).unwrap(); + } + }); + let (stderr_send, stderr_recv) = oneshot::channel(); + std::thread::spawn(move || { + if let Ok(mut stderr) = stderr_recv.blocking_recv() { + std::io::copy(&mut stderr, &mut std::io::stderr()).unwrap(); + } + }); + nix::sched::setns( + open_file_read(chroot.join("proc/1/ns/pid"))?, + CloneFlags::CLONE_NEWPID, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "set pid ns"))?; + nix::sched::setns( + open_file_read(chroot.join("proc/1/ns/cgroup"))?, + CloneFlags::CLONE_NEWCGROUP, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "set cgroup ns"))?; + nix::sched::setns( + open_file_read(chroot.join("proc/1/ns/ipc"))?, + CloneFlags::CLONE_NEWIPC, + ) + .with_ctx(|_| (ErrorKind::Filesystem, "set ipc ns"))?; + let mut child = cmd + .spawn() + .map_err(color_eyre::eyre::Report::msg) + .with_ctx(|_| (ErrorKind::Filesystem, "spawning child process"))?; + send_pid.send(child.id() as i32).unwrap_or_default(); + stdin_send + .send(child.stdin.take().unwrap()) + .unwrap_or_default(); + stdout_send + .send(child.stdout.take().unwrap()) + .unwrap_or_default(); + stderr_send + .send(child.stderr.take().unwrap()) + .unwrap_or_default(); + let exit = child + .wait() + .with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?; + if let Some(code) = exit.code() { + std::process::exit(code); + } else if exit.success() { + Ok(()) + } else { + Err(Error::new( + color_eyre::eyre::Report::msg(exit), + ErrorKind::Unknown, + )) + } +} + +pub fn exec_command(_: ContainerCliContext, params: ExecParams) -> Result<(), Error> { + params.exec() +} diff --git a/core/startos/src/service/effects/subcontainer/sync_dummy.rs b/core/startos/src/service/effects/subcontainer/sync_dummy.rs new file mode 100644 index 000000000..285bdcbc1 --- /dev/null +++ b/core/startos/src/service/effects/subcontainer/sync_dummy.rs @@ -0,0 +1,30 @@ +use crate::service::effects::prelude::*; +use crate::service::effects::ContainerCliContext; + +pub fn launch(_: ContainerCliContext) -> Result<(), Error> { + Err(Error::new( + eyre!("requires feature container-runtime"), + ErrorKind::InvalidRequest, + )) +} + +pub fn launch_init(_: ContainerCliContext) -> Result<(), Error> { + Err(Error::new( + eyre!("requires feature container-runtime"), + ErrorKind::InvalidRequest, + )) +} + +pub fn exec(_: ContainerCliContext) -> Result<(), Error> { + Err(Error::new( + eyre!("requires feature container-runtime"), + ErrorKind::InvalidRequest, + )) +} + +pub fn exec_command(_: ContainerCliContext) -> Result<(), Error> { + Err(Error::new( + eyre!("requires feature container-runtime"), + ErrorKind::InvalidRequest, + )) +} diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 6a99841d6..d73c51beb 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -1,18 +1,32 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsString; +use std::io::IsTerminal; use std::ops::Deref; +use std::os::unix::process::ExitStatusExt; +use std::path::{Path, PathBuf}; +use std::process::Stdio; use std::sync::{Arc, Weak}; use std::time::Duration; +use axum::extract::ws::WebSocket; use chrono::{DateTime, Utc}; use clap::Parser; use futures::future::BoxFuture; -use imbl::OrdMap; -use models::{HealthCheckId, PackageId, ProcedureName}; -use persistent_container::PersistentContainer; +use futures::stream::FusedStream; +use futures::{SinkExt, StreamExt, TryStreamExt}; +use imbl_value::{json, InternedString}; +use itertools::Itertools; +use models::{ActionId, HostId, ImageId, PackageId, ProcedureName}; +use nix::sys::signal::Signal; +use persistent_container::{PersistentContainer, Subcontainer}; use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor}; use serde::{Deserialize, Serialize}; use service_actor::ServiceActor; use start_stop::StartStop; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::process::Command; use tokio::sync::Notify; +use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; @@ -24,28 +38,27 @@ use crate::install::PKG_ARCHIVE_DIR; use crate::lxc::ContainerId; use crate::prelude::*; use crate::progress::{NamedProgress, Progress}; -use crate::rpc_continuations::Guid; +use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::S9pk; +use crate::service::action::update_requested_actions; use crate::service::service_map::InstallProgressHandles; -use crate::status::health_check::NamedHealthCheckResult; use crate::util::actor::concurrent::ConcurrentActor; -use crate::util::io::create_file; +use crate::util::io::{create_file, AsyncReadStream}; +use crate::util::net::WebSocketExt; use crate::util::serde::{NoOutput, Pem}; use crate::util::Never; use crate::volume::data_dir; +use crate::CAP_1_KiB; -mod action; +pub mod action; pub mod cli; -mod config; mod control; -mod dependencies; pub mod effects; pub mod persistent_container; -mod properties; mod rpc; mod service_actor; pub mod service_map; -mod start_stop; +pub mod start_stop; mod transition; mod util; @@ -68,6 +81,34 @@ pub enum LoadDisposition { Undo, } +struct RootCommand(pub String); + +#[derive(Clone, Debug, Serialize, Deserialize, Default, TS)] +pub struct MiB(pub u64); + +impl MiB { + fn new(value: u64) -> Self { + Self(value / 1024 / 1024) + } + fn from_MiB(value: u64) -> Self { + Self(value) + } +} + +impl std::fmt::Display for MiB { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} MiB", self.0) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default, TS)] +pub struct ServiceStats { + pub container_id: Arc, + pub package_id: PackageId, + pub memory_usage: MiB, + pub memory_limit: MiB, +} + pub struct ServiceRef(Arc); impl ServiceRef { pub fn weak(&self) -> Weak { @@ -81,7 +122,7 @@ impl ServiceRef { .persistent_container .execute::( Guid::new(), - ProcedureName::Uninit, + ProcedureName::PackageUninit, to_value(&target_version)?, None, ) // TODO timeout @@ -116,6 +157,7 @@ impl ServiceRef { ); Ok(()) })?; + d.as_private_mut().as_package_stores_mut().remove(&id)?; Ok(Some(pde)) } else { Ok(None) @@ -183,7 +225,7 @@ impl ServiceRef { impl Deref for ServiceRef { type Target = Service; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } impl From for ServiceRef { @@ -241,7 +283,7 @@ impl Service { tokio::fs::create_dir_all(&path).await?; } } - let start_stop = if i.as_status().as_main().de()?.running() { + let start_stop = if i.as_status().de()?.running() { StartStop::Start } else { StartStop::Stop @@ -354,7 +396,7 @@ impl Service { tracing::debug!("{e:?}") }) { - match ServiceRef::from(service).uninstall(None).await { + match service.uninstall(None).await { Err(e) => { tracing::error!("Error uninstalling service: {e}"); tracing::debug!("{e:?}") @@ -392,6 +434,7 @@ impl Service { let developer_key = s9pk.as_archive().signer(); let icon = s9pk.icon_data_url().await?; let service = Self::new(ctx.clone(), s9pk, StartStop::Stop).await?; + if let Some(recovery_source) = recovery_source { service .actor @@ -413,32 +456,79 @@ impl Service { .clone(), ); } + + let procedure_id = Guid::new(); service .seed .persistent_container .execute::( - Guid::new(), - ProcedureName::Init, + procedure_id.clone(), + ProcedureName::PackageInit, to_value(&src_version)?, None, ) // TODO timeout .await .with_kind(ErrorKind::MigrationFailed)?; // TODO: handle cancellation + if let Some(mut progress) = progress { progress.finalization_progress.complete(); progress.progress.complete(); tokio::task::yield_now().await; } + + let peek = ctx.db.peek().await; + let mut action_input: BTreeMap = BTreeMap::new(); + let requested_actions: BTreeSet<_> = peek + .as_public() + .as_package_data() + .as_entries()? + .into_iter() + .map(|(_, pde)| { + Ok(pde + .as_requested_actions() + .as_entries()? + .into_iter() + .map(|(_, r)| { + Ok::<_, Error>(if r.as_request().as_package_id().de()? == manifest.id { + Some(r.as_request().as_action_id().de()?) + } else { + None + }) + }) + .filter_map_ok(|a| a)) + }) + .flatten_ok() + .map(|a| a.and_then(|a| a)) + .try_collect()?; + for action_id in requested_actions { + if let Some(input) = service + .get_action_input(procedure_id.clone(), action_id.clone()) + .await? + .and_then(|i| i.value) + { + action_input.insert(action_id, input); + } + } ctx.db - .mutate(|d| { - let entry = d + .mutate(|db| { + for (action_id, input) in &action_input { + for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { + pde.as_requested_actions_mut().mutate(|requested_actions| { + Ok(update_requested_actions( + requested_actions, + &manifest.id, + action_id, + input, + false, + )) + })?; + } + } + let entry = db .as_public_mut() .as_package_data_mut() .as_idx_mut(&manifest.id) .or_not_found(&manifest.id)?; - if !manifest.has_config { - entry.as_status_mut().as_configured_mut().ser(&true)?; - } entry .as_state_info_mut() .ser(&PackageState::Installed(InstalledState { manifest }))?; @@ -489,11 +579,58 @@ impl Service { .clone(); Ok(container_id) } + #[instrument(skip_all)] + pub async fn stats(&self) -> Result { + let container = &self.seed.persistent_container; + let lxc_container = container.lxc_container.get().or_not_found("container")?; + let (total, used) = lxc_container + .command(&["free", "-m"]) + .await? + .split("\n") + .map(|x| x.split_whitespace().collect::>()) + .skip(1) + .filter_map(|x| { + Some(( + x.get(1)?.parse::().ok()?, + x.get(2)?.parse::().ok()?, + )) + }) + .fold((0, 0), |acc, (total, used)| (acc.0 + total, acc.1 + used)); + Ok(ServiceStats { + container_id: lxc_container.guid.clone(), + package_id: self.seed.id.clone(), + memory_limit: MiB::from_MiB(total), + memory_usage: MiB::from_MiB(used), + }) + } + + pub async fn update_host(&self, host_id: HostId) -> Result<(), Error> { + let host = self + .seed + .ctx + .db + .peek() + .await + .as_public() + .as_package_data() + .as_idx(&self.seed.id) + .or_not_found(&self.seed.id)? + .as_hosts() + .as_idx(&host_id) + .or_not_found(&host_id)? + .de()?; + self.seed + .persistent_container + .net_service + .lock() + .await + .update(host_id, host) + .await + } } #[derive(Debug, Clone)] pub struct RunningStatus { - health: OrdMap, started: DateTime, } @@ -516,7 +653,6 @@ impl ServiceActorSeed { .running_status .take() .unwrap_or_else(|| RunningStatus { - health: Default::default(), started: Utc::now(), }), ); @@ -530,6 +666,15 @@ impl ServiceActorSeed { } } +#[derive(Deserialize, Serialize, Parser, TS)] +pub struct RebuildParams { + pub id: PackageId, +} +pub async fn rebuild(ctx: RpcContext, RebuildParams { id }: RebuildParams) -> Result<(), Error> { + ctx.services.load(&ctx, &id, LoadDisposition::Retry).await?; + Ok(()) +} + #[derive(Deserialize, Serialize, Parser, TS)] pub struct ConnectParams { pub id: PackageId, @@ -580,3 +725,488 @@ pub async fn connect_rpc_cli( crate::lxc::connect_cli(&ctx, guid).await } + +#[derive(Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +pub struct AttachParams { + pub id: PackageId, + #[ts(type = "string[]")] + pub command: Vec, + pub tty: bool, + #[ts(skip)] + #[serde(rename = "__auth_session")] + session: Option, + #[ts(type = "string | null")] + subcontainer: Option, + #[ts(type = "string | null")] + name: Option, + #[ts(type = "string | null")] + image_id: Option, +} +pub async fn attach( + ctx: RpcContext, + AttachParams { + id, + command, + tty, + session, + subcontainer, + image_id, + name, + }: AttachParams, +) -> Result { + let (container_id, subcontainer_id, image_id, workdir, root_command) = { + let id = &id; + + let service = ctx.services.get(id).await; + + let service_ref = service.as_ref().or_not_found(id)?; + + let container = &service_ref.seed.persistent_container; + let root_dir = container + .lxc_container + .get() + .map(|x| x.rootfs_dir().to_owned()) + .or_not_found(format!("container for {id}"))?; + + let subcontainer = subcontainer.map(|x| AsRef::::as_ref(&x).to_uppercase()); + let name = name.map(|x| AsRef::::as_ref(&x).to_uppercase()); + let image_id = image_id.map(|x| AsRef::::as_ref(&x).to_string_lossy().to_uppercase()); + + let subcontainers = container.subcontainers.lock().await; + let subcontainer_ids: Vec<_> = subcontainers + .iter() + .filter(|(x, wrapper)| { + if let Some(subcontainer) = subcontainer.as_ref() { + AsRef::::as_ref(x).contains(AsRef::::as_ref(subcontainer)) + } else if let Some(name) = name.as_ref() { + AsRef::::as_ref(&wrapper.name) + .to_uppercase() + .contains(AsRef::::as_ref(name)) + } else if let Some(image_id) = image_id.as_ref() { + let Some(wrapper_image_id) = AsRef::::as_ref(&wrapper.image_id).to_str() + else { + return false; + }; + wrapper_image_id + .to_uppercase() + .contains(AsRef::::as_ref(&image_id)) + } else { + true + } + }) + .collect(); + let format_subcontainer_pair = |(guid, wrapper): (&Guid, &Subcontainer)| { + format!( + "{guid} imageId: {image_id} name: \"{name}\"", + name = &wrapper.name, + image_id = &wrapper.image_id + ) + }; + let Some((subcontainer_id, image_id)) = subcontainer_ids + .first() + .map::<(Guid, ImageId), _>(|&x| (x.0.clone(), x.1.image_id.clone())) + else { + drop(subcontainers); + let subcontainers = container + .subcontainers + .lock() + .await + .iter() + .map(format_subcontainer_pair) + .join("\n"); + return Err(Error::new( + eyre!("no matching subcontainers are running for {id}; some possible choices are:\n{subcontainers}"), + ErrorKind::NotFound, + )); + }; + + let passwd = root_dir + .join("media/startos/subcontainers") + .join(subcontainer_id.as_ref()) + .join("etc") + .join("passwd"); + + let root_command = get_passwd_root_command(passwd).await; + + let workdir = attach_workdir(&image_id, &root_dir).await?; + + if subcontainer_ids.len() > 1 { + let subcontainer_ids = subcontainer_ids + .into_iter() + .map(format_subcontainer_pair) + .join("\n"); + return Err(Error::new( + eyre!("multiple subcontainers found for {id}: \n{subcontainer_ids}"), + ErrorKind::InvalidRequest, + )); + } + + ( + service_ref.container_id()?, + subcontainer_id, + image_id, + workdir, + root_command, + ) + }; + + let guid = Guid::new(); + async fn handler( + ws: &mut WebSocket, + container_id: ContainerId, + subcontainer_id: Guid, + command: Vec, + tty: bool, + image_id: ImageId, + workdir: Option, + root_command: &RootCommand, + ) -> Result<(), Error> { + use axum::extract::ws::Message; + + let mut ws = ws.fuse(); + + let mut cmd = Command::new("lxc-attach"); + let root_path = Path::new("/media/startos/subcontainers").join(subcontainer_id.as_ref()); + cmd.kill_on_drop(true); + + cmd.arg(&*container_id) + .arg("--") + .arg("start-cli") + .arg("subcontainer") + .arg("exec") + .arg("--env") + .arg( + Path::new("/media/startos/images") + .join(image_id) + .with_extension("env"), + ); + + if let Some(workdir) = workdir { + cmd.arg("--workdir").arg(workdir); + } + + if tty { + cmd.arg("--force-tty"); + } + + cmd.arg(&root_path).arg("--"); + + if command.is_empty() { + cmd.arg(&root_command.0); + } else { + cmd.args(&command); + } + + let mut child = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let pid = nix::unistd::Pid::from_raw(child.id().or_not_found("child pid")? as i32); + + let mut stdin = child.stdin.take().or_not_found("child stdin")?; + + let mut current_in = "stdin".to_owned(); + let mut current_out = "stdout"; + ws.send(Message::Text(current_out.into())) + .await + .with_kind(ErrorKind::Network)?; + let mut stdout = AsyncReadStream::new( + child.stdout.take().or_not_found("child stdout")?, + 4 * CAP_1_KiB, + ) + .fuse(); + let mut stderr = AsyncReadStream::new( + child.stderr.take().or_not_found("child stderr")?, + 4 * CAP_1_KiB, + ) + .fuse(); + + loop { + futures::select_biased! { + out = stdout.try_next() => { + if let Some(out) = out? { + if current_out != "stdout" { + ws.send(Message::Text("stdout".into())) + .await + .with_kind(ErrorKind::Network)?; + current_out = "stdout"; + } + dbg!(¤t_out); + ws.send(Message::Binary(out)) + .await + .with_kind(ErrorKind::Network)?; + } + } + err = stderr.try_next() => { + if let Some(err) = err? { + if current_out != "stderr" { + ws.send(Message::Text("stderr".into())) + .await + .with_kind(ErrorKind::Network)?; + current_out = "stderr"; + } + dbg!(¤t_out); + ws.send(Message::Binary(err)) + .await + .with_kind(ErrorKind::Network)?; + } + } + msg = ws.try_next() => { + if let Some(msg) = msg.with_kind(ErrorKind::Network)? { + match msg { + Message::Text(in_ty) => { + current_in = in_ty; + } + Message::Binary(data) => { + match &*current_in { + "stdin" => { + stdin.write_all(&data).await?; + } + "signal" => { + if data.len() != 4 { + return Err(Error::new( + eyre!("invalid byte length for signal: {}", data.len()), + ErrorKind::InvalidRequest + )); + } + let mut sig_buf = [0u8; 4]; + sig_buf.clone_from_slice(&data); + nix::sys::signal::kill( + pid, + Signal::try_from(i32::from_be_bytes(sig_buf)) + .with_kind(ErrorKind::InvalidRequest)? + ).with_kind(ErrorKind::Filesystem)?; + } + _ => (), + } + } + _ => () + } + } else { + return Ok(()) + } + } + } + if stdout.is_terminated() && stderr.is_terminated() { + break; + } + } + + let exit = child.wait().await?; + ws.send(Message::Text("exit".into())) + .await + .with_kind(ErrorKind::Network)?; + ws.send(Message::Binary(i32::to_be_bytes(exit.into_raw()).to_vec())) + .await + .with_kind(ErrorKind::Network)?; + + Ok(()) + } + ctx.rpc_continuations + .add( + guid.clone(), + RpcContinuation::ws_authed( + &ctx, + session, + move |mut ws| async move { + if let Err(e) = handler( + &mut ws, + container_id, + subcontainer_id, + command, + tty, + image_id, + workdir, + &root_command, + ) + .await + { + tracing::error!("Error in attach websocket: {e}"); + tracing::debug!("{e:?}"); + ws.close_result(Err::<&str, _>(e)).await.log_err(); + } else { + ws.normal_close("exit").await.log_err(); + } + }, + Duration::from_secs(30), + ), + ) + .await; + + Ok(guid) +} + +async fn attach_workdir(image_id: &ImageId, root_dir: &Path) -> Result, Error> { + let path_str = root_dir.join("media/startos/images/"); + + let mut subcontainer_json = + tokio::fs::File::open(path_str.join(image_id).with_extension("json")).await?; + let mut contents = vec![]; + subcontainer_json.read_to_end(&mut contents).await?; + let subcontainer_json: serde_json::Value = + serde_json::from_slice(&contents).with_kind(ErrorKind::Filesystem)?; + Ok(subcontainer_json["workdir"].as_str().map(|x| x.to_string())) +} + +async fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand { + async { + let mut file = tokio::fs::File::open(etc_passwd_path).await?; + + let mut contents = vec![]; + file.read_to_end(&mut contents).await?; + + let contents = String::from_utf8_lossy(&contents); + + for line in contents.split('\n') { + let line_information = line.split(':').collect::>(); + if let (Some(&"root"), Some(shell)) = + (line_information.first(), line_information.last()) + { + return Ok(shell.to_string()); + } + } + Err(Error::new( + eyre!("Could not parse /etc/passwd for shell: {}", contents), + ErrorKind::Filesystem, + )) + } + .await + .map(RootCommand) + .unwrap_or_else(|e| { + tracing::error!("Could not get the /etc/passwd: {e}"); + tracing::debug!("{e:?}"); + RootCommand("/bin/sh".to_string()) + }) +} + +#[derive(Deserialize, Serialize, Parser)] +pub struct CliAttachParams { + pub id: PackageId, + #[arg(long)] + pub force_tty: bool, + #[arg(trailing_var_arg = true)] + pub command: Vec, + #[arg(long, short)] + subcontainer: Option, + #[arg(long, short)] + name: Option, + #[arg(long, short)] + image_id: Option, +} +pub async fn cli_attach( + HandlerArgs { + context, + parent_method, + method, + params, + .. + }: HandlerArgs, +) -> Result<(), Error> { + use tokio_tungstenite::tungstenite::Message; + + let guid: Guid = from_value( + context + .call_remote::( + &parent_method.into_iter().chain(method).join("."), + json!({ + "id": params.id, + "command": params.command, + "tty": (std::io::stdin().is_terminal() + && std::io::stdout().is_terminal() + && std::io::stderr().is_terminal()) + || params.force_tty, + "subcontainer": params.subcontainer, + "imageId": params.image_id, + "name": params.name, + }), + ) + .await?, + )?; + let mut ws = context.ws_continuation(guid).await?; + + let mut current_in = "stdin"; + let mut current_out = "stdout".to_owned(); + ws.send(Message::Text(current_in.into())) + .await + .with_kind(ErrorKind::Network)?; + let mut stdin = AsyncReadStream::new(tokio::io::stdin(), 4 * CAP_1_KiB).fuse(); + let mut stdout = tokio::io::stdout(); + let mut stderr = tokio::io::stderr(); + loop { + futures::select_biased! { + // signal = tokio:: => { + // let exit = exit?; + // if current_out != "exit" { + // ws.send(Message::Text("exit".into())) + // .await + // .with_kind(ErrorKind::Network)?; + // current_out = "exit"; + // } + // ws.send(Message::Binary( + // i32::to_be_bytes(exit.into_raw()).to_vec() + // )).await.with_kind(ErrorKind::Network)?; + // } + input = stdin.try_next() => { + if let Some(input) = input? { + if current_in != "stdin" { + ws.send(Message::Text("stdin".into())) + .await + .with_kind(ErrorKind::Network)?; + current_in = "stdin"; + } + ws.send(Message::Binary(input)) + .await + .with_kind(ErrorKind::Network)?; + } + } + msg = ws.try_next() => { + if let Some(msg) = msg.with_kind(ErrorKind::Network)? { + match msg { + Message::Text(out_ty) => { + current_out = out_ty; + } + Message::Binary(data) => { + match &*current_out { + "stdout" => { + stdout.write_all(&data).await?; + stdout.flush().await?; + } + "stderr" => { + stderr.write_all(&data).await?; + stderr.flush().await?; + } + "exit" => { + if data.len() != 4 { + return Err(Error::new( + eyre!("invalid byte length for exit code: {}", data.len()), + ErrorKind::InvalidRequest + )); + } + let mut exit_buf = [0u8; 4]; + exit_buf.clone_from_slice(&data); + let code = i32::from_be_bytes(exit_buf); + std::process::exit(code); + } + _ => (), + } + } + Message::Close(Some(close)) => { + if close.code != CloseCode::Normal { + return Err(Error::new( + color_eyre::eyre::Report::msg(close.reason), + ErrorKind::Network + )); + } + } + _ => () + } + } else { + return Ok(()) + } + } + } + } +} diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index c81322719..13cb7688c 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -1,12 +1,14 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::ops::Deref; use std::path::Path; use std::sync::{Arc, Weak}; use std::time::Duration; use futures::future::ready; -use futures::{Future, FutureExt}; +use futures::Future; use helpers::NonDetachingJoinHandle; use imbl::Vector; +use imbl_value::InternedString; use models::{ImageId, ProcedureName, VolumeId}; use rpc_toolkit::{Empty, Server, ShutdownHandle}; use serde::de::DeserializeOwned; @@ -36,7 +38,7 @@ use crate::service::{rpc, RunningStatus, Service}; use crate::util::io::create_file; use crate::util::rpc_client::UnixRpcClient; use crate::util::Invoke; -use crate::volume::{asset_dir, data_dir}; +use crate::volume::data_dir; use crate::ARCH; const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); @@ -84,6 +86,14 @@ impl ServiceState { } } +/// Want to have a wrapper for uses like the inject where we are going to be finding the subcontainer and doing some filtering on it. +/// As well, the imageName is also used for things like env. +pub struct Subcontainer { + pub(super) name: InternedString, + pub(super) image_id: ImageId, + pub(super) overlay: OverlayGuard>, +} + // @DRB On top of this we need to also have the procedures to have the effects and get the results back for them, maybe lock them to the running instance? /// This contains the LXC container running the javascript init system /// that can be used via a JSON RPC Client connected to a unix domain @@ -98,7 +108,7 @@ pub struct PersistentContainer { volumes: BTreeMap, assets: BTreeMap, pub(super) images: BTreeMap>, - pub(super) overlays: Arc>>>>, + pub(super) subcontainers: Arc>>, pub(super) state: Arc>, pub(super) net_service: Mutex, destroyed: bool, @@ -273,7 +283,7 @@ impl PersistentContainer { volumes, assets, images, - overlays: Arc::new(Mutex::new(BTreeMap::new())), + subcontainers: Arc::new(Mutex::new(BTreeMap::new())), state: Arc::new(watch::channel(ServiceState::new(start)).0), net_service: Mutex::new(net_service), destroyed: false, @@ -378,7 +388,10 @@ impl PersistentContainer { } #[instrument(skip_all)] - fn destroy(&mut self) -> Option> + 'static> { + fn destroy( + &mut self, + error: bool, + ) -> Option> + 'static> { if self.destroyed { return None; } @@ -388,11 +401,29 @@ impl PersistentContainer { let volumes = std::mem::take(&mut self.volumes); let assets = std::mem::take(&mut self.assets); let images = std::mem::take(&mut self.images); - let overlays = self.overlays.clone(); + let subcontainers = self.subcontainers.clone(); let lxc_container = self.lxc_container.take(); self.destroyed = true; Some(async move { let mut errs = ErrorCollection::new(); + if error { + if let Some(lxc_container) = &lxc_container { + if let Some(logs) = errs.handle( + crate::logs::fetch_logs( + crate::logs::LogSource::Container(lxc_container.guid.deref().clone()), + Some(50), + None, + None, + false, + ) + .await, + ) { + for log in logs.entries.iter() { + eprintln!("{log}"); + } + } + } + } if let Some((hdl, shutdown)) = rpc_server { errs.handle(rpc_client.request(rpc::Exit, Empty {}).await); shutdown.shutdown(); @@ -404,8 +435,8 @@ impl PersistentContainer { for (_, assets) in assets { errs.handle(assets.unmount(true).await); } - for (_, overlay) in std::mem::take(&mut *overlays.lock().await) { - errs.handle(overlay.unmount(true).await); + for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) { + errs.handle(overlay.overlay.unmount(true).await); } for (_, images) in images { errs.handle(images.unmount().await); @@ -420,7 +451,7 @@ impl PersistentContainer { #[instrument(skip_all)] pub async fn exit(mut self) -> Result<(), Error> { - if let Some(destroy) = self.destroy() { + if let Some(destroy) = self.destroy(false) { dbg!(destroy.await)?; } tracing::info!("Service for {} exited", self.s9pk.as_manifest().id); @@ -538,8 +569,8 @@ impl PersistentContainer { impl Drop for PersistentContainer { fn drop(&mut self) { - if let Some(destroy) = self.destroy() { - tokio::spawn(async move { destroy.await.unwrap() }); + if let Some(destroy) = self.destroy(true) { + tokio::spawn(async move { destroy.await.log_err() }); } } } diff --git a/core/startos/src/service/properties.rs b/core/startos/src/service/properties.rs deleted file mode 100644 index 3f5201f1d..000000000 --- a/core/startos/src/service/properties.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::time::Duration; - -use models::ProcedureName; - -use crate::prelude::*; -use crate::rpc_continuations::Guid; -use crate::service::Service; - -impl Service { - // TODO: leave here or switch to Actor Message? - pub async fn properties(&self) -> Result { - let container = &self.seed.persistent_container; - container - .execute::( - Guid::new(), - ProcedureName::Properties, - Value::Null, - Some(Duration::from_secs(30)), - ) - .await - .with_kind(ErrorKind::Unknown) - } -} diff --git a/core/startos/src/service/rpc.rs b/core/startos/src/service/rpc.rs index 25d8fb067..f008de5c7 100644 --- a/core/startos/src/service/rpc.rs +++ b/core/startos/src/service/rpc.rs @@ -1,10 +1,12 @@ use std::collections::BTreeSet; +use std::str::FromStr; use std::sync::{Arc, Weak}; use std::time::Duration; +use clap::builder::ValueParserFactory; use imbl::Vector; use imbl_value::Value; -use models::ProcedureName; +use models::{FromStrParser, ProcedureName}; use rpc_toolkit::yajrc::RpcMethod; use rpc_toolkit::Empty; use ts_rs::TS; @@ -153,6 +155,11 @@ impl serde::Serialize for Sandbox { pub struct CallbackId(u64); impl CallbackId { pub fn register(self, container: &PersistentContainer) -> CallbackHandle { + dbg!(eyre!( + "callback {} registered for {}", + self.0, + container.s9pk.as_manifest().id + )); let this = Arc::new(self); let res = Arc::downgrade(&this); container @@ -161,6 +168,18 @@ impl CallbackId { CallbackHandle(res) } } +impl FromStr for CallbackId { + type Err = Error; + fn from_str(s: &str) -> Result { + u64::from_str(s).map_err(Error::from).map(Self) + } +} +impl ValueParserFactory for CallbackId { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + FromStrParser::new() + } +} pub struct CallbackHandle(Weak); impl CallbackHandle { diff --git a/core/startos/src/service/service_actor.rs b/core/startos/src/service/service_actor.rs index e6578264c..712baaf1e 100644 --- a/core/startos/src/service/service_actor.rs +++ b/core/startos/src/service/service_actor.rs @@ -1,11 +1,12 @@ use std::sync::Arc; use std::time::Duration; -use imbl::OrdMap; +use imbl::vector; use super::start_stop::StartStop; use super::ServiceActorSeed; use crate::prelude::*; +use crate::service::persistent_container::ServiceStateKinds; use crate::service::transition::TransitionKind; use crate::service::SYNC_RETRY_COOLDOWN_SECONDS; use crate::status::MainStatus; @@ -46,96 +47,89 @@ async fn service_actor_loop( let id = &seed.id; let kinds = current.borrow().kinds(); if let Err(e) = async { - let main_status = match ( - kinds.transition_state, - kinds.desired_state, - kinds.running_status, - ) { - (Some(TransitionKind::Restarting), StartStop::Stop, Some(_)) => { - seed.persistent_container.stop().await?; - MainStatus::Restarting - } - (Some(TransitionKind::Restarting), StartStop::Start, _) => { - seed.persistent_container.start().await?; - MainStatus::Restarting - } - (Some(TransitionKind::Restarting), _, _) => MainStatus::Restarting, - (Some(TransitionKind::Restoring), _, _) => MainStatus::Restoring, - (Some(TransitionKind::BackingUp), StartStop::Stop, Some(status)) => { - seed.persistent_container.stop().await?; - MainStatus::BackingUp { - started: Some(status.started), - health: status.health.clone(), - } - } - (Some(TransitionKind::BackingUp), StartStop::Start, _) => { - seed.persistent_container.start().await?; - MainStatus::BackingUp { - started: None, - health: OrdMap::new(), - } - } - (Some(TransitionKind::BackingUp), _, _) => MainStatus::BackingUp { - started: None, - health: OrdMap::new(), - }, - (None, StartStop::Stop, None) => MainStatus::Stopped, - (None, StartStop::Stop, Some(_)) => { - let task_seed = seed.clone(); - seed.ctx - .db - .mutate(|d| { - if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) { - i.as_status_mut().as_main_mut().ser(&MainStatus::Stopping)?; - } - Ok(()) - }) - .await?; - task_seed.persistent_container.stop().await?; - MainStatus::Stopped - } - (None, StartStop::Start, Some(status)) => MainStatus::Running { - started: status.started, - health: status.health.clone(), - }, - (None, StartStop::Start, None) => { - seed.persistent_container.start().await?; - MainStatus::Starting - } - }; - seed.ctx + let major_changes_state = seed + .ctx .db .mutate(|d| { if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) { - let previous = i.as_status().as_main().de()?; - let previous_health = previous.health(); - let previous_started = previous.started(); - let mut main_status = main_status; - match &mut main_status { - &mut MainStatus::Running { ref mut health, .. } - | &mut MainStatus::BackingUp { ref mut health, .. } => { - *health = previous_health.unwrap_or(health).clone(); - } - _ => (), - }; - match &mut main_status { - MainStatus::Running { - ref mut started, .. - } => { - *started = previous_started.unwrap_or(*started); - } - MainStatus::BackingUp { - ref mut started, .. - } => { - *started = previous_started.map(Some).unwrap_or(*started); - } - _ => (), + let previous = i.as_status().de()?; + let main_status = match &kinds { + ServiceStateKinds { + transition_state: Some(TransitionKind::Restarting), + .. + } => MainStatus::Restarting, + ServiceStateKinds { + transition_state: Some(TransitionKind::Restoring), + .. + } => MainStatus::Restoring, + ServiceStateKinds { + transition_state: Some(TransitionKind::BackingUp), + .. + } => previous.backing_up(), + ServiceStateKinds { + running_status: Some(status), + desired_state: StartStop::Start, + .. + } => MainStatus::Running { + started: status.started, + health: previous.health().cloned().unwrap_or_default(), + }, + ServiceStateKinds { + running_status: None, + desired_state: StartStop::Start, + .. + } => MainStatus::Starting { + health: previous.health().cloned().unwrap_or_default(), + }, + ServiceStateKinds { + running_status: Some(_), + desired_state: StartStop::Stop, + .. + } => MainStatus::Stopping, + ServiceStateKinds { + running_status: None, + desired_state: StartStop::Stop, + .. + } => MainStatus::Stopped, }; - i.as_status_mut().as_main_mut().ser(&main_status)?; + let previous = i.as_status().de()?; + i.as_status_mut().ser(&main_status)?; + return Ok(previous + .major_changes(&main_status) + .then_some((previous, main_status))); } - Ok(()) + Ok(None) }) .await?; + if let Some((previous, new_state)) = major_changes_state { + if let Some(callbacks) = seed.ctx.callbacks.get_status(id) { + callbacks + .call(vector![to_value(&previous)?, to_value(&new_state)?]) + .await?; + } + } + seed.synchronized.notify_waiters(); + + match kinds { + ServiceStateKinds { + running_status: None, + desired_state: StartStop::Start, + .. + } => { + seed.persistent_container.start().await?; + } + ServiceStateKinds { + running_status: Some(_), + desired_state: StartStop::Stop, + .. + } => { + seed.persistent_container.stop().await?; + seed.persistent_container + .state + .send_if_modified(|s| s.running_status.take().is_some()); + } + _ => (), + }; Ok::<_, Error>(()) } diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 0e6a959ae..0faab8dd5 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -7,6 +7,7 @@ use futures::{Future, FutureExt}; use helpers::NonDetachingJoinHandle; use imbl::OrdMap; use imbl_value::InternedString; +use models::ErrorData; use tokio::sync::{Mutex, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock}; use tracing::instrument; @@ -22,8 +23,9 @@ use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle, ProgressT use crate::s9pk::manifest::PackageId; use crate::s9pk::merkle_archive::source::FileSource; use crate::s9pk::S9pk; +use crate::service::start_stop::StartStop; use crate::service::{LoadDisposition, Service, ServiceRef}; -use crate::status::{MainStatus, Status}; +use crate::status::MainStatus; use crate::util::serde::Pem; pub type DownloadInstallFuture = BoxFuture<'static, Result>; @@ -87,8 +89,30 @@ impl ServiceMap { if let Some(service) = service.take() { shutdown_err = service.shutdown().await; } - // TODO: retry on error? - *service = Service::load(ctx, id, disposition).await?.map(From::from); + match Service::load(ctx, id, disposition).await { + Ok(s) => *service = s.into(), + Err(e) => { + let e = ErrorData::from(e); + ctx.db + .mutate(|db| { + if let Some(pde) = db.as_public_mut().as_package_data_mut().as_idx_mut(id) { + pde.as_status_mut().map_mutate(|s| { + Ok(MainStatus::Error { + on_rebuild: if s.running() { + StartStop::Start + } else { + StartStop::Stop + }, + message: e.details, + debug: Some(e.debug), + }) + })?; + } + Ok(()) + }) + .await?; + } + } shutdown_err?; Ok(()) } @@ -174,16 +198,14 @@ impl ServiceMap { PackageState::Installing(installing) }, data_version: None, - status: Status { - configured: false, - main: MainStatus::Stopped, - }, + status: MainStatus::Stopped, registry: None, developer_key: Pem::new(developer_key), icon, last_backup: None, current_dependencies: Default::default(), actions: Default::default(), + requested_actions: Default::default(), service_interfaces: Default::default(), hosts: Default::default(), store_exposed_dependents: Default::default(), @@ -285,7 +307,6 @@ impl ServiceMap { sync_progress_task.await.map_err(|_| { Error::new(eyre!("progress sync task panicked"), ErrorKind::Unknown) })??; - Ok(()) }) .boxed()) diff --git a/core/startos/src/service/start_stop.rs b/core/startos/src/service/start_stop.rs index 178176023..64d4022d6 100644 --- a/core/startos/src/service/start_stop.rs +++ b/core/startos/src/service/start_stop.rs @@ -1,6 +1,10 @@ +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + use crate::status::MainStatus; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] pub enum StartStop { Start, Stop, @@ -11,23 +15,19 @@ impl StartStop { matches!(self, StartStop::Start) } } -impl From for StartStop { - fn from(value: MainStatus) -> Self { - match value { - MainStatus::Stopped => StartStop::Stop, - MainStatus::Restoring => StartStop::Stop, - MainStatus::Restarting => StartStop::Start, - MainStatus::Stopping { .. } => StartStop::Stop, - MainStatus::Starting => StartStop::Start, - MainStatus::Running { - started: _, - health: _, - } => StartStop::Start, - MainStatus::BackingUp { started, health: _ } if started.is_some() => StartStop::Start, - MainStatus::BackingUp { - started: _, - health: _, - } => StartStop::Stop, - } - } -} +// impl From for StartStop { +// fn from(value: MainStatus) -> Self { +// match value { +// MainStatus::Stopped => StartStop::Stop, +// MainStatus::Restoring => StartStop::Stop, +// MainStatus::Restarting => StartStop::Start, +// MainStatus::Stopping { .. } => StartStop::Stop, +// MainStatus::Starting => StartStop::Start, +// MainStatus::Running { +// started: _, +// health: _, +// } => StartStop::Start, +// MainStatus::BackingUp { on_complete } => on_complete, +// } +// } +// } diff --git a/core/startos/src/service/transition/backup.rs b/core/startos/src/service/transition/backup.rs index d8606f534..0d4116078 100644 --- a/core/startos/src/service/transition/backup.rs +++ b/core/startos/src/service/transition/backup.rs @@ -9,8 +9,7 @@ use super::TempDesiredRestore; use crate::disk::mount::filesystem::ReadWrite; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::service::config::GetConfig; -use crate::service::dependencies::DependencyConfig; +use crate::service::action::GetActionInput; use crate::service::transition::{TransitionKind, TransitionState}; use crate::service::ServiceActor; use crate::util::actor::background::BackgroundJobQueue; @@ -23,9 +22,7 @@ pub(in crate::service) struct Backup { impl Handler for ServiceActor { type Response = Result>, Error>; fn conflicts_with(_: &Backup) -> ConflictBuilder { - ConflictBuilder::everything() - .except::() - .except::() + ConflictBuilder::everything().except::() } async fn handle( &mut self, diff --git a/core/startos/src/service/transition/restart.rs b/core/startos/src/service/transition/restart.rs index 108e232ad..27bef0b91 100644 --- a/core/startos/src/service/transition/restart.rs +++ b/core/startos/src/service/transition/restart.rs @@ -3,8 +3,7 @@ use futures::FutureExt; use super::TempDesiredRestore; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::service::config::GetConfig; -use crate::service::dependencies::DependencyConfig; +use crate::service::action::GetActionInput; use crate::service::transition::{TransitionKind, TransitionState}; use crate::service::{Service, ServiceActor}; use crate::util::actor::background::BackgroundJobQueue; @@ -15,9 +14,7 @@ pub(super) struct Restart; impl Handler for ServiceActor { type Response = (); fn conflicts_with(_: &Restart) -> ConflictBuilder { - ConflictBuilder::everything() - .except::() - .except::() + ConflictBuilder::everything().except::() } async fn handle(&mut self, _: Guid, _: Restart, jobs: &BackgroundJobQueue) -> Self::Response { // So Need a handle to just a single field in the state diff --git a/core/startos/src/setup.rs b/core/startos/src/setup.rs index 642dd5476..1319ffae4 100644 --- a/core/startos/src/setup.rs +++ b/core/startos/src/setup.rs @@ -10,6 +10,7 @@ use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; +use tokio::process::Command; use tokio::try_join; use tracing::instrument; use ts_rs::TS; @@ -36,6 +37,7 @@ use crate::progress::{FullProgress, PhaseProgressTrackerHandle}; use crate::rpc_continuations::Guid; use crate::util::crypto::EncryptedWire; use crate::util::io::{create_file, dir_copy, dir_size, Counter}; +use crate::util::Invoke; use crate::{Error, ErrorKind, ResultExt}; pub fn setup() -> ParentHandler { @@ -336,6 +338,11 @@ pub async fn complete(ctx: SetupContext) -> Result { let mut guid_file = create_file("/media/startos/config/disk.guid").await?; guid_file.write_all(ctx.disk_guid.as_bytes()).await?; guid_file.sync_all().await?; + Command::new("systemd-firstboot") + .arg("--root=/media/startos/config/overlay/") + .arg(format!("--hostname={}", res.hostname.0)) + .invoke(ErrorKind::ParseSysInfo) + .await?; Ok(res.clone()) } Some(Err(e)) => Err(e.clone_output()), diff --git a/core/startos/src/ssh.rs b/core/startos/src/ssh.rs index b97fba3e4..afc5a5bf1 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -5,6 +5,7 @@ use clap::builder::ValueParserFactory; use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::InternedString; +use models::FromStrParser; use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; @@ -12,7 +13,6 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::prelude::*; -use crate::util::clap::FromStrParser; use crate::util::io::create_file; use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; @@ -25,6 +25,12 @@ impl SshKeys { Self(BTreeMap::new()) } } + +impl From>> for SshKeys { + fn from(map: BTreeMap>) -> Self { + Self(map) + } +} impl Map for SshKeys { type Key = InternedString; type Value = WithTimeData; @@ -41,7 +47,7 @@ impl Map for SshKeys { pub struct SshPubKey( #[serde(serialize_with = "crate::util::serde::serialize_display")] #[serde(deserialize_with = "crate::util::serde::deserialize_from_str")] - openssh_keys::PublicKey, + pub openssh_keys::PublicKey, ); impl ValueParserFactory for SshPubKey { type Parser = FromStrParser; @@ -86,12 +92,14 @@ pub fn ssh() -> ParentHandler { "add", from_fn_async(add) .no_display() + .with_about("Add ssh key") .with_call_remote::(), ) .subcommand( "delete", from_fn_async(delete) .no_display() + .with_about("Remove ssh key") .with_call_remote::(), ) .subcommand( @@ -101,6 +109,7 @@ pub fn ssh() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_all_ssh_keys(handle.params, result)) }) + .with_about("List ssh keys") .with_call_remote::(), ) } diff --git a/core/startos/src/status/health_check.rs b/core/startos/src/status/health_check.rs index 1b1e2a7b6..861954d29 100644 --- a/core/startos/src/status/health_check.rs +++ b/core/startos/src/status/health_check.rs @@ -1,12 +1,11 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; +use models::FromStrParser; pub use models::HealthCheckId; use serde::{Deserialize, Serialize}; use ts_rs::TS; -use crate::util::clap::FromStrParser; - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)] #[serde(rename_all = "camelCase")] pub struct NamedHealthCheckResult { diff --git a/core/startos/src/status/mod.rs b/core/startos/src/status/mod.rs index 1701a965e..398797a5a 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -1,5 +1,4 @@ use std::collections::BTreeMap; -use std::sync::Arc; use chrono::{DateTime, Utc}; use imbl::OrdMap; @@ -8,41 +7,37 @@ use ts_rs::TS; use self::health_check::HealthCheckId; use crate::prelude::*; +use crate::service::start_stop::StartStop; use crate::status::health_check::NamedHealthCheckResult; -use crate::util::GeneralGuard; pub mod health_check; -#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)] -#[serde(rename_all = "camelCase")] -#[model = "Model"] -#[ts(export)] -pub struct Status { - pub configured: bool, - pub main: MainStatus, -} #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, TS)] -#[serde(tag = "status")] +#[serde(tag = "main")] #[serde(rename_all = "camelCase")] +#[serde(rename_all_fields = "camelCase")] pub enum MainStatus { + Error { + on_rebuild: StartStop, + message: String, + debug: Option, + }, Stopped, Restarting, Restoring, Stopping, - Starting, - #[serde(rename_all = "camelCase")] + Starting { + #[ts(as = "BTreeMap")] + health: OrdMap, + }, Running { #[ts(type = "string")] started: DateTime, #[ts(as = "BTreeMap")] health: OrdMap, }, - #[serde(rename_all = "camelCase")] BackingUp { - #[ts(type = "string | null")] - started: Option>, - #[ts(as = "BTreeMap")] - health: OrdMap, + on_complete: StartStop, }, } impl MainStatus { @@ -50,60 +45,60 @@ impl MainStatus { match self { MainStatus::Starting { .. } | MainStatus::Running { .. } + | MainStatus::Restarting | MainStatus::BackingUp { - started: Some(_), .. + on_complete: StartStop::Start, + } + | MainStatus::Error { + on_rebuild: StartStop::Start, + .. } => true, MainStatus::Stopped | MainStatus::Restoring | MainStatus::Stopping { .. } - | MainStatus::Restarting - | MainStatus::BackingUp { started: None, .. } => false, + | MainStatus::BackingUp { + on_complete: StartStop::Stop, + } + | MainStatus::Error { + on_rebuild: StartStop::Stop, + .. + } => false, } } - // pub fn stop(&mut self) { - // match self { - // MainStatus::Starting { .. } | MainStatus::Running { .. } => { - // *self = MainStatus::Stopping; - // } - // MainStatus::BackingUp { started, .. } => { - // *started = None; - // } - // MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => (), - // } - // } - pub fn started(&self) -> Option> { - match self { - MainStatus::Running { started, .. } => Some(*started), - MainStatus::BackingUp { started, .. } => *started, - MainStatus::Stopped => None, - MainStatus::Restoring => None, - MainStatus::Restarting => None, - MainStatus::Stopping { .. } => None, - MainStatus::Starting { .. } => None, + + pub fn major_changes(&self, other: &Self) -> bool { + match (self, other) { + (MainStatus::Running { .. }, MainStatus::Running { .. }) => false, + (MainStatus::Starting { .. }, MainStatus::Starting { .. }) => false, + (MainStatus::Stopping, MainStatus::Stopping) => false, + (MainStatus::Stopped, MainStatus::Stopped) => false, + (MainStatus::Restarting, MainStatus::Restarting) => false, + (MainStatus::Restoring, MainStatus::Restoring) => false, + (MainStatus::BackingUp { .. }, MainStatus::BackingUp { .. }) => false, + (MainStatus::Error { .. }, MainStatus::Error { .. }) => false, + _ => true, } } - pub fn backing_up(&self) -> Self { - let (started, health) = match self { - MainStatus::Starting { .. } => (Some(Utc::now()), Default::default()), - MainStatus::Running { started, health } => (Some(started.clone()), health.clone()), - MainStatus::Stopped - | MainStatus::Stopping { .. } - | MainStatus::Restoring - | MainStatus::Restarting => (None, Default::default()), - MainStatus::BackingUp { .. } => return self.clone(), - }; - MainStatus::BackingUp { started, health } + + pub fn backing_up(self) -> Self { + MainStatus::BackingUp { + on_complete: if self.running() { + StartStop::Start + } else { + StartStop::Stop + }, + } } pub fn health(&self) -> Option<&OrdMap> { match self { - MainStatus::Running { health, .. } => Some(health), - MainStatus::BackingUp { health, .. } => Some(health), - MainStatus::Stopped + MainStatus::Running { health, .. } | MainStatus::Starting { health } => Some(health), + MainStatus::BackingUp { .. } + | MainStatus::Stopped | MainStatus::Restoring | MainStatus::Stopping { .. } - | MainStatus::Restarting => None, - MainStatus::Starting { .. } => None, + | MainStatus::Restarting + | MainStatus::Error { .. } => None, } } } diff --git a/core/startos/src/system.rs b/core/startos/src/system.rs index 7af94588b..e64f30e98 100644 --- a/core/startos/src/system.rs +++ b/core/startos/src/system.rs @@ -31,6 +31,7 @@ pub fn experimental() -> ParentHandler { "zram", from_fn_async(zram) .no_display() + .with_about("Enable zram") .with_call_remote::(), ) .subcommand( @@ -40,6 +41,7 @@ pub fn experimental() -> ParentHandler { .with_custom_display_fn(|handle, result| { Ok(display_governor_info(handle.params, result)) }) + .with_about("Show current and available CPU governors") .with_call_remote::(), ) } diff --git a/core/startos/src/upload.rs b/core/startos/src/upload.rs index 20f294400..73519c603 100644 --- a/core/startos/src/upload.rs +++ b/core/startos/src/upload.rs @@ -25,7 +25,7 @@ use crate::util::io::{create_file, TmpDir}; pub async fn upload( ctx: &RpcContext, - session: InternedString, + session: Option, ) -> Result<(Guid, UploadingFile), Error> { let guid = Guid::new(); let (mut handle, file) = UploadingFile::new().await?; diff --git a/core/startos/src/util/io.rs b/core/startos/src/util/io.rs index 6d9c8a4ff..0e7aada54 100644 --- a/core/startos/src/util/io.rs +++ b/core/startos/src/util/io.rs @@ -1,6 +1,7 @@ use std::collections::VecDeque; use std::future::Future; use std::io::Cursor; +use std::mem::MaybeUninit; use std::os::unix::prelude::MetadataExt; use std::path::{Path, PathBuf}; use std::pin::Pin; @@ -11,7 +12,7 @@ use std::time::Duration; use bytes::{Buf, BytesMut}; use futures::future::{BoxFuture, Fuse}; -use futures::{AsyncSeek, FutureExt, TryStreamExt}; +use futures::{AsyncSeek, FutureExt, Stream, TryStreamExt}; use helpers::NonDetachingJoinHandle; use nix::unistd::{Gid, Uid}; use tokio::fs::File; @@ -23,6 +24,7 @@ use tokio::sync::{Notify, OwnedMutexGuard}; use tokio::time::{Instant, Sleep}; use crate::prelude::*; +use crate::{CAP_1_KiB, CAP_1_MiB}; pub trait AsyncReadSeek: AsyncRead + AsyncSeek {} impl AsyncReadSeek for T {} @@ -440,6 +442,13 @@ impl BackTrackingIO { }, } } + pub fn read_buffer(&self) -> &[u8] { + match &self.buffer { + BTBuffer::NotBuffering => &[], + BTBuffer::Buffering { read, .. } => read, + BTBuffer::Rewound { read } => read.remaining_slice(), + } + } #[must_use] pub fn stop_buffering(&mut self) -> Vec { match std::mem::take(&mut self.buffer) { @@ -510,6 +519,28 @@ impl AsyncRead for BackTrackingIO { } } } +impl std::io::Read for BackTrackingIO { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + match &mut self.buffer { + BTBuffer::Buffering { read, .. } => { + let n = self.io.read(buf)?; + read.extend_from_slice(&buf[..n]); + Ok(n) + } + BTBuffer::NotBuffering => self.io.read(buf), + BTBuffer::Rewound { read } => { + let mut ready = false; + if (read.position() as usize) < read.get_ref().len() { + let n = std::io::Read::read(read, buf)?; + if n != 0 { + return Ok(n); + } + } + self.io.read(buf) + } + } + } +} impl AsyncWrite for BackTrackingIO { fn is_write_vectored(&self) -> bool { @@ -867,7 +898,7 @@ impl Drop for TmpDir { if self.path.exists() { let path = std::mem::take(&mut self.path); tokio::spawn(async move { - tokio::fs::remove_dir_all(&path).await.unwrap(); + tokio::fs::remove_dir_all(&path).await.log_err(); }); } } @@ -892,6 +923,20 @@ pub async fn create_file(path: impl AsRef) -> Result { .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("create {path:?}"))) } +pub async fn delete_file(path: impl AsRef) -> Result<(), Error> { + let path = path.as_ref(); + tokio::fs::remove_file(path) + .await + .or_else(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + Ok(()) + } else { + Err(e) + } + }) + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("delete {path:?}"))) +} + pub async fn rename(src: impl AsRef, dst: impl AsRef) -> Result<(), Error> { let src = src.as_ref(); let dst = dst.as_ref(); @@ -1267,3 +1312,33 @@ impl AsyncWrite for MutexIO { Pin::new(&mut *self.get_mut().0).poll_shutdown(cx) } } + +#[pin_project::pin_project] +pub struct AsyncReadStream { + buffer: Vec>, + #[pin] + pub io: T, +} +impl AsyncReadStream { + pub fn new(io: T, buffer_size: usize) -> Self { + Self { + buffer: vec![MaybeUninit::uninit(); buffer_size], + io, + } + } +} +impl Stream for AsyncReadStream { + type Item = Result, Error>; + fn poll_next( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let this = self.project(); + let mut buf = ReadBuf::uninit(this.buffer); + match futures::ready!(this.io.poll_read(cx, &mut buf)) { + Ok(()) if buf.filled().is_empty() => Poll::Ready(None), + Ok(()) => Poll::Ready(Some(Ok(buf.filled().to_vec()))), + Err(e) => Poll::Ready(Some(Err(e.into()))), + } + } +} diff --git a/core/startos/src/util/logger.rs b/core/startos/src/util/logger.rs index c7ab41ba2..c464b328d 100644 --- a/core/startos/src/util/logger.rs +++ b/core/startos/src/util/logger.rs @@ -1,3 +1,5 @@ +use std::io; + use tracing::Subscriber; use tracing_subscriber::util::SubscriberInitExt; @@ -21,7 +23,11 @@ impl EmbassyLogger { let filter_layer = filter_layer .add_directive("tokio=trace".parse().unwrap()) .add_directive("runtime=trace".parse().unwrap()); - let fmt_layer = fmt::layer().with_target(true); + let fmt_layer = fmt::layer() + .with_writer(io::stderr) + .with_line_number(true) + .with_file(true) + .with_target(true); let sub = tracing_subscriber::registry() .with(filter_layer) diff --git a/core/startos/src/util/lshw.rs b/core/startos/src/util/lshw.rs index eb5293d89..df5fff5f8 100644 --- a/core/startos/src/util/lshw.rs +++ b/core/startos/src/util/lshw.rs @@ -10,6 +10,7 @@ const KNOWN_CLASSES: &[&str] = &["processor", "display"]; #[derive(Clone, Debug, Deserialize, Serialize, TS)] #[serde(tag = "class")] #[serde(rename_all = "camelCase")] +#[ts(export)] pub enum LshwDevice { Processor(LshwProcessor), Display(LshwDisplay), diff --git a/core/startos/src/util/mod.rs b/core/startos/src/util/mod.rs index fab0b127d..0f3563018 100644 --- a/core/startos/src/util/mod.rs +++ b/core/startos/src/util/mod.rs @@ -36,7 +36,6 @@ use crate::util::serde::{deserialize_from_str, serialize_display}; use crate::{Error, ErrorKind, ResultExt as _}; pub mod actor; -pub mod clap; pub mod collections; pub mod cpupower; pub mod crypto; @@ -237,11 +236,7 @@ impl<'a> Invoke<'a> for ExtendedCommand<'a> { .or(Some(&res.stdout)) .filter(|a| !a.is_empty()) .and_then(|a| std::str::from_utf8(a).ok()) - .unwrap_or(&format!( - "{} exited with code {}", - self.cmd.as_std().get_program().to_string_lossy(), - res.status - )) + .unwrap_or(&format!("{} exited with code {}", cmd_str, res.status)) ); Ok(res.stdout) } else { @@ -268,7 +263,7 @@ impl<'a> Invoke<'a> for ExtendedCommand<'a> { if prev.is_some() { cmd.stdin(Stdio::piped()); } - let mut child = cmd.spawn().with_kind(error_kind)?; + let mut child = cmd.spawn().with_ctx(|_| (error_kind, &cmd_str))?; let input = std::mem::replace( &mut prev, child @@ -568,7 +563,7 @@ pub struct FileLock(#[allow(unused)] OwnedMutexGuard<()>, Option>); impl Drop for FileLock { fn drop(&mut self) { if let Some(fd_lock) = self.1.take() { - tokio::task::spawn_blocking(|| fd_lock.unlock(true).map_err(|(_, e)| e).unwrap()); + tokio::task::spawn_blocking(|| fd_lock.unlock(true).map_err(|(_, e)| e).log_err()); } } } diff --git a/core/startos/src/util/rpc.rs b/core/startos/src/util/rpc.rs index 80d6d9251..b2dea340e 100644 --- a/core/startos/src/util/rpc.rs +++ b/core/startos/src/util/rpc.rs @@ -1,7 +1,7 @@ use std::path::Path; use clap::Parser; -use rpc_toolkit::{from_fn_async, Context, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use url::Url; @@ -16,7 +16,10 @@ use crate::util::{Apply, PathOrUrl}; use crate::CAP_10_MiB; pub fn util() -> ParentHandler { - ParentHandler::new().subcommand("b3sum", from_fn_async(b3sum)) + ParentHandler::new().subcommand( + "b3sum", + from_fn_async(b3sum).with_about("Calculate blake3 hash for a file"), + ) } #[derive(Debug, Deserialize, Serialize, Parser)] diff --git a/core/startos/src/util/serde.rs b/core/startos/src/util/serde.rs index 88d7bfc11..5b785ce9a 100644 --- a/core/startos/src/util/serde.rs +++ b/core/startos/src/util/serde.rs @@ -1,4 +1,3 @@ -use std::any::Any; use std::collections::VecDeque; use std::marker::PhantomData; use std::ops::Deref; @@ -9,6 +8,7 @@ use clap::builder::ValueParserFactory; use clap::{ArgMatches, CommandFactory, FromArgMatches}; use color_eyre::eyre::eyre; use imbl::OrdMap; +use models::FromStrParser; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; use rpc_toolkit::{ @@ -17,12 +17,10 @@ use rpc_toolkit::{ use serde::de::DeserializeOwned; use serde::ser::{SerializeMap, SerializeSeq}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde_json::Value; use ts_rs::TS; use super::IntoDoubleEndedIterator; use crate::prelude::*; -use crate::util::clap::FromStrParser; use crate::util::Apply; pub fn deserialize_from_str< @@ -272,7 +270,7 @@ impl std::fmt::Display for IoFormat { impl std::str::FromStr for IoFormat { type Err = Error; fn from_str(s: &str) -> Result { - serde_json::from_value(Value::String(s.to_owned())) + serde_json::from_value(serde_json::Value::String(s.to_owned())) .with_kind(crate::ErrorKind::Deserialization) } } @@ -566,7 +564,7 @@ where } } -#[derive(Deserialize, Serialize, TS)] +#[derive(Deserialize, Serialize, TS, Clone)] pub struct StdinDeserializable(pub T); impl Default for StdinDeserializable where @@ -1031,6 +1029,12 @@ impl>> FromStr for Base64 { }) } } +impl>> ValueParserFactory for Base64 { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + Self::Parser::new() + } +} impl<'de, T: TryFrom>> Deserialize<'de> for Base64 { fn deserialize(deserializer: D) -> Result where @@ -1217,6 +1221,30 @@ impl PemEncoding for ed25519_dalek::SigningKey { } } +#[derive(Clone, Debug)] +pub struct Pkcs8Doc { + pub tag: String, + pub document: pkcs8::Document, +} + +impl PemEncoding for Pkcs8Doc { + fn from_pem(pem: &str) -> Result { + let (tag, document) = pkcs8::Document::from_pem(pem).map_err(E::custom)?; + Ok(Pkcs8Doc { + tag: tag.into(), + document, + }) + } + fn to_pem(&self) -> Result { + der::pem::encode_string( + &self.tag, + pkcs8::LineEnding::default(), + self.document.as_bytes(), + ) + .map_err(E::custom) + } +} + pub mod pem { use serde::{Deserialize, Deserializer, Serializer}; @@ -1358,3 +1386,19 @@ impl Serialize for MaybeUtf8String { } } } + +pub fn is_partial_of(partial: &Value, full: &Value) -> bool { + match (partial, full) { + (Value::Object(partial), Value::Object(full)) => partial.iter().all(|(k, v)| { + if let Some(v_full) = full.get(k) { + is_partial_of(v, v_full) + } else { + false + } + }), + (Value::Array(partial), Value::Array(full)) => partial + .iter() + .all(|v| full.iter().any(|v_full| is_partial_of(v, v_full))), + (_, _) => partial == full, + } +} diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index 8e114334d..f71cec8b3 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -1,10 +1,15 @@ +use std::any::Any; use std::cmp::Ordering; +use std::panic::{RefUnwindSafe, UnwindSafe}; use color_eyre::eyre::eyre; use futures::future::BoxFuture; use futures::{Future, FutureExt}; -use imbl_value::InternedString; +use imbl::Vector; +use imbl_value::{to_value, InternedString}; +use patch_db::json_ptr::{JsonPointer, ROOT}; +use crate::context::RpcContext; use crate::db::model::Database; use crate::prelude::*; use crate::progress::PhaseProgressTrackerHandle; @@ -21,8 +26,68 @@ mod v0_3_6_alpha_4; mod v0_3_6_alpha_5; mod v0_3_6_alpha_6; mod v0_3_6_alpha_7; +mod v0_3_6_alpha_8; -pub type Current = v0_3_6_alpha_4::Version; // VERSION_BUMP +pub type Current = v0_3_6_alpha_8::Version; // VERSION_BUMP + +impl Current { + #[instrument(skip(self, db))] + pub async fn pre_init(self, db: &PatchDb) -> Result<(), Error> { + let from = from_value::( + version_accessor(&mut db.dump(&ROOT).await.value) + .or_not_found("`version` in db")? + .clone(), + )? + .as_version_t()?; + match from.semver().cmp(&self.semver()) { + Ordering::Greater => { + db.apply_function(|mut db| { + rollback_to_unchecked(&from, &self, &mut db)?; + Ok::<_, Error>((db, ())) + }) + .await?; + } + Ordering::Less => { + let pre_ups = PreUps::load(&from, &self).await?; + db.apply_function(|mut db| { + migrate_from_unchecked(&from, &self, pre_ups, &mut db)?; + Ok::<_, Error>((db, ())) + }) + .await?; + } + Ordering::Equal => (), + } + Ok(()) + } +} + +pub async fn post_init(ctx: &RpcContext) -> Result<(), Error> { + let mut peek; + while let Some(version) = { + peek = ctx.db.peek().await; + peek.as_public() + .as_server_info() + .as_post_init_migration_todos() + .de()? + .first() + .cloned() + .map(Version::from_exver_version) + .as_ref() + .map(Version::as_version_t) + .transpose()? + } { + version.0.post_up(ctx).await?; + ctx.db + .mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_post_init_migration_todos_mut() + .mutate(|m| Ok(m.remove(&version.0.semver()))) + }) + .await?; + } + Ok(()) +} #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(untagged)] @@ -40,6 +105,7 @@ enum Version { V0_3_6_alpha_5(Wrapper), V0_3_6_alpha_6(Wrapper), V0_3_6_alpha_7(Wrapper), + V0_3_6_alpha_8(Wrapper), Other(exver::Version), } @@ -52,6 +118,34 @@ impl Version { Version::Other(version) }) } + fn as_version_t(&self) -> Result { + Ok(match self { + Self::LT0_3_5(_) => { + return Err(Error::new( + eyre!("cannot migrate from versions before 0.3.5"), + ErrorKind::MigrationFailed, + )) + } + Self::V0_3_5(v) => DynVersion(Box::new(v.0)), + Self::V0_3_5_1(v) => DynVersion(Box::new(v.0)), + Self::V0_3_5_2(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_0(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_1(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_2(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_3(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_4(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_5(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_6(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_7(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_8(v) => DynVersion(Box::new(v.0)), + Self::Other(v) => { + return Err(Error::new( + eyre!("unknown version {v}"), + ErrorKind::MigrationFailed, + )) + } + }) + } #[cfg(test)] fn as_exver(&self) -> exver::Version { match self { @@ -67,115 +161,262 @@ impl Version { Version::V0_3_6_alpha_5(Wrapper(x)) => x.semver(), Version::V0_3_6_alpha_6(Wrapper(x)) => x.semver(), Version::V0_3_6_alpha_7(Wrapper(x)) => x.semver(), + Version::V0_3_6_alpha_8(Wrapper(x)) => x.semver(), Version::Other(x) => x.clone(), } } } -pub trait VersionT -where - Self: Sized + Send + Sync, -{ - type Previous: VersionT; - fn new() -> Self; - fn semver(&self) -> exver::Version; - fn compat(&self) -> &'static exver::VersionRange; - fn up(&self, db: &TypedPatchDb) -> impl Future> + Send; - fn down(&self, db: &TypedPatchDb) -> impl Future> + Send; - fn commit( - &self, - db: &TypedPatchDb, - ) -> impl Future> + Send { - async { - let semver = self.semver().into(); - let compat = self.compat().clone(); - db.mutate(|d| { - d.as_public_mut() - .as_server_info_mut() - .as_version_mut() - .ser(&semver)?; - d.as_public_mut() - .as_server_info_mut() - .as_eos_version_compat_mut() - .ser(&compat)?; - Ok(()) - }) - .await?; - Ok(()) - } +fn version_accessor(db: &mut Value) -> Option<&mut Value> { + if db.get("public").is_some() { + db.get_mut("public")? + .get_mut("serverInfo")? + .get_mut("version") + } else { + db.get_mut("server-info")?.get_mut("version") } - fn migrate_to( - &self, - version: &V, - db: &TypedPatchDb, - progress: &mut PhaseProgressTrackerHandle, - ) -> impl Future> + Send { - async { - match self.semver().cmp(&version.semver()) { - Ordering::Greater => self.rollback_to_unchecked(version, db, progress).await, - Ordering::Less => version.migrate_from_unchecked(self, db, progress).await, - Ordering::Equal => Ok(()), +} + +fn version_compat_accessor(db: &mut Value) -> Option<&mut Value> { + if db.get("public").is_some() { + let server_info = db.get_mut("public")?.get_mut("serverInfo")?; + if server_info.get("packageVersionCompat").is_some() { + server_info.get_mut("packageVersionCompat") + } else { + if let Some(prev) = server_info.get("eosVersionCompat").cloned() { + server_info + .as_object_mut()? + .insert("packageVersionCompat".into(), prev); + } else if let Some(prev) = server_info.get("versionCompat").cloned() { + server_info + .as_object_mut()? + .insert("packageVersionCompat".into(), prev); } + server_info.get_mut("packageVersionCompat") } + } else { + db.get_mut("server-info")?.get_mut("eos-version-compat") } - fn migrate_from_unchecked<'a, V: VersionT>( - &'a self, - version: &'a V, - db: &'a TypedPatchDb, - progress: &'a mut PhaseProgressTrackerHandle, - ) -> BoxFuture<'a, Result<(), Error>> { - progress.add_total(1); +} + +fn post_init_migration_todos_accessor(db: &mut Value) -> Option<&mut Value> { + let server_info = if db.get("public").is_some() { + db.get_mut("public")?.get_mut("serverInfo")? + } else { + db.get_mut("server-info")? + }; + if server_info.get("postInitMigrationTodos").is_none() { + server_info + .as_object_mut()? + .insert("postInitMigrationTodos".into(), Value::Array(Vector::new())); + } + server_info.get_mut("postInitMigrationTodos") +} + +struct PreUps { + prev: Option>, + value: Box, +} +impl PreUps { + #[instrument(skip(from, to))] + fn load<'a, VFrom: DynVersionT + ?Sized, VTo: DynVersionT + ?Sized>( + from: &'a VFrom, + to: &'a VTo, + ) -> BoxFuture<'a, Result> { async { - let previous = Self::Previous::new(); - if version.semver() < previous.semver() { - previous - .migrate_from_unchecked(version, db, progress) - .await?; - } else if version.semver() > previous.semver() { - return Err(Error::new( - eyre!( - "NO PATH FROM {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION", - version.semver() - ), - crate::ErrorKind::MigrationFailed, - )); - } - tracing::info!("{} -> {}", previous.semver(), self.semver(),); - self.up(db).await?; - self.commit(db).await?; - *progress += 1; - Ok(()) + let previous = to.previous(); + let prev = match from.semver().cmp(&previous.semver()) { + Ordering::Less => Some(Box::new(PreUps::load(from, &previous).await?)), + Ordering::Greater => { + return Err(Error::new( + eyre!( + "NO PATH FROM {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION", + from.semver() + ), + crate::ErrorKind::MigrationFailed, + )) + } + Ordering::Equal => None, + }; + Ok(Self { + prev, + value: to.pre_up().await?, + }) } .boxed() } - fn rollback_to_unchecked<'a, V: VersionT>( - &'a self, - version: &'a V, - db: &'a TypedPatchDb, - progress: &'a mut PhaseProgressTrackerHandle, - ) -> BoxFuture<'a, Result<(), Error>> { - async { - let previous = Self::Previous::new(); - tracing::info!("{} -> {}", self.semver(), previous.semver(),); - self.down(db).await?; - previous.commit(db).await?; - *progress += 1; - if version.semver() < previous.semver() { - previous - .rollback_to_unchecked(version, db, progress) - .await?; - } else if version.semver() > previous.semver() { - return Err(Error::new( - eyre!( - "NO PATH TO {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION", - version.semver() - ), - crate::ErrorKind::MigrationFailed, - )); - } - Ok(()) +} + +fn migrate_from_unchecked( + from: &VFrom, + to: &VTo, + pre_ups: PreUps, + db: &mut Value, +) -> Result<(), Error> { + let previous = to.previous(); + match pre_ups.prev { + Some(prev) if from.semver() < previous.semver() => { + migrate_from_unchecked(from, &previous, *prev, db)? } - .boxed() + _ if from.semver() > previous.semver() => { + return Err(Error::new( + eyre!( + "NO PATH FROM {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION", + from.semver() + ), + crate::ErrorKind::MigrationFailed, + )); + } + _ => (), + }; + to.up(db, pre_ups.value)?; + to.commit(db)?; + Ok(()) +} + +fn rollback_to_unchecked( + from: &VFrom, + to: &VTo, + db: &mut Value, +) -> Result<(), Error> { + let previous = from.previous(); + from.down(db)?; + previous.commit(db)?; + if to.semver() < previous.semver() { + rollback_to_unchecked(&previous, to, db)? + } else if to.semver() > previous.semver() { + return Err(Error::new( + eyre!( + "NO PATH TO {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION", + to.semver() + ), + crate::ErrorKind::MigrationFailed, + )); + } + Ok(()) +} + +pub trait VersionT +where + Self: Default + Copy + Sized + RefUnwindSafe + Send + Sync + 'static, +{ + type Previous: VersionT; + type PreUpRes: Send + UnwindSafe; + fn semver(self) -> exver::Version; + fn compat(self) -> &'static exver::VersionRange; + /// MUST NOT change system state. Intended for async I/O reads + fn pre_up(self) -> impl Future> + Send + 'static; + fn up(self, db: &mut Value, input: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + /// MUST be idempotent, and is run after *all* db migrations + fn post_up<'a>( + self, + ctx: &'a RpcContext, + ) -> impl Future> + Send + 'a { + async { Ok(()) } + } + fn down(self, db: &mut Value) -> Result<(), Error> { + Err(Error::new( + eyre!("downgrades prohibited"), + ErrorKind::InvalidRequest, + )) + } + fn commit(self, db: &mut Value) -> Result<(), Error> { + *version_accessor(db).or_not_found("`version` in db")? = to_value(&self.semver())?; + *version_compat_accessor(db).or_not_found("`versionCompat` in db")? = + to_value(self.compat())?; + post_init_migration_todos_accessor(db) + .or_not_found("`serverInfo` in db")? + .as_array_mut() + .ok_or_else(|| { + Error::new( + eyre!("postInitMigrationTodos is not an array"), + ErrorKind::Database, + ) + })? + .push_back(to_value(&self.semver())?); + Ok(()) + } +} + +struct DynVersion(Box); +unsafe impl Send for DynVersion {} + +trait DynVersionT: RefUnwindSafe + Send + Sync { + fn previous(&self) -> DynVersion; + fn semver(&self) -> exver::Version; + fn compat(&self) -> &'static exver::VersionRange; + fn pre_up(&self) -> BoxFuture<'static, Result, Error>>; + fn up(&self, db: &mut Value, input: Box) -> Result<(), Error>; + fn post_up<'a>(&self, ctx: &'a RpcContext) -> BoxFuture<'a, Result<(), Error>>; + fn down(&self, db: &mut Value) -> Result<(), Error>; + fn commit(&self, db: &mut Value) -> Result<(), Error>; +} +impl DynVersionT for T +where + T: VersionT, +{ + fn previous(&self) -> DynVersion { + DynVersion(Box::new(::Previous::default())) + } + fn semver(&self) -> exver::Version { + VersionT::semver(*self) + } + fn compat(&self) -> &'static exver::VersionRange { + VersionT::compat(*self) + } + fn pre_up(&self) -> BoxFuture<'static, Result, Error>> { + let v = *self; + async move { Ok(Box::new(VersionT::pre_up(v).await?) as Box) } + .boxed() + } + fn up(&self, db: &mut Value, input: Box) -> Result<(), Error> { + VersionT::up( + *self, + db, + *input.downcast().map_err(|_| { + Error::new( + eyre!("pre_up returned unexpected type"), + ErrorKind::Incoherent, + ) + })?, + ) + } + fn post_up<'a>(&self, ctx: &'a RpcContext) -> BoxFuture<'a, Result<(), Error>> { + VersionT::post_up(*self, ctx).boxed() + } + fn down(&self, db: &mut Value) -> Result<(), Error> { + VersionT::down(*self, db) + } + fn commit(&self, db: &mut Value) -> Result<(), Error> { + VersionT::commit(*self, db) + } +} +impl DynVersionT for DynVersion { + fn previous(&self) -> DynVersion { + self.0.previous() + } + fn semver(&self) -> exver::Version { + self.0.semver() + } + fn compat(&self) -> &'static exver::VersionRange { + self.0.compat() + } + fn pre_up(&self) -> BoxFuture<'static, Result, Error>> { + self.0.pre_up() + } + fn up(&self, db: &mut Value, input: Box) -> Result<(), Error> { + self.0.up(db, input) + } + fn post_up<'a>(&self, ctx: &'a RpcContext) -> BoxFuture<'a, Result<(), Error>> { + self.0.post_up(ctx) + } + fn down(&self, db: &mut Value) -> Result<(), Error> { + self.0.down(db) + } + fn commit(&self, db: &mut Value) -> Result<(), Error> { + self.0.commit(db) } } @@ -195,7 +436,7 @@ where { fn deserialize>(deserializer: D) -> Result { let v = exver::Version::deserialize(deserializer)?; - let version = T::new(); + let version = T::default(); if v < version.semver() { Ok(Self(version, v)) } else { @@ -220,7 +461,7 @@ where { fn deserialize>(deserializer: D) -> Result { let v = exver::Version::deserialize(deserializer)?; - let version = T::new(); + let version = T::default(); if v == version.semver() { Ok(Wrapper(version)) } else { @@ -229,62 +470,6 @@ where } } -pub async fn init( - db: &TypedPatchDb, - mut progress: PhaseProgressTrackerHandle, -) -> Result<(), Error> { - progress.start(); - db.mutate(|db| { - db.as_public_mut() - .as_server_info_mut() - .as_version_mut() - .map_mutate(|v| { - Ok(if v == exver::Version::new([0, 3, 6], []) { - v0_3_6_alpha_0::Version::new().semver() - } else { - v - }) - }) - }) - .await?; // TODO: remove before releasing 0.3.6 - let version = Version::from_exver_version( - db.peek() - .await - .as_public() - .as_server_info() - .as_version() - .de()?, - ); - - match version { - Version::LT0_3_5(_) => { - return Err(Error::new( - eyre!("Cannot migrate from pre-0.3.5. Please update to v0.3.5 first."), - ErrorKind::MigrationFailed, - )); - } - Version::V0_3_5(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_5_1(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_5_2(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_0(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_1(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_2(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_3(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_4(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_5(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_6(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::V0_3_6_alpha_7(v) => v.0.migrate_to(&Current::new(), &db, &mut progress).await?, - Version::Other(_) => { - return Err(Error::new( - eyre!("Cannot downgrade"), - crate::ErrorKind::InvalidRequest, - )) - } - } - progress.complete(); - Ok(()) -} - pub const COMMIT_HASH: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../GIT_HASH.txt")); @@ -315,17 +500,17 @@ mod tests { fn versions() -> impl Strategy { prop_oneof![ - Just(Version::V0_3_5(Wrapper(v0_3_5::Version::new()))), - Just(Version::V0_3_5_1(Wrapper(v0_3_5_1::Version::new()))), - Just(Version::V0_3_5_2(Wrapper(v0_3_5_2::Version::new()))), + Just(Version::V0_3_5(Wrapper(v0_3_5::Version::default()))), + Just(Version::V0_3_5_1(Wrapper(v0_3_5_1::Version::default()))), + Just(Version::V0_3_5_2(Wrapper(v0_3_5_2::Version::default()))), Just(Version::V0_3_6_alpha_0(Wrapper( - v0_3_6_alpha_0::Version::new() + v0_3_6_alpha_0::Version::default() ))), Just(Version::V0_3_6_alpha_1(Wrapper( - v0_3_6_alpha_1::Version::new() + v0_3_6_alpha_1::Version::default() ))), Just(Version::V0_3_6_alpha_2(Wrapper( - v0_3_6_alpha_2::Version::new() + v0_3_6_alpha_2::Version::default() ))), em_version().prop_map(Version::Other), ] diff --git a/core/startos/src/version/v0_3_5.rs b/core/startos/src/version/v0_3_5.rs index 167132784..0217b6738 100644 --- a/core/startos/src/version/v0_3_5.rs +++ b/core/startos/src/version/v0_3_5.rs @@ -1,7 +1,6 @@ use exver::{ExtendedVersion, VersionRange}; use super::VersionT; -use crate::db::model::Database; use crate::prelude::*; use crate::version::Current; @@ -17,7 +16,7 @@ lazy_static::lazy_static! { VersionRange::anchor( exver::LTE, ExtendedVersion::new( - Current::new().semver(), + Current::default().semver(), exver::Version::default(), ) ), @@ -25,24 +24,26 @@ lazy_static::lazy_static! { static ref V0_3_5: exver::Version = exver::Version::new([0, 3, 5], []); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = Self; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_5.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_5_1.rs b/core/startos/src/version/v0_3_5_1.rs index c6c328f6d..5334cc2a4 100644 --- a/core/startos/src/version/v0_3_5_1.rs +++ b/core/startos/src/version/v0_3_5_1.rs @@ -2,31 +2,32 @@ use exver::VersionRange; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_5, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { static ref V0_3_5_1: exver::Version = exver::Version::new([0, 3, 5, 1], []); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_5::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_5_1.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_5_2.rs b/core/startos/src/version/v0_3_5_2.rs index 00143f8cd..780731d09 100644 --- a/core/startos/src/version/v0_3_5_2.rs +++ b/core/startos/src/version/v0_3_5_2.rs @@ -2,31 +2,32 @@ use exver::VersionRange; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_5_1, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { static ref V0_3_5_2: exver::Version = exver::Version::new([0, 3, 5, 2], []); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_5_1::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_5_2.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_0.rs b/core/startos/src/version/v0_3_6_alpha_0.rs index 1da34f708..7a6045a3a 100644 --- a/core/startos/src/version/v0_3_6_alpha_0.rs +++ b/core/startos/src/version/v0_3_6_alpha_0.rs @@ -1,9 +1,44 @@ +use std::collections::BTreeMap; +use std::future::Future; +use std::path::Path; + +use chrono::{DateTime, Utc}; +use ed25519_dalek::SigningKey; use exver::{PreReleaseSegment, VersionRange}; +use imbl_value::{json, InternedString}; +use itertools::Itertools; +use models::PackageId; +use openssl::pkey::{PKey, Private}; +use openssl::x509::X509; +use patch_db::ModelExt; +use sqlx::postgres::PgConnectOptions; +use sqlx::{PgPool, Row}; +use ssh_key::Fingerprint; +use tokio::process::Command; +use torut::onion::TorSecretKeyV3; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_5_2, VersionT}; +use crate::account::AccountInfo; +use crate::auth::Sessions; +use crate::backup::target::cifs::CifsTargets; +use crate::context::RpcContext; use crate::db::model::Database; +use crate::disk::mount::filesystem::cifs::Cifs; +use crate::disk::mount::util::unmount; +use crate::hostname::Hostname; +use crate::net::forward::AvailablePorts; +use crate::net::keys::KeyStore; +use crate::net::ssl::CertStore; +use crate::net::tor; +use crate::net::tor::OnionStore; +use crate::notifications::{Notification, Notifications}; use crate::prelude::*; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::ssh::{SshKeys, SshPubKey}; +use crate::util::crypto::ed25519_expand_key; +use crate::util::serde::{Pem, PemEncoding}; +use crate::util::Invoke; lazy_static::lazy_static! { static ref V0_3_6_alpha_0: exver::Version = exver::Version::new( @@ -12,24 +47,537 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[tracing::instrument(skip_all)] +async fn init_postgres(datadir: impl AsRef) -> Result { + let db_dir = datadir.as_ref().join("main/postgresql"); + if tokio::process::Command::new("mountpoint") + .arg("/var/lib/postgresql") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .await? + .success() + { + unmount("/var/lib/postgresql", true).await?; + } + let exists = tokio::fs::metadata(&db_dir).await.is_ok(); + if !exists { + Command::new("cp") + .arg("-ra") + .arg("/var/lib/postgresql") + .arg(&db_dir) + .invoke(crate::ErrorKind::Filesystem) + .await?; + } + Command::new("chown") + .arg("-R") + .arg("postgres:postgres") + .arg(&db_dir) + .invoke(crate::ErrorKind::Database) + .await?; + + let mut pg_paths = tokio::fs::read_dir("/usr/lib/postgresql").await?; + let mut pg_version = None; + while let Some(pg_path) = pg_paths.next_entry().await? { + let pg_path_version = pg_path + .file_name() + .to_str() + .map(|v| v.parse()) + .transpose()? + .unwrap_or(0); + if pg_path_version > pg_version.unwrap_or(0) { + pg_version = Some(pg_path_version) + } + } + let pg_version = pg_version.ok_or_else(|| { + Error::new( + eyre!("could not determine postgresql version"), + crate::ErrorKind::Database, + ) + })?; + + crate::disk::mount::util::bind(&db_dir, "/var/lib/postgresql", false).await?; + + let pg_version_string = pg_version.to_string(); + let pg_version_path = db_dir.join(&pg_version_string); + if exists + // maybe migrate + { + let incomplete_path = db_dir.join(format!("{pg_version}.migration.incomplete")); + if tokio::fs::metadata(&incomplete_path).await.is_ok() // previous migration was incomplete + && tokio::fs::metadata(&pg_version_path).await.is_ok() + { + tokio::fs::remove_dir_all(&pg_version_path).await?; + } + if tokio::fs::metadata(&pg_version_path).await.is_err() + // need to migrate + { + let conf_dir = Path::new("/etc/postgresql").join(pg_version.to_string()); + let conf_dir_tmp = { + let mut tmp = conf_dir.clone(); + tmp.set_extension("tmp"); + tmp + }; + if tokio::fs::metadata(&conf_dir).await.is_ok() { + Command::new("mv") + .arg(&conf_dir) + .arg(&conf_dir_tmp) + .invoke(ErrorKind::Filesystem) + .await?; + } + let mut old_version = pg_version; + while old_version > 13 + /* oldest pg version included in startos */ + { + old_version -= 1; + let old_datadir = db_dir.join(old_version.to_string()); + if tokio::fs::metadata(&old_datadir).await.is_ok() { + tokio::fs::File::create(&incomplete_path) + .await? + .sync_all() + .await?; + Command::new("pg_upgradecluster") + .arg(old_version.to_string()) + .arg("main") + .invoke(crate::ErrorKind::Database) + .await?; + break; + } + } + if tokio::fs::metadata(&conf_dir).await.is_ok() { + if tokio::fs::metadata(&conf_dir).await.is_ok() { + tokio::fs::remove_dir_all(&conf_dir).await?; + } + Command::new("mv") + .arg(&conf_dir_tmp) + .arg(&conf_dir) + .invoke(ErrorKind::Filesystem) + .await?; + } + tokio::fs::remove_file(&incomplete_path).await?; + } + if tokio::fs::metadata(&incomplete_path).await.is_ok() { + unreachable!() // paranoia + } + } + + Command::new("systemctl") + .arg("start") + .arg(format!("postgresql@{pg_version}-main.service")) + .invoke(crate::ErrorKind::Database) + .await?; + if !exists { + Command::new("sudo") + .arg("-u") + .arg("postgres") + .arg("createuser") + .arg("root") + .invoke(crate::ErrorKind::Database) + .await?; + Command::new("sudo") + .arg("-u") + .arg("postgres") + .arg("createdb") + .arg("secrets") + .arg("-O") + .arg("root") + .invoke(crate::ErrorKind::Database) + .await?; + } + + let secret_store = + PgPool::connect_with(PgConnectOptions::new().database("secrets").username("root")).await?; + sqlx::migrate!() + .run(&secret_store) + .await + .with_kind(crate::ErrorKind::Database)?; + dbg!("Init Postgres Done"); + Ok(secret_store) +} + +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_5_2::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> exver::Version { + type PreUpRes = (AccountInfo, SshKeys, CifsTargets, Notifications); + fn semver(self) -> exver::Version { V0_3_6_alpha_0.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { - Err(Error::new(eyre!("unimplemented"), ErrorKind::Unknown)) + async fn pre_up(self) -> Result { + let pg = init_postgres("/embassy-data").await?; + let account = previous_account_info(&pg).await?; + + let ssh_keys = previous_ssh_keys(&pg).await?; + + let cifs = previous_cifs(&pg).await?; + + let notifications = previous_notifications(pg).await?; + + Ok((account, ssh_keys, cifs, notifications)) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up( + self, + db: &mut Value, + (account, ssh_keys, cifs, notifications): Self::PreUpRes, + ) -> Result<(), Error> { + let wifi = json!({ + "infterface": db["server-info"]["wifi"]["interface"], + "ssids": db["server-info"]["wifi"]["ssids"], + "selected": db["server-info"]["wifi"]["selected"], + "last_region": db["server-info"]["wifi"]["last-region"], + }); + + let ip_info = { + let mut ip_info = json!({}); + let empty = Default::default(); + for (k, v) in db["server-info"]["ip-info"].as_object().unwrap_or(&empty) { + let k: &str = k.as_ref(); + ip_info[k] = json!({ + "ipv4Range": v["ipv4-range"], + "ipv6Range": v["ipv6-range"], + "ipv4": v["ipv4"], + "ipv6": v["ipv6"], + }); + } + ip_info + }; + + let status_info = json!({ + "backupProgress": db["server-info"]["status-info"]["backup-progress"], + "updated": db["server-info"]["status-info"]["updated"], + "updateProgress": db["server-info"]["status-info"]["update-progress"], + "shuttingDown": db["server-info"]["status-info"]["shutting-down"], + "restarting": db["server-info"]["status-info"]["restarting"], + }); + let server_info = { + let mut server_info = json!({ + "arch": db["server-info"]["arch"], + "platform": db["server-info"]["platform"], + "id": db["server-info"]["id"], + "hostname": db["server-info"]["hostname"], + "version": db["server-info"]["version"], + "versionCompat": db["server-info"]["eos-version-compat"], + "lastBackup": db["server-info"]["last-backup"], + "lanAddress": db["server-info"]["lan-address"], + }); + + server_info["postInitMigrationTodos"] = json!([]); + let tor_address: String = from_value(db["server-info"]["tor-address"].clone())?; + // Maybe we do this like the Public::init does + server_info["torAddress"] = json!(tor_address); + server_info["onionAddress"] = json!(tor_address + .replace("https://", "") + .replace("http://", "") + .replace(".onion/", "")); + server_info["ipInfo"] = ip_info; + server_info["statusInfo"] = status_info; + server_info["wifi"] = wifi; + server_info["unreadNotificationCount"] = + db["server-info"]["unread-notification-count"].clone(); + server_info["passwordHash"] = db["server-info"]["password-hash"].clone(); + + server_info["pubkey"] = db["server-info"]["pubkey"].clone(); + server_info["caFingerprint"] = db["server-info"]["ca-fingerprint"].clone(); + server_info["ntpSynced"] = db["server-info"]["ntp-synced"].clone(); + server_info["zram"] = db["server-info"]["zram"].clone(); + server_info["governor"] = db["server-info"]["governor"].clone(); + // This one should always be empty, doesn't exist in the previous. And the smtp is all single word key + server_info["smtp"] = db["server-info"]["smtp"].clone(); + server_info + }; + + let public = json!({ + "serverInfo": server_info, + "packageData": json!({}), + "ui": db["ui"], + }); + + let private = { + let mut value = json!({}); + value["keyStore"] = to_value(&KeyStore::new(&account)?)?; + value["password"] = to_value(&account.password)?; + value["compatS9pkKey"] = to_value(&crate::db::model::private::generate_compat_key())?; + value["sshPrivkey"] = to_value(Pem::new_ref(&account.ssh_key))?; + value["sshPubkeys"] = to_value(&ssh_keys)?; + value["availablePorts"] = to_value(&AvailablePorts::new())?; + value["sessions"] = to_value(&Sessions::new())?; + value["notifications"] = to_value(¬ifications)?; + value["cifs"] = to_value(&cifs)?; + value["packageStores"] = json!({}); + value + }; + let next: Value = json!({ + "public": public, + "private": private, + }); + + dbg!("Should be done with the up"); + *db = next; + Ok(()) + } + fn down(self, _db: &mut Value) -> Result<(), Error> { + Err(Error::new( + eyre!("downgrades prohibited"), + ErrorKind::InvalidRequest, + )) + } + + #[instrument(skip(self, ctx))] + /// MUST be idempotent, and is run after *all* db migrations + async fn post_up(self, ctx: &RpcContext) -> Result<(), Error> { + let path = Path::new("/embassy-data/package-data/archive/"); + if !path.is_dir() { + return Err(Error::new( + eyre!( + "expected path ({}) to be a directory", + path.to_string_lossy() + ), + ErrorKind::Filesystem, + )); + } + // Should be the name of the package + let mut paths = tokio::fs::read_dir(path).await?; + while let Some(path) = paths.next_entry().await? { + let path = path.path(); + if !path.is_dir() { + continue; + } + // Should be the version of the package + let mut paths = tokio::fs::read_dir(path).await?; + while let Some(path) = paths.next_entry().await? { + let path = path.path(); + if !path.is_dir() { + continue; + } + + // Should be s9pk + let mut paths = tokio::fs::read_dir(path).await?; + while let Some(path) = paths.next_entry().await? { + let path = path.path(); + if path.is_dir() { + continue; + } + + let package_s9pk = tokio::fs::File::open(path).await?; + let file = MultiCursorFile::open(&package_s9pk).await?; + + let key = ctx.db.peek().await.into_private().into_compat_s9pk_key(); + ctx.services + .install( + ctx.clone(), + || crate::s9pk::load(file.clone(), || Ok(key.de()?.0), None), + None::, + None, + ) + .await? + .await? + .await?; + } + } + } Ok(()) } } + +async fn previous_notifications(pg: sqlx::Pool) -> Result { + let notification_cursor = sqlx::query(r#"SELECT * FROM notifications"#) + .fetch_all(&pg) + .await?; + let notifications = { + let mut notifications = Notifications::default(); + for row in notification_cursor { + let package_id = serde_json::from_str::( + row.try_get("package_id") + .with_ctx(|_| (ErrorKind::Database, "package_id"))?, + ) + .ok(); + + let created_at = row + .try_get("created_at") + .with_ctx(|_| (ErrorKind::Database, "created_at"))?; + let code = row + .try_get::("code") + .with_ctx(|_| (ErrorKind::Database, "code"))? as u32; + let id = row + .try_get::("id") + .with_ctx(|_| (ErrorKind::Database, "id"))? as u32; + let level = serde_json::from_str( + row.try_get("level") + .with_ctx(|_| (ErrorKind::Database, "level"))?, + ) + .with_kind(ErrorKind::Database) + .with_ctx(|_| (ErrorKind::Database, "level: serde_json "))?; + let title = row + .try_get("title") + .with_ctx(|_| (ErrorKind::Database, "title"))?; + let message = row + .try_get("message") + .with_ctx(|_| (ErrorKind::Database, "message"))?; + let data = serde_json::from_str( + row.try_get("data") + .with_ctx(|_| (ErrorKind::Database, "data"))?, + ) + .unwrap_or_default(); + + notifications.0.insert( + id, + Notification { + package_id, + created_at, + code, + level, + title, + message, + data, + }, + ); + } + notifications + }; + Ok(notifications) +} + +#[tracing::instrument(skip_all)] +async fn previous_cifs(pg: &sqlx::Pool) -> Result { + let cifs = sqlx::query(r#"SELECT * FROM cifs_shares"#) + .fetch_all(pg) + .await? + .into_iter() + .map(|row| { + let id: i64 = row.try_get("id")?; + Ok::<_, Error>(( + id, + Cifs { + hostname: row + .try_get("hostname") + .with_ctx(|_| (ErrorKind::Database, "hostname"))?, + path: serde_json::from_str(row.try_get("path")?) + .with_kind(ErrorKind::Database) + .with_ctx(|_| (ErrorKind::Database, "path"))?, + username: row + .try_get("username") + .with_ctx(|_| (ErrorKind::Database, "username"))?, + password: row + .try_get("password") + .with_ctx(|_| (ErrorKind::Database, "password"))?, + }, + )) + }) + .fold(Ok::<_, Error>(CifsTargets::default()), |cifs, data| { + let mut cifs = cifs?; + let (id, cif_value) = data?; + cifs.0.insert(id as u32, cif_value); + Ok(cifs) + })?; + Ok(cifs) +} + +#[tracing::instrument(skip_all)] +async fn previous_account_info(pg: &sqlx::Pool) -> Result { + let account_query = sqlx::query(r#"SELECT * FROM account"#) + .fetch_one(pg) + .await?; + let account = { + AccountInfo { + password: account_query + .try_get("password") + .with_ctx(|_| (ErrorKind::Database, "password"))?, + tor_key: TorSecretKeyV3::try_from( + if let Some(bytes) = account_query + .try_get::>, _>("tor_key") + .with_ctx(|_| (ErrorKind::Database, "tor_key"))? + { + <[u8; 64]>::try_from(bytes) + .map_err(|e| { + Error::new( + eyre!("expected vec of len 64, got len {}", e.len()), + ErrorKind::ParseDbField, + ) + }) + .with_ctx(|_| (ErrorKind::Database, "password.u8 64"))? + } else { + ed25519_expand_key( + &<[u8; 32]>::try_from(account_query.try_get::, _>("network_key")?) + .map_err(|e| { + Error::new( + eyre!("expected vec of len 32, got len {}", e.len()), + ErrorKind::ParseDbField, + ) + }) + .with_ctx(|_| (ErrorKind::Database, "password.u8 32"))?, + ) + }, + )?, + server_id: account_query + .try_get("server_id") + .with_ctx(|_| (ErrorKind::Database, "server_id"))?, + hostname: Hostname( + account_query + .try_get::("hostname") + .with_ctx(|_| (ErrorKind::Database, "hostname"))? + .into(), + ), + root_ca_key: PKey::private_key_from_pem( + &account_query + .try_get::("root_ca_key_pem") + .with_ctx(|_| (ErrorKind::Database, "root_ca_key_pem"))? + .as_bytes(), + ) + .with_ctx(|_| (ErrorKind::Database, "private_key_from_pem"))?, + root_ca_cert: X509::from_pem( + account_query + .try_get::("root_ca_cert_pem") + .with_ctx(|_| (ErrorKind::Database, "root_ca_cert_pem"))? + .as_bytes(), + ) + .with_ctx(|_| (ErrorKind::Database, "X509::from_pem"))?, + compat_s9pk_key: SigningKey::generate(&mut rand::thread_rng()), + ssh_key: ssh_key::PrivateKey::random( + &mut rand::thread_rng(), + ssh_key::Algorithm::Ed25519, + ) + .with_ctx(|_| (ErrorKind::Database, "X509::ssh_key::PrivateKey::random"))?, + } + }; + Ok(account) +} +#[tracing::instrument(skip_all)] +async fn previous_ssh_keys(pg: &sqlx::Pool) -> Result { + let ssh_query = sqlx::query(r#"SELECT * FROM ssh_keys"#) + .fetch_all(pg) + .await?; + let ssh_keys: SshKeys = { + let keys = ssh_query.into_iter().fold( + Ok::<_, Error>(BTreeMap::>::new()), + |ssh_keys, row| { + let mut ssh_keys = ssh_keys?; + let time = row + .try_get::("created_at") + .map_err(Error::from) + .and_then(|x| x.parse::>().with_kind(ErrorKind::Database)) + .with_ctx(|_| (ErrorKind::Database, "openssh_pubkey::created_at"))?; + let value: SshPubKey = row + .try_get::("openssh_pubkey") + .map_err(Error::from) + .and_then(|x| x.parse().map(SshPubKey).with_kind(ErrorKind::Database)) + .with_ctx(|_| (ErrorKind::Database, "openssh_pubkey"))?; + let data = WithTimeData { + created_at: time, + updated_at: time, + value, + }; + let fingerprint = row + .try_get::("fingerprint") + .with_ctx(|_| (ErrorKind::Database, "fingerprint"))?; + ssh_keys.insert(fingerprint.into(), data); + Ok(ssh_keys) + }, + )?; + SshKeys::from(keys) + }; + Ok(ssh_keys) +} diff --git a/core/startos/src/version/v0_3_6_alpha_1.rs b/core/startos/src/version/v0_3_6_alpha_1.rs index 8f40c3fde..682486439 100644 --- a/core/startos/src/version/v0_3_6_alpha_1.rs +++ b/core/startos/src/version/v0_3_6_alpha_1.rs @@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_0, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { @@ -12,24 +11,26 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_0::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_1.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_2.rs b/core/startos/src/version/v0_3_6_alpha_2.rs index 4b26a05dd..cddcc44b2 100644 --- a/core/startos/src/version/v0_3_6_alpha_2.rs +++ b/core/startos/src/version/v0_3_6_alpha_2.rs @@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_1, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { @@ -12,24 +11,26 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_1::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_2.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_3.rs b/core/startos/src/version/v0_3_6_alpha_3.rs index 3f244a2a0..90164ad60 100644 --- a/core/startos/src/version/v0_3_6_alpha_3.rs +++ b/core/startos/src/version/v0_3_6_alpha_3.rs @@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_2, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { @@ -12,24 +11,26 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_2::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_3.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_4.rs b/core/startos/src/version/v0_3_6_alpha_4.rs index 0a60764e0..08ff7595e 100644 --- a/core/startos/src/version/v0_3_6_alpha_4.rs +++ b/core/startos/src/version/v0_3_6_alpha_4.rs @@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_3, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { @@ -12,24 +11,26 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_3::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_4.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_5.rs b/core/startos/src/version/v0_3_6_alpha_5.rs index 1b921d78b..649fe90ca 100644 --- a/core/startos/src/version/v0_3_6_alpha_5.rs +++ b/core/startos/src/version/v0_3_6_alpha_5.rs @@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_4, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { @@ -12,24 +11,26 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_4::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_5.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_6.rs b/core/startos/src/version/v0_3_6_alpha_6.rs index df91246ae..843e5a45b 100644 --- a/core/startos/src/version/v0_3_6_alpha_6.rs +++ b/core/startos/src/version/v0_3_6_alpha_6.rs @@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange}; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_5, VersionT}; -use crate::db::model::Database; use crate::prelude::*; lazy_static::lazy_static! { @@ -12,24 +11,26 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_5::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_6.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_7.rs b/core/startos/src/version/v0_3_6_alpha_7.rs index 7aa63fb2e..bbf9468ff 100644 --- a/core/startos/src/version/v0_3_6_alpha_7.rs +++ b/core/startos/src/version/v0_3_6_alpha_7.rs @@ -1,9 +1,11 @@ use exver::{PreReleaseSegment, VersionRange}; +use imbl_value::{json, InOMap}; +use tokio::process::Command; use super::v0_3_5::V0_3_0_COMPAT; use super::{v0_3_6_alpha_6, VersionT}; -use crate::db::model::Database; use crate::prelude::*; +use crate::util::Invoke; lazy_static::lazy_static! { static ref V0_3_6_alpha_7: exver::Version = exver::Version::new( @@ -12,24 +14,50 @@ lazy_static::lazy_static! { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct Version; impl VersionT for Version { type Previous = v0_3_6_alpha_6::Version; - fn new() -> Self { - Version + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) } - fn semver(&self) -> exver::Version { + fn semver(self) -> exver::Version { V0_3_6_alpha_7.clone() } - fn compat(&self) -> &'static VersionRange { + fn compat(self) -> &'static VersionRange { &V0_3_0_COMPAT } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + let server_info = db["public"]["serverInfo"] + .as_object_mut() + .or_not_found("public.serverInfo")?; + server_info.insert("ram".into(), json!(0)); + server_info.insert("devices".into(), json!([])); + let package_data = db["public"]["packageData"] + .as_object_mut() + .or_not_found("public.packageData")?; + for (_, pde) in package_data.iter_mut() { + if let Some(manifest) = pde["stateInfo"].get_mut("manifest") { + manifest["hardwareRequirements"]["device"] = json!([]); + } + } + Ok(()) + } + async fn post_up(self, ctx: &crate::context::RpcContext) -> Result<(), Error> { + Command::new("systemd-firstboot") + .arg("--root=/media/startos/config/overlay/") + .arg(format!( + "--hostname={}", + ctx.account.read().await.hostname.0 + )) + .invoke(ErrorKind::ParseSysInfo) + .await?; Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } } diff --git a/core/startos/src/version/v0_3_6_alpha_8.rs b/core/startos/src/version/v0_3_6_alpha_8.rs new file mode 100644 index 000000000..fcef96a69 --- /dev/null +++ b/core/startos/src/version/v0_3_6_alpha_8.rs @@ -0,0 +1,130 @@ +use exver::{PreReleaseSegment, VersionRange}; +use tokio::fs::File; + +use super::v0_3_5::V0_3_0_COMPAT; +use super::{v0_3_6_alpha_7, VersionT}; +use crate::install::PKG_ARCHIVE_DIR; +use crate::prelude::*; +use crate::s9pk::manifest::{DeviceFilter, Manifest}; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::s9pk::merkle_archive::MerkleArchive; +use crate::s9pk::v2::SIG_CONTEXT; +use crate::s9pk::S9pk; +use crate::service::LoadDisposition; +use crate::util::io::create_file; + +lazy_static::lazy_static! { + static ref V0_3_6_alpha_8: exver::Version = exver::Version::new( + [0, 3, 6], + [PreReleaseSegment::String("alpha".into()), 8.into()] + ); +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Version; + +impl VersionT for Version { + type Previous = v0_3_6_alpha_7::Version; + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) + } + fn semver(self) -> exver::Version { + V0_3_6_alpha_8.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + async fn post_up(self, ctx: &crate::context::RpcContext) -> Result<(), Error> { + let s9pk_dir = ctx.datadir.join(PKG_ARCHIVE_DIR).join("installed"); + + if tokio::fs::metadata(&s9pk_dir).await.is_ok() { + let mut read_dir = tokio::fs::read_dir(&s9pk_dir).await?; + while let Some(s9pk_ent) = read_dir.next_entry().await? { + let s9pk_path = s9pk_ent.path(); + let matches_s9pk = s9pk_path.extension().map(|x| x == "s9pk").unwrap_or(false); + if !matches_s9pk { + continue; + } + + let get_archive = async { + let multi_cursor = MultiCursorFile::from(File::open(&s9pk_path).await?); + Ok::<_, Error>(S9pk::archive(&multi_cursor, None).await?) + }; + + let archive: MerkleArchive< + crate::s9pk::merkle_archive::source::Section, + > = match get_archive.await { + Ok(a) => a, + Err(e) => { + tracing::error!("Error opening s9pk for install: {e}"); + tracing::debug!("{e:?}"); + continue; + } + }; + + let previous_manifest: Value = serde_json::from_slice::( + &archive + .contents() + .get_path("manifest.json") + .or_not_found("manifest.json")? + .read_file_to_vec() + .await?, + ) + .with_kind(ErrorKind::Deserialization)? + .into(); + + let mut manifest = previous_manifest.clone(); + + if let Some(device) = + previous_manifest["hardwareRequirements"]["device"].as_object() + { + manifest["hardwareRequirements"]["device"] = to_value( + &device + .into_iter() + .map(|(class, product)| { + Ok::<_, Error>(DeviceFilter { + pattern_description: format!( + "a {class} device matching the expression {}", + &product + ), + class: class.clone(), + pattern: from_value(product.clone())?, + }) + }) + .fold(Ok::<_, Error>(Vec::new()), |acc, value| { + let mut acc = acc?; + acc.push(value?); + Ok(acc) + })?, + )?; + } + + if previous_manifest != manifest { + let tmp_path = s9pk_path.with_extension("s9pk.tmp"); + let mut tmp_file = create_file(&tmp_path).await?; + // TODO, wouldn't this break in the later versions of the manifest that would need changes, this doesn't seem to be a good way to handle this + let manifest: Manifest = from_value(manifest.clone())?; + let id = manifest.id.clone(); + let mut s9pk: S9pk<_> = S9pk::new_with_manifest(archive, None, manifest); + let s9pk_compat_key = ctx.account.read().await.compat_s9pk_key.clone(); + s9pk.as_archive_mut() + .set_signer(s9pk_compat_key, SIG_CONTEXT); + s9pk.serialize(&mut tmp_file, true).await?; + tmp_file.sync_all().await?; + tokio::fs::rename(&tmp_path, &s9pk_path).await?; + ctx.services.load(ctx, &id, LoadDisposition::Retry).await?; + } + } + } + + Ok(()) + } + fn down(self, _db: &mut Value) -> Result<(), Error> { + Ok(()) + } +} diff --git a/core/startos/startd.service b/core/startos/startd.service index 56cf92e22..6ce17697e 100644 --- a/core/startos/startd.service +++ b/core/startos/startd.service @@ -3,7 +3,7 @@ Description=StartOS Daemon [Service] Type=simple -Environment=RUST_LOG=startos=debug,js_engine=debug,patch_db=warn +Environment=RUST_LOG=startos=debug,patch_db=warn ExecStart=/usr/bin/startd Restart=always RestartSec=3 diff --git a/debian/postinst b/debian/postinst index cafa691e0..0db121c56 100755 --- a/debian/postinst +++ b/debian/postinst @@ -79,6 +79,8 @@ sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.conf sed -i '/^\s*#\?\s*issue_discards\s*=\s*/c\issue_discards = 1' /etc/lvm/lvm.conf sed -i '/\(^\|#\)\s*unqualified-search-registries\s*=\s*/c\unqualified-search-registries = ["docker.io"]' /etc/containers/registries.conf +sed -i 's/\(#\|\^\)\s*\([^=]\+\)=\(suspend\|hibernate\)\s*$/\2=ignore/g' /etc/systemd/logind.conf +sed -i '/\(^\|#\)MulticastDNS=/c\MulticastDNS=no' /etc/systemd/resolved.conf mkdir -p /etc/nginx/ssl @@ -86,6 +88,7 @@ cat << EOF > /etc/tor/torrc SocksPort 0.0.0.0:9050 SocksPolicy accept 127.0.0.1 SocksPolicy accept 172.18.0.0/16 +SocksPolicy accept 10.0.3.0/24 SocksPolicy reject * ControlPort 9051 CookieAuthentication 1 diff --git a/image-recipe/build.sh b/image-recipe/build.sh index 5635e94f3..ae1831704 100755 --- a/image-recipe/build.sh +++ b/image-recipe/build.sh @@ -57,20 +57,14 @@ if [ "$NON_FREE" = 1 ]; then fi fi -PLATFORM_CONFIG_EXTRAS= +PLATFORM_CONFIG_EXTRAS=() if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then - PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --firmware-binary false" - PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --firmware-chroot false" - # BEGIN stupid ugly hack - # The actual name of the package is `raspberrypi-kernel` - # live-build determines thte name of the package for the kernel by combining the `linux-packages` flag, with the `linux-flavours` flag - # the `linux-flavours` flag defaults to the architecture, so there's no way to remove the suffix. - # So we're doing this, cause thank the gods our package name contains a hypen. Cause if it didn't we'd be SOL - PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --linux-packages raspberrypi" - PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --linux-flavours kernel" - # END stupid ugly hack + PLATFORM_CONFIG_EXTRAS+=( --firmware-binary false ) + PLATFORM_CONFIG_EXTRAS+=( --firmware-chroot false ) + PLATFORM_CONFIG_EXTRAS+=( --linux-packages linux-image-6.6.51+rpt ) + PLATFORM_CONFIG_EXTRAS+=( --linux-flavours "rpi-v8 rpi-2712" ) elif [ "${IB_TARGET_PLATFORM}" = "rockchip64" ]; then - PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --linux-flavours rockchip64" + PLATFORM_CONFIG_EXTRAS+=( --linux-flavours rockchip64 ) fi @@ -94,7 +88,7 @@ lb config \ --bootstrap-qemu-arch ${IB_TARGET_ARCH} \ --bootstrap-qemu-static /usr/bin/qemu-${QEMU_ARCH}-static \ --archive-areas "${ARCHIVE_AREAS}" \ - $PLATFORM_CONFIG_EXTRAS + ${PLATFORM_CONFIG_EXTRAS[@]} # Overlays @@ -148,13 +142,13 @@ sed -i -e '2i set timeout=5' config/bootloaders/grub-pc/config.cfg mkdir -p config/archives if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then - curl -fsSL https://archive.raspberrypi.org/debian/raspberrypi.gpg.key | gpg --dearmor -o config/archives/raspi.key - echo "deb https://archive.raspberrypi.org/debian/ bullseye main" > config/archives/raspi.list + curl -fsSL https://archive.raspberrypi.com/debian/raspberrypi.gpg.key | gpg --dearmor -o config/archives/raspi.key + echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/raspi.key.gpg] https://archive.raspberrypi.com/debian/ ${IB_SUITE} main" > config/archives/raspi.list fi cat > config/archives/backports.pref <<- EOF Package: * -Pin: release a=stable-backports +Pin: release n=${IB_SUITE}-backports Pin-Priority: 500 EOF @@ -180,7 +174,7 @@ if [ "$NON_FREE" = 1 ]; then fi if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then - echo 'raspberrypi-bootloader rpi-update parted' > config/package-lists/bootloader.list.chroot + echo 'raspberrypi-net-mods raspberrypi-sys-mods raspi-config raspi-firmware raspi-gpio raspi-utils rpi-eeprom rpi-update rpi.gpio-common parted' > config/package-lists/bootloader.list.chroot else echo 'grub-efi grub2-common' > config/package-lists/bootloader.list.chroot fi @@ -205,17 +199,15 @@ if [ "${IB_SUITE}" = bookworm ]; then fi if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then + ln -sf /usr/bin/pi-beep /usr/local/bin/beep + SKIP_WARNING=1 SKIP_BOOTLOADER=1 SKIP_CHECK_PARTITION=1 WANT_64BIT=1 WANT_PI4=1 WANT_PI5=1 BOOT_PART=/boot rpi-update stable for f in /usr/lib/modules/*; do v=\${f#/usr/lib/modules/} echo "Configuring raspi kernel '\$v'" extract-ikconfig "/usr/lib/modules/\$v/kernel/kernel/configs.ko.xz" > /boot/config-\$v - update-initramfs -c -k \$v done - ln -sf /usr/bin/pi-beep /usr/local/bin/beep - wget https://archive.raspberrypi.org/debian/pool/main/w/wireless-regdb/wireless-regdb_2018.05.09-0~rpt1_all.deb - echo 1b7b1076257726609535b71d146a5721622d19a0843061ee7568188e836dd10f wireless-regdb_2018.05.09-0~rpt1_all.deb | sha256sum -c - apt-get install -y --allow-downgrades ./wireless-regdb_2018.05.09-0~rpt1_all.deb - rm wireless-regdb_2018.05.09-0~rpt1_all.deb + mkinitramfs -c gzip -o /boot/initramfs8 6.6.51-v8+ + mkinitramfs -c gzip -o /boot/initramfs_2712 6.6.51-v8-16k+ fi useradd --shell /bin/bash -G embassy -m start9 @@ -317,18 +309,31 @@ elif [ "${IMAGE_TYPE}" = img ]; then TMPDIR=$(mktemp -d) - mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR - mkdir $TMPDIR/boot + mkdir -p $TMPDIR/boot $TMPDIR/root + mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR/root mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot - unsquashfs -f -d $TMPDIR $prep_results_dir/binary/live/filesystem.squashfs + unsquashfs -n -f -d $TMPDIR $prep_results_dir/binary/live/filesystem.squashfs boot + + mkdir $TMPDIR/root/images $TMPDIR/root/config + B3SUM=$(b3sum $prep_results_dir/binary/live/filesystem.squashfs | head -c 16) + cp $prep_results_dir/binary/live/filesystem.squashfs $TMPDIR/root/images/$B3SUM.rootfs + ln -rsf $TMPDIR/root/images/$B3SUM.rootfs $TMPDIR/root/config/current.rootfs + + mkdir -p $TMPDIR/next $TMPDIR/lower $TMPDIR/root/config/work $TMPDIR/root/config/overlay + mount $TMPDIR/root/config/current.rootfs $TMPDIR/lower + + mount -t overlay -o lowerdir=$TMPDIR/lower,workdir=$TMPDIR/root/config/work,upperdir=$TMPDIR/root/config/overlay overlay $TMPDIR/next if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then - sed -i 's| boot=startos| init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt - rsync -a $base_dir/raspberrypi/img/ $TMPDIR/ + sed -i 's| boot=startos| boot=startos init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt + rsync -a $base_dir/raspberrypi/img/ $TMPDIR/next/ fi + umount $TMPDIR/next + umount $TMPDIR/lower + umount $TMPDIR/boot - umount $TMPDIR + umount $TMPDIR/root e2fsck -fy `partition_for ${OUTPUT_DEVICE} 2` resize2fs -M `partition_for ${OUTPUT_DEVICE} 2` diff --git a/image-recipe/prepare.sh b/image-recipe/prepare.sh index 8962d8448..c31f6ada0 100755 --- a/image-recipe/prepare.sh +++ b/image-recipe/prepare.sh @@ -21,7 +21,8 @@ apt-get install -yq \ dosfstools \ e2fsprogs \ squashfs-tools \ - rsync + rsync \ + b3sum # TODO: remove when util-linux is released at v2.39.3 apt-get install -yq \ git \ diff --git a/image-recipe/raspberrypi/img/etc/fstab b/image-recipe/raspberrypi/img/etc/fstab index 816b32bcd..5f5164232 100644 --- a/image-recipe/raspberrypi/img/etc/fstab +++ b/image-recipe/raspberrypi/img/etc/fstab @@ -1,2 +1,2 @@ -/dev/mmcblk0p1 /boot vfat umask=0077 0 2 -/dev/mmcblk0p2 / ext4 defaults 0 1 +/dev/mmcblk0p1 /boot vfat umask=0077 0 2 +/dev/mmcblk0p2 / ext4 defaults 0 1 diff --git a/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh b/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh index cf6489f5f..1fdca1c83 100755 --- a/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh +++ b/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh @@ -1,7 +1,7 @@ #!/bin/bash get_variables () { - ROOT_PART_DEV=$(findmnt / -o source -n) + ROOT_PART_DEV=$(findmnt /media/startos/root -o source -n) ROOT_PART_NAME=$(echo "$ROOT_PART_DEV" | cut -d "/" -f 3) ROOT_DEV_NAME=$(echo /sys/block/*/"${ROOT_PART_NAME}" | cut -d "/" -f 4) ROOT_DEV="/dev/${ROOT_DEV_NAME}" @@ -89,12 +89,12 @@ main () { resize2fs $ROOT_PART_DEV - if ! systemd-machine-id-setup; then + if ! systemd-machine-id-setup --root=/media/startos/config/overlay/; then FAIL_REASON="systemd-machine-id-setup failed" return 1 fi - if ! ssh-keygen -A; then + if ! (mkdir -p /media/startos/config/overlay/etc/ssh && ssh-keygen -A -f /media/startos/config/overlay/); then FAIL_REASON="ssh host key generation failed" return 1 fi @@ -104,9 +104,6 @@ main () { return 0 } -mount -t proc proc /proc -mount -t sysfs sys /sys -mount -t tmpfs tmp /run mkdir -p /run/systemd mount /boot mount / -o remount,ro @@ -114,7 +111,7 @@ mount / -o remount,ro beep if main; then - sed -i 's| init=/usr/lib/startos/scripts/init_resize\.sh| boot=startos|' /boot/cmdline.txt + sed -i 's| init=/usr/lib/startos/scripts/init_resize\.sh||' /boot/cmdline.txt echo "Resized root filesystem. Rebooting in 5 seconds..." sleep 5 else diff --git a/image-recipe/raspberrypi/squashfs/boot/config.txt b/image-recipe/raspberrypi/squashfs/boot/config.txt index 17bd5dc4e..4e1962a65 100644 --- a/image-recipe/raspberrypi/squashfs/boot/config.txt +++ b/image-recipe/raspberrypi/squashfs/boot/config.txt @@ -83,4 +83,5 @@ arm_boost=1 [all] gpu_mem=16 dtoverlay=pwm-2chan,disable-bt -initramfs initrd.img-6.1.21-v8+ + +auto_initramfs=1 \ No newline at end of file diff --git a/image-recipe/raspberrypi/squashfs/etc/embassy/config.yaml b/image-recipe/raspberrypi/squashfs/etc/startos/config.yaml similarity index 100% rename from image-recipe/raspberrypi/squashfs/etc/embassy/config.yaml rename to image-recipe/raspberrypi/squashfs/etc/startos/config.yaml diff --git a/package-lock.json b/package-lock.json index 497f28c49..9a9ca8f48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "embassy-os", + "name": "start-os", "lockfileVersion": 2, "requires": true, "packages": {} diff --git a/sdk/.gitignore b/sdk/.gitignore index a7ca92b2d..1ac0f02e6 100644 --- a/sdk/.gitignore +++ b/sdk/.gitignore @@ -1,5 +1,6 @@ -.vscode dist/ -node_modules/ -lib/coverage -lib/test/output.ts \ No newline at end of file +baseDist/ +base/lib/coverage +base/lib/node_modules +package/lib/coverage +package/lib/node_modules \ No newline at end of file diff --git a/sdk/.prettierignore b/sdk/.prettierignore index 19b24bbe8..149281301 100644 --- a/sdk/.prettierignore +++ b/sdk/.prettierignore @@ -1 +1 @@ -/lib/exver/exver.ts \ No newline at end of file +/base/lib/exver/exver.ts \ No newline at end of file diff --git a/sdk/Makefile b/sdk/Makefile index 660a476c4..3f8ae533a 100644 --- a/sdk/Makefile +++ b/sdk/Makefile @@ -1,47 +1,73 @@ -TS_FILES := $(shell git ls-files lib) lib/test/output.ts +PACKAGE_TS_FILES := $(shell git ls-files package/lib) package/lib/test/output.ts +BASE_TS_FILES := $(shell git ls-files base/lib) package/lib/test/output.ts version = $(shell git tag --sort=committerdate | tail -1) -.PHONY: test clean bundle fmt buildOutput check +.PHONY: test base/test package/test clean bundle fmt buildOutput check all: bundle -test: $(TS_FILES) lib/test/output.ts - npm test +package/test: $(PACKAGE_TS_FILES) package/lib/test/output.ts package/node_modules base/node_modules + cd package && npm test + +base/test: $(BASE_TS_FILES) base/node_modules + cd base && npm test + +test: base/test package/test clean: + rm -rf base/node_modules rm -rf dist - rm -f lib/test/output.ts - rm -rf node_modules + rm -rf baseDist + rm -f package/lib/test/output.ts + rm -rf package/node_modules -lib/test/output.ts: node_modules lib/test/makeOutput.ts scripts/oldSpecToBuilder.ts - npm run buildOutput +package/lib/test/output.ts: package/node_modules package/lib/test/makeOutput.ts package/scripts/oldSpecToBuilder.ts + cd package && npm run buildOutput -bundle: dist | test fmt +bundle: baseDist dist | test fmt touch dist -lib/exver/exver.ts: node_modules lib/exver/exver.pegjs - npx peggy --allowed-start-rules '*' --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs +base/lib/exver/exver.ts: base/node_modules base/lib/exver/exver.pegjs + cd base && npm run peggy -dist: $(TS_FILES) package.json node_modules README.md LICENSE - npx tsc - npx tsc --project tsconfig-cjs.json - cp package.json dist/package.json - cp README.md dist/README.md - cp LICENSE dist/LICENSE +baseDist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) base/package.json base/node_modules base/README.md base/LICENSE + (cd base && npm run tsc) + rsync -ac base/node_modules baseDist/ + cp base/package.json baseDist/package.json + cp base/README.md baseDist/README.md + cp base/LICENSE baseDist/LICENSE + touch baseDist + +dist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) package/package.json package/.npmignore package/node_modules package/README.md package/LICENSE + (cd package && npm run tsc) + rsync -ac package/node_modules dist/ + cp package/.npmignore dist/.npmignore + cp package/package.json dist/package.json + cp package/README.md dist/README.md + cp package/LICENSE dist/LICENSE touch dist full-bundle: bundle check: + cd package + npm run check + cd ../base npm run check -fmt: node_modules +fmt: package/node_modules base/node_modules npx prettier . "**/*.ts" --write -node_modules: package.json - npm ci -publish: bundle package.json README.md LICENSE +package/node_modules: package/package.json + cd package && npm ci + +base/node_modules: base/package.json + cd base && npm ci + +node_modules: package/node_modules base/node_modules + +publish: bundle package/package.json package/README.md package/LICENSE cd dist && npm publish --access=public link: bundle diff --git a/sdk/base/.gitignore b/sdk/base/.gitignore new file mode 100644 index 000000000..a7ca92b2d --- /dev/null +++ b/sdk/base/.gitignore @@ -0,0 +1,5 @@ +.vscode +dist/ +node_modules/ +lib/coverage +lib/test/output.ts \ No newline at end of file diff --git a/sdk/LICENSE b/sdk/base/LICENSE similarity index 100% rename from sdk/LICENSE rename to sdk/base/LICENSE diff --git a/sdk/base/README.md b/sdk/base/README.md new file mode 100644 index 000000000..33bf5ff9d --- /dev/null +++ b/sdk/base/README.md @@ -0,0 +1 @@ +# See ../package/README.md diff --git a/sdk/jest.config.js b/sdk/base/jest.config.js similarity index 100% rename from sdk/jest.config.js rename to sdk/base/jest.config.js diff --git a/sdk/base/lib/Effects.ts b/sdk/base/lib/Effects.ts new file mode 100644 index 000000000..00d56cfba --- /dev/null +++ b/sdk/base/lib/Effects.ts @@ -0,0 +1,197 @@ +import { + ActionId, + ActionInput, + ActionMetadata, + SetMainStatus, + DependencyRequirement, + CheckDependenciesResult, + SetHealth, + BindParams, + HostId, + LanInfo, + Host, + ExportServiceInterfaceParams, + ServiceInterface, + ActionRequest, + RequestActionParams, + MainStatus, +} from "./osBindings" +import { StorePath } from "./util/PathBuilder" +import { + PackageId, + Dependencies, + ServiceInterfaceId, + SmtpValue, + ActionResult, +} from "./types" +import { UrlString } from "./util/getServiceInterface" + +/** Used to reach out from the pure js runtime */ + +export type Effects = { + constRetry: () => void + clearCallbacks: ( + options: { only: number[] } | { except: number[] }, + ) => Promise + + // action + action: { + /** Define an action that can be invoked by a user or service */ + export(options: { id: ActionId; metadata: ActionMetadata }): Promise + /** Remove all exported actions */ + clear(options: { except: ActionId[] }): Promise + getInput(options: { + packageId?: PackageId + actionId: ActionId + }): Promise + run>(options: { + packageId?: PackageId + actionId: ActionId + input?: Input + }): Promise + request>( + options: RequestActionParams, + ): Promise + clearRequests( + options: { only: string[] } | { except: string[] }, + ): Promise + } + + // control + /** restart this service's main function */ + restart(): Promise + /** stop this service's main function */ + shutdown(): Promise + /** ask the host os what the service's current status is */ + getStatus(options: { + packageId?: PackageId + callback?: () => void + }): Promise + /** indicate to the host os what runstate the service is in */ + setMainStatus(options: SetMainStatus): Promise + + // dependency + /** Set the dependencies of what the service needs, usually run during the inputSpec action as a best practice */ + setDependencies(options: { dependencies: Dependencies }): Promise + /** Get the list of the dependencies, both the dynamic set by the effect of setDependencies and the end result any required in the manifest */ + getDependencies(): Promise + /** Test whether current dependency requirements are satisfied */ + checkDependencies(options: { + packageIds?: PackageId[] + }): Promise + /** mount a volume of a dependency */ + mount(options: { + location: string + target: { + packageId: string + volumeId: string + subpath: string | null + readonly: boolean + } + }): Promise + /** Returns a list of the ids of all installed packages */ + getInstalledPackages(): Promise + /** grants access to certain paths in the store to dependents */ + exposeForDependents(options: { paths: string[] }): Promise + + // health + /** sets the result of a health check */ + setHealth(o: SetHealth): Promise + + // subcontainer + subcontainer: { + /** A low level api used by SubContainer */ + createFs(options: { + imageId: string + name: string | null + }): Promise<[string, string]> + /** A low level api used by SubContainer */ + destroyFs(options: { guid: string }): Promise + } + + // net + // bind + /** Creates a host connected to the specified port with the provided options */ + bind(options: BindParams): Promise + /** Get the port address for a service */ + getServicePortForward(options: { + packageId?: PackageId + hostId: HostId + internalPort: number + }): Promise + /** Removes all network bindings, called in the setupInputSpec */ + clearBindings(options: { + except: { id: HostId; internalPort: number }[] + }): Promise + // host + /** Returns information about the specified host, if it exists */ + getHostInfo(options: { + packageId?: PackageId + hostId: HostId + callback?: () => void + }): Promise + /** Returns the primary url that a user has selected for a host, if it exists */ + getPrimaryUrl(options: { + packageId?: PackageId + hostId: HostId + callback?: () => void + }): Promise + /** Returns the IP address of the container */ + getContainerIp(): Promise + // interface + /** Creates an interface bound to a specific host and port to show to the user */ + exportServiceInterface(options: ExportServiceInterfaceParams): Promise + /** Returns an exported service interface */ + getServiceInterface(options: { + packageId?: PackageId + serviceInterfaceId: ServiceInterfaceId + callback?: () => void + }): Promise + /** Returns all exported service interfaces for a package */ + listServiceInterfaces(options: { + packageId?: PackageId + callback?: () => void + }): Promise> + /** Removes all service interfaces */ + clearServiceInterfaces(options: { + except: ServiceInterfaceId[] + }): Promise + // ssl + /** Returns a PEM encoded fullchain for the hostnames specified */ + getSslCertificate: (options: { + hostnames: string[] + algorithm?: "ecdsa" | "ed25519" + callback?: () => void + }) => Promise<[string, string, string]> + /** Returns a PEM encoded private key corresponding to the certificate for the hostnames specified */ + getSslKey: (options: { + hostnames: string[] + algorithm?: "ecdsa" | "ed25519" + }) => Promise + + // store + store: { + /** Get a value in a json like data, can be observed and subscribed */ + get(options: { + /** If there is no packageId it is assumed the current package */ + packageId?: string + /** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */ + path: StorePath + callback?: () => void + }): Promise + /** Used to store values that can be accessed and subscribed to */ + set(options: { + /** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */ + path: StorePath + value: ExtractStore + }): Promise + } + /** sets the version that this service's data has been migrated to */ + setDataVersion(options: { version: string }): Promise + /** returns the version that this service's data has been migrated to */ + getDataVersion(): Promise + + // system + /** Returns globally configured SMTP settings, if they exist */ + getSystemSmtp(options: { callback?: () => void }): Promise +} diff --git a/sdk/base/lib/actions/index.ts b/sdk/base/lib/actions/index.ts new file mode 100644 index 000000000..4bcbec8b1 --- /dev/null +++ b/sdk/base/lib/actions/index.ts @@ -0,0 +1,100 @@ +import * as T from "../types" +import * as IST from "../actions/input/inputSpecTypes" +import { Action } from "./setupActions" +import { ExtractInputSpecType } from "./input/builder/inputSpec" + +export type RunActionInput = + | Input + | ((prev?: { spec: IST.InputSpec; value: Input | null }) => Input) + +export const runAction = async < + Input extends Record, +>(options: { + effects: T.Effects + // packageId?: T.PackageId + actionId: T.ActionId + input?: RunActionInput +}) => { + if (options.input) { + if (options.input instanceof Function) { + const prev = await options.effects.action.getInput({ + // packageId: options.packageId, + actionId: options.actionId, + }) + const input = options.input( + prev + ? { spec: prev.spec as IST.InputSpec, value: prev.value as Input } + : undefined, + ) + return options.effects.action.run({ + // packageId: options.packageId, + actionId: options.actionId, + input, + }) + } else { + return options.effects.action.run({ + // packageId: options.packageId, + actionId: options.actionId, + input: options.input, + }) + } + } else { + return options.effects.action.run({ + // packageId: options.packageId, + actionId: options.actionId, + }) + } +} +type GetActionInputType> = + A extends Action ? ExtractInputSpecType : never + +type ActionRequestBase = { + reason?: string + replayId?: string +} +type ActionRequestInput> = { + kind: "partial" + value: Partial> +} +export type ActionRequestOptions> = + ActionRequestBase & + ( + | { + when?: Exclude< + T.ActionRequestTrigger, + { condition: "input-not-matches" } + > + input?: ActionRequestInput + } + | { + when: T.ActionRequestTrigger & { condition: "input-not-matches" } + input: ActionRequestInput + } + ) + +const _validate: T.ActionRequest = {} as ActionRequestOptions & { + actionId: string + packageId: string + severity: T.ActionSeverity +} + +export const requestAction = >(options: { + effects: T.Effects + packageId: T.PackageId + action: T + severity: T.ActionSeverity + options?: ActionRequestOptions +}) => { + const request = options.options || {} + const actionId = options.action.id + const req = { + ...request, + actionId, + packageId: options.packageId, + action: undefined, + severity: options.severity, + replayId: request.replayId || `${options.packageId}:${actionId}`, + } + delete req.action + return options.effects.action.request(req) +} diff --git a/sdk/lib/config/builder/index.ts b/sdk/base/lib/actions/input/builder/index.ts similarity index 51% rename from sdk/lib/config/builder/index.ts rename to sdk/base/lib/actions/input/builder/index.ts index 6b6ddc730..618c4856f 100644 --- a/sdk/lib/config/builder/index.ts +++ b/sdk/base/lib/actions/input/builder/index.ts @@ -1,6 +1,6 @@ -import { Config } from "./config" +import { InputSpec } from "./inputSpec" import { List } from "./list" import { Value } from "./value" import { Variants } from "./variants" -export { Config, List, Value, Variants } +export { InputSpec as InputSpec, List, Value, Variants } diff --git a/sdk/lib/config/builder/config.ts b/sdk/base/lib/actions/input/builder/inputSpec.ts similarity index 67% rename from sdk/lib/config/builder/config.ts rename to sdk/base/lib/actions/input/builder/inputSpec.ts index c30f37890..611ad8a48 100644 --- a/sdk/lib/config/builder/config.ts +++ b/sdk/base/lib/actions/input/builder/inputSpec.ts @@ -1,7 +1,7 @@ -import { ValueSpec } from "../configTypes" -import { Value } from "./value" -import { _ } from "../../util" -import { Effects } from "../../types" +import { ValueSpec } from "../inputSpecTypes" +import { PartialValue, Value } from "./value" +import { _ } from "../../../util" +import { Effects } from "../../../Effects" import { Parser, object } from "ts-matches" export type LazyBuildOptions = { @@ -12,20 +12,29 @@ export type LazyBuild = ( ) => Promise | ExpectedOut // prettier-ignore -export type ExtractConfigType | Config, any> | Config, never>> = - A extends Config | Config ? B : +export type ExtractInputSpecType | InputSpec, any> | InputSpec, never>> = + A extends InputSpec | InputSpec ? B : A -export type ConfigSpecOf, Store = never> = { +export type ExtractPartialInputSpecType< + A extends + | Record + | InputSpec, any> + | InputSpec, never>, +> = A extends InputSpec | InputSpec + ? PartialValue + : PartialValue + +export type InputSpecOf, Store = never> = { [K in keyof A]: Value } export type MaybeLazyValues = LazyBuild | A /** - * Configs are the specs that are used by the os configuration form for this service. - * Here is an example of a simple configuration + * InputSpecs are the specs that are used by the os input specification form for this service. + * Here is an example of a simple input specification ```ts - const smallConfig = Config.of({ + const smallInputSpec = InputSpec.of({ test: Value.boolean({ name: "Test", description: "This is the description for the test", @@ -35,17 +44,17 @@ export type MaybeLazyValues = LazyBuild | A }); ``` - The idea of a config is that now the form is going to ask for + The idea of an inputSpec is that now the form is going to ask for Test: [ ] and the value is going to be checked as a boolean. There are more complex values like selects, lists, and objects. See {@link Value} - Also, there is the ability to get a validator/parser from this config spec. + Also, there is the ability to get a validator/parser from this inputSpec spec. ```ts - const matchSmallConfig = smallConfig.validator(); - type SmallConfig = typeof matchSmallConfig._TYPE; + const matchSmallInputSpec = smallInputSpec.validator(); + type SmallInputSpec = typeof matchSmallInputSpec._TYPE; ``` - Here is an example of a more complex configuration which came from a configuration for a service + Here is an example of a more complex input specification which came from an input specification for a service that works with bitcoin, like c-lightning. ```ts @@ -73,17 +82,19 @@ export const port = Value.number({ units: null, placeholder: null, }); -export const addNodesSpec = Config.of({ hostname: hostname, port: port }); +export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port }); ``` */ -export class Config, Store = never> { +export class InputSpec, Store = never> { private constructor( private readonly spec: { [K in keyof Type]: Value | Value }, public validator: Parser, ) {} + _TYPE: Type = null as any as Type + _PARTIAL: PartialValue = null as any as PartialValue async build(options: LazyBuildOptions) { const answer = {} as { [K in keyof Type]: ValueSpec @@ -105,7 +116,7 @@ export class Config, Store = never> { validatorObj[key] = spec[key].validator } const validator = object(validatorObj) - return new Config< + return new InputSpec< { [K in keyof Spec]: Spec[K] extends | Value @@ -119,19 +130,19 @@ export class Config, Store = never> { /** * Use this during the times that the input needs a more specific type. - * Used in types that the value/ variant/ list/ config is constructed somewhere else. + * Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else. ```ts - const a = Config.text({ + const a = InputSpec.text({ name: "a", required: false, }) - return Config.of()({ + return InputSpec.of()({ myValue: a.withStore(), }) ``` */ withStore() { - return this as any as Config + return this as any as InputSpec } } diff --git a/sdk/lib/config/builder/list.ts b/sdk/base/lib/actions/input/builder/list.ts similarity index 74% rename from sdk/lib/config/builder/list.ts rename to sdk/base/lib/actions/input/builder/list.ts index f230b8608..726dc961e 100644 --- a/sdk/lib/config/builder/list.ts +++ b/sdk/base/lib/actions/input/builder/list.ts @@ -1,4 +1,4 @@ -import { Config, LazyBuild } from "./config" +import { InputSpec, LazyBuild } from "./inputSpec" import { ListValueSpecText, Pattern, @@ -6,45 +6,55 @@ import { UniqueBy, ValueSpecList, ValueSpecListOf, -} from "../configTypes" -import { Parser, arrayOf, number, string } from "ts-matches" -/** - * Used as a subtype of Value.list -```ts -export const authorizationList = List.string({ - "name": "Authorization", - "range": "[0,*)", - "default": [], - "description": "Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.", - "warning": null -}, {"masked":false,"placeholder":null,"pattern":"^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$","patternDescription":"Each item must be of the form \":$\"."}); -export const auth = Value.list(authorizationList); -``` -*/ +} from "../inputSpecTypes" +import { Parser, arrayOf, string } from "ts-matches" + export class List { private constructor( public build: LazyBuild, public validator: Parser, ) {} + static text( a: { name: string description?: string | null warning?: string | null - /** Default = [] */ default?: string[] minLength?: number | null maxLength?: number | null }, aSpec: { - /** Default = false */ + /** + * @description Mask (aka camouflage) text input with dots: ● ● ● + * @default false + */ masked?: boolean placeholder?: string | null minLength?: number | null maxLength?: number | null - patterns: Pattern[] - /** Default = "text" */ + /** + * @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails. + * @default [] + * @example + * ``` + [ + { + regex: "[a-z]", + description: "May only contain lower case letters from the English alphabet." + } + ] + * ``` + */ + patterns?: Pattern[] + /** + * @description Informs the browser how to behave and which keyboard to display on mobile + * @default "text" + */ inputmode?: ListValueSpecText["inputmode"] + /** + * @description Displays a button that will generate a random string according to the provided charset and len attributes. + */ generate?: null | RandomString }, ) { @@ -57,6 +67,7 @@ export class List { masked: false, inputmode: "text" as const, generate: null, + patterns: aSpec.patterns || [], ...aSpec, } const built: ValueSpecListOf<"text"> = { @@ -73,6 +84,7 @@ export class List { return built }, arrayOf(string)) } + static dynamicText( getA: LazyBuild< Store, @@ -80,20 +92,17 @@ export class List { name: string description?: string | null warning?: string | null - /** Default = [] */ default?: string[] minLength?: number | null maxLength?: number | null disabled?: false | string generate?: null | RandomString spec: { - /** Default = false */ masked?: boolean placeholder?: string | null minLength?: number | null maxLength?: number | null - patterns: Pattern[] - /** Default = "text" */ + patterns?: Pattern[] inputmode?: ListValueSpecText["inputmode"] } } @@ -109,6 +118,7 @@ export class List { masked: false, inputmode: "text" as const, generate: null, + patterns: aSpec.patterns || [], ...aSpec, } const built: ValueSpecListOf<"text"> = { @@ -125,18 +135,18 @@ export class List { return built }, arrayOf(string)) } + static obj, Store>( a: { name: string description?: string | null warning?: string | null - /** Default [] */ default?: [] minLength?: number | null maxLength?: number | null }, aSpec: { - spec: Config + spec: InputSpec displayAs?: null | string uniqueBy?: null | UniqueBy }, @@ -170,14 +180,14 @@ export class List { /** * Use this during the times that the input needs a more specific type. - * Used in types that the value/ variant/ list/ config is constructed somewhere else. + * Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else. ```ts - const a = Config.text({ + const a = InputSpec.text({ name: "a", required: false, }) - return Config.of()({ + return InputSpec.of()({ myValue: a.withStore(), }) ``` diff --git a/sdk/lib/config/builder/value.ts b/sdk/base/lib/actions/input/builder/value.ts similarity index 57% rename from sdk/lib/config/builder/value.ts rename to sdk/base/lib/actions/input/builder/value.ts index 01673a6df..053bd0596 100644 --- a/sdk/lib/config/builder/value.ts +++ b/sdk/base/lib/actions/input/builder/value.ts @@ -1,19 +1,21 @@ -import { Config, LazyBuild, LazyBuildOptions } from "./config" +import { InputSpec, LazyBuild } from "./inputSpec" import { List } from "./list" -import { Variants } from "./variants" +import { PartialUnionRes, UnionRes, Variants } from "./variants" import { FilePath, Pattern, RandomString, ValueSpec, ValueSpecDatetime, + ValueSpecHidden, ValueSpecText, ValueSpecTextarea, -} from "../configTypes" -import { DefaultString } from "../configTypes" -import { _ } from "../../util" +} from "../inputSpecTypes" +import { DefaultString } from "../inputSpecTypes" +import { _, once } from "../../../util" import { Parser, + any, anyOf, arrayOf, boolean, @@ -24,43 +26,14 @@ import { string, unknown, } from "ts-matches" -import { once } from "../../util/once" +import { DeepPartial } from "../../../types" -export type RequiredDefault = - | false - | { - default: A | null - } +type AsRequired = Required extends true + ? T + : T | null | undefined -function requiredLikeToAbove, A>( - requiredLike: Input, -) { - // prettier-ignore - return { - required: (typeof requiredLike === 'object' ? true : requiredLike) as ( - Input extends { default: unknown} ? true: - Input extends true ? true : - false - ), - default:(typeof requiredLike === 'object' ? requiredLike.default : null) as ( - Input extends { default: infer Default } ? Default : - null - ) - }; -} -type AsRequired = MaybeRequiredType extends - | { default: unknown } - | never - ? Type - : Type | null | undefined - -type InputAsRequired = A extends - | { required: { default: any } | never } - | never - ? Type - : Type | null | undefined const testForAsRequiredParser = once( - () => object({ required: object({ default: unknown }) }).test, + () => object({ required: literal(true) }).test, ) function asRequiredParser< Type, @@ -73,28 +46,13 @@ function asRequiredParser< return parser.optional() as any } -/** - * A value is going to be part of the form in the FE of the OS. - * Something like a boolean, a string, a number, etc. - * in the fe it will ask for the name of value, and use the rest of the value to determine how to render it. - * While writing with a value, you will start with `Value.` then let the IDE suggest the rest. - * for things like string, the options are going to be in {}. - * Keep an eye out for another config builder types as params. - * Note, usually this is going to be used in a `Config` {@link Config} builder. - ```ts -const username = Value.string({ - name: "Username", - default: "bitcoin", - description: "The username for connecting to Bitcoin over RPC.", - warning: null, - required: true, - masked: true, - placeholder: null, - pattern: "^[a-zA-Z0-9_]+$", - patternDescription: "Must be alphanumeric (can contain underscore).", -}); - ``` - */ +export type PartialValue = + T extends UnionRes + ? PartialUnionRes + : T extends {} + ? { [P in keyof T]?: PartialValue } + : T + export class Value { protected constructor( public build: LazyBuild, @@ -103,10 +61,13 @@ export class Value { static toggle(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null default: boolean - /** Immutable means it can only be configed at the first config then never again - Default is false */ + /** + * @description Once set, the value can never be changed. + * @default false + */ immutable?: boolean }) { return new Value( @@ -145,25 +106,56 @@ export class Value { boolean, ) } - static text>(a: { + static text(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * provide a default value. + * @type { string | RandomString | null } + * @example default: null + * @example default: 'World' + * @example default: { charset: 'abcdefg', len: 16 } + */ + default: string | RandomString | null required: Required - - /** Default = false */ + /** + * @description Mask (aka camouflage) text input with dots: ● ● ● + * @default false + */ masked?: boolean placeholder?: string | null minLength?: number | null maxLength?: number | null + /** + * @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails. + * @default [] + * @example + * ``` + [ + { + regex: "[a-z]", + description: "May only contain lower case letters from the English alphabet." + } + ] + * ``` + */ patterns?: Pattern[] - /** Default = 'text' */ + /** + * @description Informs the browser how to behave and which keyboard to display on mobile + * @default "text" + */ inputmode?: ValueSpecText["inputmode"] - /** Immutable means it can only be configured at the first config then never again - * Default is false + /** + * @description Once set, the value can never be changed. + * @default false */ immutable?: boolean - generate?: null | RandomString + /** + * @description Displays a button that will generate a random string according to the provided charset and len attributes. + */ + generate?: RandomString | null }) { return new Value, never>( async () => ({ @@ -180,7 +172,6 @@ export class Value { immutable: a.immutable ?? false, generate: a.generate ?? null, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(string, a), ) @@ -192,20 +183,15 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault - - /** Default = false */ + default: DefaultString | null + required: boolean masked?: boolean placeholder?: string | null minLength?: number | null maxLength?: number | null patterns?: Pattern[] - /** Default = 'text' */ inputmode?: ValueSpecText["inputmode"] disabled?: string | false - /** Immutable means it can only be configured at the first config then never again - * Default is false - */ generate?: null | RandomString } >, @@ -226,36 +212,42 @@ export class Value { immutable: false, generate: a.generate ?? null, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static textarea(a: { + static textarea(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - required: boolean + default: string | null + required: Required minLength?: number | null maxLength?: number | null placeholder?: string | null - /** Immutable means it can only be configed at the first config then never again - Default is false */ + /** + * @description Once set, the value can never be changed. + * @default false + */ immutable?: boolean }) { - return new Value(async () => { - const built: ValueSpecTextarea = { - description: null, - warning: null, - minLength: null, - maxLength: null, - placeholder: null, - type: "textarea" as const, - disabled: false, - immutable: a.immutable ?? false, - ...a, - } - return built - }, string) + return new Value, never>( + async () => { + const built: ValueSpecTextarea = { + description: null, + warning: null, + minLength: null, + maxLength: null, + placeholder: null, + type: "textarea" as const, + disabled: false, + immutable: a.immutable ?? false, + ...a, + } + return built + }, + asRequiredParser(string, a), + ) } static dynamicTextarea( getA: LazyBuild< @@ -264,6 +256,7 @@ export class Value { name: string description?: string | null warning?: string | null + default: string | null required: boolean minLength?: number | null maxLength?: number | null @@ -272,7 +265,7 @@ export class Value { } >, ) { - return new Value(async (options) => { + return new Value(async (options) => { const a = await getA(options) return { description: null, @@ -285,22 +278,41 @@ export class Value { immutable: false, ...a, } - }, string) + }, string.optional()) } - static number>(a: { + static number(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description optionally provide a default value. + * @type { default: number | null } + * @example default: null + * @example default: 7 + */ + default: number | null required: Required min?: number | null max?: number | null - /** Default = '1' */ + /** + * @description How much does the number increase/decrease when using the arrows provided by the browser. + * @default 1 + */ step?: number | null + /** + * @description Requires the number to be an integer. + */ integer: boolean + /** + * @description Optionally display units to the right of the input box. + */ units?: string | null placeholder?: string | null - /** Immutable means it can only be configed at the first config then never again - Default is false */ + /** + * @description Once set, the value can never be changed. + * @default false + */ immutable?: boolean }) { return new Value, never>( @@ -316,7 +328,6 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(number, a), ) @@ -328,10 +339,10 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: number | null + required: boolean min?: number | null max?: number | null - /** Default = '1' */ step?: number | null integer: boolean units?: string | null @@ -354,17 +365,26 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, number.optional()) } - static color>(a: { + static color(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description optionally provide a default value. + * @type { default: string | null } + * @example default: null + * @example default: 'ffffff' + */ + default: string | null required: Required - /** Immutable means it can only be configed at the first config then never again - Default is false */ + /** + * @description Once set, the value can never be changed. + * @default false + */ immutable?: boolean }) { return new Value, never>( @@ -375,9 +395,7 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), - asRequiredParser(string, a), ) } @@ -389,7 +407,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string | null + required: boolean disabled?: false | string } >, @@ -403,21 +422,33 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static datetime>(a: { + static datetime(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description optionally provide a default value. + * @type { default: string | null } + * @example default: null + * @example default: '1985-12-16 18:00:00.000' + */ + default: string | null required: Required - /** Default = 'datetime-local' */ + /** + * @description Informs the browser how to behave and which date/time component to display. + * @default "datetime-local" + */ inputmode?: ValueSpecDatetime["inputmode"] min?: string | null max?: string | null - /** Immutable means it can only be configed at the first config then never again - Default is false */ + /** + * @description Once set, the value can never be changed. + * @default false + */ immutable?: boolean }) { return new Value, never>( @@ -432,7 +463,6 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(string, a), ) @@ -444,8 +474,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault - /** Default = 'datetime-local' */ + default: string | null + required: boolean inputmode?: ValueSpecDatetime["inputmode"] min?: string | null max?: string | null @@ -465,30 +495,40 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static select< - Required extends RequiredDefault, - B extends Record, - >(a: { + static select>(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - required: Required - values: B /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled + * @description Determines if the field is required. If so, optionally provide a default value from the list of values. + * @type { (keyof Values & string) | null } + * @example default: null + * @example default: 'radio1' + */ + default: keyof Values & string + /** + * @description A mapping of unique radio options to their human readable display format. + * @example + * ``` + { + radio1: "Radio 1" + radio2: "Radio 2" + radio3: "Radio 3" + } + * ``` + */ + values: Values + /** + * @description Once set, the value can never be changed. + * @default false */ - disabled?: false | string | (string & keyof B)[] - /** Immutable means it can only be configed at the first config then never again - Default is false */ immutable?: boolean }) { - return new Value, never>( + return new Value( () => ({ description: null, warning: null, @@ -496,14 +536,10 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), - asRequiredParser( - anyOf( - ...Object.keys(a.values).map((x: keyof B & string) => literal(x)), - ), - a, - ) as any, + anyOf( + ...Object.keys(a.values).map((x: keyof Values & string) => literal(x)), + ), ) } static dynamicSelect( @@ -513,18 +549,13 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string values: Record - /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled - */ disabled?: false | string | string[] } >, ) { - return new Value(async (options) => { + return new Value(async (options) => { const a = await getA(options) return { description: null, @@ -533,27 +564,37 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } - }, string.optional()) + }, string) } static multiselect>(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - default: string[] + /** + * @description A simple list of which options should be checked by default. + */ + default: (keyof Values & string)[] + /** + * @description A mapping of checkbox options to their human readable display format. + * @example + * ``` + { + option1: "Option 1" + option2: "Option 2" + option3: "Option 3" + } + * ``` + */ values: Values minLength?: number | null maxLength?: number | null - /** Immutable means it can only be configed at the first config then never again - Default is false */ - immutable?: boolean /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled + * @description Once set, the value can never be changed. + * @default false */ - disabled?: false | string | (string & keyof Values)[] + immutable?: boolean }) { return new Value<(keyof Values)[], never>( () => ({ @@ -582,11 +623,6 @@ export class Value { values: Record minLength?: number | null maxLength?: number | null - /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled - */ disabled?: false | string | string[] } >, @@ -609,9 +645,8 @@ export class Value { a: { name: string description?: string | null - warning?: string | null }, - spec: Config, + spec: InputSpec, ) { return new Value(async (options) => { const built = await spec.build(options as any) @@ -624,69 +659,76 @@ export class Value { } }, spec.validator) } - static file, Store>(a: { - name: string - description?: string | null - warning?: string | null - extensions: string[] - required: Required - }) { - const buildValue = { - type: "file" as const, - description: null, - warning: null, - ...a, - } - return new Value, Store>( - () => ({ - ...buildValue, - - ...requiredLikeToAbove(a.required), - }), - asRequiredParser(object({ filePath: string }), a), - ) - } - static dynamicFile( - a: LazyBuild< - Store, - { + // static file(a: { + // name: string + // description?: string | null + // extensions: string[] + // required: Required + // }) { + // const buildValue = { + // type: "file" as const, + // description: null, + // warning: null, + // ...a, + // } + // return new Value, Store>( + // () => ({ + // ...buildValue, + // }), + // asRequiredParser(object({ filePath: string }), a), + // ) + // } + // static dynamicFile( + // a: LazyBuild< + // Store, + // { + // name: string + // description?: string | null + // warning?: string | null + // extensions: string[] + // required: boolean + // } + // >, + // ) { + // return new Value( + // async (options) => ({ + // type: "file" as const, + // description: null, + // warning: null, + // ...(await a(options)), + // }), + // object({ filePath: string }).optional(), + // ) + // } + static union< + VariantValues extends { + [K in string]: { name: string - description?: string | null - warning?: string | null - extensions: string[] - required: Required + spec: InputSpec | InputSpec } - >, - ) { - return new Value( - async (options) => ({ - type: "file" as const, - description: null, - warning: null, - ...(await a(options)), - }), - string.optional(), - ) - } - static union, Type, Store>( + }, + Store, + >( a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - required: Required - /** Immutable means it can only be configed at the first config then never again - Default is false */ - immutable?: boolean /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled + * @description Provide a default value from the list of variants. + * @type { string } + * @example default: 'variant1' + */ + default: keyof VariantValues & string + /** + * @description Once set, the value can never be changed. + * @default false */ - disabled?: false | string | string[] + immutable?: boolean }, - aVariants: Variants, + aVariants: Variants, ) { - return new Value, Store>( + return new Value( async (options) => ({ type: "union" as const, description: null, @@ -694,85 +736,102 @@ export class Value { disabled: false, ...a, variants: await aVariants.build(options as any), - ...requiredLikeToAbove(a.required), immutable: a.immutable ?? false, }), - asRequiredParser(aVariants.validator, a), + aVariants.validator, ) } static filteredUnion< - Required extends RequiredDefault, - Type extends Record, - Store = never, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, >( getDisabledFn: LazyBuild, a: { name: string description?: string | null warning?: string | null - required: Required + default: keyof VariantValues & string }, - aVariants: Variants | Variants, + aVariants: Variants | Variants, ) { - return new Value, Store>( + return new Value( async (options) => ({ type: "union" as const, description: null, warning: null, ...a, variants: await aVariants.build(options as any), - ...requiredLikeToAbove(a.required), disabled: (await getDisabledFn(options)) || false, immutable: false, }), - asRequiredParser(aVariants.validator, a), + aVariants.validator, ) } static dynamicUnion< - Required extends RequiredDefault, - Type extends Record, - Store = never, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, >( getA: LazyBuild< Store, { - disabled: string[] | false | string name: string description?: string | null warning?: string | null - required: Required + default: keyof VariantValues & string + disabled: string[] | false | string } >, - aVariants: Variants | Variants, + aVariants: Variants | Variants, ) { - return new Value(async (options) => { - const newValues = await getA(options) - return { - type: "union" as const, - description: null, - warning: null, - ...newValues, - variants: await aVariants.build(options as any), - ...requiredLikeToAbove(newValues.required), - immutable: false, - } - }, aVariants.validator.optional()) + return new Value( + async (options) => { + const newValues = await getA(options) + return { + type: "union" as const, + description: null, + warning: null, + ...newValues, + variants: await aVariants.build(options as any), + immutable: false, + } + }, + aVariants.validator, + ) } static list(a: List) { return new Value((options) => a.build(options), a.validator) } + static hidden(parser: Parser = any) { + return new Value(async () => { + const built: ValueSpecHidden = { + type: "hidden" as const, + } + return built + }, parser) + } + /** * Use this during the times that the input needs a more specific type. - * Used in types that the value/ variant/ list/ config is constructed somewhere else. + * Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else. ```ts - const a = Config.text({ + const a = InputSpec.text({ name: "a", required: false, }) - return Config.of()({ + return InputSpec.of()({ myValue: a.withStore(), }) ``` diff --git a/sdk/lib/config/builder/variants.ts b/sdk/base/lib/actions/input/builder/variants.ts similarity index 50% rename from sdk/lib/config/builder/variants.ts rename to sdk/base/lib/actions/input/builder/variants.ts index 352f16828..05124f45a 100644 --- a/sdk/lib/config/builder/variants.ts +++ b/sdk/base/lib/actions/input/builder/variants.ts @@ -1,6 +1,54 @@ -import { InputSpec, ValueSpecUnion } from "../configTypes" -import { LazyBuild, Config } from "./config" -import { Parser, anyOf, literals, object } from "ts-matches" +import { DeepPartial } from "../../../types" +import { ValueSpec, ValueSpecUnion } from "../inputSpecTypes" +import { + LazyBuild, + InputSpec, + ExtractInputSpecType, + ExtractPartialInputSpecType, +} from "./inputSpec" +import { Parser, anyOf, literal, object } from "ts-matches" + +export type UnionRes< + Store, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + K extends keyof VariantValues & string = keyof VariantValues & string, +> = { + [key in keyof VariantValues]: { + selection: key + value: ExtractInputSpecType + other?: { + [key2 in Exclude]?: DeepPartial< + ExtractInputSpecType + > + } + } +}[K] + +export type PartialUnionRes< + Store, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + K extends keyof VariantValues & string = keyof VariantValues & string, +> = { + [key in keyof VariantValues]: { + selection?: key + value?: ExtractPartialInputSpecType + other?: { + [key2 in Exclude]?: DeepPartial< + ExtractInputSpecType + > + } + } +}[K] /** * Used in the the Value.select { @link './value.ts' } @@ -8,7 +56,7 @@ import { Parser, anyOf, literals, object } from "ts-matches" * key to the tag.id in the Value.select ```ts -export const disabled = Config.of({}); +export const disabled = InputSpec.of({}); export const size = Value.number({ name: "Max Chain Size", default: 550, @@ -20,7 +68,7 @@ export const size = Value.number({ units: "MiB", placeholder: null, }); -export const automatic = Config.of({ size: size }); +export const automatic = InputSpec.of({ size: size }); export const size1 = Value.number({ name: "Failsafe Chain Size", default: 65536, @@ -32,7 +80,7 @@ export const size1 = Value.number({ units: "MiB", placeholder: null, }); -export const manual = Config.of({ size: size1 }); +export const manual = InputSpec.of({ size: size1 }); export const pruningSettingsVariants = Variants.of({ disabled: { name: "Disabled", spec: disabled }, automatic: { name: "Automatic", spec: automatic }, @@ -44,51 +92,49 @@ export const pruning = Value.union( description: '- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n', warning: null, - required: true, default: "disabled", }, pruningSettingsVariants ); ``` */ -export class Variants { - static text: any +export class Variants< + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, +> { private constructor( public build: LazyBuild, - public validator: Parser, + public validator: Parser>, ) {} static of< VariantValues extends { [K in string]: { name: string - spec: Config | Config + spec: InputSpec | InputSpec } }, Store = never, >(a: VariantValues) { const validator = anyOf( - ...Object.entries(a).map(([name, { spec }]) => + ...Object.entries(a).map(([id, { spec }]) => object({ - selection: literals(name), + selection: literal(id), value: spec.validator, }), ), ) as Parser - return new Variants< - { + return new Variants(async (options) => { + const variants = {} as { [K in keyof VariantValues]: { - selection: K - // prettier-ignore - value: - VariantValues[K]["spec"] extends (Config | Config) ? B : - never + name: string + spec: Record } - }[keyof VariantValues], - Store - >(async (options) => { - const variants = {} as { - [K in keyof VariantValues]: { name: string; spec: InputSpec } } for (const key in a) { const value = a[key] @@ -102,19 +148,19 @@ export class Variants { } /** * Use this during the times that the input needs a more specific type. - * Used in types that the value/ variant/ list/ config is constructed somewhere else. + * Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else. ```ts - const a = Config.text({ + const a = InputSpec.text({ name: "a", required: false, }) - return Config.of()({ + return InputSpec.of()({ myValue: a.withStore(), }) ``` */ withStore() { - return this as any as Variants + return this as any as Variants } } diff --git a/sdk/base/lib/actions/input/index.ts b/sdk/base/lib/actions/input/index.ts new file mode 100644 index 000000000..3fc16f585 --- /dev/null +++ b/sdk/base/lib/actions/input/index.ts @@ -0,0 +1,3 @@ +export * as constants from "./inputSpecConstants" +export * as types from "./inputSpecTypes" +export * as builder from "./builder" diff --git a/sdk/lib/config/configConstants.ts b/sdk/base/lib/actions/input/inputSpecConstants.ts similarity index 62% rename from sdk/lib/config/configConstants.ts rename to sdk/base/lib/actions/input/inputSpecConstants.ts index aa0e024c9..57bf8a79b 100644 --- a/sdk/lib/config/configConstants.ts +++ b/sdk/base/lib/actions/input/inputSpecConstants.ts @@ -1,53 +1,51 @@ -import { SmtpValue } from "../types" -import { GetSystemSmtp } from "../util/GetSystemSmtp" -import { email } from "../util/patterns" -import { Config, ConfigSpecOf } from "./builder/config" +import { SmtpValue } from "../../types" +import { GetSystemSmtp, Patterns } from "../../util" +import { InputSpec, InputSpecOf } from "./builder/inputSpec" import { Value } from "./builder/value" import { Variants } from "./builder/variants" /** * Base SMTP settings, to be used by StartOS for system wide SMTP */ -export const customSmtp = Config.of, never>({ +export const customSmtp = InputSpec.of, never>({ server: Value.text({ name: "SMTP Server", - required: { - default: null, - }, + required: true, + default: null, }), port: Value.number({ name: "Port", - required: { default: 587 }, + required: true, + default: 587, min: 1, max: 65535, integer: true, }), from: Value.text({ name: "From Address", - required: { - default: null, - }, + required: true, + default: null, placeholder: "test@example.com", inputmode: "email", - patterns: [email], + patterns: [Patterns.email], }), login: Value.text({ name: "Login", - required: { - default: null, - }, + required: true, + default: null, }), password: Value.text({ name: "Password", required: false, + default: null, masked: true, }), }) /** - * For service config. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings + * For service inputSpec. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings */ -export const smtpConfig = Value.filteredUnion( +export const smtpInputSpec = Value.filteredUnion( async ({ effects }) => { const smtp = await new GetSystemSmtp(effects).once() return smtp ? [] : ["system"] @@ -55,21 +53,22 @@ export const smtpConfig = Value.filteredUnion( { name: "SMTP", description: "Optionally provide an SMTP server for sending emails", - required: { default: "disabled" }, + default: "disabled", }, Variants.of({ - disabled: { name: "Disabled", spec: Config.of({}) }, + disabled: { name: "Disabled", spec: InputSpec.of({}) }, system: { name: "System Credentials", - spec: Config.of({ + spec: InputSpec.of({ customFrom: Value.text({ name: "Custom From Address", description: "A custom from address for this service. If not provided, the system from address will be used.", required: false, + default: null, placeholder: "test@example.com", inputmode: "email", - patterns: [email], + patterns: [Patterns.email], }), }), }, diff --git a/sdk/lib/config/configTypes.ts b/sdk/base/lib/actions/input/inputSpecTypes.ts similarity index 74% rename from sdk/lib/config/configTypes.ts rename to sdk/base/lib/actions/input/inputSpecTypes.ts index 0179e531e..362a56ea1 100644 --- a/sdk/lib/config/configTypes.ts +++ b/sdk/base/lib/actions/input/inputSpecTypes.ts @@ -12,6 +12,7 @@ export type ValueType = | "object" | "file" | "union" + | "hidden" export type ValueSpec = ValueSpecOf /** core spec types. These types provide the metadata for performing validations */ // prettier-ignore @@ -28,6 +29,7 @@ export type ValueSpecOf = T extends "object" ? ValueSpecObject : T extends "file" ? ValueSpecFile : T extends "union" ? ValueSpecUnion : + T extends "hidden" ? ValueSpecHidden : never export type ValueSpecText = { @@ -48,7 +50,6 @@ export type ValueSpecText = { default: DefaultString | null disabled: false | string generate: null | RandomString - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecTextarea = { @@ -62,7 +63,6 @@ export type ValueSpecTextarea = { maxLength: number | null required: boolean disabled: false | string - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } @@ -83,7 +83,6 @@ export type ValueSpecNumber = { required: boolean default: number | null disabled: false | string - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecColor = { @@ -95,7 +94,6 @@ export type ValueSpecColor = { required: boolean default: string | null disabled: false | string - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecDatetime = { @@ -109,7 +107,6 @@ export type ValueSpecDatetime = { max: string | null default: string | null disabled: false | string - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecSelect = { @@ -118,15 +115,8 @@ export type ValueSpecSelect = { description: string | null warning: string | null type: "select" - required: boolean default: string | null - /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled - */ disabled: false | string | string[] - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecMultiselect = { @@ -139,14 +129,8 @@ export type ValueSpecMultiselect = { type: "multiselect" minLength: number | null maxLength: number | null - /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled - */ disabled: false | string | string[] default: string[] - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecToggle = { @@ -157,7 +141,6 @@ export type ValueSpecToggle = { type: "toggle" default: boolean | null disabled: false | string - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecUnion = { @@ -173,15 +156,8 @@ export type ValueSpecUnion = { spec: InputSpec } > - /** - * Disabled: false means that there is nothing disabled, good to modify - * string means that this is the message displayed and the whole thing is disabled - * string[] means that the options are disabled - */ disabled: false | string | string[] - required: boolean default: string | null - /** Immutable means it can only be configured at the first config then never again */ immutable: boolean } export type ValueSpecFile = { @@ -199,14 +175,15 @@ export type ValueSpecObject = { type: "object" spec: InputSpec } +export type ValueSpecHidden = { + type: "hidden" +} export type ListValueSpecType = "text" | "object" -/** represents a spec for the values of a list */ // prettier-ignore export type ListValueSpecOf = T extends "text" ? ListValueSpecText : T extends "object" ? ListValueSpecObject : never -/** represents a spec for a list */ export type ValueSpecList = ValueSpecListOf export type ValueSpecListOf = { name: string @@ -242,13 +219,13 @@ export type ListValueSpecText = { } export type ListValueSpecObject = { type: "object" - /** this is a mapped type of the config object at this level, replacing the object's values with specs on those values */ spec: InputSpec - /** indicates whether duplicates can be permitted in the list */ uniqueBy: UniqueBy - /** this should be a handlebars template which can make use of the entire config which corresponds to 'spec' */ displayAs: string | null } +// TODO Aiden do we really want this expressivity? Why not the below. Also what's with the "readonly" portion? +// export type UniqueBy = null | string | { any: string[] } | { all: string[] } + export type UniqueBy = | null | string diff --git a/sdk/base/lib/actions/setupActions.ts b/sdk/base/lib/actions/setupActions.ts new file mode 100644 index 000000000..081225569 --- /dev/null +++ b/sdk/base/lib/actions/setupActions.ts @@ -0,0 +1,155 @@ +import { InputSpec } from "./input/builder" +import { + ExtractInputSpecType, + ExtractPartialInputSpecType, +} from "./input/builder/inputSpec" +import * as T from "../types" +import { once } from "../util" + +export type Run< + A extends + | Record + | InputSpec, any> + | InputSpec, never>, +> = (options: { + effects: T.Effects + input: ExtractInputSpecType & Record +}) => Promise +export type GetInput< + A extends + | Record + | InputSpec, any> + | InputSpec, never>, +> = (options: { + effects: T.Effects +}) => Promise< + | null + | void + | undefined + | (ExtractPartialInputSpecType & Record) +> + +export type MaybeFn = T | ((options: { effects: T.Effects }) => Promise) +function callMaybeFn( + maybeFn: MaybeFn, + options: { effects: T.Effects }, +): Promise { + if (maybeFn instanceof Function) { + return maybeFn(options) + } else { + return Promise.resolve(maybeFn) + } +} +function mapMaybeFn( + maybeFn: MaybeFn, + map: (value: T) => U, +): MaybeFn { + if (maybeFn instanceof Function) { + return async (...args) => map(await maybeFn(...args)) + } else { + return map(maybeFn) + } +} + +export class Action< + Id extends T.ActionId, + Store, + InputSpecType extends + | Record + | InputSpec + | InputSpec, +> { + private constructor( + readonly id: Id, + private readonly metadataFn: MaybeFn, + private readonly inputSpec: InputSpecType, + private readonly getInputFn: GetInput>, + private readonly runFn: Run>, + ) {} + static withInput< + Id extends T.ActionId, + Store, + InputSpecType extends + | Record + | InputSpec + | InputSpec, + >( + id: Id, + metadata: MaybeFn>, + inputSpec: InputSpecType, + getInput: GetInput>, + run: Run>, + ): Action { + return new Action( + id, + mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })), + inputSpec, + getInput, + run, + ) + } + static withoutInput( + id: Id, + metadata: MaybeFn>, + run: Run<{}>, + ): Action { + return new Action( + id, + mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })), + {}, + async () => null, + run, + ) + } + async exportMetadata(options: { + effects: T.Effects + }): Promise { + const metadata = await callMaybeFn(this.metadataFn, options) + await options.effects.action.export({ id: this.id, metadata }) + return metadata + } + async getInput(options: { effects: T.Effects }): Promise { + return { + spec: await this.inputSpec.build(options), + value: (await this.getInputFn(options)) || null, + } + } + async run(options: { + effects: T.Effects + input: ExtractInputSpecType + }): Promise { + return (await this.runFn(options)) || null + } +} + +export class Actions< + Store, + AllActions extends Record>, +> { + private constructor(private readonly actions: AllActions) {} + static of(): Actions { + return new Actions({}) + } + addAction>( + action: A, + ): Actions { + return new Actions({ ...this.actions, [action.id]: action }) + } + async update(options: { effects: T.Effects }): Promise { + options.effects = { + ...options.effects, + constRetry: once(() => { + this.update(options) // yes, this reuses the options object, but the const retry function will be overwritten each time, so the once-ness is not a problem + }), + } + for (let action of Object.values(this.actions)) { + await action.exportMetadata(options) + } + await options.effects.action.clear({ except: Object.keys(this.actions) }) + + return null + } + get(actionId: Id): AllActions[Id] { + return this.actions[actionId] + } +} diff --git a/sdk/base/lib/backup/Backups.ts b/sdk/base/lib/backup/Backups.ts new file mode 100644 index 000000000..3e644014a --- /dev/null +++ b/sdk/base/lib/backup/Backups.ts @@ -0,0 +1,208 @@ +import * as T from "../types" +import * as child_process from "child_process" +import { asError } from "../util" + +export const DEFAULT_OPTIONS: T.SyncOptions = { + delete: true, + exclude: [], +} +export type BackupSync = { + dataPath: `/media/startos/volumes/${Volumes}/${string}` + backupPath: `/media/startos/backup/${string}` + options?: Partial + backupOptions?: Partial + restoreOptions?: Partial +} +/** + * This utility simplifies the volume backup process. + * ```ts + * export const { createBackup, restoreBackup } = Backups.volumes("main").build(); + * ``` + * + * Changing the options of the rsync, (ie excludes) use either + * ```ts + * Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() + * // or + * Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() + * ``` + * + * Using the more fine control, using the addSets for more control + * ```ts + * Backups.addSets({ + * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP + * }, { + * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} + * ).build()q + * ``` + */ +export class Backups { + private constructor( + private options = DEFAULT_OPTIONS, + private restoreOptions: Partial = {}, + private backupOptions: Partial = {}, + private backupSet = [] as BackupSync[], + ) {} + + static withVolumes( + ...volumeNames: Array + ): Backups { + return Backups.withSyncs( + ...volumeNames.map((srcVolume) => ({ + dataPath: `/media/startos/volumes/${srcVolume}/` as const, + backupPath: `/media/startos/backup/${srcVolume}/` as const, + })), + ) + } + + static withSyncs( + ...syncs: BackupSync[] + ) { + return syncs.reduce((acc, x) => acc.addSync(x), new Backups()) + } + + static withOptions( + options?: Partial, + ) { + return new Backups({ ...DEFAULT_OPTIONS, ...options }) + } + + setOptions(options?: Partial) { + this.options = { + ...this.options, + ...options, + } + return this + } + + setBackupOptions(options?: Partial) { + this.backupOptions = { + ...this.backupOptions, + ...options, + } + return this + } + + setRestoreOptions(options?: Partial) { + this.restoreOptions = { + ...this.restoreOptions, + ...options, + } + return this + } + + addVolume( + volume: M["volumes"][number], + options?: Partial<{ + options: T.SyncOptions + backupOptions: T.SyncOptions + restoreOptions: T.SyncOptions + }>, + ) { + return this.addSync({ + dataPath: `/media/startos/volumes/${volume}/` as const, + backupPath: `/media/startos/backup/${volume}/` as const, + ...options, + }) + } + addSync(sync: BackupSync) { + this.backupSet.push({ + ...sync, + options: { ...this.options, ...sync.options }, + }) + return this + } + + async createBackup() { + for (const item of this.backupSet) { + const rsyncResults = await runRsync({ + srcPath: item.dataPath, + dstPath: item.backupPath, + options: { + ...this.options, + ...this.backupOptions, + ...item.options, + ...item.backupOptions, + }, + }) + await rsyncResults.wait() + } + return + } + + async restoreBackup() { + for (const item of this.backupSet) { + const rsyncResults = await runRsync({ + srcPath: item.backupPath, + dstPath: item.dataPath, + options: { + ...this.options, + ...this.backupOptions, + ...item.options, + ...item.backupOptions, + }, + }) + await rsyncResults.wait() + } + return + } +} + +async function runRsync(rsyncOptions: { + srcPath: string + dstPath: string + options: T.SyncOptions +}): Promise<{ + id: () => Promise + wait: () => Promise + progress: () => Promise +}> { + const { srcPath, dstPath, options } = rsyncOptions + + const command = "rsync" + const args: string[] = [] + if (options.delete) { + args.push("--delete") + } + for (const exclude of options.exclude) { + args.push(`--exclude=${exclude}`) + } + args.push("-actAXH") + args.push("--info=progress2") + args.push("--no-inc-recursive") + args.push(srcPath) + args.push(dstPath) + const spawned = child_process.spawn(command, args, { detached: true }) + let percentage = 0.0 + spawned.stdout.on("data", (data: unknown) => { + const lines = String(data).replace("\r", "\n").split("\n") + for (const line of lines) { + const parsed = /$([0-9.]+)%/.exec(line)?.[1] + if (!parsed) continue + percentage = Number.parseFloat(parsed) + } + }) + + spawned.stderr.on("data", (data: unknown) => { + console.error(`Backups.runAsync`, asError(data)) + }) + + const id = async () => { + const pid = spawned.pid + if (pid === undefined) { + throw new Error("rsync process has no pid") + } + return String(pid) + } + const waitPromise = new Promise((resolve, reject) => { + spawned.on("exit", (code: any) => { + if (code === 0) { + resolve(null) + } else { + reject(new Error(`rsync exited with code ${code}`)) + } + }) + }) + const wait = () => waitPromise + const progress = () => Promise.resolve(percentage) + return { id, wait, progress } +} diff --git a/sdk/base/lib/backup/setupBackups.ts b/sdk/base/lib/backup/setupBackups.ts new file mode 100644 index 000000000..b41a61f42 --- /dev/null +++ b/sdk/base/lib/backup/setupBackups.ts @@ -0,0 +1,39 @@ +import { Backups } from "./Backups" +import * as T from "../types" +import { _ } from "../util" + +export type SetupBackupsParams = + | M["volumes"][number][] + | ((_: { effects: T.Effects }) => Promise>) + +type SetupBackupsRes = { + createBackup: T.ExpectedExports.createBackup + restoreBackup: T.ExpectedExports.restoreBackup +} + +export function setupBackups( + options: SetupBackupsParams, +) { + let backupsFactory: (_: { effects: T.Effects }) => Promise> + if (options instanceof Function) { + backupsFactory = options + } else { + backupsFactory = async () => Backups.withVolumes(...options) + } + const answer: { + createBackup: T.ExpectedExports.createBackup + restoreBackup: T.ExpectedExports.restoreBackup + } = { + get createBackup() { + return (async (options) => { + return (await backupsFactory(options)).createBackup() + }) as T.ExpectedExports.createBackup + }, + get restoreBackup() { + return (async (options) => { + return (await backupsFactory(options)).restoreBackup() + }) as T.ExpectedExports.restoreBackup + }, + } + return answer +} diff --git a/sdk/lib/dependencies/dependencies.ts b/sdk/base/lib/dependencies/dependencies.ts similarity index 85% rename from sdk/lib/dependencies/dependencies.ts rename to sdk/base/lib/dependencies/dependencies.ts index 287f63b06..20049b5e8 100644 --- a/sdk/lib/dependencies/dependencies.ts +++ b/sdk/base/lib/dependencies/dependencies.ts @@ -1,33 +1,27 @@ import { ExtendedVersion, VersionRange } from "../exver" -import { - Effects, - PackageId, - DependencyRequirement, - SetHealth, - CheckDependenciesResult, - HealthCheckId, -} from "../types" +import { PackageId, HealthCheckId } from "../types" +import { Effects } from "../Effects" export type CheckDependencies = { installedSatisfied: (packageId: DependencyId) => boolean installedVersionSatisfied: (packageId: DependencyId) => boolean runningSatisfied: (packageId: DependencyId) => boolean - configSatisfied: (packageId: DependencyId) => boolean + actionsSatisfied: (packageId: DependencyId) => boolean healthCheckSatisfied: ( packageId: DependencyId, healthCheckId: HealthCheckId, ) => boolean satisfied: () => boolean - throwIfInstalledNotSatisfied: (packageId: DependencyId) => void - throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => void - throwIfRunningNotSatisfied: (packageId: DependencyId) => void - throwIfConfigNotSatisfied: (packageId: DependencyId) => void + throwIfInstalledNotSatisfied: (packageId: DependencyId) => null + throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => null + throwIfRunningNotSatisfied: (packageId: DependencyId) => null + throwIfActionsNotSatisfied: (packageId: DependencyId) => null throwIfHealthNotSatisfied: ( packageId: DependencyId, healthCheckId?: HealthCheckId, - ) => void - throwIfNotSatisfied: (packageId?: DependencyId) => void + ) => null + throwIfNotSatisfied: (packageId?: DependencyId) => null } export async function checkDependencies< DependencyId extends PackageId = PackageId, @@ -71,8 +65,8 @@ export async function checkDependencies< const dep = find(packageId) return dep.requirement.kind !== "running" || dep.result.isRunning } - const configSatisfied = (packageId: DependencyId) => - find(packageId).result.configSatisfied + const actionsSatisfied = (packageId: DependencyId) => + Object.keys(find(packageId).result.requestedActions).length === 0 const healthCheckSatisfied = ( packageId: DependencyId, healthCheckId?: HealthCheckId, @@ -94,7 +88,7 @@ export async function checkDependencies< installedSatisfied(packageId) && installedVersionSatisfied(packageId) && runningSatisfied(packageId) && - configSatisfied(packageId) && + actionsSatisfied(packageId) && healthCheckSatisfied(packageId) const satisfied = (packageId?: DependencyId) => packageId @@ -106,6 +100,7 @@ export async function checkDependencies< if (!dep.result.installedVersion) { throw new Error(`${dep.result.title || packageId} is not installed`) } + return null } const throwIfInstalledVersionNotSatisfied = (packageId: DependencyId) => { const dep = find(packageId) @@ -123,20 +118,24 @@ export async function checkDependencies< `Installed version ${dep.result.installedVersion} of ${dep.result.title || packageId} does not match expected version range ${dep.requirement.versionRange}`, ) } + return null } const throwIfRunningNotSatisfied = (packageId: DependencyId) => { const dep = find(packageId) if (dep.requirement.kind === "running" && !dep.result.isRunning) { throw new Error(`${dep.result.title || packageId} is not running`) } + return null } - const throwIfConfigNotSatisfied = (packageId: DependencyId) => { + const throwIfActionsNotSatisfied = (packageId: DependencyId) => { const dep = find(packageId) - if (!dep.result.configSatisfied) { + const reqs = Object.keys(dep.result.requestedActions) + if (reqs.length) { throw new Error( - `${dep.result.title || packageId}'s configuration does not satisfy requirements`, + `The following action requests have not been fulfilled: ${reqs.join(", ")}`, ) } + return null } const throwIfHealthNotSatisfied = ( packageId: DependencyId, @@ -163,13 +162,15 @@ export async function checkDependencies< .join("; "), ) } + return null } const throwIfPkgNotSatisfied = (packageId: DependencyId) => { throwIfInstalledNotSatisfied(packageId) throwIfInstalledVersionNotSatisfied(packageId) throwIfRunningNotSatisfied(packageId) - throwIfConfigNotSatisfied(packageId) + throwIfActionsNotSatisfied(packageId) throwIfHealthNotSatisfied(packageId) + return null } const throwIfNotSatisfied = (packageId?: DependencyId) => packageId @@ -187,19 +188,20 @@ export async function checkDependencies< if (err.length) { throw new Error(err.join("; ")) } + return null })() return { installedSatisfied, installedVersionSatisfied, runningSatisfied, - configSatisfied, + actionsSatisfied, healthCheckSatisfied, satisfied, throwIfInstalledNotSatisfied, throwIfInstalledVersionNotSatisfied, throwIfRunningNotSatisfied, - throwIfConfigNotSatisfied, + throwIfActionsNotSatisfied, throwIfHealthNotSatisfied, throwIfNotSatisfied, } diff --git a/sdk/lib/dependencies/index.ts b/sdk/base/lib/dependencies/index.ts similarity index 78% rename from sdk/lib/dependencies/index.ts rename to sdk/base/lib/dependencies/index.ts index 3fe78b4f3..09e2b33ad 100644 --- a/sdk/lib/dependencies/index.ts +++ b/sdk/base/lib/dependencies/index.ts @@ -4,6 +4,3 @@ export type ReadonlyDeep = A extends {} ? { readonly [K in keyof A]: ReadonlyDeep } : A; export type MaybePromise = Promise | A export type Message = string - -import "./DependencyConfig" -import "./setupDependencyConfig" diff --git a/sdk/base/lib/dependencies/setupDependencies.ts b/sdk/base/lib/dependencies/setupDependencies.ts new file mode 100644 index 000000000..f694c042c --- /dev/null +++ b/sdk/base/lib/dependencies/setupDependencies.ts @@ -0,0 +1,32 @@ +import * as T from "../types" +import { once } from "../util" + +type DependencyType = { + [K in keyof Manifest["dependencies"]]: Omit +} + +export function setupDependencies( + fn: (options: { effects: T.Effects }) => Promise>, +): (options: { effects: T.Effects }) => Promise { + const cell = { updater: async (_: { effects: T.Effects }) => null } + cell.updater = async (options: { effects: T.Effects }) => { + options.effects = { + ...options.effects, + constRetry: once(() => { + cell.updater(options) + }), + } + const dependencyType = await fn(options) + return await options.effects.setDependencies({ + dependencies: Object.entries(dependencyType).map( + ([id, { versionRange, ...x }, ,]) => + ({ + id, + ...x, + versionRange: versionRange.toString(), + }) as T.DependencyRequirement, + ), + }) + } + return cell.updater +} diff --git a/sdk/lib/exver/exver.pegjs b/sdk/base/lib/exver/exver.pegjs similarity index 100% rename from sdk/lib/exver/exver.pegjs rename to sdk/base/lib/exver/exver.pegjs diff --git a/sdk/lib/exver/exver.ts b/sdk/base/lib/exver/exver.ts similarity index 100% rename from sdk/lib/exver/exver.ts rename to sdk/base/lib/exver/exver.ts diff --git a/sdk/lib/exver/index.ts b/sdk/base/lib/exver/index.ts similarity index 100% rename from sdk/lib/exver/index.ts rename to sdk/base/lib/exver/index.ts diff --git a/sdk/lib/index.browser.ts b/sdk/base/lib/index.ts similarity index 51% rename from sdk/lib/index.browser.ts rename to sdk/base/lib/index.ts index f7d645133..0aa8e4758 100644 --- a/sdk/lib/index.browser.ts +++ b/sdk/base/lib/index.ts @@ -1,13 +1,12 @@ export { S9pk } from "./s9pk" export { VersionRange, ExtendedVersion, Version } from "./exver" -export * as config from "./config" -export * as CB from "./config/builder" -export * as CT from "./config/configTypes" -export * as dependencyConfig from "./dependencies" +export * as inputSpec from "./actions/input" +export * as ISB from "./actions/input/builder" +export * as IST from "./actions/input/inputSpecTypes" export * as types from "./types" export * as T from "./types" export * as yaml from "yaml" export * as matches from "ts-matches" -export * as utils from "./util/index.browser" +export * as utils from "./util" diff --git a/sdk/lib/interfaces/AddressReceipt.ts b/sdk/base/lib/interfaces/AddressReceipt.ts similarity index 100% rename from sdk/lib/interfaces/AddressReceipt.ts rename to sdk/base/lib/interfaces/AddressReceipt.ts diff --git a/sdk/lib/interfaces/Host.ts b/sdk/base/lib/interfaces/Host.ts similarity index 82% rename from sdk/lib/interfaces/Host.ts rename to sdk/base/lib/interfaces/Host.ts index aa27a289c..b90dc1c60 100644 --- a/sdk/lib/interfaces/Host.ts +++ b/sdk/base/lib/interfaces/Host.ts @@ -1,10 +1,10 @@ -import { number, object, string } from "ts-matches" -import { Effects } from "../types" +import { object, string } from "ts-matches" +import { Effects } from "../Effects" import { Origin } from "./Origin" -import { AddSslOptions, BindParams } from ".././osBindings" -import { Security } from ".././osBindings" -import { BindOptions } from ".././osBindings" -import { AlpnInfo } from ".././osBindings" +import { AddSslOptions, BindParams } from "../osBindings" +import { Security } from "../osBindings" +import { BindOptions } from "../osBindings" +import { AlpnInfo } from "../osBindings" export { AddSslOptions, Security, BindOptions } @@ -94,6 +94,22 @@ export class Host { }, ) {} + /** + * @description Use this function to bind the host to an internal port and configured options for protocol, security, and external port. + * + * @param internalPort - The internal port to be bound. + * @param options - The protocol options for this binding. + * @returns A multi-origin that is capable of exporting one or more service interfaces. + * @example + * In this example, we bind a previously created multi-host to port 80, then select the http protocol and request an external port of 8332. + * + * ``` + const uiMultiOrigin = await uiMulti.bindPort(80, { + protocol: 'http', + preferredExternalPort: 8332, + }) + * ``` + */ async bindPort( internalPort: number, options: BindOptionsByProtocol, diff --git a/sdk/lib/interfaces/Origin.ts b/sdk/base/lib/interfaces/Origin.ts similarity index 89% rename from sdk/lib/interfaces/Origin.ts rename to sdk/base/lib/interfaces/Origin.ts index cc84728ec..5e12713e6 100644 --- a/sdk/lib/interfaces/Origin.ts +++ b/sdk/base/lib/interfaces/Origin.ts @@ -1,6 +1,6 @@ import { AddressInfo } from "../types" import { AddressReceipt } from "./AddressReceipt" -import { Host, BindOptions, Scheme } from "./Host" +import { Host, Scheme } from "./Host" import { ServiceInterfaceBuilder } from "./ServiceInterfaceBuilder" export class Origin { @@ -31,9 +31,9 @@ export class Origin { } /** - * A function to register a group of origins ( :// : ) with StartOS + * @description A function to register a group of origins ( :// : ) with StartOS * - * The returned addressReceipt serves as proof that the addresses were registered + * The returned addressReceipt serves as proof that the addresses were registered * * @param addressInfo * @returns diff --git a/sdk/lib/interfaces/ServiceInterfaceBuilder.ts b/sdk/base/lib/interfaces/ServiceInterfaceBuilder.ts similarity index 90% rename from sdk/lib/interfaces/ServiceInterfaceBuilder.ts rename to sdk/base/lib/interfaces/ServiceInterfaceBuilder.ts index 49d8020d6..4ef294b4f 100644 --- a/sdk/lib/interfaces/ServiceInterfaceBuilder.ts +++ b/sdk/base/lib/interfaces/ServiceInterfaceBuilder.ts @@ -1,5 +1,5 @@ -import { ServiceInterfaceType } from "../StartSdk" -import { Effects } from "../types" +import { ServiceInterfaceType } from "../types" +import { Effects } from "../Effects" import { Scheme } from "./Host" /** diff --git a/sdk/lib/interfaces/interfaceReceipt.ts b/sdk/base/lib/interfaces/interfaceReceipt.ts similarity index 100% rename from sdk/lib/interfaces/interfaceReceipt.ts rename to sdk/base/lib/interfaces/interfaceReceipt.ts diff --git a/sdk/base/lib/interfaces/setupInterfaces.ts b/sdk/base/lib/interfaces/setupInterfaces.ts new file mode 100644 index 000000000..ba284bcb3 --- /dev/null +++ b/sdk/base/lib/interfaces/setupInterfaces.ts @@ -0,0 +1,57 @@ +import * as T from "../types" +import { once } from "../util" +import { AddressReceipt } from "./AddressReceipt" + +declare const UpdateServiceInterfacesProof: unique symbol +export type UpdateServiceInterfacesReceipt = { + [UpdateServiceInterfacesProof]: never +} + +export type ServiceInterfacesReceipt = Array +export type SetServiceInterfaces = + (opts: { effects: T.Effects }) => Promise +export type UpdateServiceInterfaces = + (opts: { + effects: T.Effects + }) => Promise +export type SetupServiceInterfaces = ( + fn: SetServiceInterfaces, +) => UpdateServiceInterfaces +export const NO_INTERFACE_CHANGES = {} as UpdateServiceInterfacesReceipt +export const setupServiceInterfaces: SetupServiceInterfaces = < + Output extends ServiceInterfacesReceipt, +>( + fn: SetServiceInterfaces, +) => { + const cell = { + updater: (async (options: { effects: T.Effects }) => + [] as any as Output) as UpdateServiceInterfaces, + } + cell.updater = (async (options: { effects: T.Effects }) => { + options.effects = { + ...options.effects, + constRetry: once(() => { + cell.updater(options) + }), + } + const bindings: T.BindId[] = [] + const interfaces: T.ServiceInterfaceId[] = [] + const res = await fn({ + effects: { + ...options.effects, + bind: (params: T.BindParams) => { + bindings.push({ id: params.id, internalPort: params.internalPort }) + return options.effects.bind(params) + }, + exportServiceInterface: (params: T.ExportServiceInterfaceParams) => { + interfaces.push(params.id) + return options.effects.exportServiceInterface(params) + }, + }, + }) + await options.effects.clearBindings({ except: bindings }) + await options.effects.clearServiceInterfaces({ except: interfaces }) + return res + }) as UpdateServiceInterfaces + return cell.updater +} diff --git a/sdk/lib/osBindings/AcceptSigners.ts b/sdk/base/lib/osBindings/AcceptSigners.ts similarity index 100% rename from sdk/lib/osBindings/AcceptSigners.ts rename to sdk/base/lib/osBindings/AcceptSigners.ts diff --git a/sdk/base/lib/osBindings/AcmeSettings.ts b/sdk/base/lib/osBindings/AcmeSettings.ts new file mode 100644 index 000000000..bdf151ec7 --- /dev/null +++ b/sdk/base/lib/osBindings/AcmeSettings.ts @@ -0,0 +1,13 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AcmeSettings = { + provider: string + /** + * email addresses for letsencrypt + */ + contact: Array + /** + * domains to get letsencrypt certs for + */ + domains: string[] +} diff --git a/sdk/lib/osBindings/ActionId.ts b/sdk/base/lib/osBindings/ActionId.ts similarity index 100% rename from sdk/lib/osBindings/ActionId.ts rename to sdk/base/lib/osBindings/ActionId.ts diff --git a/sdk/base/lib/osBindings/ActionInput.ts b/sdk/base/lib/osBindings/ActionInput.ts new file mode 100644 index 000000000..a19a5f1a4 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionInput.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionInput = { + spec: Record + value: Record | null +} diff --git a/sdk/lib/osBindings/ActionMetadata.ts b/sdk/base/lib/osBindings/ActionMetadata.ts similarity index 74% rename from sdk/lib/osBindings/ActionMetadata.ts rename to sdk/base/lib/osBindings/ActionMetadata.ts index b103b82b0..ade129fd4 100644 --- a/sdk/lib/osBindings/ActionMetadata.ts +++ b/sdk/base/lib/osBindings/ActionMetadata.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionVisibility } from "./ActionVisibility" import type { AllowedStatuses } from "./AllowedStatuses" export type ActionMetadata = { name: string description: string warning: string | null - input: any - disabled: boolean + visibility: ActionVisibility allowedStatuses: AllowedStatuses + hasInput: boolean group: string | null } diff --git a/sdk/base/lib/osBindings/ActionRequest.ts b/sdk/base/lib/osBindings/ActionRequest.ts new file mode 100644 index 000000000..552f37bc6 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionRequest.ts @@ -0,0 +1,15 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionId } from "./ActionId" +import type { ActionRequestInput } from "./ActionRequestInput" +import type { ActionRequestTrigger } from "./ActionRequestTrigger" +import type { ActionSeverity } from "./ActionSeverity" +import type { PackageId } from "./PackageId" + +export type ActionRequest = { + packageId: PackageId + actionId: ActionId + severity: ActionSeverity + reason?: string + when?: ActionRequestTrigger + input?: ActionRequestInput +} diff --git a/sdk/lib/osBindings/AllowedStatuses.ts b/sdk/base/lib/osBindings/ActionRequestCondition.ts similarity index 61% rename from sdk/lib/osBindings/AllowedStatuses.ts rename to sdk/base/lib/osBindings/ActionRequestCondition.ts index 960187fd9..0f06caf3c 100644 --- a/sdk/lib/osBindings/AllowedStatuses.ts +++ b/sdk/base/lib/osBindings/ActionRequestCondition.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AllowedStatuses = "onlyRunning" | "onlyStopped" | "any" +export type ActionRequestCondition = "input-not-matches" diff --git a/sdk/base/lib/osBindings/ActionRequestEntry.ts b/sdk/base/lib/osBindings/ActionRequestEntry.ts new file mode 100644 index 000000000..0e716abe4 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionRequestEntry.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionRequest } from "./ActionRequest" + +export type ActionRequestEntry = { request: ActionRequest; active: boolean } diff --git a/sdk/base/lib/osBindings/ActionRequestInput.ts b/sdk/base/lib/osBindings/ActionRequestInput.ts new file mode 100644 index 000000000..a1cde7789 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionRequestInput.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionRequestInput = { + kind: "partial" + value: Record +} diff --git a/sdk/base/lib/osBindings/ActionRequestTrigger.ts b/sdk/base/lib/osBindings/ActionRequestTrigger.ts new file mode 100644 index 000000000..ebd0963e5 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionRequestTrigger.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionRequestCondition } from "./ActionRequestCondition" + +export type ActionRequestTrigger = { + once: boolean + condition: ActionRequestCondition +} diff --git a/sdk/base/lib/osBindings/ActionResult.ts b/sdk/base/lib/osBindings/ActionResult.ts new file mode 100644 index 000000000..7422dcde3 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionResult.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionResultV0 } from "./ActionResultV0" +import type { ActionResultV1 } from "./ActionResultV1" + +export type ActionResult = + | ({ version: "0" } & ActionResultV0) + | ({ version: "1" } & ActionResultV1) diff --git a/sdk/base/lib/osBindings/ActionResultMember.ts b/sdk/base/lib/osBindings/ActionResultMember.ts new file mode 100644 index 000000000..cdc23ecaa --- /dev/null +++ b/sdk/base/lib/osBindings/ActionResultMember.ts @@ -0,0 +1,15 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionResultMember = { + name: string + description: string | null +} & ( + | { + type: "single" + value: string + copyable: boolean + qr: boolean + masked: boolean + } + | { type: "group"; value: Array } +) diff --git a/sdk/base/lib/osBindings/ActionResultV0.ts b/sdk/base/lib/osBindings/ActionResultV0.ts new file mode 100644 index 000000000..7c6b43d45 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionResultV0.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionResultV0 = { + message: string + value: string | null + copyable: boolean + qr: boolean +} diff --git a/sdk/base/lib/osBindings/ActionResultV1.ts b/sdk/base/lib/osBindings/ActionResultV1.ts new file mode 100644 index 000000000..eece18477 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionResultV1.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionResultValue } from "./ActionResultValue" + +export type ActionResultV1 = { + title: string + message: string | null + result: ActionResultValue | null +} diff --git a/sdk/base/lib/osBindings/ActionResultValue.ts b/sdk/base/lib/osBindings/ActionResultValue.ts new file mode 100644 index 000000000..d1cb6c8c3 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionResultValue.ts @@ -0,0 +1,12 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionResultMember } from "./ActionResultMember" + +export type ActionResultValue = + | { + type: "single" + value: string + copyable: boolean + qr: boolean + masked: boolean + } + | { type: "group"; value: Array } diff --git a/sdk/base/lib/osBindings/ActionSeverity.ts b/sdk/base/lib/osBindings/ActionSeverity.ts new file mode 100644 index 000000000..ad339f951 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionSeverity.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionSeverity = "critical" | "important" diff --git a/sdk/base/lib/osBindings/ActionVisibility.ts b/sdk/base/lib/osBindings/ActionVisibility.ts new file mode 100644 index 000000000..ab1e6e1b9 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionVisibility.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionVisibility = "hidden" | { disabled: string } | "enabled" diff --git a/sdk/lib/osBindings/AddAdminParams.ts b/sdk/base/lib/osBindings/AddAdminParams.ts similarity index 100% rename from sdk/lib/osBindings/AddAdminParams.ts rename to sdk/base/lib/osBindings/AddAdminParams.ts diff --git a/sdk/lib/osBindings/AddAssetParams.ts b/sdk/base/lib/osBindings/AddAssetParams.ts similarity index 100% rename from sdk/lib/osBindings/AddAssetParams.ts rename to sdk/base/lib/osBindings/AddAssetParams.ts diff --git a/sdk/base/lib/osBindings/AddCategoryParams.ts b/sdk/base/lib/osBindings/AddCategoryParams.ts new file mode 100644 index 000000000..799f2d4d2 --- /dev/null +++ b/sdk/base/lib/osBindings/AddCategoryParams.ts @@ -0,0 +1,8 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AddCategoryParams = { + id: string + name: string + short: string + long: string +} diff --git a/sdk/lib/osBindings/AddPackageParams.ts b/sdk/base/lib/osBindings/AddPackageParams.ts similarity index 100% rename from sdk/lib/osBindings/AddPackageParams.ts rename to sdk/base/lib/osBindings/AddPackageParams.ts diff --git a/sdk/lib/osBindings/AddSslOptions.ts b/sdk/base/lib/osBindings/AddSslOptions.ts similarity index 100% rename from sdk/lib/osBindings/AddSslOptions.ts rename to sdk/base/lib/osBindings/AddSslOptions.ts diff --git a/sdk/lib/osBindings/AddVersionParams.ts b/sdk/base/lib/osBindings/AddVersionParams.ts similarity index 100% rename from sdk/lib/osBindings/AddVersionParams.ts rename to sdk/base/lib/osBindings/AddVersionParams.ts diff --git a/sdk/lib/osBindings/AddressInfo.ts b/sdk/base/lib/osBindings/AddressInfo.ts similarity index 100% rename from sdk/lib/osBindings/AddressInfo.ts rename to sdk/base/lib/osBindings/AddressInfo.ts diff --git a/sdk/lib/osBindings/Alerts.ts b/sdk/base/lib/osBindings/Alerts.ts similarity index 100% rename from sdk/lib/osBindings/Alerts.ts rename to sdk/base/lib/osBindings/Alerts.ts diff --git a/sdk/lib/osBindings/Algorithm.ts b/sdk/base/lib/osBindings/Algorithm.ts similarity index 100% rename from sdk/lib/osBindings/Algorithm.ts rename to sdk/base/lib/osBindings/Algorithm.ts diff --git a/sdk/lib/osBindings/AllPackageData.ts b/sdk/base/lib/osBindings/AllPackageData.ts similarity index 100% rename from sdk/lib/osBindings/AllPackageData.ts rename to sdk/base/lib/osBindings/AllPackageData.ts diff --git a/sdk/base/lib/osBindings/AllowedStatuses.ts b/sdk/base/lib/osBindings/AllowedStatuses.ts new file mode 100644 index 000000000..ed5851495 --- /dev/null +++ b/sdk/base/lib/osBindings/AllowedStatuses.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AllowedStatuses = "only-running" | "only-stopped" | "any" diff --git a/sdk/lib/osBindings/AlpnInfo.ts b/sdk/base/lib/osBindings/AlpnInfo.ts similarity index 100% rename from sdk/lib/osBindings/AlpnInfo.ts rename to sdk/base/lib/osBindings/AlpnInfo.ts diff --git a/sdk/lib/osBindings/AnySignature.ts b/sdk/base/lib/osBindings/AnySignature.ts similarity index 100% rename from sdk/lib/osBindings/AnySignature.ts rename to sdk/base/lib/osBindings/AnySignature.ts diff --git a/sdk/lib/osBindings/AnySigningKey.ts b/sdk/base/lib/osBindings/AnySigningKey.ts similarity index 100% rename from sdk/lib/osBindings/AnySigningKey.ts rename to sdk/base/lib/osBindings/AnySigningKey.ts diff --git a/sdk/lib/osBindings/AnyVerifyingKey.ts b/sdk/base/lib/osBindings/AnyVerifyingKey.ts similarity index 100% rename from sdk/lib/osBindings/AnyVerifyingKey.ts rename to sdk/base/lib/osBindings/AnyVerifyingKey.ts diff --git a/sdk/lib/osBindings/ApiState.ts b/sdk/base/lib/osBindings/ApiState.ts similarity index 100% rename from sdk/lib/osBindings/ApiState.ts rename to sdk/base/lib/osBindings/ApiState.ts diff --git a/sdk/lib/osBindings/AttachParams.ts b/sdk/base/lib/osBindings/AttachParams.ts similarity index 100% rename from sdk/lib/osBindings/AttachParams.ts rename to sdk/base/lib/osBindings/AttachParams.ts diff --git a/sdk/lib/osBindings/BackupProgress.ts b/sdk/base/lib/osBindings/BackupProgress.ts similarity index 100% rename from sdk/lib/osBindings/BackupProgress.ts rename to sdk/base/lib/osBindings/BackupProgress.ts diff --git a/sdk/lib/osBindings/BackupTargetFS.ts b/sdk/base/lib/osBindings/BackupTargetFS.ts similarity index 100% rename from sdk/lib/osBindings/BackupTargetFS.ts rename to sdk/base/lib/osBindings/BackupTargetFS.ts diff --git a/sdk/lib/osBindings/Base64.ts b/sdk/base/lib/osBindings/Base64.ts similarity index 100% rename from sdk/lib/osBindings/Base64.ts rename to sdk/base/lib/osBindings/Base64.ts diff --git a/sdk/base/lib/osBindings/BindId.ts b/sdk/base/lib/osBindings/BindId.ts new file mode 100644 index 000000000..778d95346 --- /dev/null +++ b/sdk/base/lib/osBindings/BindId.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { HostId } from "./HostId" + +export type BindId = { id: HostId; internalPort: number } diff --git a/sdk/lib/osBindings/BindInfo.ts b/sdk/base/lib/osBindings/BindInfo.ts similarity index 71% rename from sdk/lib/osBindings/BindInfo.ts rename to sdk/base/lib/osBindings/BindInfo.ts index 221b1c37c..85fc38e94 100644 --- a/sdk/lib/osBindings/BindInfo.ts +++ b/sdk/base/lib/osBindings/BindInfo.ts @@ -2,4 +2,4 @@ import type { BindOptions } from "./BindOptions" import type { LanInfo } from "./LanInfo" -export type BindInfo = { options: BindOptions; lan: LanInfo } +export type BindInfo = { enabled: boolean; options: BindOptions; lan: LanInfo } diff --git a/sdk/lib/osBindings/BindOptions.ts b/sdk/base/lib/osBindings/BindOptions.ts similarity index 100% rename from sdk/lib/osBindings/BindOptions.ts rename to sdk/base/lib/osBindings/BindOptions.ts diff --git a/sdk/lib/osBindings/BindParams.ts b/sdk/base/lib/osBindings/BindParams.ts similarity index 100% rename from sdk/lib/osBindings/BindParams.ts rename to sdk/base/lib/osBindings/BindParams.ts diff --git a/sdk/lib/osBindings/Blake3Commitment.ts b/sdk/base/lib/osBindings/Blake3Commitment.ts similarity index 100% rename from sdk/lib/osBindings/Blake3Commitment.ts rename to sdk/base/lib/osBindings/Blake3Commitment.ts diff --git a/sdk/lib/osBindings/BlockDev.ts b/sdk/base/lib/osBindings/BlockDev.ts similarity index 100% rename from sdk/lib/osBindings/BlockDev.ts rename to sdk/base/lib/osBindings/BlockDev.ts diff --git a/sdk/lib/osBindings/SetConfigured.ts b/sdk/base/lib/osBindings/BuildArg.ts similarity index 67% rename from sdk/lib/osBindings/SetConfigured.ts rename to sdk/base/lib/osBindings/BuildArg.ts index e1478422a..1157828f5 100644 --- a/sdk/lib/osBindings/SetConfigured.ts +++ b/sdk/base/lib/osBindings/BuildArg.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type SetConfigured = { configured: boolean } +export type BuildArg = string | { env: string } diff --git a/sdk/lib/osBindings/CallbackId.ts b/sdk/base/lib/osBindings/CallbackId.ts similarity index 100% rename from sdk/lib/osBindings/CallbackId.ts rename to sdk/base/lib/osBindings/CallbackId.ts diff --git a/sdk/lib/osBindings/Category.ts b/sdk/base/lib/osBindings/Category.ts similarity index 100% rename from sdk/lib/osBindings/Category.ts rename to sdk/base/lib/osBindings/Category.ts diff --git a/sdk/lib/osBindings/CheckDependenciesParam.ts b/sdk/base/lib/osBindings/CheckDependenciesParam.ts similarity index 100% rename from sdk/lib/osBindings/CheckDependenciesParam.ts rename to sdk/base/lib/osBindings/CheckDependenciesParam.ts diff --git a/sdk/lib/osBindings/CheckDependenciesResult.ts b/sdk/base/lib/osBindings/CheckDependenciesResult.ts similarity index 62% rename from sdk/lib/osBindings/CheckDependenciesResult.ts rename to sdk/base/lib/osBindings/CheckDependenciesResult.ts index a435ff87f..3fa34b600 100644 --- a/sdk/lib/osBindings/CheckDependenciesResult.ts +++ b/sdk/base/lib/osBindings/CheckDependenciesResult.ts @@ -1,14 +1,17 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionRequestEntry } from "./ActionRequestEntry" import type { HealthCheckId } from "./HealthCheckId" import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" import type { PackageId } from "./PackageId" +import type { ReplayId } from "./ReplayId" +import type { Version } from "./Version" export type CheckDependenciesResult = { packageId: PackageId title: string | null - installedVersion: string | null - satisfies: string[] + installedVersion: Version | null + satisfies: Array isRunning: boolean - configSatisfied: boolean + requestedActions: { [key: ReplayId]: ActionRequestEntry } healthChecks: { [key: HealthCheckId]: NamedHealthCheckResult } } diff --git a/sdk/lib/osBindings/Cifs.ts b/sdk/base/lib/osBindings/Cifs.ts similarity index 100% rename from sdk/lib/osBindings/Cifs.ts rename to sdk/base/lib/osBindings/Cifs.ts diff --git a/sdk/base/lib/osBindings/ClearActionRequestsParams.ts b/sdk/base/lib/osBindings/ClearActionRequestsParams.ts new file mode 100644 index 000000000..856a13de4 --- /dev/null +++ b/sdk/base/lib/osBindings/ClearActionRequestsParams.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ClearActionRequestsParams = + | { only: string[] } + | { except: string[] } diff --git a/sdk/base/lib/osBindings/ClearActionsParams.ts b/sdk/base/lib/osBindings/ClearActionsParams.ts new file mode 100644 index 000000000..68cff676a --- /dev/null +++ b/sdk/base/lib/osBindings/ClearActionsParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionId } from "./ActionId" + +export type ClearActionsParams = { except: Array } diff --git a/sdk/base/lib/osBindings/ClearBindingsParams.ts b/sdk/base/lib/osBindings/ClearBindingsParams.ts new file mode 100644 index 000000000..41f1d5741 --- /dev/null +++ b/sdk/base/lib/osBindings/ClearBindingsParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { BindId } from "./BindId" + +export type ClearBindingsParams = { except: Array } diff --git a/sdk/base/lib/osBindings/ClearCallbacksParams.ts b/sdk/base/lib/osBindings/ClearCallbacksParams.ts new file mode 100644 index 000000000..095a27f5e --- /dev/null +++ b/sdk/base/lib/osBindings/ClearCallbacksParams.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ClearCallbacksParams = { only: number[] } | { except: number[] } diff --git a/sdk/base/lib/osBindings/ClearServiceInterfacesParams.ts b/sdk/base/lib/osBindings/ClearServiceInterfacesParams.ts new file mode 100644 index 000000000..02c177978 --- /dev/null +++ b/sdk/base/lib/osBindings/ClearServiceInterfacesParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ServiceInterfaceId } from "./ServiceInterfaceId" + +export type ClearServiceInterfacesParams = { except: Array } diff --git a/sdk/base/lib/osBindings/CliSetIconParams.ts b/sdk/base/lib/osBindings/CliSetIconParams.ts new file mode 100644 index 000000000..4e47362b6 --- /dev/null +++ b/sdk/base/lib/osBindings/CliSetIconParams.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type CliSetIconParams = { icon: string } diff --git a/sdk/lib/osBindings/ContactInfo.ts b/sdk/base/lib/osBindings/ContactInfo.ts similarity index 100% rename from sdk/lib/osBindings/ContactInfo.ts rename to sdk/base/lib/osBindings/ContactInfo.ts diff --git a/sdk/lib/osBindings/CreateOverlayedImageParams.ts b/sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts similarity index 63% rename from sdk/lib/osBindings/CreateOverlayedImageParams.ts rename to sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts index aad94f01f..32d301e4a 100644 --- a/sdk/lib/osBindings/CreateOverlayedImageParams.ts +++ b/sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts @@ -1,4 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ImageId } from "./ImageId" -export type CreateOverlayedImageParams = { imageId: ImageId } +export type CreateSubcontainerFsParams = { + imageId: ImageId + name: string | null +} diff --git a/sdk/lib/osBindings/CurrentDependencies.ts b/sdk/base/lib/osBindings/CurrentDependencies.ts similarity index 100% rename from sdk/lib/osBindings/CurrentDependencies.ts rename to sdk/base/lib/osBindings/CurrentDependencies.ts diff --git a/sdk/lib/osBindings/CurrentDependencyInfo.ts b/sdk/base/lib/osBindings/CurrentDependencyInfo.ts similarity index 92% rename from sdk/lib/osBindings/CurrentDependencyInfo.ts rename to sdk/base/lib/osBindings/CurrentDependencyInfo.ts index 2096a0113..e56e2be7a 100644 --- a/sdk/lib/osBindings/CurrentDependencyInfo.ts +++ b/sdk/base/lib/osBindings/CurrentDependencyInfo.ts @@ -5,5 +5,4 @@ export type CurrentDependencyInfo = { title: string | null icon: DataUrl | null versionRange: string - configSatisfied: boolean } & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] }) diff --git a/sdk/lib/osBindings/DataUrl.ts b/sdk/base/lib/osBindings/DataUrl.ts similarity index 100% rename from sdk/lib/osBindings/DataUrl.ts rename to sdk/base/lib/osBindings/DataUrl.ts diff --git a/sdk/lib/osBindings/DepInfo.ts b/sdk/base/lib/osBindings/DepInfo.ts similarity index 100% rename from sdk/lib/osBindings/DepInfo.ts rename to sdk/base/lib/osBindings/DepInfo.ts diff --git a/sdk/lib/osBindings/Dependencies.ts b/sdk/base/lib/osBindings/Dependencies.ts similarity index 100% rename from sdk/lib/osBindings/Dependencies.ts rename to sdk/base/lib/osBindings/Dependencies.ts diff --git a/sdk/lib/osBindings/DependencyKind.ts b/sdk/base/lib/osBindings/DependencyKind.ts similarity index 100% rename from sdk/lib/osBindings/DependencyKind.ts rename to sdk/base/lib/osBindings/DependencyKind.ts diff --git a/sdk/lib/osBindings/DependencyMetadata.ts b/sdk/base/lib/osBindings/DependencyMetadata.ts similarity index 100% rename from sdk/lib/osBindings/DependencyMetadata.ts rename to sdk/base/lib/osBindings/DependencyMetadata.ts diff --git a/sdk/lib/osBindings/DependencyRequirement.ts b/sdk/base/lib/osBindings/DependencyRequirement.ts similarity index 100% rename from sdk/lib/osBindings/DependencyRequirement.ts rename to sdk/base/lib/osBindings/DependencyRequirement.ts diff --git a/sdk/lib/osBindings/Description.ts b/sdk/base/lib/osBindings/Description.ts similarity index 100% rename from sdk/lib/osBindings/Description.ts rename to sdk/base/lib/osBindings/Description.ts diff --git a/sdk/lib/osBindings/DestroyOverlayedImageParams.ts b/sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts similarity index 71% rename from sdk/lib/osBindings/DestroyOverlayedImageParams.ts rename to sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts index b5b7484a2..3f85d2217 100644 --- a/sdk/lib/osBindings/DestroyOverlayedImageParams.ts +++ b/sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Guid } from "./Guid" -export type DestroyOverlayedImageParams = { guid: Guid } +export type DestroySubcontainerFsParams = { guid: Guid } diff --git a/sdk/base/lib/osBindings/DeviceFilter.ts b/sdk/base/lib/osBindings/DeviceFilter.ts new file mode 100644 index 000000000..6e6f5810c --- /dev/null +++ b/sdk/base/lib/osBindings/DeviceFilter.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DeviceFilter = { + class: "processor" | "display" + pattern: string + patternDescription: string +} diff --git a/sdk/lib/osBindings/Duration.ts b/sdk/base/lib/osBindings/Duration.ts similarity index 100% rename from sdk/lib/osBindings/Duration.ts rename to sdk/base/lib/osBindings/Duration.ts diff --git a/sdk/lib/osBindings/EchoParams.ts b/sdk/base/lib/osBindings/EchoParams.ts similarity index 100% rename from sdk/lib/osBindings/EchoParams.ts rename to sdk/base/lib/osBindings/EchoParams.ts diff --git a/sdk/base/lib/osBindings/EditSignerParams.ts b/sdk/base/lib/osBindings/EditSignerParams.ts new file mode 100644 index 000000000..9532709a6 --- /dev/null +++ b/sdk/base/lib/osBindings/EditSignerParams.ts @@ -0,0 +1,13 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AnyVerifyingKey } from "./AnyVerifyingKey" +import type { ContactInfo } from "./ContactInfo" +import type { Guid } from "./Guid" + +export type EditSignerParams = { + id: Guid + setName: string | null + addContact: Array + addKey: Array + removeContact: Array + removeKey: Array +} diff --git a/sdk/lib/osBindings/EncryptedWire.ts b/sdk/base/lib/osBindings/EncryptedWire.ts similarity index 100% rename from sdk/lib/osBindings/EncryptedWire.ts rename to sdk/base/lib/osBindings/EncryptedWire.ts diff --git a/sdk/lib/osBindings/ExportActionParams.ts b/sdk/base/lib/osBindings/ExportActionParams.ts similarity index 58% rename from sdk/lib/osBindings/ExportActionParams.ts rename to sdk/base/lib/osBindings/ExportActionParams.ts index 8bcfbc349..d4aa33102 100644 --- a/sdk/lib/osBindings/ExportActionParams.ts +++ b/sdk/base/lib/osBindings/ExportActionParams.ts @@ -1,10 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ActionId } from "./ActionId" import type { ActionMetadata } from "./ActionMetadata" -import type { PackageId } from "./PackageId" -export type ExportActionParams = { - packageId?: PackageId - id: ActionId - metadata: ActionMetadata -} +export type ExportActionParams = { id: ActionId; metadata: ActionMetadata } diff --git a/sdk/lib/osBindings/ExportServiceInterfaceParams.ts b/sdk/base/lib/osBindings/ExportServiceInterfaceParams.ts similarity index 100% rename from sdk/lib/osBindings/ExportServiceInterfaceParams.ts rename to sdk/base/lib/osBindings/ExportServiceInterfaceParams.ts diff --git a/sdk/lib/osBindings/ExposeForDependentsParams.ts b/sdk/base/lib/osBindings/ExposeForDependentsParams.ts similarity index 100% rename from sdk/lib/osBindings/ExposeForDependentsParams.ts rename to sdk/base/lib/osBindings/ExposeForDependentsParams.ts diff --git a/sdk/lib/osBindings/FullIndex.ts b/sdk/base/lib/osBindings/FullIndex.ts similarity index 100% rename from sdk/lib/osBindings/FullIndex.ts rename to sdk/base/lib/osBindings/FullIndex.ts diff --git a/sdk/lib/osBindings/FullProgress.ts b/sdk/base/lib/osBindings/FullProgress.ts similarity index 100% rename from sdk/lib/osBindings/FullProgress.ts rename to sdk/base/lib/osBindings/FullProgress.ts diff --git a/sdk/base/lib/osBindings/GetActionInputParams.ts b/sdk/base/lib/osBindings/GetActionInputParams.ts new file mode 100644 index 000000000..568ceb907 --- /dev/null +++ b/sdk/base/lib/osBindings/GetActionInputParams.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionId } from "./ActionId" +import type { PackageId } from "./PackageId" + +export type GetActionInputParams = { packageId?: PackageId; actionId: ActionId } diff --git a/sdk/lib/osBindings/GetHostInfoParams.ts b/sdk/base/lib/osBindings/GetHostInfoParams.ts similarity index 100% rename from sdk/lib/osBindings/GetHostInfoParams.ts rename to sdk/base/lib/osBindings/GetHostInfoParams.ts diff --git a/sdk/lib/osBindings/GetOsAssetParams.ts b/sdk/base/lib/osBindings/GetOsAssetParams.ts similarity index 100% rename from sdk/lib/osBindings/GetOsAssetParams.ts rename to sdk/base/lib/osBindings/GetOsAssetParams.ts diff --git a/sdk/lib/osBindings/GetOsVersionParams.ts b/sdk/base/lib/osBindings/GetOsVersionParams.ts similarity index 100% rename from sdk/lib/osBindings/GetOsVersionParams.ts rename to sdk/base/lib/osBindings/GetOsVersionParams.ts diff --git a/sdk/lib/osBindings/GetPackageParams.ts b/sdk/base/lib/osBindings/GetPackageParams.ts similarity index 100% rename from sdk/lib/osBindings/GetPackageParams.ts rename to sdk/base/lib/osBindings/GetPackageParams.ts diff --git a/sdk/lib/osBindings/GetPackageResponse.ts b/sdk/base/lib/osBindings/GetPackageResponse.ts similarity index 100% rename from sdk/lib/osBindings/GetPackageResponse.ts rename to sdk/base/lib/osBindings/GetPackageResponse.ts diff --git a/sdk/lib/osBindings/GetPackageResponseFull.ts b/sdk/base/lib/osBindings/GetPackageResponseFull.ts similarity index 100% rename from sdk/lib/osBindings/GetPackageResponseFull.ts rename to sdk/base/lib/osBindings/GetPackageResponseFull.ts diff --git a/sdk/lib/osBindings/GetPrimaryUrlParams.ts b/sdk/base/lib/osBindings/GetPrimaryUrlParams.ts similarity index 100% rename from sdk/lib/osBindings/GetPrimaryUrlParams.ts rename to sdk/base/lib/osBindings/GetPrimaryUrlParams.ts diff --git a/sdk/lib/osBindings/GetServiceInterfaceParams.ts b/sdk/base/lib/osBindings/GetServiceInterfaceParams.ts similarity index 100% rename from sdk/lib/osBindings/GetServiceInterfaceParams.ts rename to sdk/base/lib/osBindings/GetServiceInterfaceParams.ts diff --git a/sdk/lib/osBindings/GetServicePortForwardParams.ts b/sdk/base/lib/osBindings/GetServicePortForwardParams.ts similarity index 100% rename from sdk/lib/osBindings/GetServicePortForwardParams.ts rename to sdk/base/lib/osBindings/GetServicePortForwardParams.ts diff --git a/sdk/lib/osBindings/GetSslCertificateParams.ts b/sdk/base/lib/osBindings/GetSslCertificateParams.ts similarity index 100% rename from sdk/lib/osBindings/GetSslCertificateParams.ts rename to sdk/base/lib/osBindings/GetSslCertificateParams.ts diff --git a/sdk/lib/osBindings/GetSslKeyParams.ts b/sdk/base/lib/osBindings/GetSslKeyParams.ts similarity index 100% rename from sdk/lib/osBindings/GetSslKeyParams.ts rename to sdk/base/lib/osBindings/GetSslKeyParams.ts diff --git a/sdk/base/lib/osBindings/GetStatusParams.ts b/sdk/base/lib/osBindings/GetStatusParams.ts new file mode 100644 index 000000000..a0fbe3bfc --- /dev/null +++ b/sdk/base/lib/osBindings/GetStatusParams.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { CallbackId } from "./CallbackId" +import type { PackageId } from "./PackageId" + +export type GetStatusParams = { packageId?: PackageId; callback?: CallbackId } diff --git a/sdk/lib/osBindings/GetStoreParams.ts b/sdk/base/lib/osBindings/GetStoreParams.ts similarity index 100% rename from sdk/lib/osBindings/GetStoreParams.ts rename to sdk/base/lib/osBindings/GetStoreParams.ts diff --git a/sdk/lib/osBindings/GetSystemSmtpParams.ts b/sdk/base/lib/osBindings/GetSystemSmtpParams.ts similarity index 100% rename from sdk/lib/osBindings/GetSystemSmtpParams.ts rename to sdk/base/lib/osBindings/GetSystemSmtpParams.ts diff --git a/sdk/lib/osBindings/Governor.ts b/sdk/base/lib/osBindings/Governor.ts similarity index 100% rename from sdk/lib/osBindings/Governor.ts rename to sdk/base/lib/osBindings/Governor.ts diff --git a/sdk/lib/osBindings/Guid.ts b/sdk/base/lib/osBindings/Guid.ts similarity index 100% rename from sdk/lib/osBindings/Guid.ts rename to sdk/base/lib/osBindings/Guid.ts diff --git a/sdk/lib/osBindings/HardwareRequirements.ts b/sdk/base/lib/osBindings/HardwareRequirements.ts similarity index 70% rename from sdk/lib/osBindings/HardwareRequirements.ts rename to sdk/base/lib/osBindings/HardwareRequirements.ts index e17568eec..d420f846b 100644 --- a/sdk/lib/osBindings/HardwareRequirements.ts +++ b/sdk/base/lib/osBindings/HardwareRequirements.ts @@ -1,7 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DeviceFilter } from "./DeviceFilter" export type HardwareRequirements = { - device: { display?: string; processor?: string } + device: Array ram: number | null arch: string[] | null } diff --git a/sdk/lib/osBindings/HealthCheckId.ts b/sdk/base/lib/osBindings/HealthCheckId.ts similarity index 100% rename from sdk/lib/osBindings/HealthCheckId.ts rename to sdk/base/lib/osBindings/HealthCheckId.ts diff --git a/sdk/lib/osBindings/Host.ts b/sdk/base/lib/osBindings/Host.ts similarity index 100% rename from sdk/lib/osBindings/Host.ts rename to sdk/base/lib/osBindings/Host.ts diff --git a/sdk/lib/osBindings/HostAddress.ts b/sdk/base/lib/osBindings/HostAddress.ts similarity index 100% rename from sdk/lib/osBindings/HostAddress.ts rename to sdk/base/lib/osBindings/HostAddress.ts diff --git a/sdk/lib/osBindings/HostId.ts b/sdk/base/lib/osBindings/HostId.ts similarity index 100% rename from sdk/lib/osBindings/HostId.ts rename to sdk/base/lib/osBindings/HostId.ts diff --git a/sdk/lib/osBindings/HostKind.ts b/sdk/base/lib/osBindings/HostKind.ts similarity index 100% rename from sdk/lib/osBindings/HostKind.ts rename to sdk/base/lib/osBindings/HostKind.ts diff --git a/sdk/lib/osBindings/HostnameInfo.ts b/sdk/base/lib/osBindings/HostnameInfo.ts similarity index 100% rename from sdk/lib/osBindings/HostnameInfo.ts rename to sdk/base/lib/osBindings/HostnameInfo.ts diff --git a/sdk/lib/osBindings/Hosts.ts b/sdk/base/lib/osBindings/Hosts.ts similarity index 100% rename from sdk/lib/osBindings/Hosts.ts rename to sdk/base/lib/osBindings/Hosts.ts diff --git a/sdk/lib/osBindings/ImageConfig.ts b/sdk/base/lib/osBindings/ImageConfig.ts similarity index 100% rename from sdk/lib/osBindings/ImageConfig.ts rename to sdk/base/lib/osBindings/ImageConfig.ts diff --git a/sdk/lib/osBindings/ImageId.ts b/sdk/base/lib/osBindings/ImageId.ts similarity index 100% rename from sdk/lib/osBindings/ImageId.ts rename to sdk/base/lib/osBindings/ImageId.ts diff --git a/sdk/lib/osBindings/ImageMetadata.ts b/sdk/base/lib/osBindings/ImageMetadata.ts similarity index 100% rename from sdk/lib/osBindings/ImageMetadata.ts rename to sdk/base/lib/osBindings/ImageMetadata.ts diff --git a/sdk/base/lib/osBindings/ImageSource.ts b/sdk/base/lib/osBindings/ImageSource.ts new file mode 100644 index 000000000..58d2c2a79 --- /dev/null +++ b/sdk/base/lib/osBindings/ImageSource.ts @@ -0,0 +1,13 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { BuildArg } from "./BuildArg" + +export type ImageSource = + | "packed" + | { + dockerBuild: { + workdir: string | null + dockerfile: string | null + buildArgs?: { [key: string]: BuildArg } + } + } + | { dockerTag: string } diff --git a/sdk/lib/osBindings/InitProgressRes.ts b/sdk/base/lib/osBindings/InitProgressRes.ts similarity index 100% rename from sdk/lib/osBindings/InitProgressRes.ts rename to sdk/base/lib/osBindings/InitProgressRes.ts diff --git a/sdk/lib/osBindings/InstallParams.ts b/sdk/base/lib/osBindings/InstallParams.ts similarity index 100% rename from sdk/lib/osBindings/InstallParams.ts rename to sdk/base/lib/osBindings/InstallParams.ts diff --git a/sdk/lib/osBindings/InstalledState.ts b/sdk/base/lib/osBindings/InstalledState.ts similarity index 100% rename from sdk/lib/osBindings/InstalledState.ts rename to sdk/base/lib/osBindings/InstalledState.ts diff --git a/sdk/lib/osBindings/InstalledVersionParams.ts b/sdk/base/lib/osBindings/InstalledVersionParams.ts similarity index 100% rename from sdk/lib/osBindings/InstalledVersionParams.ts rename to sdk/base/lib/osBindings/InstalledVersionParams.ts diff --git a/sdk/lib/osBindings/InstallingInfo.ts b/sdk/base/lib/osBindings/InstallingInfo.ts similarity index 100% rename from sdk/lib/osBindings/InstallingInfo.ts rename to sdk/base/lib/osBindings/InstallingInfo.ts diff --git a/sdk/lib/osBindings/InstallingState.ts b/sdk/base/lib/osBindings/InstallingState.ts similarity index 100% rename from sdk/lib/osBindings/InstallingState.ts rename to sdk/base/lib/osBindings/InstallingState.ts diff --git a/sdk/lib/osBindings/IpHostname.ts b/sdk/base/lib/osBindings/IpHostname.ts similarity index 100% rename from sdk/lib/osBindings/IpHostname.ts rename to sdk/base/lib/osBindings/IpHostname.ts diff --git a/sdk/lib/osBindings/IpInfo.ts b/sdk/base/lib/osBindings/IpInfo.ts similarity index 100% rename from sdk/lib/osBindings/IpInfo.ts rename to sdk/base/lib/osBindings/IpInfo.ts diff --git a/sdk/lib/osBindings/LanInfo.ts b/sdk/base/lib/osBindings/LanInfo.ts similarity index 100% rename from sdk/lib/osBindings/LanInfo.ts rename to sdk/base/lib/osBindings/LanInfo.ts diff --git a/sdk/lib/osBindings/GetConfiguredParams.ts b/sdk/base/lib/osBindings/ListPackageSignersParams.ts similarity index 72% rename from sdk/lib/osBindings/GetConfiguredParams.ts rename to sdk/base/lib/osBindings/ListPackageSignersParams.ts index 66fb6e320..73cd6a745 100644 --- a/sdk/lib/osBindings/GetConfiguredParams.ts +++ b/sdk/base/lib/osBindings/ListPackageSignersParams.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { PackageId } from "./PackageId" -export type GetConfiguredParams = { packageId?: PackageId } +export type ListPackageSignersParams = { id: PackageId } diff --git a/sdk/lib/osBindings/ListServiceInterfacesParams.ts b/sdk/base/lib/osBindings/ListServiceInterfacesParams.ts similarity index 100% rename from sdk/lib/osBindings/ListServiceInterfacesParams.ts rename to sdk/base/lib/osBindings/ListServiceInterfacesParams.ts diff --git a/sdk/lib/osBindings/ListVersionSignersParams.ts b/sdk/base/lib/osBindings/ListVersionSignersParams.ts similarity index 100% rename from sdk/lib/osBindings/ListVersionSignersParams.ts rename to sdk/base/lib/osBindings/ListVersionSignersParams.ts diff --git a/sdk/lib/osBindings/LoginParams.ts b/sdk/base/lib/osBindings/LoginParams.ts similarity index 100% rename from sdk/lib/osBindings/LoginParams.ts rename to sdk/base/lib/osBindings/LoginParams.ts diff --git a/sdk/base/lib/osBindings/LshwDevice.ts b/sdk/base/lib/osBindings/LshwDevice.ts new file mode 100644 index 000000000..f2c624be7 --- /dev/null +++ b/sdk/base/lib/osBindings/LshwDevice.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { LshwDisplay } from "./LshwDisplay" +import type { LshwProcessor } from "./LshwProcessor" + +export type LshwDevice = + | ({ class: "processor" } & LshwProcessor) + | ({ class: "display" } & LshwDisplay) diff --git a/sdk/base/lib/osBindings/LshwDisplay.ts b/sdk/base/lib/osBindings/LshwDisplay.ts new file mode 100644 index 000000000..25d14b12f --- /dev/null +++ b/sdk/base/lib/osBindings/LshwDisplay.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LshwDisplay = { product: string } diff --git a/sdk/base/lib/osBindings/LshwProcessor.ts b/sdk/base/lib/osBindings/LshwProcessor.ts new file mode 100644 index 000000000..17a47d477 --- /dev/null +++ b/sdk/base/lib/osBindings/LshwProcessor.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type LshwProcessor = { product: string } diff --git a/sdk/lib/osBindings/MainStatus.ts b/sdk/base/lib/osBindings/MainStatus.ts similarity index 55% rename from sdk/lib/osBindings/MainStatus.ts rename to sdk/base/lib/osBindings/MainStatus.ts index a528aa187..64e081ab9 100644 --- a/sdk/lib/osBindings/MainStatus.ts +++ b/sdk/base/lib/osBindings/MainStatus.ts @@ -1,20 +1,26 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { HealthCheckId } from "./HealthCheckId" import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" +import type { StartStop } from "./StartStop" export type MainStatus = - | { status: "stopped" } - | { status: "restarting" } - | { status: "restoring" } - | { status: "stopping" } - | { status: "starting" } | { - status: "running" - started: string + main: "error" + onRebuild: StartStop + message: string + debug: string | null + } + | { main: "stopped" } + | { main: "restarting" } + | { main: "restoring" } + | { main: "stopping" } + | { + main: "starting" health: { [key: HealthCheckId]: NamedHealthCheckResult } } | { - status: "backingUp" - started: string | null + main: "running" + started: string health: { [key: HealthCheckId]: NamedHealthCheckResult } } + | { main: "backingUp"; onComplete: StartStop } diff --git a/sdk/lib/osBindings/Manifest.ts b/sdk/base/lib/osBindings/Manifest.ts similarity index 98% rename from sdk/lib/osBindings/Manifest.ts rename to sdk/base/lib/osBindings/Manifest.ts index d40223236..8007b565b 100644 --- a/sdk/lib/osBindings/Manifest.ts +++ b/sdk/base/lib/osBindings/Manifest.ts @@ -32,5 +32,4 @@ export type Manifest = { hardwareRequirements: HardwareRequirements gitHash: string | null osVersion: string - hasConfig: boolean } diff --git a/sdk/lib/osBindings/MaybeUtf8String.ts b/sdk/base/lib/osBindings/MaybeUtf8String.ts similarity index 100% rename from sdk/lib/osBindings/MaybeUtf8String.ts rename to sdk/base/lib/osBindings/MaybeUtf8String.ts diff --git a/sdk/lib/osBindings/MerkleArchiveCommitment.ts b/sdk/base/lib/osBindings/MerkleArchiveCommitment.ts similarity index 100% rename from sdk/lib/osBindings/MerkleArchiveCommitment.ts rename to sdk/base/lib/osBindings/MerkleArchiveCommitment.ts diff --git a/sdk/lib/osBindings/MountParams.ts b/sdk/base/lib/osBindings/MountParams.ts similarity index 100% rename from sdk/lib/osBindings/MountParams.ts rename to sdk/base/lib/osBindings/MountParams.ts diff --git a/sdk/lib/osBindings/MountTarget.ts b/sdk/base/lib/osBindings/MountTarget.ts similarity index 100% rename from sdk/lib/osBindings/MountTarget.ts rename to sdk/base/lib/osBindings/MountTarget.ts diff --git a/sdk/lib/osBindings/NamedHealthCheckResult.ts b/sdk/base/lib/osBindings/NamedHealthCheckResult.ts similarity index 100% rename from sdk/lib/osBindings/NamedHealthCheckResult.ts rename to sdk/base/lib/osBindings/NamedHealthCheckResult.ts diff --git a/sdk/lib/osBindings/NamedProgress.ts b/sdk/base/lib/osBindings/NamedProgress.ts similarity index 100% rename from sdk/lib/osBindings/NamedProgress.ts rename to sdk/base/lib/osBindings/NamedProgress.ts diff --git a/sdk/lib/osBindings/OnionHostname.ts b/sdk/base/lib/osBindings/OnionHostname.ts similarity index 100% rename from sdk/lib/osBindings/OnionHostname.ts rename to sdk/base/lib/osBindings/OnionHostname.ts diff --git a/sdk/lib/osBindings/OsIndex.ts b/sdk/base/lib/osBindings/OsIndex.ts similarity index 100% rename from sdk/lib/osBindings/OsIndex.ts rename to sdk/base/lib/osBindings/OsIndex.ts diff --git a/sdk/lib/osBindings/OsVersionInfo.ts b/sdk/base/lib/osBindings/OsVersionInfo.ts similarity index 100% rename from sdk/lib/osBindings/OsVersionInfo.ts rename to sdk/base/lib/osBindings/OsVersionInfo.ts diff --git a/sdk/lib/osBindings/OsVersionInfoMap.ts b/sdk/base/lib/osBindings/OsVersionInfoMap.ts similarity index 100% rename from sdk/lib/osBindings/OsVersionInfoMap.ts rename to sdk/base/lib/osBindings/OsVersionInfoMap.ts diff --git a/sdk/lib/osBindings/PackageDataEntry.ts b/sdk/base/lib/osBindings/PackageDataEntry.ts similarity index 83% rename from sdk/lib/osBindings/PackageDataEntry.ts rename to sdk/base/lib/osBindings/PackageDataEntry.ts index 41bd98bba..86df7b767 100644 --- a/sdk/lib/osBindings/PackageDataEntry.ts +++ b/sdk/base/lib/osBindings/PackageDataEntry.ts @@ -1,25 +1,27 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ActionId } from "./ActionId" import type { ActionMetadata } from "./ActionMetadata" +import type { ActionRequestEntry } from "./ActionRequestEntry" import type { CurrentDependencies } from "./CurrentDependencies" import type { DataUrl } from "./DataUrl" import type { Hosts } from "./Hosts" +import type { MainStatus } from "./MainStatus" import type { PackageState } from "./PackageState" import type { ServiceInterface } from "./ServiceInterface" import type { ServiceInterfaceId } from "./ServiceInterfaceId" -import type { Status } from "./Status" import type { Version } from "./Version" export type PackageDataEntry = { stateInfo: PackageState dataVersion: Version | null - status: Status + status: MainStatus registry: string | null developerKey: string icon: DataUrl lastBackup: string | null currentDependencies: CurrentDependencies actions: { [key: ActionId]: ActionMetadata } + requestedActions: { [key: string]: ActionRequestEntry } serviceInterfaces: { [key: ServiceInterfaceId]: ServiceInterface } hosts: Hosts storeExposedDependents: string[] diff --git a/sdk/lib/osBindings/PackageDetailLevel.ts b/sdk/base/lib/osBindings/PackageDetailLevel.ts similarity index 100% rename from sdk/lib/osBindings/PackageDetailLevel.ts rename to sdk/base/lib/osBindings/PackageDetailLevel.ts diff --git a/sdk/lib/osBindings/PackageId.ts b/sdk/base/lib/osBindings/PackageId.ts similarity index 100% rename from sdk/lib/osBindings/PackageId.ts rename to sdk/base/lib/osBindings/PackageId.ts diff --git a/sdk/lib/osBindings/PackageIndex.ts b/sdk/base/lib/osBindings/PackageIndex.ts similarity index 100% rename from sdk/lib/osBindings/PackageIndex.ts rename to sdk/base/lib/osBindings/PackageIndex.ts diff --git a/sdk/lib/osBindings/PackageInfo.ts b/sdk/base/lib/osBindings/PackageInfo.ts similarity index 100% rename from sdk/lib/osBindings/PackageInfo.ts rename to sdk/base/lib/osBindings/PackageInfo.ts diff --git a/sdk/lib/osBindings/PackageInfoShort.ts b/sdk/base/lib/osBindings/PackageInfoShort.ts similarity index 100% rename from sdk/lib/osBindings/PackageInfoShort.ts rename to sdk/base/lib/osBindings/PackageInfoShort.ts diff --git a/sdk/base/lib/osBindings/PackageSignerParams.ts b/sdk/base/lib/osBindings/PackageSignerParams.ts new file mode 100644 index 000000000..b131e3e23 --- /dev/null +++ b/sdk/base/lib/osBindings/PackageSignerParams.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Guid } from "./Guid" +import type { PackageId } from "./PackageId" + +export type PackageSignerParams = { id: PackageId; signer: Guid } diff --git a/sdk/lib/osBindings/PackageState.ts b/sdk/base/lib/osBindings/PackageState.ts similarity index 100% rename from sdk/lib/osBindings/PackageState.ts rename to sdk/base/lib/osBindings/PackageState.ts diff --git a/sdk/lib/osBindings/PackageVersionInfo.ts b/sdk/base/lib/osBindings/PackageVersionInfo.ts similarity index 100% rename from sdk/lib/osBindings/PackageVersionInfo.ts rename to sdk/base/lib/osBindings/PackageVersionInfo.ts diff --git a/sdk/lib/osBindings/PasswordType.ts b/sdk/base/lib/osBindings/PasswordType.ts similarity index 100% rename from sdk/lib/osBindings/PasswordType.ts rename to sdk/base/lib/osBindings/PasswordType.ts diff --git a/sdk/lib/osBindings/PathOrUrl.ts b/sdk/base/lib/osBindings/PathOrUrl.ts similarity index 100% rename from sdk/lib/osBindings/PathOrUrl.ts rename to sdk/base/lib/osBindings/PathOrUrl.ts diff --git a/sdk/lib/osBindings/ProcedureId.ts b/sdk/base/lib/osBindings/ProcedureId.ts similarity index 100% rename from sdk/lib/osBindings/ProcedureId.ts rename to sdk/base/lib/osBindings/ProcedureId.ts diff --git a/sdk/lib/osBindings/Progress.ts b/sdk/base/lib/osBindings/Progress.ts similarity index 100% rename from sdk/lib/osBindings/Progress.ts rename to sdk/base/lib/osBindings/Progress.ts diff --git a/sdk/lib/osBindings/Public.ts b/sdk/base/lib/osBindings/Public.ts similarity index 100% rename from sdk/lib/osBindings/Public.ts rename to sdk/base/lib/osBindings/Public.ts diff --git a/sdk/lib/osBindings/RecoverySource.ts b/sdk/base/lib/osBindings/RecoverySource.ts similarity index 100% rename from sdk/lib/osBindings/RecoverySource.ts rename to sdk/base/lib/osBindings/RecoverySource.ts diff --git a/sdk/lib/osBindings/RegistryAsset.ts b/sdk/base/lib/osBindings/RegistryAsset.ts similarity index 100% rename from sdk/lib/osBindings/RegistryAsset.ts rename to sdk/base/lib/osBindings/RegistryAsset.ts diff --git a/sdk/lib/osBindings/RegistryInfo.ts b/sdk/base/lib/osBindings/RegistryInfo.ts similarity index 100% rename from sdk/lib/osBindings/RegistryInfo.ts rename to sdk/base/lib/osBindings/RegistryInfo.ts diff --git a/sdk/base/lib/osBindings/RemoveCategoryParams.ts b/sdk/base/lib/osBindings/RemoveCategoryParams.ts new file mode 100644 index 000000000..4f468fee7 --- /dev/null +++ b/sdk/base/lib/osBindings/RemoveCategoryParams.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoveCategoryParams = { id: string } diff --git a/sdk/lib/osBindings/RemoveVersionParams.ts b/sdk/base/lib/osBindings/RemoveVersionParams.ts similarity index 100% rename from sdk/lib/osBindings/RemoveVersionParams.ts rename to sdk/base/lib/osBindings/RemoveVersionParams.ts diff --git a/sdk/base/lib/osBindings/ReplayId.ts b/sdk/base/lib/osBindings/ReplayId.ts new file mode 100644 index 000000000..048ef183a --- /dev/null +++ b/sdk/base/lib/osBindings/ReplayId.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ReplayId = string diff --git a/sdk/base/lib/osBindings/RequestActionParams.ts b/sdk/base/lib/osBindings/RequestActionParams.ts new file mode 100644 index 000000000..ccc8d0e61 --- /dev/null +++ b/sdk/base/lib/osBindings/RequestActionParams.ts @@ -0,0 +1,17 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionId } from "./ActionId" +import type { ActionRequestInput } from "./ActionRequestInput" +import type { ActionRequestTrigger } from "./ActionRequestTrigger" +import type { ActionSeverity } from "./ActionSeverity" +import type { PackageId } from "./PackageId" +import type { ReplayId } from "./ReplayId" + +export type RequestActionParams = { + replayId: ReplayId + packageId: PackageId + actionId: ActionId + severity: ActionSeverity + reason?: string + when?: ActionRequestTrigger + input?: ActionRequestInput +} diff --git a/sdk/lib/osBindings/RequestCommitment.ts b/sdk/base/lib/osBindings/RequestCommitment.ts similarity index 100% rename from sdk/lib/osBindings/RequestCommitment.ts rename to sdk/base/lib/osBindings/RequestCommitment.ts diff --git a/sdk/lib/osBindings/ExecuteAction.ts b/sdk/base/lib/osBindings/RunActionParams.ts similarity index 88% rename from sdk/lib/osBindings/ExecuteAction.ts rename to sdk/base/lib/osBindings/RunActionParams.ts index 6e3c44f79..33864d1e6 100644 --- a/sdk/lib/osBindings/ExecuteAction.ts +++ b/sdk/base/lib/osBindings/RunActionParams.ts @@ -2,7 +2,7 @@ import type { ActionId } from "./ActionId" import type { PackageId } from "./PackageId" -export type ExecuteAction = { +export type RunActionParams = { packageId?: PackageId actionId: ActionId input: any diff --git a/sdk/lib/osBindings/Security.ts b/sdk/base/lib/osBindings/Security.ts similarity index 100% rename from sdk/lib/osBindings/Security.ts rename to sdk/base/lib/osBindings/Security.ts diff --git a/sdk/lib/osBindings/ServerInfo.ts b/sdk/base/lib/osBindings/ServerInfo.ts similarity index 78% rename from sdk/lib/osBindings/ServerInfo.ts rename to sdk/base/lib/osBindings/ServerInfo.ts index 76840cfc4..89d7fc1b0 100644 --- a/sdk/lib/osBindings/ServerInfo.ts +++ b/sdk/base/lib/osBindings/ServerInfo.ts @@ -1,6 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AcmeSettings } from "./AcmeSettings" import type { Governor } from "./Governor" import type { IpInfo } from "./IpInfo" +import type { LshwDevice } from "./LshwDevice" import type { ServerStatus } from "./ServerStatus" import type { SmtpValue } from "./SmtpValue" import type { WifiInfo } from "./WifiInfo" @@ -11,8 +13,9 @@ export type ServerInfo = { id: string hostname: string version: string + packageVersionCompat: string + postInitMigrationTodos: string[] lastBackup: string | null - eosVersionCompat: string lanAddress: string onionAddress: string /** @@ -20,6 +23,7 @@ export type ServerInfo = { */ torAddress: string ipInfo: { [key: string]: IpInfo } + acme: AcmeSettings | null statusInfo: ServerStatus wifi: WifiInfo unreadNotificationCount: number @@ -30,4 +34,6 @@ export type ServerInfo = { zram: boolean governor: Governor | null smtp: SmtpValue | null + ram: number + devices: Array } diff --git a/sdk/lib/osBindings/ServerSpecs.ts b/sdk/base/lib/osBindings/ServerSpecs.ts similarity index 100% rename from sdk/lib/osBindings/ServerSpecs.ts rename to sdk/base/lib/osBindings/ServerSpecs.ts diff --git a/sdk/lib/osBindings/ServerStatus.ts b/sdk/base/lib/osBindings/ServerStatus.ts similarity index 100% rename from sdk/lib/osBindings/ServerStatus.ts rename to sdk/base/lib/osBindings/ServerStatus.ts diff --git a/sdk/lib/osBindings/ServiceInterface.ts b/sdk/base/lib/osBindings/ServiceInterface.ts similarity index 100% rename from sdk/lib/osBindings/ServiceInterface.ts rename to sdk/base/lib/osBindings/ServiceInterface.ts diff --git a/sdk/lib/osBindings/ServiceInterfaceId.ts b/sdk/base/lib/osBindings/ServiceInterfaceId.ts similarity index 100% rename from sdk/lib/osBindings/ServiceInterfaceId.ts rename to sdk/base/lib/osBindings/ServiceInterfaceId.ts diff --git a/sdk/lib/osBindings/ServiceInterfaceType.ts b/sdk/base/lib/osBindings/ServiceInterfaceType.ts similarity index 100% rename from sdk/lib/osBindings/ServiceInterfaceType.ts rename to sdk/base/lib/osBindings/ServiceInterfaceType.ts diff --git a/sdk/lib/osBindings/Session.ts b/sdk/base/lib/osBindings/Session.ts similarity index 100% rename from sdk/lib/osBindings/Session.ts rename to sdk/base/lib/osBindings/Session.ts diff --git a/sdk/lib/osBindings/SessionList.ts b/sdk/base/lib/osBindings/SessionList.ts similarity index 67% rename from sdk/lib/osBindings/SessionList.ts rename to sdk/base/lib/osBindings/SessionList.ts index a85a42dee..af36aaa8a 100644 --- a/sdk/lib/osBindings/SessionList.ts +++ b/sdk/base/lib/osBindings/SessionList.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Sessions } from "./Sessions" -export type SessionList = { current: string; sessions: Sessions } +export type SessionList = { current: string | null; sessions: Sessions } diff --git a/sdk/lib/osBindings/Sessions.ts b/sdk/base/lib/osBindings/Sessions.ts similarity index 100% rename from sdk/lib/osBindings/Sessions.ts rename to sdk/base/lib/osBindings/Sessions.ts diff --git a/sdk/lib/osBindings/SetDataVersionParams.ts b/sdk/base/lib/osBindings/SetDataVersionParams.ts similarity index 100% rename from sdk/lib/osBindings/SetDataVersionParams.ts rename to sdk/base/lib/osBindings/SetDataVersionParams.ts diff --git a/sdk/lib/osBindings/SetDependenciesParams.ts b/sdk/base/lib/osBindings/SetDependenciesParams.ts similarity index 82% rename from sdk/lib/osBindings/SetDependenciesParams.ts rename to sdk/base/lib/osBindings/SetDependenciesParams.ts index bbc9b325f..7b34b50c9 100644 --- a/sdk/lib/osBindings/SetDependenciesParams.ts +++ b/sdk/base/lib/osBindings/SetDependenciesParams.ts @@ -1,8 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DependencyRequirement } from "./DependencyRequirement" -import type { Guid } from "./Guid" export type SetDependenciesParams = { - procedureId: Guid dependencies: Array } diff --git a/sdk/lib/osBindings/SetHealth.ts b/sdk/base/lib/osBindings/SetHealth.ts similarity index 100% rename from sdk/lib/osBindings/SetHealth.ts rename to sdk/base/lib/osBindings/SetHealth.ts diff --git a/sdk/base/lib/osBindings/SetIconParams.ts b/sdk/base/lib/osBindings/SetIconParams.ts new file mode 100644 index 000000000..f10eaa50b --- /dev/null +++ b/sdk/base/lib/osBindings/SetIconParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DataUrl } from "./DataUrl" + +export type SetIconParams = { icon: DataUrl } diff --git a/sdk/lib/osBindings/SetMainStatus.ts b/sdk/base/lib/osBindings/SetMainStatus.ts similarity index 100% rename from sdk/lib/osBindings/SetMainStatus.ts rename to sdk/base/lib/osBindings/SetMainStatus.ts diff --git a/sdk/lib/osBindings/SetMainStatusStatus.ts b/sdk/base/lib/osBindings/SetMainStatusStatus.ts similarity index 100% rename from sdk/lib/osBindings/SetMainStatusStatus.ts rename to sdk/base/lib/osBindings/SetMainStatusStatus.ts diff --git a/sdk/base/lib/osBindings/SetNameParams.ts b/sdk/base/lib/osBindings/SetNameParams.ts new file mode 100644 index 000000000..df49d7be5 --- /dev/null +++ b/sdk/base/lib/osBindings/SetNameParams.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SetNameParams = { name: string } diff --git a/sdk/lib/osBindings/SetStoreParams.ts b/sdk/base/lib/osBindings/SetStoreParams.ts similarity index 100% rename from sdk/lib/osBindings/SetStoreParams.ts rename to sdk/base/lib/osBindings/SetStoreParams.ts diff --git a/sdk/lib/osBindings/SetupExecuteParams.ts b/sdk/base/lib/osBindings/SetupExecuteParams.ts similarity index 100% rename from sdk/lib/osBindings/SetupExecuteParams.ts rename to sdk/base/lib/osBindings/SetupExecuteParams.ts diff --git a/sdk/lib/osBindings/SetupProgress.ts b/sdk/base/lib/osBindings/SetupProgress.ts similarity index 100% rename from sdk/lib/osBindings/SetupProgress.ts rename to sdk/base/lib/osBindings/SetupProgress.ts diff --git a/sdk/lib/osBindings/SetupResult.ts b/sdk/base/lib/osBindings/SetupResult.ts similarity index 91% rename from sdk/lib/osBindings/SetupResult.ts rename to sdk/base/lib/osBindings/SetupResult.ts index 464aeb4b7..d81c7f039 100644 --- a/sdk/lib/osBindings/SetupResult.ts +++ b/sdk/base/lib/osBindings/SetupResult.ts @@ -2,6 +2,7 @@ export type SetupResult = { torAddress: string + hostname: string lanAddress: string rootCa: string } diff --git a/sdk/lib/osBindings/SetupStatusRes.ts b/sdk/base/lib/osBindings/SetupStatusRes.ts similarity index 100% rename from sdk/lib/osBindings/SetupStatusRes.ts rename to sdk/base/lib/osBindings/SetupStatusRes.ts diff --git a/sdk/lib/osBindings/SignAssetParams.ts b/sdk/base/lib/osBindings/SignAssetParams.ts similarity index 100% rename from sdk/lib/osBindings/SignAssetParams.ts rename to sdk/base/lib/osBindings/SignAssetParams.ts diff --git a/sdk/lib/osBindings/SignerInfo.ts b/sdk/base/lib/osBindings/SignerInfo.ts similarity index 100% rename from sdk/lib/osBindings/SignerInfo.ts rename to sdk/base/lib/osBindings/SignerInfo.ts diff --git a/sdk/lib/osBindings/SmtpValue.ts b/sdk/base/lib/osBindings/SmtpValue.ts similarity index 100% rename from sdk/lib/osBindings/SmtpValue.ts rename to sdk/base/lib/osBindings/SmtpValue.ts diff --git a/sdk/base/lib/osBindings/StartStop.ts b/sdk/base/lib/osBindings/StartStop.ts new file mode 100644 index 000000000..c8be35fb7 --- /dev/null +++ b/sdk/base/lib/osBindings/StartStop.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type StartStop = "start" | "stop" diff --git a/sdk/lib/osBindings/UpdatingState.ts b/sdk/base/lib/osBindings/UpdatingState.ts similarity index 100% rename from sdk/lib/osBindings/UpdatingState.ts rename to sdk/base/lib/osBindings/UpdatingState.ts diff --git a/sdk/lib/osBindings/VerifyCifsParams.ts b/sdk/base/lib/osBindings/VerifyCifsParams.ts similarity index 100% rename from sdk/lib/osBindings/VerifyCifsParams.ts rename to sdk/base/lib/osBindings/VerifyCifsParams.ts diff --git a/sdk/lib/osBindings/Version.ts b/sdk/base/lib/osBindings/Version.ts similarity index 100% rename from sdk/lib/osBindings/Version.ts rename to sdk/base/lib/osBindings/Version.ts diff --git a/sdk/lib/osBindings/VersionSignerParams.ts b/sdk/base/lib/osBindings/VersionSignerParams.ts similarity index 100% rename from sdk/lib/osBindings/VersionSignerParams.ts rename to sdk/base/lib/osBindings/VersionSignerParams.ts diff --git a/sdk/lib/osBindings/VolumeId.ts b/sdk/base/lib/osBindings/VolumeId.ts similarity index 100% rename from sdk/lib/osBindings/VolumeId.ts rename to sdk/base/lib/osBindings/VolumeId.ts diff --git a/sdk/lib/osBindings/WifiInfo.ts b/sdk/base/lib/osBindings/WifiInfo.ts similarity index 100% rename from sdk/lib/osBindings/WifiInfo.ts rename to sdk/base/lib/osBindings/WifiInfo.ts diff --git a/sdk/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts similarity index 76% rename from sdk/lib/osBindings/index.ts rename to sdk/base/lib/osBindings/index.ts index 59cbb7b13..f76f595c9 100644 --- a/sdk/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -1,8 +1,23 @@ export { AcceptSigners } from "./AcceptSigners" +export { AcmeSettings } from "./AcmeSettings" export { ActionId } from "./ActionId" +export { ActionInput } from "./ActionInput" export { ActionMetadata } from "./ActionMetadata" +export { ActionRequestCondition } from "./ActionRequestCondition" +export { ActionRequestEntry } from "./ActionRequestEntry" +export { ActionRequestInput } from "./ActionRequestInput" +export { ActionRequestTrigger } from "./ActionRequestTrigger" +export { ActionRequest } from "./ActionRequest" +export { ActionResultMember } from "./ActionResultMember" +export { ActionResult } from "./ActionResult" +export { ActionResultV0 } from "./ActionResultV0" +export { ActionResultV1 } from "./ActionResultV1" +export { ActionResultValue } from "./ActionResultValue" +export { ActionSeverity } from "./ActionSeverity" +export { ActionVisibility } from "./ActionVisibility" export { AddAdminParams } from "./AddAdminParams" export { AddAssetParams } from "./AddAssetParams" +export { AddCategoryParams } from "./AddCategoryParams" export { AddPackageParams } from "./AddPackageParams" export { AddressInfo } from "./AddressInfo" export { AddSslOptions } from "./AddSslOptions" @@ -20,18 +35,26 @@ export { AttachParams } from "./AttachParams" export { BackupProgress } from "./BackupProgress" export { BackupTargetFS } from "./BackupTargetFS" export { Base64 } from "./Base64" +export { BindId } from "./BindId" export { BindInfo } from "./BindInfo" export { BindOptions } from "./BindOptions" export { BindParams } from "./BindParams" export { Blake3Commitment } from "./Blake3Commitment" export { BlockDev } from "./BlockDev" +export { BuildArg } from "./BuildArg" export { CallbackId } from "./CallbackId" export { Category } from "./Category" export { CheckDependenciesParam } from "./CheckDependenciesParam" export { CheckDependenciesResult } from "./CheckDependenciesResult" export { Cifs } from "./Cifs" +export { ClearActionRequestsParams } from "./ClearActionRequestsParams" +export { ClearActionsParams } from "./ClearActionsParams" +export { ClearBindingsParams } from "./ClearBindingsParams" +export { ClearCallbacksParams } from "./ClearCallbacksParams" +export { ClearServiceInterfacesParams } from "./ClearServiceInterfacesParams" +export { CliSetIconParams } from "./CliSetIconParams" export { ContactInfo } from "./ContactInfo" -export { CreateOverlayedImageParams } from "./CreateOverlayedImageParams" +export { CreateSubcontainerFsParams } from "./CreateSubcontainerFsParams" export { CurrentDependencies } from "./CurrentDependencies" export { CurrentDependencyInfo } from "./CurrentDependencyInfo" export { DataUrl } from "./DataUrl" @@ -41,17 +64,18 @@ export { DependencyMetadata } from "./DependencyMetadata" export { DependencyRequirement } from "./DependencyRequirement" export { DepInfo } from "./DepInfo" export { Description } from "./Description" -export { DestroyOverlayedImageParams } from "./DestroyOverlayedImageParams" +export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" +export { DeviceFilter } from "./DeviceFilter" export { Duration } from "./Duration" export { EchoParams } from "./EchoParams" +export { EditSignerParams } from "./EditSignerParams" export { EncryptedWire } from "./EncryptedWire" -export { ExecuteAction } from "./ExecuteAction" export { ExportActionParams } from "./ExportActionParams" export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams" export { ExposeForDependentsParams } from "./ExposeForDependentsParams" export { FullIndex } from "./FullIndex" export { FullProgress } from "./FullProgress" -export { GetConfiguredParams } from "./GetConfiguredParams" +export { GetActionInputParams } from "./GetActionInputParams" export { GetHostInfoParams } from "./GetHostInfoParams" export { GetOsAssetParams } from "./GetOsAssetParams" export { GetOsVersionParams } from "./GetOsVersionParams" @@ -63,6 +87,7 @@ export { GetServiceInterfaceParams } from "./GetServiceInterfaceParams" export { GetServicePortForwardParams } from "./GetServicePortForwardParams" export { GetSslCertificateParams } from "./GetSslCertificateParams" export { GetSslKeyParams } from "./GetSslKeyParams" +export { GetStatusParams } from "./GetStatusParams" export { GetStoreParams } from "./GetStoreParams" export { GetSystemSmtpParams } from "./GetSystemSmtpParams" export { Governor } from "./Governor" @@ -88,9 +113,13 @@ export { InstallParams } from "./InstallParams" export { IpHostname } from "./IpHostname" export { IpInfo } from "./IpInfo" export { LanInfo } from "./LanInfo" +export { ListPackageSignersParams } from "./ListPackageSignersParams" export { ListServiceInterfacesParams } from "./ListServiceInterfacesParams" export { ListVersionSignersParams } from "./ListVersionSignersParams" export { LoginParams } from "./LoginParams" +export { LshwDevice } from "./LshwDevice" +export { LshwDisplay } from "./LshwDisplay" +export { LshwProcessor } from "./LshwProcessor" export { MainStatus } from "./MainStatus" export { Manifest } from "./Manifest" export { MaybeUtf8String } from "./MaybeUtf8String" @@ -109,6 +138,7 @@ export { PackageId } from "./PackageId" export { PackageIndex } from "./PackageIndex" export { PackageInfoShort } from "./PackageInfoShort" export { PackageInfo } from "./PackageInfo" +export { PackageSignerParams } from "./PackageSignerParams" export { PackageState } from "./PackageState" export { PackageVersionInfo } from "./PackageVersionInfo" export { PasswordType } from "./PasswordType" @@ -119,8 +149,12 @@ export { Public } from "./Public" export { RecoverySource } from "./RecoverySource" export { RegistryAsset } from "./RegistryAsset" export { RegistryInfo } from "./RegistryInfo" +export { RemoveCategoryParams } from "./RemoveCategoryParams" export { RemoveVersionParams } from "./RemoveVersionParams" +export { ReplayId } from "./ReplayId" +export { RequestActionParams } from "./RequestActionParams" export { RequestCommitment } from "./RequestCommitment" +export { RunActionParams } from "./RunActionParams" export { Security } from "./Security" export { ServerInfo } from "./ServerInfo" export { ServerSpecs } from "./ServerSpecs" @@ -131,12 +165,13 @@ export { ServiceInterfaceType } from "./ServiceInterfaceType" export { SessionList } from "./SessionList" export { Sessions } from "./Sessions" export { Session } from "./Session" -export { SetConfigured } from "./SetConfigured" export { SetDataVersionParams } from "./SetDataVersionParams" export { SetDependenciesParams } from "./SetDependenciesParams" export { SetHealth } from "./SetHealth" +export { SetIconParams } from "./SetIconParams" export { SetMainStatusStatus } from "./SetMainStatusStatus" export { SetMainStatus } from "./SetMainStatus" +export { SetNameParams } from "./SetNameParams" export { SetStoreParams } from "./SetStoreParams" export { SetupExecuteParams } from "./SetupExecuteParams" export { SetupProgress } from "./SetupProgress" @@ -145,7 +180,7 @@ export { SetupStatusRes } from "./SetupStatusRes" export { SignAssetParams } from "./SignAssetParams" export { SignerInfo } from "./SignerInfo" export { SmtpValue } from "./SmtpValue" -export { Status } from "./Status" +export { StartStop } from "./StartStop" export { UpdatingState } from "./UpdatingState" export { VerifyCifsParams } from "./VerifyCifsParams" export { VersionSignerParams } from "./VersionSignerParams" diff --git a/sdk/lib/s9pk/index.ts b/sdk/base/lib/s9pk/index.ts similarity index 90% rename from sdk/lib/s9pk/index.ts rename to sdk/base/lib/s9pk/index.ts index eba8ed3f8..84a1ec644 100644 --- a/sdk/lib/s9pk/index.ts +++ b/sdk/base/lib/s9pk/index.ts @@ -1,6 +1,6 @@ import { DataUrl, Manifest, MerkleArchiveCommitment } from "../osBindings" import { ArrayBufferReader, MerkleArchive } from "./merkleArchive" -import mime from "mime" +import mime from "mime-types" const magicAndVersion = new Uint8Array([59, 59, 2]) @@ -52,13 +52,14 @@ export class S9pk { async icon(): Promise { const iconName = Object.keys(this.archive.contents.contents).find( (name) => - name.startsWith("icon.") && mime.getType(name)?.startsWith("image/"), + name.startsWith("icon.") && + (mime.contentType(name) || null)?.startsWith("image/"), ) if (!iconName) { throw new Error("no icon found in archive") } return ( - `data:${mime.getType(iconName)};base64,` + + `data:${mime.contentType(iconName)};base64,` + Buffer.from( await this.archive.contents.getPath([iconName])!.verifiedFileContents(), ).toString("base64") diff --git a/sdk/lib/s9pk/merkleArchive/directoryContents.ts b/sdk/base/lib/s9pk/merkleArchive/directoryContents.ts similarity index 100% rename from sdk/lib/s9pk/merkleArchive/directoryContents.ts rename to sdk/base/lib/s9pk/merkleArchive/directoryContents.ts diff --git a/sdk/lib/s9pk/merkleArchive/fileContents.ts b/sdk/base/lib/s9pk/merkleArchive/fileContents.ts similarity index 100% rename from sdk/lib/s9pk/merkleArchive/fileContents.ts rename to sdk/base/lib/s9pk/merkleArchive/fileContents.ts diff --git a/sdk/lib/s9pk/merkleArchive/index.ts b/sdk/base/lib/s9pk/merkleArchive/index.ts similarity index 100% rename from sdk/lib/s9pk/merkleArchive/index.ts rename to sdk/base/lib/s9pk/merkleArchive/index.ts diff --git a/sdk/lib/s9pk/merkleArchive/varint.ts b/sdk/base/lib/s9pk/merkleArchive/varint.ts similarity index 96% rename from sdk/lib/s9pk/merkleArchive/varint.ts rename to sdk/base/lib/s9pk/merkleArchive/varint.ts index 016505307..a6a425289 100644 --- a/sdk/lib/s9pk/merkleArchive/varint.ts +++ b/sdk/base/lib/s9pk/merkleArchive/varint.ts @@ -1,4 +1,4 @@ -import { asError } from "../../util/asError" +import { asError } from "../../util" const msb = 0x80 const dropMsb = 0x7f diff --git a/sdk/lib/test/exverList.test.ts b/sdk/base/lib/test/exverList.test.ts similarity index 100% rename from sdk/lib/test/exverList.test.ts rename to sdk/base/lib/test/exverList.test.ts diff --git a/sdk/lib/test/graph.test.ts b/sdk/base/lib/test/graph.test.ts similarity index 99% rename from sdk/lib/test/graph.test.ts rename to sdk/base/lib/test/graph.test.ts index 7f02adc2e..a738123d6 100644 --- a/sdk/lib/test/graph.test.ts +++ b/sdk/base/lib/test/graph.test.ts @@ -1,4 +1,4 @@ -import { Graph } from "../util/graph" +import { Graph } from "../util" describe("graph", () => { { diff --git a/sdk/lib/test/configTypes.test.ts b/sdk/base/lib/test/inputSpecTypes.test.ts similarity index 64% rename from sdk/lib/test/configTypes.test.ts rename to sdk/base/lib/test/inputSpecTypes.test.ts index 6d0b9a3d8..6767faf20 100644 --- a/sdk/lib/test/configTypes.test.ts +++ b/sdk/base/lib/test/inputSpecTypes.test.ts @@ -1,15 +1,18 @@ -import { ListValueSpecOf, isValueSpecListOf } from "../config/configTypes" -import { Config } from "../config/builder/config" -import { List } from "../config/builder/list" -import { Value } from "../config/builder/value" +import { + ListValueSpecOf, + isValueSpecListOf, +} from "../actions/input/inputSpecTypes" +import { InputSpec } from "../actions/input/builder/inputSpec" +import { List } from "../actions/input/builder/list" +import { Value } from "../actions/input/builder/value" -describe("Config Types", () => { +describe("InputSpec Types", () => { test("isValueSpecListOf", async () => { const options = [List.obj, List.text] for (const option of options) { const test = (option as any)( {} as any, - { spec: Config.of({}) } as any, + { spec: InputSpec.of({}) } as any, ) as any const someList = await Value.list(test).build({} as any) if (isValueSpecListOf(someList, "text")) { diff --git a/sdk/lib/test/startosTypeValidation.test.ts b/sdk/base/lib/test/startosTypeValidation.test.ts similarity index 62% rename from sdk/lib/test/startosTypeValidation.test.ts rename to sdk/base/lib/test/startosTypeValidation.test.ts index 661f21079..509da0894 100644 --- a/sdk/lib/test/startosTypeValidation.test.ts +++ b/sdk/base/lib/test/startosTypeValidation.test.ts @@ -1,16 +1,22 @@ import { Effects } from "../types" import { CheckDependenciesParam, - ExecuteAction, - GetConfiguredParams, + ClearActionRequestsParams, + ClearActionsParams, + ClearBindingsParams, + ClearCallbacksParams, + ClearServiceInterfacesParams, + GetActionInputParams, + GetStatusParams, + RequestActionParams, + RunActionParams, SetDataVersionParams, SetMainStatus, } from ".././osBindings" -import { CreateOverlayedImageParams } from ".././osBindings" -import { DestroyOverlayedImageParams } from ".././osBindings" +import { CreateSubcontainerFsParams } from ".././osBindings" +import { DestroySubcontainerFsParams } from ".././osBindings" import { BindParams } from ".././osBindings" import { GetHostInfoParams } from ".././osBindings" -import { SetConfigured } from ".././osBindings" import { SetHealth } from ".././osBindings" import { ExposeForDependentsParams } from ".././osBindings" import { GetSslCertificateParams } from ".././osBindings" @@ -24,29 +30,42 @@ import { GetPrimaryUrlParams } from ".././osBindings" import { ListServiceInterfacesParams } from ".././osBindings" import { ExportActionParams } from ".././osBindings" import { MountParams } from ".././osBindings" +import { StringObject } from "../util" function typeEquality(_a: ExpectedType) {} type WithCallback = Omit & { callback: () => void } +type EffectsTypeChecker = { + [K in keyof T]: T[K] extends (args: infer A) => any + ? A + : T[K] extends StringObject + ? EffectsTypeChecker + : never +} + describe("startosTypeValidation ", () => { test(`checking the params match`, () => { - const testInput: any = {} - typeEquality<{ - [K in keyof Effects]: Effects[K] extends (args: infer A) => any - ? A - : never - }>({ - executeAction: {} as ExecuteAction, - createOverlayedImage: {} as CreateOverlayedImageParams, - destroyOverlayedImage: {} as DestroyOverlayedImageParams, - clearBindings: undefined, + typeEquality({ + constRetry: {}, + clearCallbacks: {} as ClearCallbacksParams, + action: { + clear: {} as ClearActionsParams, + export: {} as ExportActionParams, + getInput: {} as GetActionInputParams, + run: {} as RunActionParams, + request: {} as RequestActionParams, + clearRequests: {} as ClearActionRequestsParams, + }, + subcontainer: { + createFs: {} as CreateSubcontainerFsParams, + destroyFs: {} as DestroySubcontainerFsParams, + }, + clearBindings: {} as ClearBindingsParams, getInstalledPackages: undefined, bind: {} as BindParams, getHostInfo: {} as WithCallback, - getConfigured: {} as GetConfiguredParams, restart: undefined, shutdown: undefined, - setConfigured: {} as SetConfigured, setDataVersion: {} as SetDataVersionParams, getDataVersion: undefined, setHealth: {} as SetHealth, @@ -55,23 +74,22 @@ describe("startosTypeValidation ", () => { getSslKey: {} as GetSslKeyParams, getServiceInterface: {} as WithCallback, setDependencies: {} as SetDependenciesParams, - store: {} as never, + store: { + get: {} as any, // as GetStoreParams, + set: {} as any, // as SetStoreParams, + }, getSystemSmtp: {} as WithCallback, getContainerIp: undefined, getServicePortForward: {} as GetServicePortForwardParams, - clearServiceInterfaces: undefined, + clearServiceInterfaces: {} as ClearServiceInterfacesParams, exportServiceInterface: {} as ExportServiceInterfaceParams, getPrimaryUrl: {} as WithCallback, listServiceInterfaces: {} as WithCallback, - exportAction: {} as ExportActionParams, - clearActions: undefined, mount: {} as MountParams, checkDependencies: {} as CheckDependenciesParam, getDependencies: undefined, + getStatus: {} as WithCallback, setMainStatus: {} as SetMainStatus, }) - typeEquality[0]>( - testInput as ExecuteAction, - ) }) }) diff --git a/sdk/lib/test/util.deepMerge.test.ts b/sdk/base/lib/test/util.deepMerge.test.ts similarity index 70% rename from sdk/lib/test/util.deepMerge.test.ts rename to sdk/base/lib/test/util.deepMerge.test.ts index 25a4a7d22..74ff92c26 100644 --- a/sdk/lib/test/util.deepMerge.test.ts +++ b/sdk/base/lib/test/util.deepMerge.test.ts @@ -1,5 +1,5 @@ -import { deepEqual } from "../util/deepEqual" -import { deepMerge } from "../util/deepMerge" +import { deepEqual } from "../util" +import { deepMerge } from "../util" describe("deepMerge", () => { test("deepMerge({}, {a: 1}, {b: 2}) should return {a: 1, b: 2}", () => { @@ -20,7 +20,11 @@ describe("deepMerge", () => { }), ).toBeTruthy() }) - test("deepMerge([1,2,3], [2,3,4]) should equal [2,3,4]", () => { - expect(deepMerge([1, 2, 3], [2, 3, 4])).toEqual([2, 3, 4]) + test("Test that merging lists has Set semantics", () => { + const merge = deepMerge(["a", "b"], ["b", "c"]) + expect(merge).toHaveLength(3) + expect(merge).toContain("a") + expect(merge).toContain("b") + expect(merge).toContain("c") }) }) diff --git a/sdk/lib/test/util.getNetworkInterface.test.ts b/sdk/base/lib/test/util.getNetworkInterface.test.ts similarity index 100% rename from sdk/lib/test/util.getNetworkInterface.test.ts rename to sdk/base/lib/test/util.getNetworkInterface.test.ts diff --git a/sdk/base/lib/types.ts b/sdk/base/lib/types.ts new file mode 100644 index 000000000..ab1acaa87 --- /dev/null +++ b/sdk/base/lib/types.ts @@ -0,0 +1,229 @@ +export * as inputSpecTypes from "./actions/input/inputSpecTypes" + +import { + DependencyRequirement, + NamedHealthCheckResult, + Manifest, + ServiceInterface, + ActionId, +} from "./osBindings" +import { Affine, StringObject, ToKebab } from "./util" +import { Action, Actions } from "./actions/setupActions" +import { Effects } from "./Effects" +export { Effects } +export * from "./osBindings" +export { SDKManifest } from "./types/ManifestTypes" + +export type ExposedStorePaths = string[] & Affine<"ExposedStorePaths"> +declare const HealthProof: unique symbol +export type HealthReceipt = { + [HealthProof]: never +} + +export type DaemonBuildable = { + build(): Promise<{ + term(): Promise + }> +} + +export type ServiceInterfaceType = "ui" | "p2p" | "api" +export type Signals = NodeJS.Signals +export const SIGTERM: Signals = "SIGTERM" +export const SIGKILL: Signals = "SIGKILL" +export const NO_TIMEOUT = -1 + +export type PathMaker = (options: { volume: string; path: string }) => string +export type MaybePromise = Promise | A +export namespace ExpectedExports { + version: 1 + + /** For backing up service data though the startOS UI */ + export type createBackup = (options: { effects: Effects }) => Promise + /** For restoring service data that was previously backed up using the startOS UI create backup flow. Backup restores are also triggered via the startOS UI, or doing a system restore flow during setup. */ + export type restoreBackup = (options: { + effects: Effects + }) => Promise + + /** + * This is the entrypoint for the main container. Used to start up something like the service that the + * package represents, like running a bitcoind in a bitcoind-wrapper. + */ + export type main = (options: { + effects: Effects + started(onTerm: () => PromiseLike): PromiseLike + }) => Promise + + /** + * After a shutdown, if we wanted to do any operations to clean up things, like + * set the action as unavailable or something. + */ + export type afterShutdown = (options: { + effects: Effects + }) => Promise + + /** + * Every time a service launches (both on startup, and on install) this function is called before packageInit + * Can be used to register callbacks + */ + export type containerInit = (options: { + effects: Effects + }) => Promise + + /** + * Every time a package completes an install, this function is called before the main. + * Can be used to do migration like things. + */ + export type packageInit = (options: { effects: Effects }) => Promise + /** This will be ran during any time a package is uninstalled, for example during a update + * this will be called. + */ + export type packageUninit = (options: { + effects: Effects + nextVersion: null | string + }) => Promise + + export type manifest = Manifest + + export type actions = Actions< + any, + Record> + > +} +export type ABI = { + createBackup: ExpectedExports.createBackup + restoreBackup: ExpectedExports.restoreBackup + main: ExpectedExports.main + afterShutdown: ExpectedExports.afterShutdown + containerInit: ExpectedExports.containerInit + packageInit: ExpectedExports.packageInit + packageUninit: ExpectedExports.packageUninit + manifest: ExpectedExports.manifest + actions: ExpectedExports.actions +} +export type TimeMs = number +export type VersionString = string + +declare const DaemonProof: unique symbol +export type DaemonReceipt = { + [DaemonProof]: never +} +export type Daemon = { + wait(): Promise + term(): Promise + [DaemonProof]: never +} + +export type HealthStatus = NamedHealthCheckResult["result"] +export type SmtpValue = { + server: string + port: number + from: string + login: string + password: string | null | undefined +} + +export type CommandType = string | [string, ...string[]] + +export type DaemonReturned = { + wait(): Promise + term(options?: { signal?: Signals; timeout?: number }): Promise +} + +export declare const hostName: unique symbol +// asdflkjadsf.onion | 1.2.3.4 +export type Hostname = string & { [hostName]: never } + +export type HostnameInfoIp = { + kind: "ip" + networkInterfaceId: string + public: boolean + hostname: + | { + kind: "ipv4" | "ipv6" | "local" + value: string + port: number | null + sslPort: number | null + } + | { + kind: "domain" + domain: string + subdomain: string | null + port: number | null + sslPort: number | null + } +} + +export type HostnameInfoOnion = { + kind: "onion" + hostname: { value: string; port: number | null; sslPort: number | null } +} + +export type HostnameInfo = HostnameInfoIp | HostnameInfoOnion + +export type ServiceInterfaceId = string + +export { ServiceInterface } +export type ExposeServicePaths = { + /** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */ + paths: ExposedStorePaths +} + +export type EffectMethod = { + [K in keyof T]-?: K extends string + ? T[K] extends Function + ? ToKebab + : T[K] extends StringObject + ? `${ToKebab}.${EffectMethod}` + : never + : never +}[keyof T] + +export type SyncOptions = { + /** delete files that exist in the target directory, but not in the source directory */ + delete: boolean + /** do not sync files with paths that match these patterns */ + exclude: string[] +} + +/** + * This is the metadata that is returned from the metadata call. + */ +export type Metadata = { + fileType: string + isDir: boolean + isFile: boolean + isSymlink: boolean + len: number + modified?: Date + accessed?: Date + created?: Date + readonly: boolean + uid: number + gid: number + mode: number +} + +export type SetResult = { + dependsOn: DependsOn + signal: Signals +} + +export type PackageId = string +export type Message = string +export type DependencyKind = "running" | "exists" + +export type DependsOn = { + [packageId: string]: string[] | readonly string[] +} + +export type KnownError = + | { error: string } + | { + errorCode: [number, string] | readonly [number, string] + } + +export type Dependencies = Array + +export type DeepPartial = T extends {} + ? { [P in keyof T]?: DeepPartial } + : T diff --git a/sdk/base/lib/types/ManifestTypes.ts b/sdk/base/lib/types/ManifestTypes.ts new file mode 100644 index 000000000..86ecf9140 --- /dev/null +++ b/sdk/base/lib/types/ManifestTypes.ts @@ -0,0 +1,159 @@ +import { T } from ".." +import { ImageId, ImageSource } from "../types" + +export type SDKManifest = { + /** + * The package identifier used by StartOS. This must be unique amongst all other known packages. + * @example nextcloud + * */ + readonly id: string + /** + * The human readable name of your service + * @example Nextcloud + * */ + readonly title: string + /** + * The name of the software license for this project. The license itself should be included in the root of the project directory. + * @example MIT + */ + readonly license: string + /** + * URL of the StartOS package repository + * @example `https://github.com/Start9Labs/nextcloud-startos` + */ + readonly wrapperRepo: string + /** + * URL of the upstream service repository + * @example `https://github.com/nextcloud/docker` + */ + readonly upstreamRepo: string + /** + * URL where users can get help using the upstream service + * @example `https://github.com/nextcloud/docker/issues` + */ + readonly supportSite: string + /** + * URL where users can learn more about the upstream service + * @example `https://nextcloud.com` + */ + readonly marketingSite: string + /** + * (optional) URL where users can donate to the upstream project + * @example `https://nextcloud.com/contribute/` + */ + readonly donationUrl: string | null + readonly description: { + /** Short description to display on the marketplace list page. Max length 80 chars. */ + readonly short: string + /** Long description to display on the marketplace details page for this service. Max length 500 chars. */ + readonly long: string + } + /** + * @description A mapping of OS images needed to run the container processes. Each image ID is a unique key. + * @example + * Using dockerTag... + * + * ``` + images: { + main: { + source: { + dockerTag: 'start9/hello-world', + }, + }, + }, + * ``` + * @example + * Using dockerBuild... + * + * ``` + images: { + main: { + source: { + dockerBuild: { + dockerFile: '../Dockerfile', + workdir: '.', + }, + }, + }, + }, + * ``` + */ + readonly images: Record + /** + * @description A list of readonly asset directories that will mount to the container. Each item here must + * correspond to a directory in the /assets directory of this project. + * + * Most projects will not make use of this. + * @example [] + */ + readonly assets: string[] + /** + * @description A list of data volumes that will mount to the container. Must contain at least one volume. + * @example ['main'] + */ + readonly volumes: string[] + + readonly alerts?: { + /** An warning alert requiring user confirmation before proceeding with initial installation of this service. */ + readonly install?: string | null + /** An warning alert requiring user confirmation before updating this service. */ + readonly update?: string | null + /** An warning alert requiring user confirmation before uninstalling this service. */ + readonly uninstall?: string | null + /** An warning alert requiring user confirmation before restoring this service from backup. */ + readonly restore?: string | null + /** An warning alert requiring user confirmation before starting this service. */ + readonly start?: string | null + /** An warning alert requiring user confirmation before stopping this service. */ + readonly stop?: string | null + } + /** + * @description A mapping of service dependencies to be displayed to users when viewing the Marketplace + * @property {string} description - An explanation of why this service is a dependency. + * @property {boolean} optional - Whether or not this dependency is required or contingent on user configuration. + * @property {string} s9pk - A path or url to an s9pk of the dependency to extract metadata at build time + * @example + * ``` + dependencies: { + 'hello-world': { + description: 'A moon needs a world', + optional: false, + s9pk: '', + }, + }, + * ``` + */ + readonly dependencies: Record + /** + * @description (optional) A set of hardware requirements for this service. If the user's machine + * does not meet these requirements, they will not be able to install this service. + * @property {object[]} devices - TODO Aiden confirm type on the left. List of required devices (displays or processors). + * @property {number} ram - Minimum RAM requirement (in megabytes MB) + * @property {string[]} arch - List of supported arches + * @example + * ``` + TODO Aiden verify below and provide examples for devices + hardwareRequirements: { + devices: [ + { class: 'display', value: '' }, + { class: 'processor', value: '' }, + ], + ram: 8192, + arch: ['x86-64'], + }, + * ``` + */ + readonly hardwareRequirements?: { + readonly device?: T.DeviceFilter[] + readonly ram?: number | null + readonly arch?: string[] | null + } +} + +export type SDKImageInputSpec = { + source: Exclude + arch?: string[] + emulateMissingAs?: string | null +} + +export type ManifestDependency = T.Manifest["dependencies"][string] diff --git a/sdk/lib/util/GetSystemSmtp.ts b/sdk/base/lib/util/GetSystemSmtp.ts similarity index 90% rename from sdk/lib/util/GetSystemSmtp.ts rename to sdk/base/lib/util/GetSystemSmtp.ts index 498a2d8b2..cd87a9db6 100644 --- a/sdk/lib/util/GetSystemSmtp.ts +++ b/sdk/base/lib/util/GetSystemSmtp.ts @@ -1,4 +1,4 @@ -import { Effects } from "../types" +import { Effects } from "../Effects" export class GetSystemSmtp { constructor(readonly effects: Effects) {} @@ -8,7 +8,7 @@ export class GetSystemSmtp { */ const() { return this.effects.getSystemSmtp({ - callback: this.effects.restart, + callback: () => this.effects.constRetry(), }) } /** diff --git a/sdk/lib/util/Hostname.ts b/sdk/base/lib/util/Hostname.ts similarity index 100% rename from sdk/lib/util/Hostname.ts rename to sdk/base/lib/util/Hostname.ts diff --git a/sdk/lib/store/PathBuilder.ts b/sdk/base/lib/util/PathBuilder.ts similarity index 100% rename from sdk/lib/store/PathBuilder.ts rename to sdk/base/lib/util/PathBuilder.ts diff --git a/sdk/base/lib/util/asError.ts b/sdk/base/lib/util/asError.ts new file mode 100644 index 000000000..c3454e0e4 --- /dev/null +++ b/sdk/base/lib/util/asError.ts @@ -0,0 +1,9 @@ +export const asError = (e: unknown) => { + if (e instanceof Error) { + return new Error(e as any) + } + if (typeof e === "string") { + return new Error(`${e}`) + } + return new Error(`${JSON.stringify(e)}`) +} diff --git a/sdk/lib/util/deepEqual.ts b/sdk/base/lib/util/deepEqual.ts similarity index 100% rename from sdk/lib/util/deepEqual.ts rename to sdk/base/lib/util/deepEqual.ts diff --git a/sdk/base/lib/util/deepMerge.ts b/sdk/base/lib/util/deepMerge.ts new file mode 100644 index 000000000..72392a887 --- /dev/null +++ b/sdk/base/lib/util/deepMerge.ts @@ -0,0 +1,86 @@ +export function partialDiff( + prev: T, + next: T, +): { diff: Partial } | undefined { + if (prev === next) { + return + } else if (Array.isArray(prev) && Array.isArray(next)) { + const res = { diff: [] as any[] } + for (let newItem of next) { + let anyEq = false + for (let oldItem of prev) { + if (!partialDiff(oldItem, newItem)) { + anyEq = true + break + } + } + if (!anyEq) { + res.diff.push(newItem) + } + } + if (res.diff.length) { + return res as any + } else { + return + } + } else if (typeof prev === "object" && typeof next === "object") { + if (prev === null) { + return { diff: next } + } + if (next === null) return + const res = { diff: {} as Record } + for (let key in next) { + const diff = partialDiff(prev[key], next[key]) + if (diff) { + res.diff[key] = diff.diff + } + } + if (Object.keys(res.diff).length) { + return res + } else { + return + } + } else { + return { diff: next } + } +} + +export function deepMerge(...args: unknown[]): unknown { + const lastItem = (args as any)[args.length - 1] + if (typeof lastItem !== "object" || !lastItem) return lastItem + if (Array.isArray(lastItem)) + return deepMergeList( + ...(args.filter((x) => Array.isArray(x)) as unknown[][]), + ) + return deepMergeObject( + ...(args.filter( + (x) => typeof x === "object" && x && !Array.isArray(x), + ) as object[]), + ) +} + +function deepMergeList(...args: unknown[][]): unknown[] { + const res: unknown[] = [] + for (let arg of args) { + for (let item of arg) { + if (!res.some((x) => !partialDiff(x, item))) { + res.push(item) + } + } + } + return res +} + +function deepMergeObject(...args: object[]): object { + const lastItem = (args as any)[args.length - 1] + if (args.length === 0) return lastItem as any + if (args.length === 1) args.unshift({}) + const allKeys = new Set(args.flatMap((x) => Object.keys(x))) + for (const key of allKeys) { + const filteredValues = args.flatMap((x) => + key in x ? [(x as any)[key]] : [], + ) + ;(args as any)[0][key] = deepMerge(...filteredValues) + } + return args[0] as any +} diff --git a/sdk/lib/util/getDefaultString.ts b/sdk/base/lib/util/getDefaultString.ts similarity index 79% rename from sdk/lib/util/getDefaultString.ts rename to sdk/base/lib/util/getDefaultString.ts index fa35b4e66..2bbf8d279 100644 --- a/sdk/lib/util/getDefaultString.ts +++ b/sdk/base/lib/util/getDefaultString.ts @@ -1,4 +1,4 @@ -import { DefaultString } from "../config/configTypes" +import { DefaultString } from "../actions/input/inputSpecTypes" import { getRandomString } from "./getRandomString" export function getDefaultString(defaultSpec: DefaultString): string { diff --git a/sdk/lib/util/getRandomCharInSet.ts b/sdk/base/lib/util/getRandomCharInSet.ts similarity index 100% rename from sdk/lib/util/getRandomCharInSet.ts rename to sdk/base/lib/util/getRandomCharInSet.ts diff --git a/sdk/lib/util/getRandomString.ts b/sdk/base/lib/util/getRandomString.ts similarity index 79% rename from sdk/lib/util/getRandomString.ts rename to sdk/base/lib/util/getRandomString.ts index ea0989bcd..7b52041d8 100644 --- a/sdk/lib/util/getRandomString.ts +++ b/sdk/base/lib/util/getRandomString.ts @@ -1,4 +1,4 @@ -import { RandomString } from "../config/configTypes" +import { RandomString } from "../actions/input/inputSpecTypes" import { getRandomCharInSet } from "./getRandomCharInSet" export function getRandomString(generator: RandomString): string { diff --git a/sdk/lib/util/getServiceInterface.ts b/sdk/base/lib/util/getServiceInterface.ts similarity index 98% rename from sdk/lib/util/getServiceInterface.ts rename to sdk/base/lib/util/getServiceInterface.ts index fd0fef779..cbbb345cb 100644 --- a/sdk/lib/util/getServiceInterface.ts +++ b/sdk/base/lib/util/getServiceInterface.ts @@ -1,8 +1,7 @@ -import { ServiceInterfaceType } from "../StartSdk" +import { ServiceInterfaceType } from "../types" import { knownProtocols } from "../interfaces/Host" import { AddressInfo, - Effects, Host, HostAddress, Hostname, @@ -11,6 +10,7 @@ import { HostnameInfoOnion, IpInfo, } from "../types" +import { Effects } from "../Effects" export type UrlString = string export type HostId = string @@ -232,7 +232,7 @@ export class GetServiceInterface { */ async const() { const { id, packageId } = this.opts - const callback = this.effects.restart + const callback = () => this.effects.constRetry() const interfaceFilled = await makeInterfaceFilled({ effects: this.effects, id, diff --git a/sdk/lib/util/getServiceInterfaces.ts b/sdk/base/lib/util/getServiceInterfaces.ts similarity index 96% rename from sdk/lib/util/getServiceInterfaces.ts rename to sdk/base/lib/util/getServiceInterfaces.ts index 9f0e242b8..1d83684d6 100644 --- a/sdk/lib/util/getServiceInterfaces.ts +++ b/sdk/base/lib/util/getServiceInterfaces.ts @@ -1,4 +1,4 @@ -import { Effects } from "../types" +import { Effects } from "../Effects" import { ServiceInterfaceFilled, filledAddress, @@ -63,7 +63,7 @@ export class GetServiceInterfaces { */ async const() { const { packageId } = this.opts - const callback = this.effects.restart + const callback = () => this.effects.constRetry() const interfaceFilled: ServiceInterfaceFilled[] = await makeManyInterfaceFilled({ effects: this.effects, diff --git a/sdk/lib/util/graph.ts b/sdk/base/lib/util/graph.ts similarity index 89% rename from sdk/lib/util/graph.ts rename to sdk/base/lib/util/graph.ts index 5ad71a04d..682ccf63e 100644 --- a/sdk/lib/util/graph.ts +++ b/sdk/base/lib/util/graph.ts @@ -1,17 +1,17 @@ import { boolean } from "ts-matches" -export type Vertex = { +export type Vertex = { metadata: VMetadata edges: Array> } -export type Edge = { +export type Edge = { metadata: EMetadata from: Vertex to: Vertex } -export class Graph { +export class Graph { private readonly vertices: Array> = [] constructor() {} addVertex( @@ -46,7 +46,7 @@ export class Graph { } findVertex( predicate: (vertex: Vertex) => boolean, - ): Generator, void> { + ): Generator, null> { const veritces = this.vertices function* gen() { for (let vertex of veritces) { @@ -54,6 +54,7 @@ export class Graph { yield vertex } } + return null } return gen() } @@ -75,13 +76,13 @@ export class Graph { from: | Vertex | ((vertex: Vertex) => boolean), - ): Generator, void> { + ): Generator, null> { const visited: Array> = [] function* rec( vertex: Vertex, - ): Generator, void> { + ): Generator, null> { if (visited.includes(vertex)) { - return + return null } visited.push(vertex) yield vertex @@ -99,6 +100,7 @@ export class Graph { } } } + return null } if (from instanceof Function) { @@ -115,6 +117,7 @@ export class Graph { } } } + return null })() } else { return rec(from) @@ -124,13 +127,13 @@ export class Graph { to: | Vertex | ((vertex: Vertex) => boolean), - ): Generator, void> { + ): Generator, null> { const visited: Array> = [] function* rec( vertex: Vertex, - ): Generator, void> { + ): Generator, null> { if (visited.includes(vertex)) { - return + return null } visited.push(vertex) yield vertex @@ -148,6 +151,7 @@ export class Graph { } } } + return null } if (to instanceof Function) { @@ -164,6 +168,7 @@ export class Graph { } } } + return null })() } else { return rec(to) @@ -176,7 +181,7 @@ export class Graph { to: | Vertex | ((vertex: Vertex) => boolean), - ): Array> | void { + ): Array> | null { const isDone = to instanceof Function ? to @@ -186,12 +191,12 @@ export class Graph { function* check( vertex: Vertex, path: Array>, - ): Generator> | undefined> { + ): Generator> | null> { if (isDone(vertex)) { return path } if (visited.includes(vertex)) { - return + return null } visited.push(vertex) yield @@ -213,6 +218,7 @@ export class Graph { } } } + return null } if (from instanceof Function) { @@ -240,5 +246,6 @@ export class Graph { } } } + return null } } diff --git a/sdk/lib/util/inMs.test.ts b/sdk/base/lib/util/inMs.test.ts similarity index 100% rename from sdk/lib/util/inMs.test.ts rename to sdk/base/lib/util/inMs.test.ts diff --git a/sdk/lib/util/inMs.ts b/sdk/base/lib/util/inMs.ts similarity index 95% rename from sdk/lib/util/inMs.ts rename to sdk/base/lib/util/inMs.ts index 547fb8bea..548eb14bf 100644 --- a/sdk/lib/util/inMs.ts +++ b/sdk/base/lib/util/inMs.ts @@ -1,5 +1,3 @@ -import { DEFAULT_SIGTERM_TIMEOUT } from "../mainFn" - const matchTimeRegex = /^\s*(\d+)?(\.\d+)?\s*(ms|s|m|h|d)/ const unitMultiplier = (unit?: string) => { diff --git a/sdk/base/lib/util/index.ts b/sdk/base/lib/util/index.ts new file mode 100644 index 000000000..4c9e803bb --- /dev/null +++ b/sdk/base/lib/util/index.ts @@ -0,0 +1,22 @@ +/// Currently being used +export { addressHostToUrl } from "./getServiceInterface" +export { getDefaultString } from "./getDefaultString" + +/// Not being used, but known to be browser compatible +export { GetServiceInterface, getServiceInterface } from "./getServiceInterface" +export { getServiceInterfaces } from "./getServiceInterfaces" +export { once } from "./once" +export { asError } from "./asError" +export * as Patterns from "./patterns" +export * from "./typeHelpers" +export { GetSystemSmtp } from "./GetSystemSmtp" +export { Graph, Vertex } from "./graph" +export { inMs } from "./inMs" +export { splitCommand } from "./splitCommand" +export { nullIfEmpty } from "./nullIfEmpty" +export { deepMerge, partialDiff } from "./deepMerge" +export { deepEqual } from "./deepEqual" +export { hostnameInfoToAddress } from "./Hostname" +export { PathBuilder, extractJsonPath, StorePath } from "./PathBuilder" +export * as regexes from "./regexes" +export { stringFromStdErrOut } from "./stringFromStdErrOut" diff --git a/sdk/base/lib/util/nullIfEmpty.ts b/sdk/base/lib/util/nullIfEmpty.ts new file mode 100644 index 000000000..b24907b7d --- /dev/null +++ b/sdk/base/lib/util/nullIfEmpty.ts @@ -0,0 +1,10 @@ +/** + * A useful tool when doing a getInputSpec. + * Look into the inputSpec {@link FileHelper} for an example of the use. + * @param s + * @returns + */ +export function nullIfEmpty>(s: null | A) { + if (s === null) return null + return Object.keys(s).length === 0 ? null : s +} diff --git a/sdk/lib/util/once.ts b/sdk/base/lib/util/once.ts similarity index 100% rename from sdk/lib/util/once.ts rename to sdk/base/lib/util/once.ts diff --git a/sdk/lib/util/patterns.ts b/sdk/base/lib/util/patterns.ts similarity index 96% rename from sdk/lib/util/patterns.ts rename to sdk/base/lib/util/patterns.ts index ac281b081..2c9c7010d 100644 --- a/sdk/lib/util/patterns.ts +++ b/sdk/base/lib/util/patterns.ts @@ -1,4 +1,4 @@ -import { Pattern } from "../config/configTypes" +import { Pattern } from "../actions/input/inputSpecTypes" import * as regexes from "./regexes" export const ipv6: Pattern = { diff --git a/sdk/lib/util/regexes.ts b/sdk/base/lib/util/regexes.ts similarity index 100% rename from sdk/lib/util/regexes.ts rename to sdk/base/lib/util/regexes.ts diff --git a/sdk/lib/util/splitCommand.ts b/sdk/base/lib/util/splitCommand.ts similarity index 100% rename from sdk/lib/util/splitCommand.ts rename to sdk/base/lib/util/splitCommand.ts diff --git a/sdk/lib/util/stringFromStdErrOut.ts b/sdk/base/lib/util/stringFromStdErrOut.ts similarity index 100% rename from sdk/lib/util/stringFromStdErrOut.ts rename to sdk/base/lib/util/stringFromStdErrOut.ts diff --git a/sdk/base/lib/util/typeHelpers.ts b/sdk/base/lib/util/typeHelpers.ts new file mode 100644 index 000000000..d29d5c986 --- /dev/null +++ b/sdk/base/lib/util/typeHelpers.ts @@ -0,0 +1,116 @@ +import * as T from "../types" + +// prettier-ignore +export type FlattenIntersection = +T extends ArrayLike ? T : +T extends object ? {} & {[P in keyof T]: T[P]} : + T; + +export type _ = FlattenIntersection + +export const isKnownError = (e: unknown): e is T.KnownError => + e instanceof Object && ("error" in e || "error-code" in e) + +declare const affine: unique symbol + +export type Affine = { [affine]: A } + +type NeverPossible = { [affine]: string } +export type NoAny = NeverPossible extends A + ? keyof NeverPossible extends keyof A + ? never + : A + : A + +type CapitalLetters = + | "A" + | "B" + | "C" + | "D" + | "E" + | "F" + | "G" + | "H" + | "I" + | "J" + | "K" + | "L" + | "M" + | "N" + | "O" + | "P" + | "Q" + | "R" + | "S" + | "T" + | "U" + | "V" + | "W" + | "X" + | "Y" + | "Z" + +type Numbers = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + +type CapitalChars = CapitalLetters | Numbers + +export type ToKebab = S extends string + ? S extends `${infer Head}${CapitalChars}${infer Tail}` // string has a capital char somewhere + ? Head extends "" // there is a capital char in the first position + ? Tail extends "" + ? Lowercase /* 'A' */ + : S extends `${infer Caps}${Tail}` // tail exists, has capital characters + ? Caps extends CapitalChars + ? Tail extends CapitalLetters + ? `${Lowercase}-${Lowercase}` /* 'AB' */ + : Tail extends `${CapitalLetters}${string}` + ? `${ToKebab}-${ToKebab}` /* first tail char is upper? 'ABcd' */ + : `${ToKebab}${ToKebab}` /* 'AbCD','AbcD', */ /* TODO: if tail is only numbers, append without underscore */ + : never /* never reached, used for inference of caps */ + : never + : Tail extends "" /* 'aB' 'abCD' 'ABCD' 'AB' */ + ? S extends `${Head}${infer Caps}` + ? Caps extends CapitalChars + ? Head extends Lowercase /* 'abcD' */ + ? Caps extends Numbers + ? // Head exists and is lowercase, tail does not, Caps is a number, we may be in a sub-select + // if head ends with number, don't split head an Caps, keep contiguous numbers together + Head extends `${string}${Numbers}` + ? never + : // head does not end in number, safe to split. 'abc2' -> 'abc-2' + `${ToKebab}-${Caps}` + : `${ToKebab}-${ToKebab}` /* 'abcD' 'abc25' */ + : never /* stop union type forming */ + : never + : never /* never reached, used for inference of caps */ + : S extends `${Head}${infer Caps}${Tail}` /* 'abCd' 'ABCD' 'AbCd' 'ABcD' */ + ? Caps extends CapitalChars + ? Head extends Lowercase /* is 'abCd' 'abCD' ? */ + ? Tail extends CapitalLetters /* is 'abCD' where Caps = 'C' */ + ? `${ToKebab}-${ToKebab}-${Lowercase}` /* aBCD Tail = 'D', Head = 'aB' */ + : Tail extends `${CapitalLetters}${string}` /* is 'aBCd' where Caps = 'B' */ + ? Head extends Numbers + ? never /* stop union type forming */ + : Head extends `${string}${Numbers}` + ? never /* stop union type forming */ + : `${Head}-${ToKebab}-${ToKebab}` /* 'aBCd' => `${'a'}-${Lowercase<'B'>}-${ToSnake<'Cd'>}` */ + : `${ToKebab}-${Lowercase}${ToKebab}` /* 'aBcD' where Caps = 'B' tail starts as lowercase */ + : never + : never + : never + : S /* 'abc' */ + : never + +export type StringObject = Record + +function test() { + // prettier-ignore + const t = (a: ( + A extends B ? ( + B extends A ? null : never + ) : never + )) =>{ } + t<"foo-bar", ToKebab<"FooBar">>(null) + // @ts-expect-error + t<"foo-3ar", ToKebab<"FooBar">>(null) +} diff --git a/sdk/base/package-lock.json b/sdk/base/package-lock.json new file mode 100644 index 000000000..4c8f58364 --- /dev/null +++ b/sdk/base/package-lock.json @@ -0,0 +1,4693 @@ +{ + "name": "@start9labs/start-sdk-base", + "version": "0.3.6-alpha8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@start9labs/start-sdk-base", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime-types": "^2.1.35", + "ts-matches": "^6.0.0", + "yaml": "^2.2.2" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/lodash.merge": "^4.6.2", + "@types/mime-types": "^2.1.4", + "jest": "^29.4.3", + "peggy": "^3.0.2", + "prettier": "^3.2.5", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "ts-pegjs": "^4.2.1", + "tsx": "^4.7.1", + "typescript": "^5.0.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.19.0.tgz", + "integrity": "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.12", + "minimatch": "^7.4.3", + "mkdirp": "^2.1.6", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", + "dev": true + }, + "node_modules/@types/lodash.merge": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz", + "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", + "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001470", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", + "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", + "dev": true + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "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/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.341", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.341.tgz", + "integrity": "sha512-R4A8VfUBQY9WmAhuqY5tjHRf5fH2AAf6vqitBOE0y6u2PgHgqHSrhZmu78dIX3fVZtjqlwJNX1i2zwC3VpHtQQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "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/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "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/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isomorphic-fetch/node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/peggy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/peggy/-/peggy-3.0.2.tgz", + "integrity": "sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^10.0.0", + "source-map-generator": "0.8.0" + }, + "bin": { + "peggy": "bin/peggy.js" + }, + "engines": { + "node": ">=14" + } + }, + "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/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/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/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", + "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-generator": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", + "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-jest": { + "version": "29.0.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", + "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-matches": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.0.0.tgz", + "integrity": "sha512-vR4hhz9bYMW30qIJUuLaeAWlsR54vse6ZI2riVhVLMBE6/vss43jwrOvbHheiyU7e26ssT/yWx69aJHD2REJSA==", + "license": "MIT" + }, + "node_modules/ts-morph": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz", + "integrity": "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==", + "dev": true, + "dependencies": { + "@ts-morph/common": "~0.19.0", + "code-block-writer": "^12.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-pegjs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ts-pegjs/-/ts-pegjs-4.2.1.tgz", + "integrity": "sha512-mK/O2pu6lzWUeKpEMA/wsa0GdYblfjJI1y0s0GqH6xCTvugQDOWPJbm5rY6AHivpZICuXIriCb+a7Cflbdtc2w==", + "dev": true, + "dependencies": { + "prettier": "^2.8.8", + "ts-morph": "^18.0.0" + }, + "bin": { + "tspegjs": "dist/cli.mjs" + }, + "peerDependencies": { + "peggy": "^3.0.2" + } + }, + "node_modules/ts-pegjs/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "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": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "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/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sdk/base/package.json b/sdk/base/package.json new file mode 100644 index 000000000..509e621de --- /dev/null +++ b/sdk/base/package.json @@ -0,0 +1,52 @@ +{ + "name": "@start9labs/start-sdk-base", + "main": "./index.js", + "types": "./index.d.ts", + "sideEffects": true, + "scripts": { + "peggy": "peggy --allowed-start-rules \"*\" --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs", + "test": "jest -c ./jest.config.js --coverage", + "buildOutput": "npx prettier --write \"**/*.ts\"", + "check": "tsc --noEmit", + "tsc": "tsc" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Start9Labs/start-sdk.git" + }, + "author": "Start9 Labs", + "license": "MIT", + "bugs": { + "url": "https://github.com/Start9Labs/start-sdk/issues" + }, + "homepage": "https://github.com/Start9Labs/start-sdk#readme", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime-types": "^2.1.35", + "ts-matches": "^6.0.0", + "yaml": "^2.2.2" + }, + "prettier": { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": false + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/lodash.merge": "^4.6.2", + "@types/mime-types": "^2.1.4", + "jest": "^29.4.3", + "peggy": "^3.0.2", + "prettier": "^3.2.5", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "ts-pegjs": "^4.2.1", + "tsx": "^4.7.1", + "typescript": "^5.0.4" + } +} diff --git a/sdk/tsconfig-base.json b/sdk/base/tsconfig.json similarity index 78% rename from sdk/tsconfig-base.json rename to sdk/base/tsconfig.json index cc14a817c..cd73f3164 100644 --- a/sdk/tsconfig-base.json +++ b/sdk/base/tsconfig.json @@ -1,18 +1,18 @@ { "compilerOptions": { - "module": "esnext", "strict": true, - "outDir": "dist", "preserveConstEnums": true, "sourceMap": true, - "target": "es2017", "pretty": true, "declaration": true, "noImplicitAny": true, "esModuleInterop": true, "types": ["node", "jest"], "moduleResolution": "node", - "skipLibCheck": true + "skipLibCheck": true, + "module": "commonjs", + "outDir": "../baseDist", + "target": "es2018" }, "include": ["lib/**/*"], "exclude": ["lib/**/*.spec.ts", "lib/**/*.gen.ts", "list", "node_modules"] diff --git a/sdk/lib/Dependency.ts b/sdk/lib/Dependency.ts deleted file mode 100644 index 067ed653e..000000000 --- a/sdk/lib/Dependency.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { VersionRange } from "./exver" - -export class Dependency { - constructor( - readonly data: - | { - type: "running" - versionRange: VersionRange - registryUrl: string - healthChecks: string[] - } - | { - type: "exists" - versionRange: VersionRange - registryUrl: string - }, - ) {} -} diff --git a/sdk/lib/StartSdk.ts b/sdk/lib/StartSdk.ts deleted file mode 100644 index 0b38d90af..000000000 --- a/sdk/lib/StartSdk.ts +++ /dev/null @@ -1,773 +0,0 @@ -import { RequiredDefault, Value } from "./config/builder/value" -import { Config, ExtractConfigType, LazyBuild } from "./config/builder/config" -import { - DefaultString, - ListValueSpecText, - Pattern, - RandomString, - UniqueBy, - ValueSpecDatetime, - ValueSpecText, -} from "./config/configTypes" -import { Variants } from "./config/builder/variants" -import { CreatedAction, createAction } from "./actions/createAction" -import { - ActionMetadata, - Effects, - ActionResult, - BackupOptions, - DeepPartial, - MaybePromise, - ServiceInterfaceId, - PackageId, -} from "./types" -import * as patterns from "./util/patterns" -import { DependencyConfig, Update } from "./dependencies/DependencyConfig" -import { BackupSet, Backups } from "./backup/Backups" -import { smtpConfig } from "./config/configConstants" -import { Daemons } from "./mainFn/Daemons" -import { healthCheck, HealthCheckParams } from "./health/HealthCheck" -import { checkPortListening } from "./health/checkFns/checkPortListening" -import { checkWebUrl, runHealthScript } from "./health/checkFns" -import { List } from "./config/builder/list" -import { VersionInfo, VersionOptions } from "./versionInfo/VersionInfo" -import { Install, InstallFn } from "./inits/setupInstall" -import { setupActions } from "./actions/setupActions" -import { setupDependencyConfig } from "./dependencies/setupDependencyConfig" -import { SetupBackupsParams, setupBackups } from "./backup/setupBackups" -import { setupInit } from "./inits/setupInit" -import { - EnsureUniqueId, - VersionGraph, - setupVersionGraph, -} from "./versionInfo/setupVersionGraph" -import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall" -import { setupMain } from "./mainFn" -import { defaultTrigger } from "./trigger/defaultTrigger" -import { changeOnFirstSuccess, cooldownTrigger } from "./trigger" -import setupConfig, { - DependenciesReceipt, - Read, - Save, -} from "./config/setupConfig" -import { - InterfacesReceipt, - SetInterfaces, - setupInterfaces, -} from "./interfaces/setupInterfaces" -import { successFailure } from "./trigger/successFailure" -import { HealthReceipt } from "./health/HealthReceipt" -import { MultiHost, Scheme } from "./interfaces/Host" -import { ServiceInterfaceBuilder } from "./interfaces/ServiceInterfaceBuilder" -import { GetSystemSmtp } from "./util/GetSystemSmtp" -import nullIfEmpty from "./util/nullIfEmpty" -import { - GetServiceInterface, - getServiceInterface, -} from "./util/getServiceInterface" -import { getServiceInterfaces } from "./util/getServiceInterfaces" -import { getStore } from "./store/getStore" -import { CommandOptions, MountOptions, Overlay } from "./util/Overlay" -import { splitCommand } from "./util/splitCommand" -import { Mounts } from "./mainFn/Mounts" -import { Dependency } from "./Dependency" -import * as T from "./types" -import { testTypeVersion, ValidateExVer } from "./exver" -import { ExposedStorePaths } from "./store/setupExposeStore" -import { PathBuilder, extractJsonPath, pathBuilder } from "./store/PathBuilder" -import { - CheckDependencies, - checkDependencies, -} from "./dependencies/dependencies" -import { health } from "." -import { GetSslCertificate } from "./util/GetSslCertificate" - -export const SDKVersion = testTypeVersion("0.3.6") - -// prettier-ignore -type AnyNeverCond = - T extends [] ? Else : - T extends [never, ...Array] ? Then : - T extends [any, ...infer U] ? AnyNeverCond : - never - -export type ServiceInterfaceType = "ui" | "p2p" | "api" -export type MainEffects = Effects & { - _type: "main" - clearCallbacks: () => Promise -} -export type Signals = NodeJS.Signals -export const SIGTERM: Signals = "SIGTERM" -export const SIGKILL: Signals = "SIGKILL" -export const NO_TIMEOUT = -1 - -function removeCallbackTypes(effects: E) { - return (t: T) => { - if ("_type" in effects && effects._type === "main") { - return t as E extends MainEffects ? T : Omit - } else { - if ("const" in t) { - delete t.const - } - if ("watch" in t) { - delete t.watch - } - return t as E extends MainEffects ? T : Omit - } - } -} - -export class StartSdk { - private constructor(readonly manifest: Manifest) {} - static of() { - return new StartSdk(null as never) - } - withManifest(manifest: Manifest) { - return new StartSdk(manifest) - } - withStore>() { - return new StartSdk(this.manifest) - } - - build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) { - type DependencyType = { - [K in keyof { - [K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false - ? K - : never - }]: Dependency - } & { - [K in keyof { - [K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true - ? K - : never - }]?: Dependency - } - - return { - checkDependencies: checkDependencies as < - DependencyId extends keyof Manifest["dependencies"] & - PackageId = keyof Manifest["dependencies"] & PackageId, - >( - effects: Effects, - packageIds?: DependencyId[], - ) => Promise>, - serviceInterface: { - getOwn: (effects: E, id: ServiceInterfaceId) => - removeCallbackTypes(effects)( - getServiceInterface(effects, { - id, - }), - ), - get: ( - effects: E, - opts: { id: ServiceInterfaceId; packageId: PackageId }, - ) => - removeCallbackTypes(effects)(getServiceInterface(effects, opts)), - getAllOwn: (effects: E) => - removeCallbackTypes(effects)(getServiceInterfaces(effects, {})), - getAll: ( - effects: E, - opts: { packageId: PackageId }, - ) => - removeCallbackTypes(effects)(getServiceInterfaces(effects, opts)), - }, - - store: { - get: ( - effects: E, - packageId: string, - path: PathBuilder, - ) => - removeCallbackTypes(effects)( - getStore(effects, path, { - packageId, - }), - ), - getOwn: ( - effects: E, - path: PathBuilder, - ) => - removeCallbackTypes(effects)( - getStore(effects, path), - ), - setOwn: >( - effects: E, - path: Path, - value: Path extends PathBuilder ? Value : never, - ) => - effects.store.set({ - value, - path: extractJsonPath(path), - }), - }, - - host: { - // static: (effects: Effects, id: string) => - // new StaticHost({ id, effects }), - // single: (effects: Effects, id: string) => - // new SingleHost({ id, effects }), - multi: (effects: Effects, id: string) => new MultiHost({ id, effects }), - }, - nullIfEmpty, - runCommand: async ( - effects: Effects, - image: { - id: keyof Manifest["images"] & T.ImageId - sharedRun?: boolean - }, - command: T.CommandType, - options: CommandOptions & { - mounts?: { path: string; options: MountOptions }[] - }, - ): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => { - return runCommand(effects, image, command, options) - }, - - createAction: < - ConfigType extends - | Record - | Config - | Config, - Type extends Record = ExtractConfigType, - >( - id: string, - metaData: Omit & { - input: Config | Config - }, - fn: (options: { - effects: Effects - input: Type - }) => Promise, - ) => { - const { input, ...rest } = metaData - return createAction( - id, - rest, - fn, - input, - ) - }, - configConstants: { smtpConfig }, - createInterface: ( - effects: Effects, - options: { - name: string - id: string - description: string - hasPrimary: boolean - type: ServiceInterfaceType - username: null | string - path: string - search: Record - schemeOverride: { ssl: Scheme; noSsl: Scheme } | null - masked: boolean - }, - ) => new ServiceInterfaceBuilder({ ...options, effects }), - getSystemSmtp: (effects: E) => - removeCallbackTypes(effects)(new GetSystemSmtp(effects)), - - getSslCerificate: ( - effects: E, - hostnames: string[], - algorithm?: T.Algorithm, - ) => - removeCallbackTypes(effects)( - new GetSslCertificate(effects, hostnames, algorithm), - ), - - createDynamicAction: < - ConfigType extends - | Record - | Config - | Config, - Type extends Record = ExtractConfigType, - >( - id: string, - metaData: (options: { - effects: Effects - }) => MaybePromise>, - fn: (options: { - effects: Effects - input: Type - }) => Promise, - input: Config | Config, - ) => { - return createAction( - id, - metaData, - fn, - input, - ) - }, - HealthCheck: { - of(o: HealthCheckParams) { - return healthCheck(o) - }, - }, - Dependency: { - of(data: Dependency["data"]) { - return new Dependency({ ...data }) - }, - }, - healthCheck: { - checkPortListening, - checkWebUrl, - runHealthScript, - }, - patterns, - setupActions: (...createdActions: CreatedAction[]) => - setupActions(...createdActions), - setupBackups: (...args: SetupBackupsParams) => - setupBackups(this.manifest, ...args), - setupConfig: < - ConfigType extends Config | Config, - Type extends Record = ExtractConfigType, - >( - spec: ConfigType, - write: Save, - read: Read, - ) => setupConfig(spec, write, read), - setupConfigRead: < - ConfigSpec extends - | Config, any> - | Config, never>, - >( - _configSpec: ConfigSpec, - fn: Read, - ) => fn, - setupConfigSave: < - ConfigSpec extends - | Config, any> - | Config, never>, - >( - _configSpec: ConfigSpec, - fn: Save, - ) => fn, - setupDependencyConfig: >( - config: Config | Config, - autoConfigs: { - [K in keyof Manifest["dependencies"]]: DependencyConfig< - Manifest, - Store, - Input, - any - > | null - }, - ) => setupDependencyConfig(config, autoConfigs), - setupDependencies: >( - fn: (options: { - effects: Effects - input: Input | null - }) => Promise, - ) => { - return async (options: { effects: Effects; input: Input }) => { - const dependencyType = await fn(options) - return await options.effects.setDependencies({ - dependencies: Object.entries(dependencyType).map( - ([ - id, - { - data: { versionRange, ...x }, - }, - ]) => ({ - id, - ...x, - ...(x.type === "running" - ? { - kind: "running", - healthChecks: x.healthChecks, - } - : { - kind: "exists", - }), - versionRange: versionRange.toString(), - }), - ), - }) - } - }, - setupInit: ( - versions: VersionGraph, - install: Install, - uninstall: Uninstall, - setInterfaces: SetInterfaces, - setDependencies: (options: { - effects: Effects - input: any - }) => Promise, - exposedStore: ExposedStorePaths, - ) => - setupInit( - versions, - install, - uninstall, - setInterfaces, - setDependencies, - exposedStore, - ), - setupInstall: (fn: InstallFn) => Install.of(fn), - setupInterfaces: < - ConfigInput extends Record, - Output extends InterfacesReceipt, - >( - config: Config, - fn: SetInterfaces, - ) => setupInterfaces(config, fn), - setupMain: ( - fn: (o: { - effects: MainEffects - started(onTerm: () => PromiseLike): PromiseLike - }) => Promise>, - ) => setupMain(fn), - setupVersionGraph: < - CurrentVersion extends string, - OtherVersions extends Array>, - >( - current: VersionInfo, - ...other: EnsureUniqueId - ) => setupVersionGraph(current, ...other), - setupProperties: - ( - fn: (options: { effects: Effects }) => Promise, - ): T.ExpectedExports.properties => - (options) => - fn(options).then(nullifyProperties), - setupUninstall: (fn: UninstallFn) => - setupUninstall(fn), - trigger: { - defaultTrigger, - cooldownTrigger, - changeOnFirstSuccess, - successFailure, - }, - Mounts: { - of() { - return Mounts.of() - }, - }, - Backups: { - volumes: ( - ...volumeNames: Array - ) => Backups.volumes(...volumeNames), - addSets: ( - ...options: BackupSet[] - ) => Backups.addSets(...options), - withOptions: (options?: Partial) => - Backups.with_options(options), - }, - Config: { - of: < - Spec extends Record | Value>, - >( - spec: Spec, - ) => Config.of(spec), - }, - Daemons: { - of(config: { - effects: Effects - started: (onTerm: () => PromiseLike) => PromiseLike - healthReceipts: HealthReceipt[] - }) { - return Daemons.of(config) - }, - }, - DependencyConfig: { - of< - LocalConfig extends Record, - RemoteConfig extends Record, - >({ - localConfigSpec, - remoteConfigSpec, - dependencyConfig, - update, - }: { - localConfigSpec: - | Config - | Config - remoteConfigSpec: - | Config - | Config - dependencyConfig: (options: { - effects: Effects - localConfig: LocalConfig - }) => Promise> - update?: Update, RemoteConfig> - }) { - return new DependencyConfig< - Manifest, - Store, - LocalConfig, - RemoteConfig - >(dependencyConfig, update) - }, - }, - List: { - text: List.text, - obj: >( - a: { - name: string - description?: string | null - warning?: string | null - /** Default [] */ - default?: [] - minLength?: number | null - maxLength?: number | null - }, - aSpec: { - spec: Config - displayAs?: null | string - uniqueBy?: null | UniqueBy - }, - ) => List.obj(a, aSpec), - dynamicText: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - /** Default = [] */ - default?: string[] - minLength?: number | null - maxLength?: number | null - disabled?: false | string - generate?: null | RandomString - spec: { - /** Default = false */ - masked?: boolean - placeholder?: string | null - minLength?: number | null - maxLength?: number | null - patterns: Pattern[] - /** Default = "text" */ - inputmode?: ListValueSpecText["inputmode"] - } - } - >, - ) => List.dynamicText(getA), - }, - VersionInfo: { - of: (options: VersionOptions) => - VersionInfo.of(options), - }, - StorePath: pathBuilder(), - Value: { - toggle: Value.toggle, - text: Value.text, - textarea: Value.textarea, - number: Value.number, - color: Value.color, - datetime: Value.datetime, - select: Value.select, - multiselect: Value.multiselect, - object: Value.object, - union: Value.union, - list: Value.list, - dynamicToggle: ( - a: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - default: boolean - disabled?: false | string - } - >, - ) => Value.dynamicToggle(a), - dynamicText: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - required: RequiredDefault - - /** Default = false */ - masked?: boolean - placeholder?: string | null - minLength?: number | null - maxLength?: number | null - patterns?: Pattern[] - /** Default = 'text' */ - inputmode?: ValueSpecText["inputmode"] - generate?: null | RandomString - } - >, - ) => Value.dynamicText(getA), - dynamicTextarea: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - required: boolean - minLength?: number | null - maxLength?: number | null - placeholder?: string | null - disabled?: false | string - generate?: null | RandomString - } - >, - ) => Value.dynamicTextarea(getA), - dynamicNumber: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - required: RequiredDefault - min?: number | null - max?: number | null - /** Default = '1' */ - step?: number | null - integer: boolean - units?: string | null - placeholder?: string | null - disabled?: false | string - } - >, - ) => Value.dynamicNumber(getA), - dynamicColor: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - required: RequiredDefault - - disabled?: false | string - } - >, - ) => Value.dynamicColor(getA), - dynamicDatetime: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - required: RequiredDefault - /** Default = 'datetime-local' */ - inputmode?: ValueSpecDatetime["inputmode"] - min?: string | null - max?: string | null - disabled?: false | string - } - >, - ) => Value.dynamicDatetime(getA), - dynamicSelect: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - required: RequiredDefault - values: Record - disabled?: false | string - } - >, - ) => Value.dynamicSelect(getA), - dynamicMultiselect: ( - getA: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - default: string[] - values: Record - minLength?: number | null - maxLength?: number | null - disabled?: false | string - } - >, - ) => Value.dynamicMultiselect(getA), - filteredUnion: < - Required extends RequiredDefault, - Type extends Record, - >( - getDisabledFn: LazyBuild, - a: { - name: string - description?: string | null - warning?: string | null - required: Required - }, - aVariants: Variants | Variants, - ) => - Value.filteredUnion( - getDisabledFn, - a, - aVariants, - ), - - dynamicUnion: < - Required extends RequiredDefault, - Type extends Record, - >( - getA: LazyBuild< - Store, - { - disabled: string[] | false | string - name: string - description?: string | null - warning?: string | null - required: Required - } - >, - aVariants: Variants | Variants, - ) => Value.dynamicUnion(getA, aVariants), - }, - Variants: { - of: < - VariantValues extends { - [K in string]: { - name: string - spec: Config - } - }, - >( - a: VariantValues, - ) => Variants.of(a), - }, - } - } -} - -export async function runCommand( - effects: Effects, - image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean }, - command: string | [string, ...string[]], - options: CommandOptions & { - mounts?: { path: string; options: MountOptions }[] - }, -): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> { - const commands = splitCommand(command) - return Overlay.with(effects, image, options.mounts || [], (overlay) => - overlay.exec(commands), - ) -} -function nullifyProperties(value: T.SdkPropertiesReturn): T.PropertiesReturn { - return Object.fromEntries( - Object.entries(value).map(([k, v]) => [k, nullifyProperties_(v)]), - ) -} -function nullifyProperties_(value: T.SdkPropertiesValue): T.PropertiesValue { - if (value.type === "string") { - return { description: null, copyable: null, qr: null, ...value } - } - return { - description: null, - ...value, - value: Object.fromEntries( - Object.entries(value.value).map(([k, v]) => [k, nullifyProperties_(v)]), - ), - } -} diff --git a/sdk/lib/actions/createAction.ts b/sdk/lib/actions/createAction.ts deleted file mode 100644 index 4fa858d56..000000000 --- a/sdk/lib/actions/createAction.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as T from "../types" -import { Config, ExtractConfigType } from "../config/builder/config" - -import { ActionMetadata, ActionResult, Effects, ExportedAction } from "../types" - -export type MaybeFn = - | Value - | ((options: { effects: Effects }) => Promise | Value) -export class CreatedAction< - Manifest extends T.Manifest, - Store, - ConfigType extends - | Record - | Config - | Config, - Type extends Record = ExtractConfigType, -> { - private constructor( - public readonly id: string, - public readonly myMetadata: MaybeFn< - Manifest, - Store, - Omit - >, - readonly fn: (options: { - effects: Effects - input: Type - }) => Promise, - readonly input: Config, - public validator = input.validator, - ) {} - - static of< - Manifest extends T.Manifest, - Store, - ConfigType extends - | Record - | Config - | Config, - Type extends Record = ExtractConfigType, - >( - id: string, - metadata: MaybeFn>, - fn: (options: { effects: Effects; input: Type }) => Promise, - inputConfig: Config | Config, - ) { - return new CreatedAction( - id, - metadata, - fn, - inputConfig as Config, - ) - } - - exportedAction: ExportedAction = ({ effects, input }) => { - return this.fn({ - effects, - input: this.validator.unsafeCast(input), - }) - } - - run = async ({ effects, input }: { effects: Effects; input?: Type }) => { - return this.fn({ - effects, - input: this.validator.unsafeCast(input), - }) - } - - async metadata(options: { effects: Effects }) { - if (this.myMetadata instanceof Function) - return await this.myMetadata(options) - return this.myMetadata - } - - async ActionMetadata(options: { effects: Effects }): Promise { - return { - ...(await this.metadata(options)), - input: await this.input.build(options), - } - } - - async getConfig({ effects }: { effects: Effects }) { - return this.input.build({ - effects, - }) - } -} - -export const createAction = CreatedAction.of diff --git a/sdk/lib/actions/index.ts b/sdk/lib/actions/index.ts deleted file mode 100644 index 603684b67..000000000 --- a/sdk/lib/actions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import "./createAction" - -import "./setupActions" diff --git a/sdk/lib/actions/setupActions.ts b/sdk/lib/actions/setupActions.ts deleted file mode 100644 index 07b4e2606..000000000 --- a/sdk/lib/actions/setupActions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as T from "../types" -import { Effects, ExpectedExports } from "../types" -import { CreatedAction } from "./createAction" - -export function setupActions( - ...createdActions: CreatedAction[] -) { - const myActions = async (options: { effects: Effects }) => { - const actions: Record> = {} - for (const action of createdActions) { - actions[action.id] = action - } - return actions - } - const answer: { - actions: ExpectedExports.actions - actionsMetadata: ExpectedExports.actionsMetadata - } = { - actions(options: { effects: Effects }) { - return myActions(options) - }, - async actionsMetadata({ effects }: { effects: Effects }) { - return Promise.all( - createdActions.map((x) => x.ActionMetadata({ effects })), - ) - }, - } - return answer -} diff --git a/sdk/lib/backup/Backups.ts b/sdk/lib/backup/Backups.ts deleted file mode 100644 index 031ac4e4c..000000000 --- a/sdk/lib/backup/Backups.ts +++ /dev/null @@ -1,209 +0,0 @@ -import * as T from "../types" - -import * as child_process from "child_process" -import { promises as fsPromises } from "fs" -import { asError } from "../util" - -export type BACKUP = "BACKUP" -export const DEFAULT_OPTIONS: T.BackupOptions = { - delete: true, - force: true, - ignoreExisting: false, - exclude: [], -} -export type BackupSet = { - srcPath: string - srcVolume: Volumes | BACKUP - dstPath: string - dstVolume: Volumes | BACKUP - options?: Partial -} -/** - * This utility simplifies the volume backup process. - * ```ts - * export const { createBackup, restoreBackup } = Backups.volumes("main").build(); - * ``` - * - * Changing the options of the rsync, (ie exludes) use either - * ```ts - * Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() - * // or - * Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() - * ``` - * - * Using the more fine control, using the addSets for more control - * ```ts - * Backups.addSets({ - * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP - * }, { - * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} - * ).build()q - * ``` - */ -export class Backups { - static BACKUP: BACKUP = "BACKUP" - - private constructor( - private options = DEFAULT_OPTIONS, - private backupSet = [] as BackupSet[], - ) {} - static volumes( - ...volumeNames: Array - ): Backups { - return new Backups().addSets( - ...volumeNames.map((srcVolume) => ({ - srcVolume, - srcPath: "./", - dstPath: `./${srcVolume}/`, - dstVolume: Backups.BACKUP, - })), - ) - } - static addSets( - ...options: BackupSet[] - ) { - return new Backups().addSets(...options) - } - static with_options( - options?: Partial, - ) { - return new Backups({ ...DEFAULT_OPTIONS, ...options }) - } - - static withOptions = Backups.with_options - setOptions(options?: Partial) { - this.options = { - ...this.options, - ...options, - } - return this - } - volumes(...volumeNames: Array) { - return this.addSets( - ...volumeNames.map((srcVolume) => ({ - srcVolume, - srcPath: "./", - dstPath: `./${srcVolume}/`, - dstVolume: Backups.BACKUP, - })), - ) - } - addSets(...options: BackupSet[]) { - options.forEach((x) => - this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }), - ) - return this - } - build(pathMaker: T.PathMaker) { - const createBackup: T.ExpectedExports.createBackup = async ({ - effects, - }) => { - for (const item of this.backupSet) { - const rsyncResults = await runRsync( - { - dstPath: item.dstPath, - dstVolume: item.dstVolume, - options: { ...this.options, ...item.options }, - srcPath: item.srcPath, - srcVolume: item.srcVolume, - }, - pathMaker, - ) - await rsyncResults.wait() - } - return - } - const restoreBackup: T.ExpectedExports.restoreBackup = async ({ - effects, - }) => { - for (const item of this.backupSet) { - const rsyncResults = await runRsync( - { - dstPath: item.dstPath, - dstVolume: item.dstVolume, - options: { ...this.options, ...item.options }, - srcPath: item.srcPath, - srcVolume: item.srcVolume, - }, - pathMaker, - ) - await rsyncResults.wait() - } - return - } - return { createBackup, restoreBackup } - } -} -function notEmptyPath(file: string) { - return ["", ".", "./"].indexOf(file) === -1 -} -async function runRsync( - rsyncOptions: { - srcVolume: string - dstVolume: string - srcPath: string - dstPath: string - options: T.BackupOptions - }, - pathMaker: T.PathMaker, -): Promise<{ - id: () => Promise - wait: () => Promise - progress: () => Promise -}> { - const { srcVolume, dstVolume, srcPath, dstPath, options } = rsyncOptions - - const command = "rsync" - const args: string[] = [] - if (options.delete) { - args.push("--delete") - } - if (options.force) { - args.push("--force") - } - if (options.ignoreExisting) { - args.push("--ignore-existing") - } - for (const exclude of options.exclude) { - args.push(`--exclude=${exclude}`) - } - args.push("-actAXH") - args.push("--info=progress2") - args.push("--no-inc-recursive") - args.push(pathMaker({ volume: srcVolume, path: srcPath })) - args.push(pathMaker({ volume: dstVolume, path: dstPath })) - const spawned = child_process.spawn(command, args, { detached: true }) - let percentage = 0.0 - spawned.stdout.on("data", (data: unknown) => { - const lines = String(data).replace("\r", "\n").split("\n") - for (const line of lines) { - const parsed = /$([0-9.]+)%/.exec(line)?.[1] - if (!parsed) continue - percentage = Number.parseFloat(parsed) - } - }) - - spawned.stderr.on("data", (data: unknown) => { - console.error(`Backups.runAsync`, asError(data)) - }) - - const id = async () => { - const pid = spawned.pid - if (pid === undefined) { - throw new Error("rsync process has no pid") - } - return String(pid) - } - const waitPromise = new Promise((resolve, reject) => { - spawned.on("exit", (code: any) => { - if (code === 0) { - resolve(null) - } else { - reject(new Error(`rsync exited with code ${code}`)) - } - }) - }) - const wait = () => waitPromise - const progress = () => Promise.resolve(percentage) - return { id, wait, progress } -} diff --git a/sdk/lib/backup/setupBackups.ts b/sdk/lib/backup/setupBackups.ts deleted file mode 100644 index c12f1d2ed..000000000 --- a/sdk/lib/backup/setupBackups.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Backups } from "./Backups" - -import * as T from "../types" -import { _ } from "../util" - -export type SetupBackupsParams = Array< - M["volumes"][number] | Backups -> - -export function setupBackups( - manifest: M, - ...args: _> -) { - const backups = Array>() - const volumes = new Set() - for (const arg of args) { - if (arg instanceof Backups) { - backups.push(arg) - } else { - volumes.add(arg) - } - } - backups.push(Backups.volumes(...volumes)) - const answer: { - createBackup: T.ExpectedExports.createBackup - restoreBackup: T.ExpectedExports.restoreBackup - } = { - get createBackup() { - return (async (options) => { - for (const backup of backups) { - await backup.build(options.pathMaker).createBackup(options) - } - }) as T.ExpectedExports.createBackup - }, - get restoreBackup() { - return (async (options) => { - for (const backup of backups) { - await backup.build(options.pathMaker).restoreBackup(options) - } - await options.effects.setDataVersion({ version: manifest.version }) - }) as T.ExpectedExports.restoreBackup - }, - } - return answer -} diff --git a/sdk/lib/config/configDependencies.ts b/sdk/lib/config/configDependencies.ts deleted file mode 100644 index d9865f25c..000000000 --- a/sdk/lib/config/configDependencies.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as T from "../types" - -export type ConfigDependencies = { - exists(id: keyof T["dependencies"]): T.Dependencies[number] - running( - id: keyof T["dependencies"], - healthChecks: string[], - ): T.Dependencies[number] -} - -export const configDependenciesSet = < - T extends T.Manifest, ->(): ConfigDependencies => ({ - exists(id: keyof T["dependencies"]) { - return { - id, - kind: "exists", - } as T.Dependencies[number] - }, - - running(id: keyof T["dependencies"], healthChecks: string[]) { - return { - id, - kind: "running", - healthChecks, - } as T.Dependencies[number] - }, -}) diff --git a/sdk/lib/config/index.ts b/sdk/lib/config/index.ts deleted file mode 100644 index 35c3e274e..000000000 --- a/sdk/lib/config/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * as constants from "./configConstants" -export * as types from "./configTypes" -export * as builder from "./builder" diff --git a/sdk/lib/config/setupConfig.ts b/sdk/lib/config/setupConfig.ts deleted file mode 100644 index f354c81ed..000000000 --- a/sdk/lib/config/setupConfig.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as T from "../types" - -import * as D from "./configDependencies" -import { Config, ExtractConfigType } from "./builder/config" -import nullIfEmpty from "../util/nullIfEmpty" -import { InterfacesReceipt as InterfacesReceipt } from "../interfaces/setupInterfaces" - -declare const dependencyProof: unique symbol -export type DependenciesReceipt = void & { - [dependencyProof]: never -} - -export type Save< - A extends - | Record - | Config, any> - | Config, never>, -> = (options: { - effects: T.Effects - input: ExtractConfigType & Record -}) => Promise<{ - dependenciesReceipt: DependenciesReceipt - interfacesReceipt: InterfacesReceipt - restart: boolean -}> -export type Read< - Manifest extends T.Manifest, - Store, - A extends - | Record - | Config, any> - | Config, never>, -> = (options: { - effects: T.Effects -}) => Promise & Record)> -/** - * We want to setup a config export with a get and set, this - * is going to be the default helper to setup config, because it will help - * enforce that we have a spec, write, and reading. - * @param options - * @returns - */ -export function setupConfig< - Store, - ConfigType extends - | Record - | Config - | Config, - Manifest extends T.Manifest, - Type extends Record = ExtractConfigType, ->( - spec: Config | Config, - write: Save, - read: Read, -) { - const validator = spec.validator - return { - setConfig: (async ({ effects, input }) => { - if (!validator.test(input)) { - await console.error( - new Error(validator.errorMessage(input)?.toString()), - ) - return { error: "Set config type error for config" } - } - await effects.clearBindings() - await effects.clearServiceInterfaces() - const { restart } = await write({ - input: JSON.parse(JSON.stringify(input)) as any, - effects, - }) - if (restart) { - await effects.restart() - } - }) as T.ExpectedExports.setConfig, - getConfig: (async ({ effects }) => { - const configValue = nullIfEmpty((await read({ effects })) || null) - return { - spec: await spec.build({ - effects, - }), - config: configValue, - } - }) as T.ExpectedExports.getConfig, - } -} - -export default setupConfig diff --git a/sdk/lib/dependencies/DependencyConfig.ts b/sdk/lib/dependencies/DependencyConfig.ts deleted file mode 100644 index b48bf56d3..000000000 --- a/sdk/lib/dependencies/DependencyConfig.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as T from "../types" -import { deepEqual } from "../util/deepEqual" -import { deepMerge } from "../util/deepMerge" - -export type Update = (options: { - remoteConfig: RemoteConfig - queryResults: QueryResults -}) => Promise - -export class DependencyConfig< - Manifest extends T.Manifest, - Store, - Input extends Record, - RemoteConfig extends Record, -> { - static defaultUpdate = async (options: { - queryResults: unknown - remoteConfig: unknown - }): Promise => { - return deepMerge({}, options.remoteConfig, options.queryResults || {}) - } - constructor( - readonly dependencyConfig: (options: { - effects: T.Effects - localConfig: Input - }) => Promise>, - readonly update: Update< - void | T.DeepPartial, - RemoteConfig - > = DependencyConfig.defaultUpdate as any, - ) {} - - async query(options: { effects: T.Effects; localConfig: unknown }) { - return this.dependencyConfig({ - localConfig: options.localConfig as Input, - effects: options.effects, - }) - } -} diff --git a/sdk/lib/dependencies/setupDependencyConfig.ts b/sdk/lib/dependencies/setupDependencyConfig.ts deleted file mode 100644 index 2fde4bce5..000000000 --- a/sdk/lib/dependencies/setupDependencyConfig.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Config } from "../config/builder/config" - -import * as T from "../types" -import { DependencyConfig } from "./DependencyConfig" - -export function setupDependencyConfig< - Store, - Input extends Record, - Manifest extends T.Manifest, ->( - _config: Config | Config, - autoConfigs: { - [key in keyof Manifest["dependencies"] & string]: DependencyConfig< - Manifest, - Store, - Input, - any - > | null - }, -): T.ExpectedExports.dependencyConfig { - return autoConfigs -} diff --git a/sdk/lib/health/HealthReceipt.ts b/sdk/lib/health/HealthReceipt.ts deleted file mode 100644 index a0995ba0a..000000000 --- a/sdk/lib/health/HealthReceipt.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare const HealthProof: unique symbol -export type HealthReceipt = { - [HealthProof]: never -} diff --git a/sdk/lib/health/index.ts b/sdk/lib/health/index.ts deleted file mode 100644 index b6e1d26f5..000000000 --- a/sdk/lib/health/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import "./checkFns" - -import "./HealthReceipt" diff --git a/sdk/lib/index.ts b/sdk/lib/index.ts deleted file mode 100644 index 935ffc023..000000000 --- a/sdk/lib/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -export { Daemons } from "./mainFn/Daemons" -export { Overlay } from "./util/Overlay" -export { StartSdk } from "./StartSdk" -export { setupManifest } from "./manifest/setupManifest" -export { FileHelper } from "./util/fileHelper" -export { setupExposeStore } from "./store/setupExposeStore" -export { pathBuilder } from "./store/PathBuilder" -export { S9pk } from "./s9pk" -export { VersionRange, ExtendedVersion, Version } from "./exver" - -export * as actions from "./actions" -export * as backup from "./backup" -export * as config from "./config" -export * as CB from "./config/builder" -export * as CT from "./config/configTypes" -export * as dependencyConfig from "./dependencies" -export * as daemons from "./mainFn/Daemons" -export * as health from "./health" -export * as healthFns from "./health/checkFns" -export * as inits from "./inits" -export * as mainFn from "./mainFn" -export * as manifest from "./manifest" -export * as toml from "@iarna/toml" -export * as types from "./types" -export * as T from "./types" -export * as yaml from "yaml" -export * as startSdk from "./StartSdk" -export * as utils from "./util" -export * as matches from "ts-matches" -export * as YAML from "yaml" -export * as TOML from "@iarna/toml" diff --git a/sdk/lib/inits/setupInstall.ts b/sdk/lib/inits/setupInstall.ts deleted file mode 100644 index ab21380a0..000000000 --- a/sdk/lib/inits/setupInstall.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as T from "../types" - -export type InstallFn = (opts: { - effects: T.Effects -}) => Promise -export class Install { - private constructor(readonly fn: InstallFn) {} - static of( - fn: InstallFn, - ) { - return new Install(fn) - } - - async install({ effects }: Parameters[0]) { - await this.fn({ - effects, - }) - } -} - -export function setupInstall( - fn: InstallFn, -) { - return Install.of(fn) -} diff --git a/sdk/lib/inits/setupUninstall.ts b/sdk/lib/inits/setupUninstall.ts deleted file mode 100644 index 918f417e5..000000000 --- a/sdk/lib/inits/setupUninstall.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as T from "../types" - -export type UninstallFn = (opts: { - effects: T.Effects -}) => Promise -export class Uninstall { - private constructor(readonly fn: UninstallFn) {} - static of( - fn: UninstallFn, - ) { - return new Uninstall(fn) - } - - async uninstall({ - effects, - nextVersion, - }: Parameters[0]) { - if (!nextVersion) - await this.fn({ - effects, - }) - } -} - -export function setupUninstall( - fn: UninstallFn, -) { - return Uninstall.of(fn) -} diff --git a/sdk/lib/interfaces/setupInterfaces.ts b/sdk/lib/interfaces/setupInterfaces.ts deleted file mode 100644 index c82b69e0b..000000000 --- a/sdk/lib/interfaces/setupInterfaces.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Config } from "../config/builder/config" - -import * as T from "../types" -import { AddressReceipt } from "./AddressReceipt" - -export type InterfacesReceipt = Array -export type SetInterfaces< - Manifest extends T.Manifest, - Store, - ConfigInput extends Record, - Output extends InterfacesReceipt, -> = (opts: { effects: T.Effects; input: null | ConfigInput }) => Promise -export type SetupInterfaces = < - Manifest extends T.Manifest, - Store, - ConfigInput extends Record, - Output extends InterfacesReceipt, ->( - config: Config, - fn: SetInterfaces, -) => SetInterfaces -export const NO_INTERFACE_CHANGES = [] as InterfacesReceipt -export const setupInterfaces: SetupInterfaces = (_config, fn) => fn diff --git a/sdk/lib/mainFn/CommandController.ts b/sdk/lib/mainFn/CommandController.ts deleted file mode 100644 index 8635f76e6..000000000 --- a/sdk/lib/mainFn/CommandController.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { DEFAULT_SIGTERM_TIMEOUT } from "." -import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk" - -import * as T from "../types" -import { asError } from "../util/asError" -import { - ExecSpawnable, - MountOptions, - NonDestroyableOverlay, - Overlay, -} from "../util/Overlay" -import { splitCommand } from "../util/splitCommand" -import { cpExecFile, cpExec } from "./Daemons" - -export class CommandController { - private constructor( - readonly runningAnswer: Promise, - private readonly overlay: ExecSpawnable, - readonly pid: number | undefined, - readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, - ) {} - static of() { - return async ( - effects: T.Effects, - imageId: { - id: keyof Manifest["images"] & T.ImageId - sharedRun?: boolean - }, - command: T.CommandType, - options: { - // Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms - sigtermTimeout?: number - mounts?: { path: string; options: MountOptions }[] - overlay?: ExecSpawnable - env?: - | { - [variable: string]: string - } - | undefined - cwd?: string | undefined - user?: string | undefined - onStdout?: (x: Buffer) => void - onStderr?: (x: Buffer) => void - }, - ) => { - const commands = splitCommand(command) - const overlay = - options.overlay || - (await (async () => { - const overlay = await Overlay.of(effects, imageId) - for (let mount of options.mounts || []) { - await overlay.mount(mount.options, mount.path) - } - return overlay - })()) - const childProcess = await overlay.spawn(commands, { - env: options.env, - }) - const answer = new Promise((resolve, reject) => { - childProcess.stdout.on( - "data", - options.onStdout ?? - ((data: any) => { - console.log(data.toString()) - }), - ) - childProcess.stderr.on( - "data", - options.onStderr ?? - ((data: any) => { - console.error(asError(data)) - }), - ) - - childProcess.on("exit", (code: any) => { - if (code === 0) { - return resolve(null) - } - return reject(new Error(`${commands[0]} exited with code ${code}`)) - }) - }) - - const pid = childProcess.pid - - return new CommandController(answer, overlay, pid, options.sigtermTimeout) - } - } - get nonDestroyableOverlay() { - return new NonDestroyableOverlay(this.overlay) - } - async wait({ timeout = NO_TIMEOUT } = {}) { - if (timeout > 0) - setTimeout(() => { - this.term() - }, timeout) - try { - return await this.runningAnswer - } finally { - if (this.pid !== undefined) { - await cpExecFile("pkill", ["-9", "-s", String(this.pid)]).catch( - (_) => {}, - ) - } - await this.overlay.destroy?.().catch((_) => {}) - } - } - async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) { - if (this.pid === undefined) return - try { - await cpExecFile("pkill", [ - `-${signal.replace("SIG", "")}`, - "-s", - String(this.pid), - ]) - - const didTimeout = await waitSession(this.pid, timeout) - if (didTimeout) { - await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch( - (_) => {}, - ) - } - } finally { - await this.overlay.destroy?.() - } - } -} - -function waitSession( - sid: number, - timeout = NO_TIMEOUT, - interval = 100, -): Promise { - let nextInterval = interval * 2 - if (timeout >= 0 && timeout < nextInterval) { - nextInterval = timeout - } - let nextTimeout = timeout - if (timeout > 0) { - if (timeout >= interval) { - nextTimeout -= interval - } else { - nextTimeout = 0 - } - } - return new Promise((resolve, reject) => { - let next: NodeJS.Timeout | null = null - if (timeout !== 0) { - next = setTimeout(() => { - waitSession(sid, nextTimeout, nextInterval).then(resolve, reject) - }, interval) - } - cpExecFile("ps", [`--sid=${sid}`, "-o", "--pid="]).then( - (_) => { - if (timeout === 0) { - resolve(true) - } - }, - (e) => { - if (next) { - clearTimeout(next) - } - if (typeof e === "object" && e && "code" in e && e.code) { - resolve(false) - } else { - reject(e) - } - }, - ) - }) -} diff --git a/sdk/lib/manifest/ManifestTypes.ts b/sdk/lib/manifest/ManifestTypes.ts deleted file mode 100644 index cc564de2d..000000000 --- a/sdk/lib/manifest/ManifestTypes.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ValidateExVer, ValidateExVers } from "../exver" -import { - ActionMetadata, - HardwareRequirements, - ImageConfig, - ImageId, - ImageSource, -} from "../types" - -export type SDKManifest = { - /** The package identifier used by the OS. This must be unique amongst all other known packages */ - readonly id: string - /** A human readable service title */ - readonly title: string - /** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/ - readonly license: string // name of license - /** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this), - * any scripts necessary for configuration, backups, actions, or health checks (more below). This key - * must exist. But could be embedded into the source repository - */ - readonly wrapperRepo: string - /** The original project repository URL. There is no upstream repo in this example */ - readonly upstreamRepo: string - /** URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues */ - readonly supportSite: string - /** URL to the marketing site for the project. If there is no marketing site, it can link to the original project repository */ - readonly marketingSite: string - /** URL where users can donate to the upstream project */ - readonly donationUrl: string | null - /**Human readable descriptions for the service. These are used throughout the StartOS user interface, primarily in the marketplace. */ - readonly description: { - /**This is the first description visible to the user in the marketplace */ - readonly short: string - /** This description will display with additional details in the service's individual marketplace page */ - readonly long: string - } - - /** Defines the os images needed to run the container processes */ - readonly images: Record - /** This denotes readonly asset directories that should be available to mount to the container. - * These directories are expected to be found in `assets/` at pack time. - **/ - readonly assets: string[] - /** This denotes any data volumes that should be available to mount to the container */ - readonly volumes: string[] - - readonly alerts?: { - readonly install?: string | null - readonly update?: string | null - readonly uninstall?: string | null - readonly restore?: string | null - readonly start?: string | null - readonly stop?: string | null - } - readonly hasConfig?: boolean - readonly dependencies: Readonly> - readonly hardwareRequirements?: { - readonly device?: { display?: RegExp; processor?: RegExp } - readonly ram?: number | null - readonly arch?: string[] | null - } -} - -export type SDKImageConfig = { - source: Exclude - arch?: string[] - emulateMissingAs?: string | null -} - -export type ManifestDependency = { - /** - * A human readable explanation on what the dependency is used for - */ - readonly description: string | null - /** - * Determines if the dependency is optional or not. Times that optional that are good include such situations - * such as being able to toggle other services or to use a different service for the same purpose. - */ - readonly optional: boolean - /** - * A url or local path for an s9pk that satisfies this dependency - */ - readonly s9pk: string -} diff --git a/sdk/lib/manifest/index.ts b/sdk/lib/manifest/index.ts deleted file mode 100644 index 806ef5e61..000000000 --- a/sdk/lib/manifest/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import "./setupManifest" -import "./ManifestTypes" diff --git a/sdk/lib/osBindings/ImageSource.ts b/sdk/lib/osBindings/ImageSource.ts deleted file mode 100644 index a71684e46..000000000 --- a/sdk/lib/osBindings/ImageSource.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -export type ImageSource = - | "packed" - | { dockerBuild: { workdir: string | null; dockerfile: string | null } } - | { dockerTag: string } diff --git a/sdk/lib/osBindings/Status.ts b/sdk/lib/osBindings/Status.ts deleted file mode 100644 index b784f4d6a..000000000 --- a/sdk/lib/osBindings/Status.ts +++ /dev/null @@ -1,4 +0,0 @@ -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { MainStatus } from "./MainStatus" - -export type Status = { configured: boolean; main: MainStatus } diff --git a/sdk/lib/store/setupExposeStore.ts b/sdk/lib/store/setupExposeStore.ts deleted file mode 100644 index 9272a9a6b..000000000 --- a/sdk/lib/store/setupExposeStore.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Affine, _ } from "../util" -import { PathBuilder, extractJsonPath, pathBuilder } from "./PathBuilder" - -export type ExposedStorePaths = string[] & Affine<"ExposedStorePaths"> - -export const setupExposeStore = >( - fn: (pathBuilder: PathBuilder) => PathBuilder[], -) => { - return fn(pathBuilder()).map( - (x) => extractJsonPath(x) as string, - ) as ExposedStorePaths -} diff --git a/sdk/lib/test/output.sdk.ts b/sdk/lib/test/output.sdk.ts deleted file mode 100644 index 3d8058bfa..000000000 --- a/sdk/lib/test/output.sdk.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { StartSdk } from "../StartSdk" -import { setupManifest } from "../manifest/setupManifest" -import { VersionInfo } from "../versionInfo/VersionInfo" -import { VersionGraph } from "../versionInfo/setupVersionGraph" - -export type Manifest = any -export const sdk = StartSdk.of() - .withManifest( - setupManifest( - { - id: "testOutput", - title: "", - license: "", - replaces: [], - wrapperRepo: "", - upstreamRepo: "", - supportSite: "", - marketingSite: "", - donationUrl: null, - description: { - short: "", - long: "", - }, - containers: {}, - images: {}, - volumes: [], - assets: [], - alerts: { - install: null, - update: null, - uninstall: null, - restore: null, - start: null, - stop: null, - }, - dependencies: { - "remote-test": { - description: "", - optional: false, - s9pk: "https://example.com/remote-test.s9pk", - }, - }, - }, - VersionGraph.of( - VersionInfo.of({ - version: "1.0.0:0", - releaseNotes: "", - migrations: {}, - }) - .satisfies("#other:1.0.0:0") - .satisfies("#other:2.0.0:0"), - ), - ), - ) - .withStore<{ storeRoot: { storeLeaf: "value" } }>() - .build(true) diff --git a/sdk/lib/test/setupDependencyConfig.test.ts b/sdk/lib/test/setupDependencyConfig.test.ts deleted file mode 100644 index 622559eb6..000000000 --- a/sdk/lib/test/setupDependencyConfig.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { sdk } from "./output.sdk" - -describe("setupDependencyConfig", () => { - test("test", () => { - const testConfig = sdk.Config.of({ - test: sdk.Value.text({ - name: "testValue", - required: false, - }), - }) - - const testConfig2 = sdk.Config.of({ - test2: sdk.Value.text({ - name: "testValue2", - required: false, - }), - }) - const remoteTest = sdk.DependencyConfig.of({ - localConfigSpec: testConfig, - remoteConfigSpec: testConfig2, - dependencyConfig: async ({}) => {}, - }) - sdk.setupDependencyConfig(testConfig, { - "remote-test": remoteTest, - }) - }) -}) diff --git a/sdk/lib/types.ts b/sdk/lib/types.ts deleted file mode 100644 index 2611a0b84..000000000 --- a/sdk/lib/types.ts +++ /dev/null @@ -1,509 +0,0 @@ -export * as configTypes from "./config/configTypes" - -import { - DependencyRequirement, - SetHealth, - NamedHealthCheckResult, - SetMainStatus, - ServiceInterface, - Host, - ExportServiceInterfaceParams, - GetPrimaryUrlParams, - LanInfo, - BindParams, - Manifest, - CheckDependenciesResult, - ActionId, - HostId, -} from "./osBindings" - -import { MainEffects, ServiceInterfaceType, Signals } from "./StartSdk" -import { InputSpec } from "./config/configTypes" -import { DependenciesReceipt } from "./config/setupConfig" -import { BindOptions, Scheme } from "./interfaces/Host" -import { Daemons } from "./mainFn/Daemons" -import { StorePath } from "./store/PathBuilder" -import { ExposedStorePaths } from "./store/setupExposeStore" -import { UrlString } from "./util/getServiceInterface" -export * from "./osBindings" -export { SDKManifest } from "./manifest/ManifestTypes" -export { HealthReceipt } from "./health/HealthReceipt" - -export type PathMaker = (options: { volume: string; path: string }) => string -export type ExportedAction = (options: { - effects: Effects - input?: Record -}) => Promise -export type MaybePromise = Promise | A -export namespace ExpectedExports { - version: 1 - /** Set configuration is called after we have modified and saved the configuration in the start9 ui. Use this to make a file for the docker to read from for configuration. */ - export type setConfig = (options: { - effects: Effects - input: Record - }) => Promise - /** Get configuration returns a shape that describes the format that the start9 ui will generate, and later send to the set config */ - export type getConfig = (options: { effects: Effects }) => Promise - // /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */ - // export type dependencies = Dependencies; - /** For backing up service data though the startOS UI */ - export type createBackup = (options: { - effects: Effects - pathMaker: PathMaker - }) => Promise - /** For restoring service data that was previously backed up using the startOS UI create backup flow. Backup restores are also triggered via the startOS UI, or doing a system restore flow during setup. */ - export type restoreBackup = (options: { - effects: Effects - pathMaker: PathMaker - }) => Promise - - // /** Health checks are used to determine if the service is working properly after starting - // * A good use case is if we are using a web server, seeing if we can get to the web server. - // */ - // export type health = { - // /** Should be the health check id */ - // [id: string]: (options: { effects: Effects; input: TimeMs }) => Promise; - // }; - - /** - * Actions are used so we can effect the service, like deleting a directory. - * One old use case is to add a action where we add a file, that will then be run during the - * service starting, and that file would indicate that it would rescan all the data. - */ - export type actions = (options: { effects: Effects }) => MaybePromise<{ - [id: string]: { - run: ExportedAction - getConfig: (options: { effects: Effects }) => Promise - } - }> - - export type actionsMetadata = (options: { - effects: Effects - }) => Promise> - - /** - * This is the entrypoint for the main container. Used to start up something like the service that the - * package represents, like running a bitcoind in a bitcoind-wrapper. - */ - export type main = (options: { - effects: MainEffects - started(onTerm: () => PromiseLike): PromiseLike - }) => Promise> - - /** - * After a shutdown, if we wanted to do any operations to clean up things, like - * set the action as unavailable or something. - */ - export type afterShutdown = (options: { - effects: Effects - }) => Promise - - /** - * Every time a package completes an install, this function is called before the main. - * Can be used to do migration like things. - */ - export type init = (options: { effects: Effects }) => Promise - /** This will be ran during any time a package is uninstalled, for example during a update - * this will be called. - */ - export type uninit = (options: { - effects: Effects - nextVersion: null | string - }) => Promise - - /** Auto configure is used to make sure that other dependencies have the values t - * that this service could use. - */ - export type dependencyConfig = Record - - export type properties = (options: { - effects: Effects - }) => Promise - - export type manifest = Manifest -} -export type ABI = { - setConfig: ExpectedExports.setConfig - getConfig: ExpectedExports.getConfig - createBackup: ExpectedExports.createBackup - restoreBackup: ExpectedExports.restoreBackup - actions: ExpectedExports.actions - actionsMetadata: ExpectedExports.actionsMetadata - main: ExpectedExports.main - afterShutdown: ExpectedExports.afterShutdown - init: ExpectedExports.init - uninit: ExpectedExports.uninit - dependencyConfig: ExpectedExports.dependencyConfig - properties: ExpectedExports.properties - manifest: ExpectedExports.manifest -} -export type TimeMs = number -export type VersionString = string - -/** - * AutoConfigure is used as the value to the key of package id, - * this is used to make sure that other dependencies have the values that this service could use. - */ -export type DependencyConfig = { - /** During autoconfigure, we have access to effects and local data. We are going to figure out all the data that we need and send it to update. For the sdk it is the desired delta */ - query(options: { effects: Effects }): Promise - /** This is the second part. Given the query results off the previous function, we will determine what to change the remote config to. In our sdk normall we are going to use the previous as a deep merge. */ - update(options: { - queryResults: unknown - remoteConfig: unknown - }): Promise -} - -export type ConfigRes = { - /** This should be the previous config, that way during set config we start with the previous */ - config?: null | Record - /** Shape that is describing the form in the ui */ - spec: InputSpec -} - -declare const DaemonProof: unique symbol -export type DaemonReceipt = { - [DaemonProof]: never -} -export type Daemon = { - wait(): Promise - term(): Promise - [DaemonProof]: never -} - -export type HealthStatus = NamedHealthCheckResult["result"] -export type SmtpValue = { - server: string - port: number - from: string - login: string - password: string | null | undefined -} - -export type CommandType = string | [string, ...string[]] - -export type DaemonReturned = { - wait(): Promise - term(options?: { signal?: Signals; timeout?: number }): Promise -} - -export type ActionMetadata = { - name: string - description: string - warning: string | null - input: InputSpec - disabled: boolean - allowedStatuses: "onlyRunning" | "onlyStopped" | "any" - /** - * So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions - */ - group: string | null -} -export declare const hostName: unique symbol -// asdflkjadsf.onion | 1.2.3.4 -export type Hostname = string & { [hostName]: never } - -export type HostnameInfoIp = { - kind: "ip" - networkInterfaceId: string - public: boolean - hostname: - | { - kind: "ipv4" | "ipv6" | "local" - value: string - port: number | null - sslPort: number | null - } - | { - kind: "domain" - domain: string - subdomain: string | null - port: number | null - sslPort: number | null - } -} - -export type HostnameInfoOnion = { - kind: "onion" - hostname: { value: string; port: number | null; sslPort: number | null } -} - -export type HostnameInfo = HostnameInfoIp | HostnameInfoOnion - -export type ServiceInterfaceId = string - -export { ServiceInterface } -export type ExposeServicePaths = { - /** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */ - paths: ExposedStorePaths -} - -export type SdkPropertiesValue = - | { - type: "object" - value: { [k: string]: SdkPropertiesValue } - description?: string - } - | { - type: "string" - /** The value to display to the user */ - value: string - /** A human readable description or explanation of the value */ - description?: string - /** Whether or not to mask the value, for example, when displaying a password */ - masked: boolean - /** Whether or not to include a button for copying the value to clipboard */ - copyable?: boolean - /** Whether or not to include a button for displaying the value as a QR code */ - qr?: boolean - } - -export type SdkPropertiesReturn = { - [key: string]: SdkPropertiesValue -} - -export type PropertiesValue = - | { - type: "object" - value: { [k: string]: PropertiesValue } - description: string | null - } - | { - type: "string" - /** The value to display to the user */ - value: string - /** A human readable description or explanation of the value */ - description: string | null - /** Whether or not to mask the value, for example, when displaying a password */ - masked: boolean - /** Whether or not to include a button for copying the value to clipboard */ - copyable: boolean | null - /** Whether or not to include a button for displaying the value as a QR code */ - qr: boolean | null - } - -export type PropertiesReturn = { - [key: string]: PropertiesValue -} - -/** Used to reach out from the pure js runtime */ -export type Effects = { - // action - - /** Run an action exported by a service */ - executeAction(opts: { - packageId?: PackageId - actionId: ActionId - input: Input - }): Promise - /** Define an action that can be invoked by a user or service */ - exportAction(options: { - id: ActionId - metadata: ActionMetadata - }): Promise - /** Remove all exported actions */ - clearActions(): Promise - - // config - - /** Returns whether or not the package has been configured */ - getConfigured(options: { packageId?: PackageId }): Promise - /** Indicates that this package has been configured. Called during setConfig or init */ - setConfigured(options: { configured: boolean }): Promise - - // control - - /** restart this service's main function */ - restart(): Promise - /** stop this service's main function */ - shutdown(): Promise - /** indicate to the host os what runstate the service is in */ - setMainStatus(options: SetMainStatus): Promise - - // dependency - - /** Set the dependencies of what the service needs, usually run during the set config as a best practice */ - setDependencies(options: { - dependencies: Dependencies - }): Promise - /** Get the list of the dependencies, both the dynamic set by the effect of setDependencies and the end result any required in the manifest */ - getDependencies(): Promise - /** Test whether current dependency requirements are satisfied */ - checkDependencies(options: { - packageIds?: PackageId[] - }): Promise - /** mount a volume of a dependency */ - mount(options: { - location: string - target: { - packageId: string - volumeId: string - subpath: string | null - readonly: boolean - } - }): Promise - /** Returns a list of the ids of all installed packages */ - getInstalledPackages(): Promise - /** grants access to certain paths in the store to dependents */ - exposeForDependents(options: { paths: string[] }): Promise - - // health - - /** sets the result of a health check */ - setHealth(o: SetHealth): Promise - - // image - - /** A low level api used by Overlay */ - createOverlayedImage(options: { imageId: string }): Promise<[string, string]> - /** A low level api used by Overlay */ - destroyOverlayedImage(options: { guid: string }): Promise - - // net - - // bind - /** Creates a host connected to the specified port with the provided options */ - bind(options: BindParams): Promise - /** Get the port address for a service */ - getServicePortForward(options: { - packageId?: PackageId - hostId: HostId - internalPort: number - }): Promise - /** Removes all network bindings */ - clearBindings(): Promise - // host - /** Returns information about the specified host, if it exists */ - getHostInfo(options: { - packageId?: PackageId - hostId: HostId - callback?: () => void - }): Promise - /** Returns the primary url that a user has selected for a host, if it exists */ - getPrimaryUrl(options: { - packageId?: PackageId - hostId: HostId - callback?: () => void - }): Promise - /** Returns the IP address of the container */ - getContainerIp(): Promise - // interface - /** Creates an interface bound to a specific host and port to show to the user */ - exportServiceInterface(options: ExportServiceInterfaceParams): Promise - /** Returns an exported service interface */ - getServiceInterface(options: { - packageId?: PackageId - serviceInterfaceId: ServiceInterfaceId - callback?: () => void - }): Promise - /** Returns all exported service interfaces for a package */ - listServiceInterfaces(options: { - packageId?: PackageId - callback?: () => void - }): Promise> - /** Removes all service interfaces */ - clearServiceInterfaces(): Promise - // ssl - /** Returns a PEM encoded fullchain for the hostnames specified */ - getSslCertificate: (options: { - hostnames: string[] - algorithm?: "ecdsa" | "ed25519" - callback?: () => void - }) => Promise<[string, string, string]> - /** Returns a PEM encoded private key corresponding to the certificate for the hostnames specified */ - getSslKey: (options: { - hostnames: string[] - algorithm?: "ecdsa" | "ed25519" - }) => Promise - - // store - - store: { - /** Get a value in a json like data, can be observed and subscribed */ - get(options: { - /** If there is no packageId it is assumed the current package */ - packageId?: string - /** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */ - path: StorePath - callback?: () => void - }): Promise - /** Used to store values that can be accessed and subscribed to */ - set(options: { - /** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */ - path: StorePath - value: ExtractStore - }): Promise - } - /** sets the version that this service's data has been migrated to */ - setDataVersion(options: { version: string }): Promise - /** returns the version that this service's data has been migrated to */ - getDataVersion(): Promise - - // system - - /** Returns globally configured SMTP settings, if they exist */ - getSystemSmtp(options: { callback?: () => void }): Promise -} - -/** rsync options: https://linux.die.net/man/1/rsync - */ -export type BackupOptions = { - delete: boolean - force: boolean - ignoreExisting: boolean - exclude: string[] -} -/** - * This is the metadata that is returned from the metadata call. - */ -export type Metadata = { - fileType: string - isDir: boolean - isFile: boolean - isSymlink: boolean - len: number - modified?: Date - accessed?: Date - created?: Date - readonly: boolean - uid: number - gid: number - mode: number -} - -export type MigrationRes = { - configured: boolean -} - -export type ActionResult = { - message: string - value: null | { - value: string - copyable: boolean - qr: boolean - } -} -export type SetResult = { - dependsOn: DependsOn - signal: Signals -} - -export type PackageId = string -export type Message = string -export type DependencyKind = "running" | "exists" - -export type DependsOn = { - [packageId: string]: string[] | readonly string[] -} - -export type KnownError = - | { error: string } - | { - errorCode: [number, string] | readonly [number, string] - } - -export type Dependencies = Array - -export type DeepPartial = T extends {} - ? { [P in keyof T]?: DeepPartial } - : T diff --git a/sdk/lib/util/asError.ts b/sdk/lib/util/asError.ts deleted file mode 100644 index 6e98afb6a..000000000 --- a/sdk/lib/util/asError.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const asError = (e: unknown) => { - if (e instanceof Error) { - return new Error(e as any) - } - return new Error(`${e}`) -} diff --git a/sdk/lib/util/deepMerge.ts b/sdk/lib/util/deepMerge.ts deleted file mode 100644 index ae68c242f..000000000 --- a/sdk/lib/util/deepMerge.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { object } from "ts-matches" - -export function deepMerge(...args: unknown[]): unknown { - const lastItem = (args as any)[args.length - 1] - if (!object.test(lastItem)) return lastItem - const objects = args.filter(object.test).filter((x) => !Array.isArray(x)) - if (objects.length === 0) return lastItem as any - if (objects.length === 1) objects.unshift({}) - const allKeys = new Set(objects.flatMap((x) => Object.keys(x))) - for (const key of allKeys) { - const filteredValues = objects.flatMap((x) => - key in x ? [(x as any)[key]] : [], - ) - ;(objects as any)[0][key] = deepMerge(...filteredValues) - } - return objects[0] as any -} diff --git a/sdk/lib/util/fileHelper.ts b/sdk/lib/util/fileHelper.ts deleted file mode 100644 index 383b4fd31..000000000 --- a/sdk/lib/util/fileHelper.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as matches from "ts-matches" -import * as YAML from "yaml" -import * as TOML from "@iarna/toml" -import merge from "lodash.merge" -import * as T from "../types" -import * as fs from "node:fs/promises" - -const previousPath = /(.+?)\/([^/]*)$/ - -/** - * Used in the get config and the set config exported functions. - * The idea is that we are going to be reading/ writing to a file, or multiple files. And then we use this tool - * to keep the same path on the read and write, and have methods for helping with structured data. - * And if we are not using a structured data, we can use the raw method which forces the construction of a BiMap - * ```ts - import {InputSpec} from './InputSpec.ts' - import {matches, T} from '../deps.ts'; - const { object, string, number, boolean, arrayOf, array, anyOf, allOf } = matches - const someValidator = object({ - data: string - }) - const jsonFile = FileHelper.json({ - path: 'data.json', - validator: someValidator, - volume: 'main' - }) - const tomlFile = FileHelper.toml({ - path: 'data.toml', - validator: someValidator, - volume: 'main' - }) - const rawFile = FileHelper.raw({ - path: 'data.amazingSettings', - volume: 'main' - fromData(dataIn: Data): string { - return `myDatais ///- ${dataIn.data}` - }, - toData(rawData: string): Data { - const [,data] = /myDatais \/\/\/- (.*)/.match(rawData) - return {data} - } - }) - - export const setConfig : T.ExpectedExports.setConfig= async (effects, config) => { - await jsonFile.write({ data: 'here lies data'}, effects) - } - - export const getConfig: T.ExpectedExports.getConfig = async (effects, config) => ({ - spec: InputSpec, - config: nullIfEmpty({ - ...jsonFile.get(effects) - }) - ``` - */ -export class FileHelper { - protected constructor( - readonly path: string, - readonly writeData: (dataIn: A) => string, - readonly readData: (stringValue: string) => A, - ) {} - async write(data: A, effects: T.Effects) { - const parent = previousPath.exec(this.path) - if (parent) { - await fs.mkdir(parent[1], { recursive: true }) - } - - await fs.writeFile(this.path, this.writeData(data)) - } - async read(effects: T.Effects) { - if ( - !(await fs.access(this.path).then( - () => true, - () => false, - )) - ) { - return null - } - return this.readData( - await fs.readFile(this.path).then((data) => data.toString("utf-8")), - ) - } - - async merge(data: A, effects: T.Effects) { - const fileData = (await this.read(effects).catch(() => ({}))) || {} - const mergeData = merge({}, fileData, data) - return await this.write(mergeData, effects) - } - /** - * Create a File Helper for an arbitrary file type. - * - * Provide custom functions for translating data to the file format and visa versa. - */ - static raw( - path: string, - toFile: (dataIn: A) => string, - fromFile: (rawData: string) => A, - ) { - return new FileHelper(path, toFile, fromFile) - } - /** - * Create a File Helper for a .json file - */ - static json(path: string, shape: matches.Validator) { - return new FileHelper( - path, - (inData) => { - return JSON.stringify(inData, null, 2) - }, - (inString) => { - return shape.unsafeCast(JSON.parse(inString)) - }, - ) - } - /** - * Create a File Helper for a .toml file - */ - static toml>( - path: string, - shape: matches.Validator, - ) { - return new FileHelper( - path, - (inData) => { - return TOML.stringify(inData as any) - }, - (inString) => { - return shape.unsafeCast(TOML.parse(inString)) - }, - ) - } - /** - * Create a File Helper for a .yaml file - */ - static yaml>( - path: string, - shape: matches.Validator, - ) { - return new FileHelper( - path, - (inData) => { - return YAML.stringify(inData, null, 2) - }, - (inString) => { - return shape.unsafeCast(YAML.parse(inString)) - }, - ) - } -} - -export default FileHelper diff --git a/sdk/lib/util/index.browser.ts b/sdk/lib/util/index.browser.ts deleted file mode 100644 index 94339e7f7..000000000 --- a/sdk/lib/util/index.browser.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as T from "../types" - -/// Currently being used -export { addressHostToUrl } from "./getServiceInterface" -export { getDefaultString } from "./getDefaultString" - -/// Not being used, but known to be browser compatible -export { GetServiceInterface, getServiceInterface } from "./getServiceInterface" -export { getServiceInterfaces } from "./getServiceInterfaces" -export * from "./typeHelpers" diff --git a/sdk/lib/util/index.ts b/sdk/lib/util/index.ts deleted file mode 100644 index d7606d5d0..000000000 --- a/sdk/lib/util/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import "./nullIfEmpty" -import "./fileHelper" -import "../store/getStore" -import "./deepEqual" -import "./deepMerge" -import "./Overlay" -import "./once" - -export { GetServiceInterface, getServiceInterface } from "./getServiceInterface" -export { asError } from "./asError" -export { getServiceInterfaces } from "./getServiceInterfaces" -export { addressHostToUrl } from "./getServiceInterface" -export { hostnameInfoToAddress } from "./Hostname" -export * from "./typeHelpers" -export { getDefaultString } from "./getDefaultString" -export { inMs } from "./inMs" diff --git a/sdk/lib/util/nullIfEmpty.ts b/sdk/lib/util/nullIfEmpty.ts deleted file mode 100644 index 337b9098f..000000000 --- a/sdk/lib/util/nullIfEmpty.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * A useful tool when doing a getConfig. - * Look into the config {@link FileHelper} for an example of the use. - * @param s - * @returns - */ -export default function nullIfEmpty>( - s: null | A, -) { - if (s === null) return null - return Object.keys(s).length === 0 ? null : s -} diff --git a/sdk/lib/util/typeHelpers.ts b/sdk/lib/util/typeHelpers.ts deleted file mode 100644 index f45a46f1e..000000000 --- a/sdk/lib/util/typeHelpers.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as T from "../types" - -// prettier-ignore -export type FlattenIntersection = -T extends ArrayLike ? T : -T extends object ? {} & {[P in keyof T]: T[P]} : - T; - -export type _ = FlattenIntersection - -export const isKnownError = (e: unknown): e is T.KnownError => - e instanceof Object && ("error" in e || "error-code" in e) - -declare const affine: unique symbol - -export type Affine = { [affine]: A } - -type NeverPossible = { [affine]: string } -export type NoAny = NeverPossible extends A - ? keyof NeverPossible extends keyof A - ? never - : A - : A diff --git a/sdk/package-lock.json b/sdk/package-lock.json index 0e2bbf7cb..9699e0851 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -1,4676 +1,6 @@ { - "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha7", + "name": "sdk", "lockfileVersion": 3, "requires": true, - "packages": { - "": { - "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha7", - "license": "MIT", - "dependencies": { - "@iarna/toml": "^2.2.5", - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.4.0", - "isomorphic-fetch": "^3.0.0", - "lodash.merge": "^4.6.2", - "mime": "^4.0.3", - "ts-matches": "^5.5.1", - "yaml": "^2.2.2" - }, - "devDependencies": { - "@types/jest": "^29.4.0", - "@types/lodash.merge": "^4.6.2", - "jest": "^29.4.3", - "peggy": "^3.0.2", - "prettier": "^3.2.5", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "ts-pegjs": "^4.2.1", - "tsx": "^4.7.1", - "typescript": "^5.0.4" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.21.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", - "dev": true, - "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.4.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.25.16" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0" - } - }, - "node_modules/@ts-morph/common": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.19.0.tgz", - "integrity": "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==", - "dev": true, - "dependencies": { - "fast-glob": "^3.2.12", - "minimatch": "^7.4.3", - "mkdirp": "^2.1.6", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@ts-morph/common/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", - "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", - "dev": true - }, - "node_modules/@types/lodash.merge": { - "version": "4.6.9", - "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz", - "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "18.15.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", - "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/babel-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001470", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", - "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/code-block-writer": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", - "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", - "dev": true - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "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/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.341", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.341.tgz", - "integrity": "sha512-R4A8VfUBQY9WmAhuqY5tjHRf5fH2AAf6vqitBOE0y6u2PgHgqHSrhZmu78dIX3fVZtjqlwJNX1i2zwC3VpHtQQ==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "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/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", - "dev": true, - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "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/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "node_modules/isomorphic-fetch/node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", - "dev": true, - "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", - "import-local": "^3.0.2", - "jest-cli": "^29.5.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", - "dev": true, - "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.5.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "leven": "^3.1.0", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.5.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.5.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "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/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.3.tgz", - "integrity": "sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==", - "funding": [ - "https://github.com/sponsors/broofa" - ], - "bin": { - "mime": "bin/cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", - "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/peggy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/peggy/-/peggy-3.0.2.tgz", - "integrity": "sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==", - "dev": true, - "dependencies": { - "commander": "^10.0.0", - "source-map-generator": "0.8.0" - }, - "bin": { - "peggy": "bin/peggy.js" - }, - "engines": { - "node": ">=14" - } - }, - "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/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/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/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", - "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ] - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "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/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "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/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-generator": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", - "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/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==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/ts-matches": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.5.1.tgz", - "integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg==" - }, - "node_modules/ts-morph": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz", - "integrity": "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==", - "dev": true, - "dependencies": { - "@ts-morph/common": "~0.19.0", - "code-block-writer": "^12.0.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-pegjs": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ts-pegjs/-/ts-pegjs-4.2.1.tgz", - "integrity": "sha512-mK/O2pu6lzWUeKpEMA/wsa0GdYblfjJI1y0s0GqH6xCTvugQDOWPJbm5rY6AHivpZICuXIriCb+a7Cflbdtc2w==", - "dev": true, - "dependencies": { - "prettier": "^2.8.8", - "ts-morph": "^18.0.0" - }, - "bin": { - "tspegjs": "dist/cli.mjs" - }, - "peerDependencies": { - "peggy": "^3.0.2" - } - }, - "node_modules/ts-pegjs/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", - "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", - "dev": true, - "dependencies": { - "esbuild": "~0.19.10", - "get-tsconfig": "^4.7.2" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "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": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "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/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "packages": {} } diff --git a/sdk/package/.gitignore b/sdk/package/.gitignore new file mode 100644 index 000000000..a7ca92b2d --- /dev/null +++ b/sdk/package/.gitignore @@ -0,0 +1,5 @@ +.vscode +dist/ +node_modules/ +lib/coverage +lib/test/output.ts \ No newline at end of file diff --git a/sdk/package/.npmignore b/sdk/package/.npmignore new file mode 100644 index 000000000..40b878db5 --- /dev/null +++ b/sdk/package/.npmignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/sdk/package/.prettierignore b/sdk/package/.prettierignore new file mode 100644 index 000000000..19b24bbe8 --- /dev/null +++ b/sdk/package/.prettierignore @@ -0,0 +1 @@ +/lib/exver/exver.ts \ No newline at end of file diff --git a/sdk/package/LICENSE b/sdk/package/LICENSE new file mode 100644 index 000000000..793257b96 --- /dev/null +++ b/sdk/package/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Start9 Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdk/README.md b/sdk/package/README.md similarity index 100% rename from sdk/README.md rename to sdk/package/README.md diff --git a/sdk/package/jest.config.js b/sdk/package/jest.config.js new file mode 100644 index 000000000..c38fa5062 --- /dev/null +++ b/sdk/package/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + automock: false, + testEnvironment: "node", + rootDir: "./lib/", + modulePathIgnorePatterns: ["./dist/"], +} diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts new file mode 100644 index 000000000..28a14e1e5 --- /dev/null +++ b/sdk/package/lib/StartSdk.ts @@ -0,0 +1,1368 @@ +import { Value } from "../../base/lib/actions/input/builder/value" +import { + InputSpec, + ExtractInputSpecType, + LazyBuild, +} from "../../base/lib/actions/input/builder/inputSpec" +import { + DefaultString, + ListValueSpecText, + Pattern, + RandomString, + UniqueBy, + ValueSpecDatetime, + ValueSpecText, +} from "../../base/lib/actions/input/inputSpecTypes" +import { Variants } from "../../base/lib/actions/input/builder/variants" +import { Action, Actions } from "../../base/lib/actions/setupActions" +import { + SyncOptions, + ServiceInterfaceId, + PackageId, + HealthReceipt, + ServiceInterfaceType, + Effects, +} from "../../base/lib/types" +import * as patterns from "../../base/lib/util/patterns" +import { BackupSync, Backups } from "./backup/Backups" +import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants" +import { Daemons } from "./mainFn/Daemons" +import { healthCheck, HealthCheckParams } from "./health/HealthCheck" +import { checkPortListening } from "./health/checkFns/checkPortListening" +import { checkWebUrl, runHealthScript } from "./health/checkFns" +import { List } from "../../base/lib/actions/input/builder/list" +import { Install, InstallFn } from "./inits/setupInstall" +import { SetupBackupsParams, setupBackups } from "./backup/setupBackups" +import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall" +import { setupMain } from "./mainFn" +import { defaultTrigger } from "./trigger/defaultTrigger" +import { changeOnFirstSuccess, cooldownTrigger } from "./trigger" +import { + ServiceInterfacesReceipt, + UpdateServiceInterfaces, + setupServiceInterfaces, +} from "../../base/lib/interfaces/setupInterfaces" +import { successFailure } from "./trigger/successFailure" +import { MultiHost, Scheme } from "../../base/lib/interfaces/Host" +import { ServiceInterfaceBuilder } from "../../base/lib/interfaces/ServiceInterfaceBuilder" +import { GetSystemSmtp } from "./util" +import { nullIfEmpty } from "./util" +import { getServiceInterface, getServiceInterfaces } from "./util" +import { getStore } from "./store/getStore" +import { CommandOptions, MountOptions, SubContainer } from "./util/SubContainer" +import { splitCommand } from "./util" +import { Mounts } from "./mainFn/Mounts" +import { setupDependencies } from "../../base/lib/dependencies/setupDependencies" +import * as T from "../../base/lib/types" +import { testTypeVersion } from "../../base/lib/exver" +import { ExposedStorePaths } from "./store/setupExposeStore" +import { + PathBuilder, + extractJsonPath, + pathBuilder, +} from "../../base/lib/util/PathBuilder" +import { + CheckDependencies, + checkDependencies, +} from "../../base/lib/dependencies/dependencies" +import { GetSslCertificate } from "./util" +import { VersionGraph } from "./version" +import { MaybeFn } from "../../base/lib/actions/setupActions" +import { GetInput } from "../../base/lib/actions/setupActions" +import { Run } from "../../base/lib/actions/setupActions" +import * as actions from "../../base/lib/actions" +import { setupInit } from "./inits/setupInit" + +export const SDKVersion = testTypeVersion("0.3.6") + +// prettier-ignore +type AnyNeverCond = + T extends [] ? Else : + T extends [never, ...Array] ? Then : + T extends [any, ...infer U] ? AnyNeverCond : + never + +export class StartSdk { + private constructor(readonly manifest: Manifest) {} + static of() { + return new StartSdk(null as never) + } + withManifest(manifest: Manifest) { + return new StartSdk(manifest) + } + withStore>() { + return new StartSdk(this.manifest) + } + + build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) { + type NestedEffects = "subcontainer" | "store" | "action" + type InterfaceEffects = + | "getServiceInterface" + | "listServiceInterfaces" + | "exportServiceInterface" + | "clearServiceInterfaces" + | "bind" + | "getHostInfo" + | "getPrimaryUrl" + type MainUsedEffects = "setMainStatus" | "setHealth" + type CallbackEffects = "constRetry" | "clearCallbacks" + type AlreadyExposed = "getSslCertificate" | "getSystemSmtp" + + // prettier-ignore + type StartSdkEffectWrapper = { + [K in keyof Omit]: (effects: Effects, ...args: Parameters) => ReturnType + } + const startSdkEffectWrapper: StartSdkEffectWrapper = { + restart: (effects, ...args) => effects.restart(...args), + setDependencies: (effects, ...args) => effects.setDependencies(...args), + checkDependencies: (effects, ...args) => + effects.checkDependencies(...args), + mount: (effects, ...args) => effects.mount(...args), + getInstalledPackages: (effects, ...args) => + effects.getInstalledPackages(...args), + exposeForDependents: (effects, ...args) => + effects.exposeForDependents(...args), + getServicePortForward: (effects, ...args) => + effects.getServicePortForward(...args), + clearBindings: (effects, ...args) => effects.clearBindings(...args), + getContainerIp: (effects, ...args) => effects.getContainerIp(...args), + getSslKey: (effects, ...args) => effects.getSslKey(...args), + setDataVersion: (effects, ...args) => effects.setDataVersion(...args), + getDataVersion: (effects, ...args) => effects.getDataVersion(...args), + shutdown: (effects, ...args) => effects.shutdown(...args), + getDependencies: (effects, ...args) => effects.getDependencies(...args), + getStatus: (effects, ...args) => effects.getStatus(...args), + } + + return { + ...startSdkEffectWrapper, + action: { + run: actions.runAction, + request: >( + effects: T.Effects, + packageId: T.PackageId, + action: T, + severity: T.ActionSeverity, + options?: actions.ActionRequestOptions, + ) => + actions.requestAction({ + effects, + packageId, + action, + severity, + options: options, + }), + requestOwn: >( + effects: T.Effects, + action: T, + severity: T.ActionSeverity, + options?: actions.ActionRequestOptions, + ) => + actions.requestAction({ + effects, + packageId: this.manifest.id, + action, + severity, + options: options, + }), + clearRequest: (effects: T.Effects, ...replayIds: string[]) => + effects.action.clearRequests({ only: replayIds }), + }, + checkDependencies: checkDependencies as < + DependencyId extends keyof Manifest["dependencies"] & + PackageId = keyof Manifest["dependencies"] & PackageId, + >( + effects: Effects, + packageIds?: DependencyId[], + ) => Promise>, + serviceInterface: { + getOwn: (effects: E, id: ServiceInterfaceId) => + getServiceInterface(effects, { + id, + }), + get: ( + effects: E, + opts: { id: ServiceInterfaceId; packageId: PackageId }, + ) => getServiceInterface(effects, opts), + getAllOwn: (effects: E) => + getServiceInterfaces(effects, {}), + getAll: ( + effects: E, + opts: { packageId: PackageId }, + ) => getServiceInterfaces(effects, opts), + }, + + store: { + get: ( + effects: E, + packageId: string, + path: PathBuilder, + ) => + getStore(effects, path, { + packageId, + }), + getOwn: ( + effects: E, + path: PathBuilder, + ) => getStore(effects, path), + setOwn: >( + effects: E, + path: Path, + value: Path extends PathBuilder ? Value : never, + ) => + effects.store.set({ + value, + path: extractJsonPath(path), + }), + }, + + host: { + // static: (effects: Effects, id: string) => + // new StaticHost({ id, effects }), + // single: (effects: Effects, id: string) => + // new SingleHost({ id, effects }), + multi: (effects: Effects, id: string) => new MultiHost({ id, effects }), + }, + nullIfEmpty, + runCommand: async ( + effects: Effects, + image: { + id: keyof Manifest["images"] & T.ImageId + sharedRun?: boolean + }, + command: T.CommandType, + options: CommandOptions & { + mounts?: { path: string; options: MountOptions }[] + }, + name: string, + ): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => { + return runCommand(effects, image, command, options, name) + }, + /** + * TODO: rewrite this + * @description Use this function to create a static Action, including optional form input. + * + * By convention, each Action should receive its own file. + * + * @param id + * @param metaData + * @param fn + * @returns + * @example + * In this example, we create an Action that prints a name to the console. We present a user + * with a form for optionally entering a temp name. If no temp name is provided, we use the name + * from the underlying `inputSpec.yaml` file. If no name is there, we use "Unknown". Then, we return + * a message to the user informing them what happened. + * + * ``` + import { sdk } from '../sdk' + const { InputSpec, Value } = sdk + import { yamlFile } from '../file-models/inputSpec.yml' + + const input = InputSpec.of({ + nameToPrint: Value.text({ + name: 'Temp Name', + description: 'If no name is provided, the name from inputSpec will be used', + required: false, + }), + }) + + export const nameToLog = sdk.createAction( + // id + 'nameToLogs', + + // metadata + { + name: 'Name to Logs', + description: 'Prints "Hello [Name]" to the service logs.', + warning: null, + disabled: false, + input, + allowedStatuses: 'onlyRunning', + group: null, + }, + + // the execution function + async ({ effects, input }) => { + const name = + input.nameToPrint || (await yamlFile.read(effects))?.name || 'Unknown' + + console.info(`Hello ${name}`) + + return { + version: '0', + message: `"Hello ${name}" has been written to the service logs. Open your logs to view it.`, + value: name, + copyable: true, + qr: false, + } + }, + ) + * ``` + */ + Action: { + withInput: < + Id extends T.ActionId, + InputSpecType extends + | Record + | InputSpec + | InputSpec, + Type extends + ExtractInputSpecType = ExtractInputSpecType, + >( + id: Id, + metadata: MaybeFn>, + inputSpec: InputSpecType, + getInput: GetInput, + run: Run, + ) => Action.withInput(id, metadata, inputSpec, getInput, run), + withoutInput: ( + id: Id, + metadata: MaybeFn>, + run: Run<{}>, + ) => Action.withoutInput(id, metadata, run), + }, + inputSpecConstants: { smtpInputSpec }, + /** + * @description Use this function to create a service interface. + * @param effects + * @param options + * @example + * In this example, we create a standard web UI + * + * ``` + const ui = sdk.createInterface(effects, { + name: 'Web UI', + id: 'ui', + description: 'The primary web app for this service.', + type: 'ui', + hasPrimary: false, + masked: false, + schemeOverride: null, + username: null, + path: '', + search: {}, + }) + * ``` + */ + createInterface: ( + effects: Effects, + options: { + /** The human readable name of this service interface. */ + name: string + /** A unique ID for this service interface. */ + id: string + /** The human readable description. */ + description: string + /** Not available until StartOS v0.4.0. If true, forces the user to select one URL (i.e. .onion, .local, or IP address) as the primary URL. This is needed by some services to function properly. */ + hasPrimary: boolean + /** Affects how the interface appears to the user. One of: 'ui', 'api', 'p2p'. */ + type: ServiceInterfaceType + /** (optional) prepends the provided username to all URLs. */ + username: null | string + /** (optional) appends the provided path to all URLs. */ + path: string + /** (optional) appends the provided query params to all URLs. */ + search: Record + /** (optional) overrides the protocol prefix provided by the bind function. + * + * @example `ftp://` + */ + schemeOverride: { ssl: Scheme; noSsl: Scheme } | null + /** TODO Aiden how would someone include a password in the URL? Whether or not to mask the URLs on the screen, for example, when they contain a password */ + masked: boolean + }, + ) => new ServiceInterfaceBuilder({ ...options, effects }), + getSystemSmtp: (effects: E) => + new GetSystemSmtp(effects), + getSslCerificate: ( + effects: E, + hostnames: string[], + algorithm?: T.Algorithm, + ) => new GetSslCertificate(effects, hostnames, algorithm), + HealthCheck: { + of(effects: T.Effects, o: Omit) { + return healthCheck({ effects, ...o }) + }, + }, + healthCheck: { + checkPortListening, + checkWebUrl, + runHealthScript, + }, + patterns, + /** + * @description Use this function to list every Action offered by the service. Actions will be displayed in the provided order. + * + * By convention, each Action should receive its own file in the "actions" directory. + * @example + * + * ``` + import { sdk } from '../sdk' + import { config } from './config' + import { nameToLogs } from './nameToLogs' + + export const actions = sdk.Actions.of().addAction(config).addAction(nameToLogs) + * ``` + */ + Actions: Actions, + /** + * @description Use this function to determine which volumes are backed up when a user creates a backup, including advanced options. + * @example + * In this example, we back up the entire "main" volume and nothing else. + * + * ``` + export const { createBackup, restoreBackup } = sdk.setupBackups(sdk.Backups.addVolume('main')) + * ``` + * @example + * In this example, we back up the "main" and the "other" volume, but exclude hypothetical directory "excludedDir" from the "other". + * + * ``` + export const { createBackup, restoreBackup } = sdk.setupBackups(sdk.Backups + .addVolume('main') + .addVolume('other', { exclude: ['path/to/excludedDir'] }) + ) + * ``` + */ + setupBackups: (options: SetupBackupsParams) => + setupBackups(options), + /** + * @description Use this function to set dependency information. + * + * The function executes on service install, update, and inputSpec save. "input" will be of type `Input` for inputSpec save. It will be `null` for install and update. + * @example + * In this example, we create a static dependency on Hello World >=1.0.0:0, where Hello World must be running and passing its "webui" health check. + * + * ``` + export const setDependencies = sdk.setupDependencies( + async ({ effects, input }) => { + return { + 'hello-world': sdk.Dependency.of({ + type: 'running', + versionRange: VersionRange.parse('>=1.0.0:0'), + healthChecks: ['webui'], + }), + } + }, + ) + * ``` + * @example + * In this example, we create a conditional dependency on Hello World based on a hypothetical "needsWorld" boolean in the store. + * + * ``` + export const setDependencies = sdk.setupDependencies( + async ({ effects }) => { + if (sdk.store.getOwn(sdk.StorePath.needsWorld).const()) { + return { + 'hello-world': sdk.Dependency.of({ + type: 'running', + versionRange: VersionRange.parse('>=1.0.0:0'), + healthChecks: ['webui'], + }), + } + } + return {} + }, + ) + * ``` + */ + setupDependencies: setupDependencies, + setupInit: setupInit, + /** + * @description Use this function to execute arbitrary logic *once*, on initial install only. + * @example + * In the this example, we bootstrap our Store with a random, 16-char admin password. + * + * ``` + const install = sdk.setupInstall(async ({ effects }) => { + await sdk.store.setOwn( + effects, + sdk.StorePath.adminPassword, + utils.getDefaultString({ + charset: 'a-z,A-Z,1-9,!,@,$,%,&,', + len: 16, + }), + ) + }) + * ``` + */ + setupInstall: (fn: InstallFn) => Install.of(fn), + /** + * @description Use this function to determine how this service will be hosted and served. The function executes on service install, service update, and inputSpec save. + * + * "input" will be of type `Input` for inputSpec save. It will be `null` for install and update. + * + * To learn about creating multi-hosts and interfaces, check out the {@link https://docs.start9.com/packaging-guide/learn/interfaces documentation}. + * @param inputSpec - The inputSpec spec of this service as exported from /inputSpec/spec. + * @param fn - an async function that returns an array of interface receipts. The function always has access to `effects`; it has access to `input` only after inputSpec save, otherwise `input` will be null. + * @example + * In this example, we create two UIs from one multi-host, and one API from another multi-host. + * + * ``` + export const setInterfaces = sdk.setupInterfaces( + inputSpecSpec, + async ({ effects, input }) => { + // ** UI multi-host ** + const uiMulti = sdk.host.multi(effects, 'ui-multi') + const uiMultiOrigin = await uiMulti.bindPort(80, { + protocol: 'http', + }) + // Primary UI + const primaryUi = sdk.createInterface(effects, { + name: 'Primary UI', + id: 'primary-ui', + description: 'The primary web app for this service.', + type: 'ui', + hasPrimary: false, + masked: false, + schemeOverride: null, + username: null, + path: '', + search: {}, + }) + // Admin UI + const adminUi = sdk.createInterface(effects, { + name: 'Admin UI', + id: 'admin-ui', + description: 'The admin web app for this service.', + type: 'ui', + hasPrimary: false, + masked: false, + schemeOverride: null, + username: null, + path: '/admin', + search: {}, + }) + // UI receipt + const uiReceipt = await uiMultiOrigin.export([primaryUi, adminUi]) + + // ** API multi-host ** + const apiMulti = sdk.host.multi(effects, 'api-multi') + const apiMultiOrigin = await apiMulti.bindPort(5959, { + protocol: 'http', + }) + // API + const api = sdk.createInterface(effects, { + name: 'Admin API', + id: 'api', + description: 'The advanced API for this service.', + type: 'api', + hasPrimary: false, + masked: false, + schemeOverride: null, + username: null, + path: '', + search: {}, + }) + // API receipt + const apiReceipt = await apiMultiOrigin.export([api]) + + // ** Return receipts ** + return [uiReceipt, apiReceipt] + }, + ) + * ``` + */ + setupInterfaces: setupServiceInterfaces, + setupMain: ( + fn: (o: { + effects: Effects + started(onTerm: () => PromiseLike): PromiseLike + }) => Promise>, + ) => setupMain(fn), + /** + * Use this function to execute arbitrary logic *once*, on uninstall only. Most services will not use this. + */ + setupUninstall: (fn: UninstallFn) => + setupUninstall(fn), + trigger: { + defaultTrigger, + cooldownTrigger, + changeOnFirstSuccess, + successFailure, + }, + Mounts: { + of() { + return Mounts.of() + }, + }, + Backups: { + volumes: ( + ...volumeNames: Array + ) => Backups.withVolumes(...volumeNames), + addSets: ( + ...options: BackupSync[] + ) => Backups.withSyncs(...options), + withOptions: (options?: Partial) => + Backups.withOptions(options), + }, + InputSpec: { + /** + * @description Use this function to define the inputSpec specification that will ultimately present to the user as validated form inputs. + * + * Most form controls are supported, including text, textarea, number, toggle, select, multiselect, list, color, datetime, object (sub form), and union (conditional sub form). + * @example + * In this example, we define a inputSpec form with two value: name and makePublic. + * + * ``` + import { sdk } from '../sdk' + const { InputSpec, Value } = sdk + + export const inputSpecSpec = InputSpec.of({ + name: Value.text({ + name: 'Name', + description: + 'When you launch the Hello World UI, it will display "Hello [Name]"', + required: { default: 'World' }, + }), + makePublic: Value.toggle({ + name: 'Make Public', + description: 'Whether or not to expose the service to the network', + default: false, + }), + }) + * ``` + */ + of: < + Spec extends Record | Value>, + >( + spec: Spec, + ) => InputSpec.of(spec), + }, + Daemons: { + of( + effects: Effects, + started: (onTerm: () => PromiseLike) => PromiseLike, + healthReceipts: HealthReceipt[], + ) { + return Daemons.of({ effects, started, healthReceipts }) + }, + }, + List: { + /** + * @description Create a list of text inputs. + * @param a - attributes of the list itself. + * @param aSpec - attributes describing each member of the list. + */ + text: List.text, + /** + * @description Create a list of objects. + * @param a - attributes of the list itself. + * @param aSpec - attributes describing each member of the list. + */ + obj: >( + a: { + name: string + description?: string | null + /** Presents a warning before adding/removing/editing a list item. */ + warning?: string | null + default?: [] + minLength?: number | null + maxLength?: number | null + }, + aSpec: { + spec: InputSpec + /** + * @description The ID of a required field on the inner object whose value will be used to display items in the list. + * @example + * In this example, we use the value of the `label` field to display members of the list. + * + * ``` + spec: InputSpec.of({ + label: Value.text({ + name: 'Label', + required: false, + }) + }) + displayAs: 'label', + uniqueBy: null, + * ``` + * + */ + displayAs?: null | string + /** + * @description The ID(s) of required fields on the inner object whose value(s) will be used to enforce uniqueness in the list. + * @example + * In this example, we use the `label` field to enforce uniqueness, meaning the label field must be unique from other entries. + * + * ``` + spec: InputSpec.of({ + label: Value.text({ + name: 'Label', + required: { default: null }, + }) + pubkey: Value.text({ + name: 'Pubkey', + required: { default: null }, + }) + }) + displayAs: 'label', + uniqueBy: 'label', + * ``` + * @example + * In this example, we use the `label` field AND the `pubkey` field to enforce uniqueness, meaning both these fields must be unique from other entries. + * + * ``` + spec: InputSpec.of({ + label: Value.text({ + name: 'Label', + required: { default: null }, + }) + pubkey: Value.text({ + name: 'Pubkey', + required: { default: null }, + }) + }) + displayAs: 'label', + uniqueBy: { all: ['label', 'pubkey'] }, + * ``` + */ + uniqueBy?: null | UniqueBy + }, + ) => List.obj(a, aSpec), + /** + * @description Create a list of dynamic text inputs. + * @param a - attributes of the list itself. + * @param aSpec - attributes describing each member of the list. + */ + dynamicText: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + warning?: string | null + default?: string[] + minLength?: number | null + maxLength?: number | null + disabled?: false | string + generate?: null | RandomString + spec: { + masked?: boolean + placeholder?: string | null + minLength?: number | null + maxLength?: number | null + patterns: Pattern[] + inputmode?: ListValueSpecText["inputmode"] + } + } + >, + ) => List.dynamicText(getA), + }, + StorePath: pathBuilder(), + Value: { + /** + * @description Displays a boolean toggle to enable/disable + * @example + * ``` + toggleExample: Value.toggle({ + // required + name: 'Toggle Example', + default: true, + + // optional + description: null, + warning: null, + immutable: false, + }), + * ``` + */ + toggle: Value.toggle, + /** + * @description Displays a text input field + * @example + * ``` + textExample: Value.text({ + // required + name: 'Text Example', + required: false, + + // optional + description: null, + placeholder: null, + warning: null, + generate: null, + inputmode: 'text', + masked: false, + minLength: null, + maxLength: null, + patterns: [], + immutable: false, + }), + * ``` + */ + text: Value.text, + /** + * @description Displays a large textarea field for long form entry. + * @example + * ``` + textareaExample: Value.textarea({ + // required + name: 'Textarea Example', + required: false, + + // optional + description: null, + placeholder: null, + warning: null, + minLength: null, + maxLength: null, + immutable: false, + }), + * ``` + */ + textarea: Value.textarea, + /** + * @description Displays a number input field + * @example + * ``` + numberExample: Value.number({ + // required + name: 'Number Example', + required: false, + integer: true, + + // optional + description: null, + placeholder: null, + warning: null, + min: null, + max: null, + immutable: false, + step: null, + units: null, + }), + * ``` + */ + number: Value.number, + /** + * @description Displays a browser-native color selector. + * @example + * ``` + colorExample: Value.color({ + // required + name: 'Color Example', + required: false, + + // optional + description: null, + warning: null, + immutable: false, + }), + * ``` + */ + color: Value.color, + /** + * @description Displays a browser-native date/time selector. + * @example + * ``` + datetimeExample: Value.datetime({ + // required + name: 'Datetime Example', + required: false, + + // optional + description: null, + warning: null, + immutable: false, + inputmode: 'datetime-local', + min: null, + max: null, + }), + * ``` + */ + datetime: Value.datetime, + /** + * @description Displays a select modal with radio buttons, allowing for a single selection. + * @example + * ``` + selectExample: Value.select({ + // required + name: 'Select Example', + required: false, + values: { + radio1: 'Radio 1', + radio2: 'Radio 2', + }, + + // optional + description: null, + warning: null, + immutable: false, + disabled: false, + }), + * ``` + */ + select: Value.select, + /** + * @description Displays a select modal with checkboxes, allowing for multiple selections. + * @example + * ``` + multiselectExample: Value.multiselect({ + // required + name: 'Multiselect Example', + values: { + option1: 'Option 1', + option2: 'Option 2', + }, + default: [], + + // optional + description: null, + warning: null, + immutable: false, + disabled: false, + minlength: null, + maxLength: null, + }), + * ``` + */ + multiselect: Value.multiselect, + /** + * @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form. + * @example + * ``` + objectExample: Value.object( + { + // required + name: 'Object Example', + + // optional + description: null, + warning: null, + }, + InputSpec.of({}), + ), + * ``` + */ + object: Value.object, + /** + * @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented. + * @example + * ``` + unionExample: Value.union( + { + // required + name: 'Union Example', + required: false, + + // optional + description: null, + warning: null, + disabled: false, + immutable: false, + }, + Variants.of({ + option1: { + name: 'Option 1', + spec: InputSpec.of({}), + }, + option2: { + name: 'Option 2', + spec: InputSpec.of({}), + }, + }), + ), + * ``` + */ + union: Value.union, + /** + * @description Presents an interface to add/remove/edit items in a list. + * @example + * In this example, we create a list of text inputs. + * + * ``` + listExampleText: Value.list( + List.text( + { + // required + name: 'Text List', + + // optional + description: null, + warning: null, + default: [], + minLength: null, + maxLength: null, + }, + { + // required + patterns: [], + + // optional + placeholder: null, + generate: null, + inputmode: 'url', + masked: false, + minLength: null, + maxLength: null, + }, + ), + ), + * ``` + * @example + * In this example, we create a list of objects. + * + * ``` + listExampleObject: Value.list( + List.obj( + { + // required + name: 'Object List', + + // optional + description: null, + warning: null, + default: [], + minLength: null, + maxLength: null, + }, + { + // required + spec: InputSpec.of({}), + + // optional + displayAs: null, + uniqueBy: null, + }, + ), + ), + * ``` + */ + list: Value.list, + hidden: Value.hidden, + dynamicToggle: ( + a: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + default: boolean + disabled?: false | string + } + >, + ) => Value.dynamicToggle(a), + dynamicText: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description optionally provide a default value. + * @type { string | RandomString | null } + * @example default: null + * @example default: 'World' + * @example default: { charset: 'abcdefg', len: 16 } + */ + default: DefaultString | null + required: boolean + /** + * @description Mask (aka camouflage) text input with dots: ● ● ● + * @default false + */ + masked?: boolean + placeholder?: string | null + minLength?: number | null + maxLength?: number | null + /** + * @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails. + * @default [] + * @example + * ``` + [ + { + regex: "[a-z]", + description: "May only contain lower case letters from the English alphabet." + } + ] + * ``` + */ + patterns?: Pattern[] + /** + * @description Informs the browser how to behave and which keyboard to display on mobile + * @default "text" + */ + inputmode?: ValueSpecText["inputmode"] + /** + * @description Displays a button that will generate a random string according to the provided charset and len attributes. + */ + generate?: null | RandomString + } + >, + ) => Value.dynamicText(getA), + dynamicTextarea: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + default: string | null + required: boolean + minLength?: number | null + maxLength?: number | null + placeholder?: string | null + disabled?: false | string + } + >, + ) => Value.dynamicTextarea(getA), + dynamicNumber: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description optionally provide a default value. + * @type { number | null } + * @example default: null + * @example default: 7 + */ + default: number | null + required: boolean + min?: number | null + max?: number | null + /** + * @description How much does the number increase/decrease when using the arrows provided by the browser. + * @default 1 + */ + step?: number | null + /** + * @description Requires the number to be an integer. + */ + integer: boolean + /** + * @description Optionally display units to the right of the input box. + */ + units?: string | null + placeholder?: string | null + disabled?: false | string + } + >, + ) => Value.dynamicNumber(getA), + dynamicColor: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description optionally provide a default value. + * @type { string | null } + * @example default: null + * @example default: 'ffffff' + */ + default: string | null + required: boolean + disabled?: false | string + } + >, + ) => Value.dynamicColor(getA), + dynamicDatetime: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description optionally provide a default value. + * @type { string | null } + * @example default: null + * @example default: '1985-12-16 18:00:00.000' + */ + default: string + required: boolean + /** + * @description Informs the browser how to behave and which date/time component to display. + * @default "datetime-local" + */ + inputmode?: ValueSpecDatetime["inputmode"] + min?: string | null + max?: string | null + disabled?: false | string + } + >, + ) => Value.dynamicDatetime(getA), + dynamicSelect: >( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description provide a default value from the list of values. + * @type { default: string } + * @example default: 'radio1' + */ + default: keyof Variants & string + required: boolean + /** + * @description A mapping of unique radio options to their human readable display format. + * @example + * ``` + { + radio1: "Radio 1" + radio2: "Radio 2" + radio3: "Radio 3" + } + * ``` + */ + values: Variants + /** + * @options + * - false - The field can be modified. + * - string - The field cannot be modified. The provided text explains why. + * - string[] - The field can be modified, but the values contained in the array cannot be selected. + * @default false + */ + disabled?: false | string | string[] + } + >, + ) => Value.dynamicSelect(getA), + dynamicMultiselect: ( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description A simple list of which options should be checked by default. + */ + default: string[] + /** + * @description A mapping of checkbox options to their human readable display format. + * @example + * ``` + { + option1: "Option 1" + option2: "Option 2" + option3: "Option 3" + } + * ``` + */ + values: Record + minLength?: number | null + maxLength?: number | null + /** + * @options + * - false - The field can be modified. + * - string - The field cannot be modified. The provided text explains why. + * - string[] - The field can be modified, but the values contained in the array cannot be selected. + * @default false + */ + disabled?: false | string | string[] + } + >, + ) => Value.dynamicMultiselect(getA), + filteredUnion: < + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + >( + getDisabledFn: LazyBuild, + a: { + name: string + description?: string | null + warning?: string | null + default: keyof VariantValues & string + }, + aVariants: + | Variants + | Variants, + ) => + Value.filteredUnion( + getDisabledFn, + a, + aVariants, + ), + + dynamicUnion: < + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + >( + getA: LazyBuild< + Store, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description provide a default value from the list of variants. + * @type { string } + * @example default: 'variant1' + */ + default: keyof VariantValues & string + required: boolean + /** + * @options + * - false - The field can be modified. + * - string - The field cannot be modified. The provided text explains why. + * - string[] - The field can be modified, but the values contained in the array cannot be selected. + * @default false + */ + disabled: false | string | string[] + } + >, + aVariants: + | Variants + | Variants, + ) => Value.dynamicUnion(getA, aVariants), + }, + Variants: { + of: < + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec + } + }, + >( + a: VariantValues, + ) => Variants.of(a), + }, + } + } +} + +export async function runCommand( + effects: Effects, + image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean }, + command: string | [string, ...string[]], + options: CommandOptions & { + mounts?: { path: string; options: MountOptions }[] + }, + name: string, +): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> { + const commands = splitCommand(command) + return SubContainer.with( + effects, + image, + options.mounts || [], + name, + (subcontainer) => subcontainer.exec(commands), + ) +} diff --git a/sdk/package/lib/backup/Backups.ts b/sdk/package/lib/backup/Backups.ts new file mode 100644 index 000000000..c27f2be72 --- /dev/null +++ b/sdk/package/lib/backup/Backups.ts @@ -0,0 +1,208 @@ +import * as T from "../../../base/lib/types" +import * as child_process from "child_process" +import { asError } from "../util" + +export const DEFAULT_OPTIONS: T.SyncOptions = { + delete: true, + exclude: [], +} +export type BackupSync = { + dataPath: `/media/startos/volumes/${Volumes}/${string}` + backupPath: `/media/startos/backup/${string}` + options?: Partial + backupOptions?: Partial + restoreOptions?: Partial +} +/** + * This utility simplifies the volume backup process. + * ```ts + * export const { createBackup, restoreBackup } = Backups.volumes("main").build(); + * ``` + * + * Changing the options of the rsync, (ie excludes) use either + * ```ts + * Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() + * // or + * Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() + * ``` + * + * Using the more fine control, using the addSets for more control + * ```ts + * Backups.addSets({ + * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP + * }, { + * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} + * ).build()q + * ``` + */ +export class Backups { + private constructor( + private options = DEFAULT_OPTIONS, + private restoreOptions: Partial = {}, + private backupOptions: Partial = {}, + private backupSet = [] as BackupSync[], + ) {} + + static withVolumes( + ...volumeNames: Array + ): Backups { + return Backups.withSyncs( + ...volumeNames.map((srcVolume) => ({ + dataPath: `/media/startos/volumes/${srcVolume}/` as const, + backupPath: `/media/startos/backup/${srcVolume}/` as const, + })), + ) + } + + static withSyncs( + ...syncs: BackupSync[] + ) { + return syncs.reduce((acc, x) => acc.addSync(x), new Backups()) + } + + static withOptions( + options?: Partial, + ) { + return new Backups({ ...DEFAULT_OPTIONS, ...options }) + } + + setOptions(options?: Partial) { + this.options = { + ...this.options, + ...options, + } + return this + } + + setBackupOptions(options?: Partial) { + this.backupOptions = { + ...this.backupOptions, + ...options, + } + return this + } + + setRestoreOptions(options?: Partial) { + this.restoreOptions = { + ...this.restoreOptions, + ...options, + } + return this + } + + addVolume( + volume: M["volumes"][number], + options?: Partial<{ + options: T.SyncOptions + backupOptions: T.SyncOptions + restoreOptions: T.SyncOptions + }>, + ) { + return this.addSync({ + dataPath: `/media/startos/volumes/${volume}/` as const, + backupPath: `/media/startos/backup/${volume}/` as const, + ...options, + }) + } + addSync(sync: BackupSync) { + this.backupSet.push({ + ...sync, + options: { ...this.options, ...sync.options }, + }) + return this + } + + async createBackup() { + for (const item of this.backupSet) { + const rsyncResults = await runRsync({ + srcPath: item.dataPath, + dstPath: item.backupPath, + options: { + ...this.options, + ...this.backupOptions, + ...item.options, + ...item.backupOptions, + }, + }) + await rsyncResults.wait() + } + return + } + + async restoreBackup() { + for (const item of this.backupSet) { + const rsyncResults = await runRsync({ + srcPath: item.backupPath, + dstPath: item.dataPath, + options: { + ...this.options, + ...this.backupOptions, + ...item.options, + ...item.backupOptions, + }, + }) + await rsyncResults.wait() + } + return + } +} + +async function runRsync(rsyncOptions: { + srcPath: string + dstPath: string + options: T.SyncOptions +}): Promise<{ + id: () => Promise + wait: () => Promise + progress: () => Promise +}> { + const { srcPath, dstPath, options } = rsyncOptions + + const command = "rsync" + const args: string[] = [] + if (options.delete) { + args.push("--delete") + } + for (const exclude of options.exclude) { + args.push(`--exclude=${exclude}`) + } + args.push("-actAXH") + args.push("--info=progress2") + args.push("--no-inc-recursive") + args.push(srcPath) + args.push(dstPath) + const spawned = child_process.spawn(command, args, { detached: true }) + let percentage = 0.0 + spawned.stdout.on("data", (data: unknown) => { + const lines = String(data).replace("\r", "\n").split("\n") + for (const line of lines) { + const parsed = /$([0-9.]+)%/.exec(line)?.[1] + if (!parsed) continue + percentage = Number.parseFloat(parsed) + } + }) + + spawned.stderr.on("data", (data: unknown) => { + console.error(`Backups.runAsync`, asError(data)) + }) + + const id = async () => { + const pid = spawned.pid + if (pid === undefined) { + throw new Error("rsync process has no pid") + } + return String(pid) + } + const waitPromise = new Promise((resolve, reject) => { + spawned.on("exit", (code: any) => { + if (code === 0) { + resolve(null) + } else { + reject(new Error(`rsync exited with code ${code}`)) + } + }) + }) + const wait = () => waitPromise + const progress = () => Promise.resolve(percentage) + return { id, wait, progress } +} diff --git a/sdk/lib/backup/index.ts b/sdk/package/lib/backup/index.ts similarity index 97% rename from sdk/lib/backup/index.ts rename to sdk/package/lib/backup/index.ts index fe9cd8569..1e7995252 100644 --- a/sdk/lib/backup/index.ts +++ b/sdk/package/lib/backup/index.ts @@ -1,3 +1,2 @@ import "./Backups" - import "./setupBackups" diff --git a/sdk/package/lib/backup/setupBackups.ts b/sdk/package/lib/backup/setupBackups.ts new file mode 100644 index 000000000..722d245ff --- /dev/null +++ b/sdk/package/lib/backup/setupBackups.ts @@ -0,0 +1,39 @@ +import { Backups } from "./Backups" +import * as T from "../../../base/lib/types" +import { _ } from "../util" + +export type SetupBackupsParams = + | M["volumes"][number][] + | ((_: { effects: T.Effects }) => Promise>) + +type SetupBackupsRes = { + createBackup: T.ExpectedExports.createBackup + restoreBackup: T.ExpectedExports.restoreBackup +} + +export function setupBackups( + options: SetupBackupsParams, +) { + let backupsFactory: (_: { effects: T.Effects }) => Promise> + if (options instanceof Function) { + backupsFactory = options + } else { + backupsFactory = async () => Backups.withVolumes(...options) + } + const answer: { + createBackup: T.ExpectedExports.createBackup + restoreBackup: T.ExpectedExports.restoreBackup + } = { + get createBackup() { + return (async (options) => { + return (await backupsFactory(options)).createBackup() + }) as T.ExpectedExports.createBackup + }, + get restoreBackup() { + return (async (options) => { + return (await backupsFactory(options)).restoreBackup() + }) as T.ExpectedExports.restoreBackup + }, + } + return answer +} diff --git a/sdk/lib/health/HealthCheck.ts b/sdk/package/lib/health/HealthCheck.ts similarity index 88% rename from sdk/lib/health/HealthCheck.ts rename to sdk/package/lib/health/HealthCheck.ts index 8186a3cce..05c1a214a 100644 --- a/sdk/lib/health/HealthCheck.ts +++ b/sdk/package/lib/health/HealthCheck.ts @@ -1,14 +1,10 @@ -import { Effects } from "../types" +import { Effects, HealthReceipt } from "../../../base/lib/types" import { HealthCheckResult } from "./checkFns/HealthCheckResult" -import { HealthReceipt } from "./HealthReceipt" import { Trigger } from "../trigger" import { TriggerInput } from "../trigger/TriggerInput" import { defaultTrigger } from "../trigger/defaultTrigger" -import { once } from "../util/once" -import { Overlay } from "../util/Overlay" +import { once, asError } from "../util" import { object, unknown } from "ts-matches" -import * as T from "../types" -import { asError } from "../util/asError" export type HealthCheckParams = { effects: Effects diff --git a/sdk/lib/health/checkFns/HealthCheckResult.ts b/sdk/package/lib/health/checkFns/HealthCheckResult.ts similarity index 63% rename from sdk/lib/health/checkFns/HealthCheckResult.ts rename to sdk/package/lib/health/checkFns/HealthCheckResult.ts index ba2468488..92d4afddf 100644 --- a/sdk/lib/health/checkFns/HealthCheckResult.ts +++ b/sdk/package/lib/health/checkFns/HealthCheckResult.ts @@ -1,3 +1,3 @@ -import { T } from "../.." +import { T } from "../../../../base/lib" export type HealthCheckResult = Omit diff --git a/sdk/lib/health/checkFns/checkPortListening.ts b/sdk/package/lib/health/checkFns/checkPortListening.ts similarity index 94% rename from sdk/lib/health/checkFns/checkPortListening.ts rename to sdk/package/lib/health/checkFns/checkPortListening.ts index 94d0becc0..e745bce4f 100644 --- a/sdk/lib/health/checkFns/checkPortListening.ts +++ b/sdk/package/lib/health/checkFns/checkPortListening.ts @@ -1,5 +1,5 @@ -import { Effects } from "../../types" -import { stringFromStdErrOut } from "../../util/stringFromStdErrOut" +import { Effects } from "../../../../base/lib/types" +import { stringFromStdErrOut } from "../../util" import { HealthCheckResult } from "./HealthCheckResult" import { promisify } from "node:util" diff --git a/sdk/lib/health/checkFns/checkWebUrl.ts b/sdk/package/lib/health/checkFns/checkWebUrl.ts similarity index 90% rename from sdk/lib/health/checkFns/checkWebUrl.ts rename to sdk/package/lib/health/checkFns/checkWebUrl.ts index 042115211..e04ee7531 100644 --- a/sdk/lib/health/checkFns/checkWebUrl.ts +++ b/sdk/package/lib/health/checkFns/checkWebUrl.ts @@ -1,5 +1,5 @@ -import { Effects } from "../../types" -import { asError } from "../../util/asError" +import { Effects } from "../../../../base/lib/types" +import { asError } from "../../util" import { HealthCheckResult } from "./HealthCheckResult" import { timeoutPromise } from "./index" import "isomorphic-fetch" diff --git a/sdk/lib/health/checkFns/index.ts b/sdk/package/lib/health/checkFns/index.ts similarity index 100% rename from sdk/lib/health/checkFns/index.ts rename to sdk/package/lib/health/checkFns/index.ts diff --git a/sdk/lib/health/checkFns/runHealthScript.ts b/sdk/package/lib/health/checkFns/runHealthScript.ts similarity index 83% rename from sdk/lib/health/checkFns/runHealthScript.ts rename to sdk/package/lib/health/checkFns/runHealthScript.ts index 87fc6c69c..7ecd1ad75 100644 --- a/sdk/lib/health/checkFns/runHealthScript.ts +++ b/sdk/package/lib/health/checkFns/runHealthScript.ts @@ -1,8 +1,6 @@ -import { Effects } from "../../types" -import { Overlay } from "../../util/Overlay" -import { stringFromStdErrOut } from "../../util/stringFromStdErrOut" import { HealthCheckResult } from "./HealthCheckResult" import { timeoutPromise } from "./index" +import { SubContainer } from "../../util/SubContainer" /** * Running a health script, is used when we want to have a simple @@ -13,7 +11,7 @@ import { timeoutPromise } from "./index" */ export const runHealthScript = async ( runCommand: string[], - overlay: Overlay, + subcontainer: SubContainer, { timeout = 30000, errorMessage = `Error while running command: ${runCommand}`, @@ -22,7 +20,7 @@ export const runHealthScript = async ( } = {}, ): Promise => { const res = await Promise.race([ - overlay.exec(runCommand), + subcontainer.exec(runCommand), timeoutPromise(timeout), ]).catch((e) => { console.warn(errorMessage) diff --git a/sdk/package/lib/health/index.ts b/sdk/package/lib/health/index.ts new file mode 100644 index 000000000..b969037a5 --- /dev/null +++ b/sdk/package/lib/health/index.ts @@ -0,0 +1 @@ +import "./checkFns" diff --git a/sdk/package/lib/index.ts b/sdk/package/lib/index.ts new file mode 100644 index 000000000..3619765e1 --- /dev/null +++ b/sdk/package/lib/index.ts @@ -0,0 +1,48 @@ +import { + S9pk, + Version, + VersionRange, + ExtendedVersion, + inputSpec, + ISB, + IST, + types, + T, + matches, + utils, +} from "../../base/lib" + +export { + S9pk, + Version, + VersionRange, + ExtendedVersion, + inputSpec, + ISB, + IST, + types, + T, + matches, + utils, +} +export { Daemons } from "./mainFn/Daemons" +export { SubContainer } from "./util/SubContainer" +export { StartSdk } from "./StartSdk" +export { setupManifest, buildManifest } from "./manifest/setupManifest" +export { FileHelper } from "./util/fileHelper" +export { setupExposeStore } from "./store/setupExposeStore" +export { pathBuilder } from "../../base/lib/util/PathBuilder" + +export * as actions from "../../base/lib/actions" +export * as backup from "./backup" +export * as daemons from "./mainFn/Daemons" +export * as health from "./health" +export * as healthFns from "./health/checkFns" +export * as inits from "./inits" +export * as mainFn from "./mainFn" +export * as toml from "@iarna/toml" +export * as yaml from "yaml" +export * as startSdk from "./StartSdk" +export * as YAML from "yaml" +export * as TOML from "@iarna/toml" +export * from "./version" diff --git a/sdk/lib/inits/index.ts b/sdk/package/lib/inits/index.ts similarity index 100% rename from sdk/lib/inits/index.ts rename to sdk/package/lib/inits/index.ts diff --git a/sdk/lib/inits/setupInit.ts b/sdk/package/lib/inits/setupInit.ts similarity index 53% rename from sdk/lib/inits/setupInit.ts rename to sdk/package/lib/inits/setupInit.ts index 35971d3d3..8d30f5b1f 100644 --- a/sdk/lib/inits/setupInit.ts +++ b/sdk/package/lib/inits/setupInit.ts @@ -1,29 +1,29 @@ -import { DependenciesReceipt } from "../config/setupConfig" -import { ExtendedVersion, VersionRange } from "../exver" -import { SetInterfaces } from "../interfaces/setupInterfaces" - -import { ExposedStorePaths } from "../store/setupExposeStore" -import * as T from "../types" -import { VersionGraph } from "../versionInfo/setupVersionGraph" +import { Actions } from "../../../base/lib/actions/setupActions" +import { ExtendedVersion } from "../../../base/lib/exver" +import { UpdateServiceInterfaces } from "../../../base/lib/interfaces/setupInterfaces" +import { ExposedStorePaths } from "../../../base/lib/types" +import * as T from "../../../base/lib/types" +import { VersionGraph } from "../version/VersionGraph" import { Install } from "./setupInstall" import { Uninstall } from "./setupUninstall" -export function setupInit( - versions: VersionGraph, +export function setupInit( + versions: VersionGraph, install: Install, uninstall: Uninstall, - setInterfaces: SetInterfaces, + setServiceInterfaces: UpdateServiceInterfaces, setDependencies: (options: { effects: T.Effects - input: any - }) => Promise, + }) => Promise, + actions: Actions, exposedStore: ExposedStorePaths, ): { - init: T.ExpectedExports.init - uninit: T.ExpectedExports.uninit + packageInit: T.ExpectedExports.packageInit + packageUninit: T.ExpectedExports.packageUninit + containerInit: T.ExpectedExports.containerInit } { return { - init: async (opts) => { + packageInit: async (opts) => { const prev = await opts.effects.getDataVersion() if (prev) { await versions.migrate({ @@ -37,14 +37,8 @@ export function setupInit( version: versions.current.options.version, }) } - await setInterfaces({ - ...opts, - input: null, - }) - await opts.effects.exposeForDependents({ paths: exposedStore }) - await setDependencies({ effects: opts.effects, input: null }) }, - uninit: async (opts) => { + packageUninit: async (opts) => { if (opts.nextVersion) { const prev = await opts.effects.getDataVersion() if (prev) { @@ -58,5 +52,13 @@ export function setupInit( await uninstall.uninstall(opts) } }, + containerInit: async (opts) => { + await setServiceInterfaces({ + ...opts, + }) + await actions.update({ effects: opts.effects }) + await opts.effects.exposeForDependents({ paths: exposedStore }) + await setDependencies({ effects: opts.effects }) + }, } } diff --git a/sdk/package/lib/inits/setupInstall.ts b/sdk/package/lib/inits/setupInstall.ts new file mode 100644 index 000000000..38a96a00b --- /dev/null +++ b/sdk/package/lib/inits/setupInstall.ts @@ -0,0 +1,25 @@ +import * as T from "../../../base/lib/types" + +export type InstallFn = (opts: { + effects: T.Effects +}) => Promise +export class Install { + private constructor(readonly fn: InstallFn) {} + static of( + fn: InstallFn, + ) { + return new Install(fn) + } + + async install({ effects }: Parameters[0]) { + await this.fn({ + effects, + }) + } +} + +export function setupInstall( + fn: InstallFn, +) { + return Install.of(fn) +} diff --git a/sdk/package/lib/inits/setupUninstall.ts b/sdk/package/lib/inits/setupUninstall.ts new file mode 100644 index 000000000..fc4a71b8e --- /dev/null +++ b/sdk/package/lib/inits/setupUninstall.ts @@ -0,0 +1,29 @@ +import * as T from "../../../base/lib/types" + +export type UninstallFn = (opts: { + effects: T.Effects +}) => Promise +export class Uninstall { + private constructor(readonly fn: UninstallFn) {} + static of( + fn: UninstallFn, + ) { + return new Uninstall(fn) + } + + async uninstall({ + effects, + nextVersion, + }: Parameters[0]) { + if (!nextVersion) + await this.fn({ + effects, + }) + } +} + +export function setupUninstall( + fn: UninstallFn, +) { + return Uninstall.of(fn) +} diff --git a/sdk/package/lib/mainFn/CommandController.ts b/sdk/package/lib/mainFn/CommandController.ts new file mode 100644 index 000000000..1aefc854f --- /dev/null +++ b/sdk/package/lib/mainFn/CommandController.ts @@ -0,0 +1,149 @@ +import { DEFAULT_SIGTERM_TIMEOUT } from "." +import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../../../base/lib/types" + +import * as T from "../../../base/lib/types" +import { asError } from "../../../base/lib/util/asError" +import { + ExecSpawnable, + MountOptions, + SubContainerHandle, + SubContainer, +} from "../util/SubContainer" +import { splitCommand } from "../util" +import * as cp from "child_process" + +export class CommandController { + private constructor( + readonly runningAnswer: Promise, + private state: { exited: boolean }, + private readonly subcontainer: SubContainer, + private process: cp.ChildProcess, + readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, + ) {} + static of() { + return async ( + effects: T.Effects, + subcontainer: + | { + id: keyof Manifest["images"] & T.ImageId + sharedRun?: boolean + } + | SubContainer, + command: T.CommandType, + options: { + subcontainerName?: string + // Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms + sigtermTimeout?: number + mounts?: { path: string; options: MountOptions }[] + runAsInit?: boolean + env?: + | { + [variable: string]: string + } + | undefined + cwd?: string | undefined + user?: string | undefined + onStdout?: (chunk: Buffer | string | any) => void + onStderr?: (chunk: Buffer | string | any) => void + }, + ) => { + const commands = splitCommand(command) + const subc = + subcontainer instanceof SubContainer + ? subcontainer + : await (async () => { + const subc = await SubContainer.of( + effects, + subcontainer, + options?.subcontainerName || commands.join(" "), + ) + for (let mount of options.mounts || []) { + await subc.mount(mount.options, mount.path) + } + return subc + })() + let childProcess: cp.ChildProcess + if (options.runAsInit) { + childProcess = await subc.launch(commands, { + env: options.env, + }) + } else { + childProcess = await subc.spawn(commands, { + env: options.env, + stdio: options.onStdout || options.onStderr ? "pipe" : "inherit", + }) + } + + if (options.onStdout) childProcess.stdout?.on("data", options.onStdout) + if (options.onStderr) childProcess.stderr?.on("data", options.onStderr) + + const state = { exited: false } + const answer = new Promise((resolve, reject) => { + childProcess.on("exit", (code) => { + state.exited = true + if ( + code === 0 || + code === 143 || + (code === null && childProcess.signalCode == "SIGTERM") + ) { + return resolve(null) + } + if (code) { + return reject(new Error(`${commands[0]} exited with code ${code}`)) + } else { + return reject( + new Error( + `${commands[0]} exited with signal ${childProcess.signalCode}`, + ), + ) + } + }) + }) + + return new CommandController( + answer, + state, + subc, + childProcess, + options.sigtermTimeout, + ) + } + } + get subContainerHandle() { + return new SubContainerHandle(this.subcontainer) + } + async wait({ timeout = NO_TIMEOUT } = {}) { + if (timeout > 0) + setTimeout(() => { + this.term() + }, timeout) + try { + return await this.runningAnswer + } finally { + if (!this.state.exited) { + this.process.kill("SIGKILL") + } + await this.subcontainer.destroy?.().catch((_) => {}) + } + } + async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) { + try { + if (!this.state.exited) { + if (signal !== "SIGKILL") { + setTimeout(() => { + if (!this.state.exited) this.process.kill("SIGKILL") + }, timeout) + } + if (!this.process.kill(signal)) { + console.error( + `failed to send signal ${signal} to pid ${this.process.pid}`, + ) + } + } + + await this.runningAnswer + } finally { + await this.subcontainer.destroy?.() + } + } +} diff --git a/sdk/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts similarity index 72% rename from sdk/lib/mainFn/Daemon.ts rename to sdk/package/lib/mainFn/Daemon.ts index 20a067ff6..d9db89b6a 100644 --- a/sdk/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -1,6 +1,6 @@ -import * as T from "../types" -import { asError } from "../util/asError" -import { ExecSpawnable, MountOptions, Overlay } from "../util/Overlay" +import * as T from "../../../base/lib/types" +import { asError } from "../../../base/lib/util/asError" +import { ExecSpawnable, MountOptions, SubContainer } from "../util/SubContainer" import { CommandController } from "./CommandController" const TIMEOUT_INCREMENT_MS = 1000 @@ -14,20 +14,22 @@ export class Daemon { private commandController: CommandController | null = null private shouldBeRunning = false constructor(private startCommand: () => Promise) {} - get overlay(): undefined | ExecSpawnable { - return this.commandController?.nonDestroyableOverlay + get subContainerHandle(): undefined | ExecSpawnable { + return this.commandController?.subContainerHandle } - static of() { + static of() { return async ( effects: T.Effects, - imageId: { - id: keyof Manifest["images"] & T.ImageId - sharedRun?: boolean - }, + subcontainer: + | { + id: keyof Manifest["images"] & T.ImageId + sharedRun?: boolean + } + | SubContainer, command: T.CommandType, options: { + subcontainerName?: string mounts?: { path: string; options: MountOptions }[] - overlay?: Overlay env?: | { [variable: string]: string @@ -35,13 +37,18 @@ export class Daemon { | undefined cwd?: string | undefined user?: string | undefined - onStdout?: (x: Buffer) => void - onStderr?: (x: Buffer) => void + onStdout?: (chunk: Buffer | string | any) => void + onStderr?: (chunk: Buffer | string | any) => void sigtermTimeout?: number }, ) => { const startCommand = () => - CommandController.of()(effects, imageId, command, options) + CommandController.of()( + effects, + subcontainer, + command, + options, + ) return new Daemon(startCommand) } } diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts similarity index 82% rename from sdk/lib/mainFn/Daemons.ts rename to sdk/package/lib/mainFn/Daemons.ts index 3134d0459..cefa06698 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -1,14 +1,11 @@ -import { NO_TIMEOUT, SIGKILL, SIGTERM, Signals } from "../StartSdk" -import { HealthReceipt } from "../health/HealthReceipt" +import { HealthReceipt, Signals } from "../../../base/lib/types" + import { HealthCheckResult } from "../health/checkFns" import { Trigger } from "../trigger" -import { TriggerInput } from "../trigger/TriggerInput" -import { defaultTrigger } from "../trigger/defaultTrigger" -import * as T from "../types" +import * as T from "../../../base/lib/types" import { Mounts } from "./Mounts" -import { CommandOptions, MountOptions, Overlay } from "../util/Overlay" -import { splitCommand } from "../util/splitCommand" +import { ExecSpawnable, MountOptions } from "../util/SubContainer" import { promisify } from "node:util" import * as CP from "node:child_process" @@ -23,12 +20,14 @@ export const cpExec = promisify(CP.exec) export const cpExecFile = promisify(CP.execFile) export type Ready = { display: string | null - fn: () => Promise | HealthCheckResult + fn: ( + spawnable: ExecSpawnable, + ) => Promise | HealthCheckResult trigger?: Trigger } type DaemonsParams< - Manifest extends T.Manifest, + Manifest extends T.SDKManifest, Ids extends string, Command extends string, Id extends string, @@ -40,11 +39,13 @@ type DaemonsParams< ready: Ready requires: Exclude[] sigtermTimeout?: number + onStdout?: (chunk: Buffer | string | any) => void + onStderr?: (chunk: Buffer | string | any) => void } type ErrorDuplicateId = `The id '${Id}' is already used` -export const runCommand = () => +export const runCommand = () => CommandController.of() /** @@ -70,32 +71,34 @@ Daemons.of({ }) ``` */ -export class Daemons { +export class Daemons + implements T.DaemonBuildable +{ private constructor( readonly effects: T.Effects, - readonly started: (onTerm: () => PromiseLike) => PromiseLike, + readonly started: (onTerm: () => PromiseLike) => PromiseLike, readonly daemons: Promise[], readonly ids: Ids[], readonly healthDaemons: HealthDaemon[], ) {} /** - * Returns an empty new Daemons class with the provided config. + * Returns an empty new Daemons class with the provided inputSpec. * * Call .addDaemon() on the returned class to add a daemon. * * Daemons run in the order they are defined, with latter daemons being capable of * depending on prior daemons - * @param config + * @param options * @returns */ - static of(config: { + static of(options: { effects: T.Effects - started: (onTerm: () => PromiseLike) => PromiseLike + started: (onTerm: () => PromiseLike) => PromiseLike healthReceipts: HealthReceipt[] }) { return new Daemons( - config.effects, - config.started, + options.effects, + options.started, [], [], [], @@ -120,6 +123,7 @@ export class Daemons { const daemon = Daemon.of()(this.effects, options.image, options.command, { ...options, mounts: options.mounts.build(), + subcontainerName: id, }) const healthDaemon = new HealthDaemon( daemon, diff --git a/sdk/lib/mainFn/HealthDaemon.ts b/sdk/package/lib/mainFn/HealthDaemon.ts similarity index 82% rename from sdk/lib/mainFn/HealthDaemon.ts rename to sdk/package/lib/mainFn/HealthDaemon.ts index 1a036532f..b66e3e406 100644 --- a/sdk/lib/mainFn/HealthDaemon.ts +++ b/sdk/package/lib/mainFn/HealthDaemon.ts @@ -2,9 +2,9 @@ import { HealthCheckResult } from "../health/checkFns" import { defaultTrigger } from "../trigger/defaultTrigger" import { Ready } from "./Daemons" import { Daemon } from "./Daemon" -import { Effects, SetHealth } from "../types" +import { SetHealth, Effects } from "../../../base/lib/types" import { DEFAULT_SIGTERM_TIMEOUT } from "." -import { asError } from "../util/asError" +import { asError } from "../../../base/lib/util/asError" const oncePromise = () => { let resolve: (value: T) => void @@ -81,7 +81,7 @@ export class HealthDaemon { } } - private healthCheckCleanup: (() => void) | null = null + private healthCheckCleanup: (() => null) | null = null private turnOffHealthCheck() { this.healthCheckCleanup?.() } @@ -100,22 +100,32 @@ export class HealthDaemon { !res.done; res = await Promise.race([status, trigger.next()]) ) { - const response: HealthCheckResult = await Promise.resolve( - this.ready.fn(), - ).catch((err) => { - console.error(asError(err)) - return { + const handle = (await this.daemon).subContainerHandle + + if (handle) { + const response: HealthCheckResult = await Promise.resolve( + this.ready.fn(handle), + ).catch((err) => { + console.error(asError(err)) + return { + result: "failure", + message: "message" in err ? err.message : String(err), + } + }) + await this.setHealth(response) + } else { + await this.setHealth({ result: "failure", - message: "message" in err ? err.message : String(err), - } - }) - await this.setHealth(response) + message: "Daemon not running", + }) + } } }).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`)) this.healthCheckCleanup = () => { setStatus({ done: true }) this.healthCheckCleanup = null + return null } } diff --git a/sdk/lib/mainFn/Mounts.ts b/sdk/package/lib/mainFn/Mounts.ts similarity index 90% rename from sdk/lib/mainFn/Mounts.ts rename to sdk/package/lib/mainFn/Mounts.ts index 968b77b4e..38b3ce2a7 100644 --- a/sdk/lib/mainFn/Mounts.ts +++ b/sdk/package/lib/mainFn/Mounts.ts @@ -1,9 +1,9 @@ -import * as T from "../types" -import { MountOptions } from "../util/Overlay" +import * as T from "../../../base/lib/types" +import { MountOptions } from "../util/SubContainer" type MountArray = { path: string; options: MountOptions }[] -export class Mounts { +export class Mounts { private constructor( readonly volumes: { id: Manifest["volumes"][number] @@ -25,7 +25,7 @@ export class Mounts { }[], ) {} - static of() { + static of() { return new Mounts([], [], []) } @@ -57,7 +57,7 @@ export class Mounts { return this } - addDependency( + addDependency( dependencyId: keyof Manifest["dependencies"] & string, volumeId: DependencyManifest["volumes"][number], subpath: string | null, diff --git a/sdk/lib/mainFn/index.ts b/sdk/package/lib/mainFn/index.ts similarity index 63% rename from sdk/lib/mainFn/index.ts rename to sdk/package/lib/mainFn/index.ts index 7a094a31a..be30c652d 100644 --- a/sdk/lib/mainFn/index.ts +++ b/sdk/package/lib/mainFn/index.ts @@ -1,11 +1,7 @@ -import * as T from "../types" +import * as T from "../../../base/lib/types" import { Daemons } from "./Daemons" -import "../interfaces/ServiceInterfaceBuilder" -import "../interfaces/Origin" - -import "./Daemons" - -import { MainEffects } from "../StartSdk" +import "../../../base/lib/interfaces/ServiceInterfaceBuilder" +import "../../../base/lib/interfaces/Origin" export const DEFAULT_SIGTERM_TIMEOUT = 30_000 /** @@ -18,10 +14,10 @@ export const DEFAULT_SIGTERM_TIMEOUT = 30_000 * @param fn * @returns */ -export const setupMain = ( +export const setupMain = ( fn: (o: { - effects: MainEffects - started(onTerm: () => PromiseLike): PromiseLike + effects: T.Effects + started(onTerm: () => PromiseLike): PromiseLike }) => Promise>, ): T.ExpectedExports.main => { return async (options) => { diff --git a/sdk/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts similarity index 58% rename from sdk/lib/manifest/setupManifest.ts rename to sdk/package/lib/manifest/setupManifest.ts index 2f836d05e..c529f1ab7 100644 --- a/sdk/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -1,15 +1,43 @@ -import * as T from "../types" -import { ImageConfig, ImageId, VolumeId } from "../osBindings" -import { SDKManifest, SDKImageConfig } from "./ManifestTypes" +import * as T from "../../../base/lib/types" +import { ImageConfig, ImageId, VolumeId } from "../../../base/lib/types" +import { + SDKManifest, + SDKImageInputSpec, +} from "../../../base/lib/types/ManifestTypes" import { SDKVersion } from "../StartSdk" -import { VersionGraph } from "../versionInfo/setupVersionGraph" +import { VersionGraph } from "../version/VersionGraph" +import { execSync } from "child_process" /** - * This is an example of a function that takes a manifest and returns a new manifest with additional properties - * @param manifest Manifests are the description of the package - * @returns The manifest with additional properties + * @description Use this function to define critical information about your package + * + * @param versions Every version of the package, imported from ./versions + * @param manifest Static properties of the package */ export function setupManifest< + Id extends string, + VolumesTypes extends VolumeId, + AssetTypes extends VolumeId, + Manifest extends { + id: Id + assets: AssetTypes[] + volumes: VolumesTypes[] + } & SDKManifest, +>(manifest: Manifest): Manifest { + return manifest +} + +function gitHash(): string { + const hash = execSync("git rev-parse HEAD").toString().trim() + try { + execSync("git diff-index --quiet HEAD --") + return hash + } catch (e) { + return hash + "-modified" + } +} + +export function buildManifest< Id extends string, Version extends string, Dependencies extends Record, @@ -20,13 +48,12 @@ export function setupManifest< dependencies: Dependencies id: Id assets: AssetTypes[] - images: Record + images: Record volumes: VolumesTypes[] }, - Satisfies extends string[] = [], >( - manifest: SDKManifest & Manifest, versions: VersionGraph, + manifest: SDKManifest & Manifest, ): Manifest & T.Manifest { const images = Object.entries(manifest.images).reduce( (images, [k, v]) => { @@ -40,7 +67,7 @@ export function setupManifest< ) return { ...manifest, - gitHash: null, + gitHash: gitHash(), osVersion: SDKVersion, version: versions.current.options.version, releaseNotes: versions.current.options.releaseNotes, @@ -56,25 +83,20 @@ export function setupManifest< start: manifest.alerts?.start || null, stop: manifest.alerts?.stop || null, }, - hasConfig: manifest.hasConfig === undefined ? true : manifest.hasConfig, hardwareRequirements: { - device: Object.fromEntries( - Object.entries(manifest.hardwareRequirements?.device || {}).map( - ([k, v]) => [k, v.source], - ), - ), + device: manifest.hardwareRequirements?.device || [], ram: manifest.hardwareRequirements?.ram || null, arch: manifest.hardwareRequirements?.arch === undefined ? Object.values(images).reduce( - (arch, config) => { - if (config.emulateMissingAs) { + (arch, inputSpec) => { + if (inputSpec.emulateMissingAs) { return arch } if (arch === null) { - return config.arch + return inputSpec.arch } - return arch.filter((a) => config.arch.includes(a)) + return arch.filter((a) => inputSpec.arch.includes(a)) }, null as string[] | null, ) diff --git a/sdk/lib/store/getStore.ts b/sdk/package/lib/store/getStore.ts similarity index 90% rename from sdk/lib/store/getStore.ts rename to sdk/package/lib/store/getStore.ts index 5250a02a1..3bde46a25 100644 --- a/sdk/lib/store/getStore.ts +++ b/sdk/package/lib/store/getStore.ts @@ -1,5 +1,5 @@ -import { Effects } from "../types" -import { PathBuilder, extractJsonPath } from "./PathBuilder" +import { Effects } from "../../../base/lib/Effects" +import { PathBuilder, extractJsonPath } from "../util" export class GetStore { constructor( @@ -18,7 +18,7 @@ export class GetStore { return this.effects.store.get({ ...this.options, path: extractJsonPath(this.path), - callback: this.effects.restart, + callback: () => this.effects.constRetry(), }) } /** diff --git a/sdk/package/lib/store/setupExposeStore.ts b/sdk/package/lib/store/setupExposeStore.ts new file mode 100644 index 000000000..1ae0bf13f --- /dev/null +++ b/sdk/package/lib/store/setupExposeStore.ts @@ -0,0 +1,28 @@ +import { ExposedStorePaths } from "../../../base/lib/types" +import { Affine, _ } from "../util" +import { + PathBuilder, + extractJsonPath, + pathBuilder, +} from "../../../base/lib/util/PathBuilder" + +/** + * @description Use this function to determine which Store values to expose and make available to other services running on StartOS. Store values not exposed here will be kept private. Use the type safe pathBuilder to traverse your Store's structure. + * @example + * In this example, we expose the hypothetical Store values "adminPassword" and "nameLastUpdatedAt". + * + * ``` + export const exposedStore = setupExposeStore((pathBuilder) => [ + pathBuilder.adminPassword + pathBuilder.nameLastUpdatedAt, + ]) + * ``` + */ +export const setupExposeStore = >( + fn: (pathBuilder: PathBuilder) => PathBuilder[], +) => { + return fn(pathBuilder()).map( + (x) => extractJsonPath(x) as string, + ) as ExposedStorePaths +} +export { ExposedStorePaths } diff --git a/sdk/lib/test/health.readyCheck.test.ts b/sdk/package/lib/test/health.readyCheck.test.ts similarity index 100% rename from sdk/lib/test/health.readyCheck.test.ts rename to sdk/package/lib/test/health.readyCheck.test.ts diff --git a/sdk/lib/test/host.test.ts b/sdk/package/lib/test/host.test.ts similarity index 78% rename from sdk/lib/test/host.test.ts rename to sdk/package/lib/test/host.test.ts index 64d486a94..87f22b8bd 100644 --- a/sdk/lib/test/host.test.ts +++ b/sdk/package/lib/test/host.test.ts @@ -1,6 +1,6 @@ -import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder" -import { Effects } from "../types" -import { sdk } from "./output.sdk" +import { ServiceInterfaceBuilder } from "../../../base/lib/interfaces/ServiceInterfaceBuilder" +import { Effects } from "../../../base/lib/Effects" +import { sdk } from "../test/output.sdk" describe("host", () => { test("Testing that the types work", () => { diff --git a/sdk/lib/test/configBuilder.test.ts b/sdk/package/lib/test/inputSpecBuilder.test.ts similarity index 82% rename from sdk/lib/test/configBuilder.test.ts rename to sdk/package/lib/test/inputSpecBuilder.test.ts index bd0ddeab1..195acb40a 100644 --- a/sdk/lib/test/configBuilder.test.ts +++ b/sdk/package/lib/test/inputSpecBuilder.test.ts @@ -1,23 +1,24 @@ import { testOutput } from "./output.test" -import { Config } from "../config/builder/config" -import { List } from "../config/builder/list" -import { Value } from "../config/builder/value" -import { Variants } from "../config/builder/variants" -import { ValueSpec } from "../config/configTypes" +import { InputSpec } from "../../../base/lib/actions/input/builder/inputSpec" +import { List } from "../../../base/lib/actions/input/builder/list" +import { Value } from "../../../base/lib/actions/input/builder/value" +import { Variants } from "../../../base/lib/actions/input/builder/variants" +import { ValueSpec } from "../../../base/lib/actions/input/inputSpecTypes" import { setupManifest } from "../manifest/setupManifest" import { StartSdk } from "../StartSdk" -import { VersionGraph } from "../versionInfo/setupVersionGraph" -import { VersionInfo } from "../versionInfo/VersionInfo" +import { VersionGraph } from "../version/VersionGraph" +import { VersionInfo } from "../version/VersionInfo" describe("builder tests", () => { test("text", async () => { const bitcoinPropertiesBuilt: { "peer-tor-address": ValueSpec - } = await Config.of({ + } = await InputSpec.of({ "peer-tor-address": Value.text({ name: "Peer tor address", description: "The Tor address of the peer interface", - required: { default: null }, + required: true, + default: null, }), }).build({} as any) expect(bitcoinPropertiesBuilt).toMatchObject({ @@ -55,7 +56,8 @@ describe("values", () => { test("text", async () => { const value = Value.text({ name: "Testing", - required: { default: null }, + required: true, + default: null, }) const validator = value.validator const rawIs = await value.build({} as any) @@ -66,7 +68,8 @@ describe("values", () => { test("text with default", async () => { const value = Value.text({ name: "Testing", - required: { default: "this is a default value" }, + required: true, + default: "this is a default value", }) const validator = value.validator const rawIs = await value.build({} as any) @@ -78,6 +81,7 @@ describe("values", () => { const value = Value.text({ name: "Testing", required: false, + default: null, }) const validator = value.validator const rawIs = await value.build({} as any) @@ -89,6 +93,7 @@ describe("values", () => { const value = Value.color({ name: "Testing", required: false, + default: null, description: null, warning: null, }) @@ -99,7 +104,8 @@ describe("values", () => { test("datetime", async () => { const value = Value.datetime({ name: "Testing", - required: { default: null }, + required: true, + default: null, description: null, warning: null, inputmode: "date", @@ -114,6 +120,7 @@ describe("values", () => { const value = Value.datetime({ name: "Testing", required: false, + default: null, description: null, warning: null, inputmode: "date", @@ -128,6 +135,7 @@ describe("values", () => { const value = Value.textarea({ name: "Testing", required: false, + default: null, description: null, warning: null, minLength: null, @@ -136,12 +144,13 @@ describe("values", () => { }) const validator = value.validator validator.unsafeCast("test text") - testOutput()(null) + testOutput()(null) }) test("number", async () => { const value = Value.number({ name: "Testing", - required: { default: null }, + required: true, + default: null, integer: false, description: null, warning: null, @@ -159,6 +168,7 @@ describe("values", () => { const value = Value.number({ name: "Testing", required: false, + default: null, integer: false, description: null, warning: null, @@ -175,7 +185,7 @@ describe("values", () => { test("select", async () => { const value = Value.select({ name: "Testing", - required: { default: null }, + default: "a", values: { a: "A", b: "B", @@ -192,7 +202,7 @@ describe("values", () => { test("nullable select", async () => { const value = Value.select({ name: "Testing", - required: false, + default: "a", values: { a: "A", b: "B", @@ -203,8 +213,7 @@ describe("values", () => { const validator = value.validator validator.unsafeCast("a") validator.unsafeCast("b") - validator.unsafeCast(null) - testOutput()(null) + testOutput()(null) }) test("multiselect", async () => { const value = Value.multiselect({ @@ -232,9 +241,8 @@ describe("values", () => { { name: "Testing", description: null, - warning: null, }, - Config.of({ + InputSpec.of({ a: Value.toggle({ name: "test", description: null, @@ -251,14 +259,14 @@ describe("values", () => { const value = Value.union( { name: "Testing", - required: { default: null }, + default: "a", description: null, warning: null, }, Variants.of({ a: { name: "a", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -272,12 +280,21 @@ describe("values", () => { const validator = value.validator validator.unsafeCast({ selection: "a", value: { b: false } }) type Test = typeof validator._TYPE - testOutput()(null) + testOutput< + Test, + { + selection: "a" + value: { + b: boolean + } + other?: {} + } + >()(null) }) describe("dynamic", () => { const fakeOptions = { - config: "config", + inputSpec: "inputSpec", effects: "effects", utils: "utils", } as any @@ -302,7 +319,8 @@ describe("values", () => { test("text", async () => { const value = Value.dynamicText(async () => ({ name: "Testing", - required: { default: null }, + required: true, + default: null, })) const validator = value.validator const rawIs = await value.build({} as any) @@ -318,7 +336,8 @@ describe("values", () => { test("text with default", async () => { const value = Value.dynamicText(async () => ({ name: "Testing", - required: { default: "this is a default value" }, + required: true, + default: "this is a default value", })) const validator = value.validator validator.unsafeCast("test text") @@ -334,6 +353,7 @@ describe("values", () => { const value = Value.dynamicText(async () => ({ name: "Testing", required: false, + default: null, })) const validator = value.validator const rawIs = await value.build({} as any) @@ -350,6 +370,7 @@ describe("values", () => { const value = Value.dynamicColor(async () => ({ name: "Testing", required: false, + default: null, description: null, warning: null, })) @@ -368,48 +389,39 @@ describe("values", () => { test("datetime", async () => { const sdk = StartSdk.of() .withManifest( - setupManifest( - { - id: "testOutput", - title: "", - license: "", - wrapperRepo: "", - upstreamRepo: "", - supportSite: "", - marketingSite: "", - donationUrl: null, - description: { - short: "", - long: "", - }, - containers: {}, - images: {}, - volumes: [], - assets: [], - alerts: { - install: null, - update: null, - uninstall: null, - restore: null, - start: null, - stop: null, - }, - dependencies: { - "remote-test": { - description: "", - optional: true, - s9pk: "https://example.com/remote-test.s9pk", - }, + setupManifest({ + id: "testOutput", + title: "", + license: "", + wrapperRepo: "", + upstreamRepo: "", + supportSite: "", + marketingSite: "", + donationUrl: null, + description: { + short: "", + long: "", + }, + containers: {}, + images: {}, + volumes: [], + assets: [], + alerts: { + install: null, + update: null, + uninstall: null, + restore: null, + start: null, + stop: null, + }, + dependencies: { + "remote-test": { + description: "", + optional: true, + s9pk: "https://example.com/remote-test.s9pk", }, }, - VersionGraph.of( - VersionInfo.of({ - version: "1.0.0:0", - releaseNotes: "", - migrations: {}, - }), - ), - ), + }), ) .withStore<{ test: "a" }>() .build(true) @@ -424,7 +436,8 @@ describe("values", () => { return { name: "Testing", - required: { default: null }, + required: true, + default: null, inputmode: "date", } }, @@ -446,6 +459,7 @@ describe("values", () => { const value = Value.dynamicTextarea(async () => ({ name: "Testing", required: false, + default: null, description: null, warning: null, minLength: null, @@ -454,8 +468,7 @@ describe("values", () => { })) const validator = value.validator validator.unsafeCast("test text") - expect(() => validator.unsafeCast(null)).toThrowError() - testOutput()(null) + testOutput()(null) expect(await value.build(fakeOptions)).toMatchObject({ name: "Testing", required: false, @@ -464,7 +477,8 @@ describe("values", () => { test("number", async () => { const value = Value.dynamicNumber(() => ({ name: "Testing", - required: { default: null }, + required: true, + default: null, integer: false, description: null, warning: null, @@ -487,7 +501,7 @@ describe("values", () => { test("select", async () => { const value = Value.dynamicSelect(() => ({ name: "Testing", - required: { default: null }, + default: "a", values: { a: "A", b: "B", @@ -499,11 +513,9 @@ describe("values", () => { validator.unsafeCast("a") validator.unsafeCast("b") validator.unsafeCast("c") - validator.unsafeCast(null) - testOutput()(null) + testOutput()(null) expect(await value.build(fakeOptions)).toMatchObject({ name: "Testing", - required: true, }) }) test("multiselect", async () => { @@ -539,14 +551,14 @@ describe("values", () => { () => ["a", "c"], { name: "Testing", - required: { default: null }, + default: "a", description: null, warning: null, }, Variants.of({ a: { name: "a", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -557,7 +569,7 @@ describe("values", () => { }, b: { name: "b", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -573,8 +585,28 @@ describe("values", () => { type Test = typeof validator._TYPE testOutput< Test, - | { selection: "a"; value: { b: boolean } } - | { selection: "b"; value: { b: boolean } } + | { + selection: "a" + value: { + b: boolean + } + other?: { + b?: { + b?: boolean + } + } + } + | { + selection: "b" + value: { + b: boolean + } + other?: { + a?: { + b?: boolean + } + } + } >()(null) const built = await value.build({} as any) @@ -606,14 +638,14 @@ describe("values", () => { () => ({ disabled: ["a", "c"], name: "Testing", - required: { default: null }, + default: "b", description: null, warning: null, }), Variants.of({ a: { name: "a", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -624,7 +656,7 @@ describe("values", () => { }, b: { name: "b", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -640,10 +672,28 @@ describe("values", () => { type Test = typeof validator._TYPE testOutput< Test, - | { selection: "a"; value: { b: boolean } } - | { selection: "b"; value: { b: boolean } } - | null - | undefined + | { + selection: "a" + value: { + b: boolean + } + other?: { + b?: { + b?: boolean + } + } + } + | { + selection: "b" + value: { + b: boolean + } + other?: { + a?: { + b?: boolean + } + } + } >()(null) const built = await value.build({} as any) @@ -679,7 +729,7 @@ describe("Builder List", () => { name: "test", }, { - spec: Config.of({ + spec: InputSpec.of({ test: Value.toggle({ name: "test", description: null, @@ -732,12 +782,13 @@ describe("Builder List", () => { describe("Nested nullable values", () => { test("Testing text", async () => { - const value = Config.of({ + const value = InputSpec.of({ a: Value.text({ name: "Temp Name", description: - "If no name is provided, the name from config will be used", + "If no name is provided, the name from inputSpec will be used", required: false, + default: null, }), }) const validator = value.validator @@ -747,12 +798,13 @@ describe("Nested nullable values", () => { testOutput()(null) }) test("Testing number", async () => { - const value = Config.of({ + const value = InputSpec.of({ a: Value.number({ name: "Temp Name", description: - "If no name is provided, the name from config will be used", + "If no name is provided, the name from inputSpec will be used", required: false, + default: null, warning: null, placeholder: null, integer: false, @@ -769,12 +821,13 @@ describe("Nested nullable values", () => { testOutput()(null) }) test("Testing color", async () => { - const value = Config.of({ + const value = InputSpec.of({ a: Value.color({ name: "Temp Name", description: - "If no name is provided, the name from config will be used", + "If no name is provided, the name from inputSpec will be used", required: false, + default: null, warning: null, }), }) @@ -785,12 +838,12 @@ describe("Nested nullable values", () => { testOutput()(null) }) test("Testing select", async () => { - const value = Config.of({ + const value = InputSpec.of({ a: Value.select({ name: "Temp Name", description: - "If no name is provided, the name from config will be used", - required: false, + "If no name is provided, the name from inputSpec will be used", + default: "a", warning: null, values: { a: "A", @@ -799,8 +852,9 @@ describe("Nested nullable values", () => { }) const higher = await Value.select({ name: "Temp Name", - description: "If no name is provided, the name from config will be used", - required: false, + description: + "If no name is provided, the name from inputSpec will be used", + default: "a", warning: null, values: { a: "A", @@ -808,17 +862,16 @@ describe("Nested nullable values", () => { }).build({} as any) const validator = value.validator - validator.unsafeCast({ a: null }) validator.unsafeCast({ a: "a" }) expect(() => validator.unsafeCast({ a: "4" })).toThrowError() - testOutput()(null) + testOutput()(null) }) test("Testing multiselect", async () => { - const value = Config.of({ + const value = InputSpec.of({ a: Value.multiselect({ name: "Temp Name", description: - "If no name is provided, the name from config will be used", + "If no name is provided, the name from inputSpec will be used", warning: null, default: [], diff --git a/sdk/lib/test/makeOutput.ts b/sdk/package/lib/test/makeOutput.ts similarity index 99% rename from sdk/lib/test/makeOutput.ts rename to sdk/package/lib/test/makeOutput.ts index cef17a7e8..434484be9 100644 --- a/sdk/lib/test/makeOutput.ts +++ b/sdk/package/lib/test/makeOutput.ts @@ -3,7 +3,7 @@ import { oldSpecToBuilder } from "../../scripts/oldSpecToBuilder" oldSpecToBuilder( // Make the location "./lib/test/output.ts", - // Put the config here + // Put the inputSpec here { mediasources: { type: "list", diff --git a/sdk/package/lib/test/output.sdk.ts b/sdk/package/lib/test/output.sdk.ts new file mode 100644 index 000000000..d87446585 --- /dev/null +++ b/sdk/package/lib/test/output.sdk.ts @@ -0,0 +1,45 @@ +import { StartSdk } from "../StartSdk" +import { setupManifest } from "../manifest/setupManifest" +import { VersionInfo } from "../version/VersionInfo" +import { VersionGraph } from "../version/VersionGraph" + +export type Manifest = any +export const sdk = StartSdk.of() + .withManifest( + setupManifest({ + id: "testOutput", + title: "", + license: "", + replaces: [], + wrapperRepo: "", + upstreamRepo: "", + supportSite: "", + marketingSite: "", + donationUrl: null, + description: { + short: "", + long: "", + }, + containers: {}, + images: {}, + volumes: [], + assets: [], + alerts: { + install: null, + update: null, + uninstall: null, + restore: null, + start: null, + stop: null, + }, + dependencies: { + "remote-test": { + description: "", + optional: false, + s9pk: "https://example.com/remote-test.s9pk", + }, + }, + }), + ) + .withStore<{ storeRoot: { storeLeaf: "value" } }>() + .build(true) diff --git a/sdk/lib/test/output.test.ts b/sdk/package/lib/test/output.test.ts similarity index 71% rename from sdk/lib/test/output.test.ts rename to sdk/package/lib/test/output.test.ts index 1df84f3af..53d006274 100644 --- a/sdk/lib/test/output.test.ts +++ b/sdk/package/lib/test/output.test.ts @@ -1,7 +1,7 @@ -import { ConfigSpec, matchConfigSpec } from "./output" +import { InputSpecSpec, matchInputSpecSpec } from "./output" import * as _I from "../index" import { camelCase } from "../../scripts/oldSpecToBuilder" -import { deepMerge } from "../util/deepMerge" +import { deepMerge } from "../../../base/lib/util" export type IfEquals = (() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2 ? Y : N @@ -10,37 +10,40 @@ export function testOutput(): (c: IfEquals) => null { } /// Testing the types of the input spec -testOutput()(null) -testOutput()(null) -testOutput()(null) +testOutput()(null) +testOutput()(null) +testOutput()(null) -testOutput()(null) +testOutput()(null) testOutput< - ConfigSpec["rpc"]["advanced"]["serialversion"], + InputSpecSpec["rpc"]["advanced"]["serialversion"], "segwit" | "non-segwit" >()(null) -testOutput()(null) +testOutput()(null) testOutput< - ConfigSpec["advanced"]["peers"]["addnode"][0]["hostname"], + InputSpecSpec["advanced"]["peers"]["addnode"][0]["hostname"], string | null | undefined >()(null) -testOutput()( +testOutput< + InputSpecSpec["testListUnion"][0]["union"]["value"]["name"], + string +>()(null) +testOutput()( null, ) -testOutput()(null) -testOutput>()( +testOutput>()( null, ) // @ts-expect-error Because enable should be a boolean -testOutput()(null) +testOutput()(null) // prettier-ignore // @ts-expect-error Expect that the string is the one above -testOutput()(null); +testOutput()(null); -/// Here we test the output of the matchConfigSpec function +/// Here we test the output of the matchInputSpecSpec function describe("Inputs", () => { - const validInput: ConfigSpec = { + const validInput: InputSpecSpec = { mediasources: ["filebrowser"], testListUnion: [ { @@ -84,7 +87,7 @@ describe("Inputs", () => { dbcache: 5, pruning: { selection: "disabled", - value: {}, + value: { disabled: {} }, }, blockfilters: { blockfilterindex: false, @@ -95,24 +98,24 @@ describe("Inputs", () => { } test("test valid input", () => { - const output = matchConfigSpec.unsafeCast(validInput) + const output = matchInputSpecSpec.unsafeCast(validInput) expect(output).toEqual(validInput) }) test("test no longer care about the conversion of min/max and validating", () => { - matchConfigSpec.unsafeCast( + matchInputSpecSpec.unsafeCast( deepMerge({}, validInput, { rpc: { advanced: { threads: 0 } } }), ) }) test("test errors should throw for number in string", () => { expect(() => - matchConfigSpec.unsafeCast( + matchInputSpecSpec.unsafeCast( deepMerge({}, validInput, { rpc: { enable: 2 } }), ), ).toThrowError() }) test("Test that we set serialversion to something not segwit or non-segwit", () => { expect(() => - matchConfigSpec.unsafeCast( + matchInputSpecSpec.unsafeCast( deepMerge({}, validInput, { rpc: { advanced: { serialversion: "testing" } }, }), diff --git a/sdk/lib/test/store.test.ts b/sdk/package/lib/test/store.test.ts similarity index 52% rename from sdk/lib/test/store.test.ts rename to sdk/package/lib/test/store.test.ts index fa7bc4a4c..f876e76d8 100644 --- a/sdk/lib/test/store.test.ts +++ b/sdk/package/lib/test/store.test.ts @@ -1,9 +1,9 @@ -import { MainEffects, StartSdk } from "../StartSdk" -import { extractJsonPath } from "../store/PathBuilder" -import { Effects } from "../types" +import { Effects } from "../../../base/lib/types" +import { extractJsonPath } from "../../../base/lib/util/PathBuilder" +import { StartSdk } from "../StartSdk" type Store = { - config: { + inputSpec: { someValue: "a" | "b" } } @@ -23,16 +23,16 @@ const storePath = sdk.StorePath describe("Store", () => { test("types", async () => { ;async () => { - sdk.store.setOwn(todo(), storePath.config, { + sdk.store.setOwn(todo(), storePath.inputSpec, { someValue: "a", }) - sdk.store.setOwn(todo(), storePath.config.someValue, "b") + sdk.store.setOwn(todo(), storePath.inputSpec.someValue, "b") sdk.store.setOwn(todo(), storePath, { - config: { someValue: "b" }, + inputSpec: { someValue: "b" }, }) sdk.store.setOwn( todo(), - storePath.config.someValue, + storePath.inputSpec.someValue, // @ts-expect-error Type is wrong for the setting value 5, @@ -40,73 +40,70 @@ describe("Store", () => { sdk.store.setOwn( todo(), // @ts-expect-error Path is wrong - "/config/someVae3lue", + "/inputSpec/someVae3lue", "someValue", ) todo().store.set({ - path: extractJsonPath(storePath.config.someValue), + path: extractJsonPath(storePath.inputSpec.someValue), value: "b", }) - todo().store.set({ - path: extractJsonPath(storePath.config.someValue), + todo().store.set({ + path: extractJsonPath(storePath.inputSpec.someValue), //@ts-expect-error Path is wrong value: "someValueIn", }) ;(await sdk.store - .getOwn(todo(), storePath.config.someValue) + .getOwn(todo(), storePath.inputSpec.someValue) .const()) satisfies string ;(await sdk.store - .getOwn(todo(), storePath.config) - .const()) satisfies Store["config"] + .getOwn(todo(), storePath.inputSpec) + .const()) satisfies Store["inputSpec"] await sdk.store // @ts-expect-error Path is wrong - .getOwn(todo(), "/config/somdsfeValue") + .getOwn(todo(), "/inputSpec/somdsfeValue") .const() /// ----------------- ERRORS ----------------- - sdk.store.setOwn(todo(), storePath, { + sdk.store.setOwn(todo(), storePath, { // @ts-expect-error Type is wrong for the setting value - config: { someValue: "notInAOrB" }, + inputSpec: { someValue: "notInAOrB" }, }) sdk.store.setOwn( - todo(), - sdk.StorePath.config.someValue, + todo(), + sdk.StorePath.inputSpec.someValue, // @ts-expect-error Type is wrong for the setting value "notInAOrB", ) ;(await sdk.store - .getOwn(todo(), storePath.config.someValue) - // @ts-expect-error Const should normally not be callable + .getOwn(todo(), storePath.inputSpec.someValue) .const()) satisfies string ;(await sdk.store - .getOwn(todo(), storePath.config) - // @ts-expect-error Const should normally not be callable - .const()) satisfies Store["config"] + .getOwn(todo(), storePath.inputSpec) + .const()) satisfies Store["inputSpec"] await sdk.store // @ts-expect-error Path is wrong - .getOwn("/config/somdsfeValue") - // @ts-expect-error Const should normally not be callable + .getOwn("/inputSpec/somdsfeValue") .const() /// ;(await sdk.store - .getOwn(todo(), storePath.config.someValue) + .getOwn(todo(), storePath.inputSpec.someValue) // @ts-expect-error satisfies type is wrong .const()) satisfies number await sdk.store // @ts-expect-error Path is wrong - .getOwn(todo(), extractJsonPath(storePath.config)) + .getOwn(todo(), extractJsonPath(storePath.inputSpec)) .const() ;(await todo().store.get({ - path: extractJsonPath(storePath.config.someValue), + path: extractJsonPath(storePath.inputSpec.someValue), callback: noop, })) satisfies string - await todo().store.get({ + await todo().store.get({ // @ts-expect-error Path is wrong as in it doesn't match above - path: "/config/someV2alue", + path: "/inputSpec/someV2alue", callback: noop, }) - await todo().store.get({ + await todo().store.get({ // @ts-expect-error Path is wrong as in it doesn't exists in wrapper type - path: "/config/someV2alue", + path: "/inputSpec/someV2alue", callback: noop, }) } diff --git a/sdk/lib/trigger/TriggerInput.ts b/sdk/package/lib/trigger/TriggerInput.ts similarity index 52% rename from sdk/lib/trigger/TriggerInput.ts rename to sdk/package/lib/trigger/TriggerInput.ts index 82fe79e07..e15cca9b7 100644 --- a/sdk/lib/trigger/TriggerInput.ts +++ b/sdk/package/lib/trigger/TriggerInput.ts @@ -1,4 +1,4 @@ -import { HealthStatus } from "../types" +import { HealthStatus } from "../../../base/lib/types" export type TriggerInput = { lastResult?: HealthStatus diff --git a/sdk/lib/trigger/changeOnFirstSuccess.ts b/sdk/package/lib/trigger/changeOnFirstSuccess.ts similarity index 100% rename from sdk/lib/trigger/changeOnFirstSuccess.ts rename to sdk/package/lib/trigger/changeOnFirstSuccess.ts diff --git a/sdk/lib/trigger/cooldownTrigger.ts b/sdk/package/lib/trigger/cooldownTrigger.ts similarity index 100% rename from sdk/lib/trigger/cooldownTrigger.ts rename to sdk/package/lib/trigger/cooldownTrigger.ts diff --git a/sdk/lib/trigger/defaultTrigger.ts b/sdk/package/lib/trigger/defaultTrigger.ts similarity index 100% rename from sdk/lib/trigger/defaultTrigger.ts rename to sdk/package/lib/trigger/defaultTrigger.ts diff --git a/sdk/lib/trigger/index.ts b/sdk/package/lib/trigger/index.ts similarity index 100% rename from sdk/lib/trigger/index.ts rename to sdk/package/lib/trigger/index.ts diff --git a/sdk/lib/trigger/lastStatus.ts b/sdk/package/lib/trigger/lastStatus.ts similarity index 93% rename from sdk/lib/trigger/lastStatus.ts rename to sdk/package/lib/trigger/lastStatus.ts index 90b8c9851..01e737314 100644 --- a/sdk/lib/trigger/lastStatus.ts +++ b/sdk/package/lib/trigger/lastStatus.ts @@ -1,5 +1,5 @@ import { Trigger } from "." -import { HealthStatus } from "../types" +import { HealthStatus } from "../../../base/lib/types" export type LastStatusTriggerParams = { [k in HealthStatus]?: Trigger } & { default: Trigger diff --git a/sdk/lib/trigger/successFailure.ts b/sdk/package/lib/trigger/successFailure.ts similarity index 100% rename from sdk/lib/trigger/successFailure.ts rename to sdk/package/lib/trigger/successFailure.ts diff --git a/sdk/lib/util/GetSslCertificate.ts b/sdk/package/lib/util/GetSslCertificate.ts similarity index 91% rename from sdk/lib/util/GetSslCertificate.ts rename to sdk/package/lib/util/GetSslCertificate.ts index df19607d4..72bf9e22d 100644 --- a/sdk/lib/util/GetSslCertificate.ts +++ b/sdk/package/lib/util/GetSslCertificate.ts @@ -1,5 +1,5 @@ import { T } from ".." -import { Effects } from "../types" +import { Effects } from "../../../base/lib/Effects" export class GetSslCertificate { constructor( @@ -15,7 +15,7 @@ export class GetSslCertificate { return this.effects.getSslCertificate({ hostnames: this.hostnames, algorithm: this.algorithm, - callback: this.effects.restart, + callback: () => this.effects.constRetry(), }) } /** diff --git a/sdk/lib/util/Overlay.ts b/sdk/package/lib/util/SubContainer.ts similarity index 53% rename from sdk/lib/util/Overlay.ts rename to sdk/package/lib/util/SubContainer.ts index 14908b90f..7274e22d4 100644 --- a/sdk/lib/util/Overlay.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -1,11 +1,12 @@ import * as fs from "fs/promises" -import * as T from "../types" +import * as T from "../../../base/lib/types" import * as cp from "child_process" import { promisify } from "util" import { Buffer } from "node:buffer" +import { once } from "../../../base/lib/util/once" export const execFile = promisify(cp.execFile) const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/` - +const False = () => false type ExecResults = { exitCode: number | null exitSignal: NodeJS.Signals | null @@ -13,23 +14,29 @@ type ExecResults = { stderr: string | Buffer } +export type ExecOptions = { + input?: string | Buffer +} + +const TIMES_TO_WAIT_FOR_PROC = 100 + /** - * This is the type that is going to describe what an overlay could do. The main point of the - * overlay is to have commands that run in a chrooted environment. This is useful for running + * This is the type that is going to describe what an subcontainer could do. The main point of the + * subcontainer is to have commands that run in a chrooted environment. This is useful for running * commands in a containerized environment. But, I wanted the destroy to sometimes be doable, for example the - * case where the overlay isn't owned by the process, the overlay shouldn't be destroyed. + * case where the subcontainer isn't owned by the process, the subcontainer shouldn't be destroyed. */ export interface ExecSpawnable { - get destroy(): undefined | (() => Promise) + get destroy(): undefined | (() => Promise) exec( command: string[], - options?: CommandOptions, + options?: CommandOptions & ExecOptions, timeoutMs?: number | null, ): Promise spawn( command: string[], - options?: CommandOptions, - ): Promise + options?: CommandOptions & StdioOptions, + ): Promise } /** * Want to limit what we can do in a container, so we want to launch a container with a specific image and the mounts. @@ -37,29 +44,63 @@ export interface ExecSpawnable { * Implements: * @see {@link ExecSpawnable} */ -export class Overlay implements ExecSpawnable { - private destroyed = false +export class SubContainer implements ExecSpawnable { + private leader: cp.ChildProcess + private leaderExited: boolean = false + private waitProc: () => Promise private constructor( readonly effects: T.Effects, readonly imageId: T.ImageId, readonly rootfs: string, readonly guid: T.Guid, - ) {} + ) { + this.leaderExited = false + this.leader = cp.spawn("start-cli", ["subcontainer", "launch", rootfs], { + killSignal: "SIGKILL", + stdio: "inherit", + }) + this.leader.on("exit", () => { + this.leaderExited = true + }) + this.waitProc = once( + () => + new Promise(async (resolve, reject) => { + let count = 0 + while ( + !(await fs.stat(`${this.rootfs}/proc/1`).then((x) => !!x, False)) + ) { + if (count++ > TIMES_TO_WAIT_FOR_PROC) { + console.debug("Failed to start subcontainer", { + guid: this.guid, + imageId: this.imageId, + rootfs: this.rootfs, + }) + reject(new Error(`Failed to start subcontainer ${this.imageId}`)) + } + await wait(1) + } + resolve(null) + }), + ) + } static async of( effects: T.Effects, image: { id: T.ImageId; sharedRun?: boolean }, + name: string, ) { const { id, sharedRun } = image - const [rootfs, guid] = await effects.createOverlayedImage({ + const [rootfs, guid] = await effects.subcontainer.createFs({ imageId: id as string, + name, }) - const shared = ["dev", "sys", "proc"] + const shared = ["dev", "sys"] if (!!sharedRun) { shared.push("run") } - fs.copyFile("/etc/resolv.conf", `${rootfs}/etc/resolv.conf`) + await fs.mkdir(`${rootfs}/etc`, { recursive: true }) + await fs.copyFile("/etc/resolv.conf", `${rootfs}/etc/resolv.conf`) for (const dirPart of shared) { const from = `/${dirPart}` @@ -69,27 +110,28 @@ export class Overlay implements ExecSpawnable { await execFile("mount", ["--rbind", from, to]) } - return new Overlay(effects, id, rootfs, guid) + return new SubContainer(effects, id, rootfs, guid) } static async with( effects: T.Effects, image: { id: T.ImageId; sharedRun?: boolean }, mounts: { options: MountOptions; path: string }[], - fn: (overlay: Overlay) => Promise, + name: string, + fn: (subContainer: SubContainer) => Promise, ): Promise { - const overlay = await Overlay.of(effects, image) + const subContainer = await SubContainer.of(effects, image, name) try { for (let mount of mounts) { - await overlay.mount(mount.options, mount.path) + await subContainer.mount(mount.options, mount.path) } - return await fn(overlay) + return await fn(subContainer) } finally { - await overlay.destroy() + await subContainer.destroy() } } - async mount(options: MountOptions, path: string): Promise { + async mount(options: MountOptions, path: string): Promise { path = path.startsWith("/") ? `${this.rootfs}${path}` : `${this.rootfs}/${path}` @@ -134,19 +176,38 @@ export class Overlay implements ExecSpawnable { return this } + private async killLeader() { + if (this.leaderExited) { + return + } + return new Promise((resolve, reject) => { + try { + let timeout = setTimeout(() => this.leader.kill("SIGKILL"), 30000) + this.leader.on("exit", () => { + clearTimeout(timeout) + resolve(null) + }) + if (!this.leader.kill("SIGTERM")) { + reject(new Error("kill(2) failed")) + } + } catch (e) { + reject(e) + } + }) + } + get destroy() { return async () => { - if (this.destroyed) return - this.destroyed = true - const imageId = this.imageId const guid = this.guid - await this.effects.destroyOverlayedImage({ guid }) + await this.killLeader() + await this.effects.subcontainer.destroyFs({ guid }) + return null } } async exec( command: string[], - options?: CommandOptions, + options?: CommandOptions & ExecOptions, timeoutMs: number | null = 30000, ): Promise<{ exitCode: number | null @@ -154,6 +215,7 @@ export class Overlay implements ExecSpawnable { stdout: string | Buffer stderr: string | Buffer }> { + await this.waitProc() const imageMeta: T.ImageMetadata = await fs .readFile(`/media/startos/images/${this.imageId}.json`, { encoding: "utf8", @@ -173,7 +235,8 @@ export class Overlay implements ExecSpawnable { const child = cp.spawn( "start-cli", [ - "chroot", + "subcontainer", + "exec", `--env=/media/startos/images/${this.imageId}.env`, `--workdir=${workdir}`, ...extra, @@ -182,48 +245,54 @@ export class Overlay implements ExecSpawnable { ], options || {}, ) + if (options?.input) { + await new Promise((resolve, reject) => + child.stdin.write(options.input, (e) => { + if (e) { + reject(e) + } else { + resolve(null) + } + }), + ) + await new Promise((resolve) => child.stdin.end(resolve)) + } const pid = child.pid - const stdout = { data: "" as string | Buffer } - const stderr = { data: "" as string | Buffer } + const stdout = { data: "" as string } + const stderr = { data: "" as string } const appendData = - (appendTo: { data: string | Buffer }) => - (chunk: string | Buffer | any) => { - if (typeof appendTo.data === "string" && typeof chunk === "string") { - appendTo.data += chunk - } else if (typeof chunk === "string" || chunk instanceof Buffer) { - appendTo.data = Buffer.concat([ - Buffer.from(appendTo.data), - Buffer.from(chunk), - ]) + (appendTo: { data: string }) => (chunk: string | Buffer | any) => { + if (typeof chunk === "string" || chunk instanceof Buffer) { + appendTo.data += chunk.toString() } else { console.error("received unexpected chunk", chunk) } } return new Promise((resolve, reject) => { child.on("error", reject) - if (timeoutMs !== null && pid) { - setTimeout( - () => execFile("pkill", ["-9", "-s", String(pid)]).catch((_) => {}), - timeoutMs, - ) + let killTimeout: NodeJS.Timeout | undefined + if (timeoutMs !== null && child.pid) { + killTimeout = setTimeout(() => child.kill("SIGKILL"), timeoutMs) } child.stdout.on("data", appendData(stdout)) child.stderr.on("data", appendData(stderr)) - child.on("exit", (code, signal) => + child.on("exit", (code, signal) => { + clearTimeout(killTimeout) resolve({ exitCode: code, exitSignal: signal, stdout: stdout.data, stderr: stderr.data, - }), - ) + }) + }) }) } - async spawn( + async launch( command: string[], options?: CommandOptions, ): Promise { + await this.waitProc() const imageMeta: any = await fs .readFile(`/media/startos/images/${this.imageId}.json`, { encoding: "utf8", @@ -240,10 +309,53 @@ export class Overlay implements ExecSpawnable { workdir = options.cwd delete options.cwd } + await this.killLeader() + this.leaderExited = false + this.leader = cp.spawn( + "start-cli", + [ + "subcontainer", + "launch", + `--env=/media/startos/images/${this.imageId}.env`, + `--workdir=${workdir}`, + ...extra, + this.rootfs, + ...command, + ], + { ...options, stdio: "inherit" }, + ) + this.leader.on("exit", () => { + this.leaderExited = true + }) + return this.leader as cp.ChildProcessWithoutNullStreams + } + + async spawn( + command: string[], + options: CommandOptions & StdioOptions = { stdio: "inherit" }, + ): Promise { + await this.waitProc() + const imageMeta: any = await fs + .readFile(`/media/startos/images/${this.imageId}.json`, { + encoding: "utf8", + }) + .catch(() => "{}") + .then(JSON.parse) + let extra: string[] = [] + if (options.user) { + extra.push(`--user=${options.user}`) + delete options.user + } + let workdir = imageMeta.workdir || "/" + if (options.cwd) { + workdir = options.cwd + delete options.cwd + } return cp.spawn( "start-cli", [ - "chroot", + "subcontainer", + "exec", `--env=/media/startos/images/${this.imageId}.env`, `--workdir=${workdir}`, ...extra, @@ -256,12 +368,12 @@ export class Overlay implements ExecSpawnable { } /** - * Take an overlay but remove the ability to add the mounts and the destroy function. + * Take an subcontainer but remove the ability to add the mounts and the destroy function. * Lets other functions, like health checks, to not destroy the parents. * */ -export class NonDestroyableOverlay implements ExecSpawnable { - constructor(private overlay: ExecSpawnable) {} +export class SubContainerHandle implements ExecSpawnable { + constructor(private subContainer: ExecSpawnable) {} get destroy() { return undefined } @@ -271,13 +383,13 @@ export class NonDestroyableOverlay implements ExecSpawnable { options?: CommandOptions, timeoutMs?: number | null, ): Promise { - return this.overlay.exec(command, options, timeoutMs) + return this.subContainer.exec(command, options, timeoutMs) } spawn( command: string[], - options?: CommandOptions, - ): Promise { - return this.overlay.spawn(command, options) + options: CommandOptions & StdioOptions = { stdio: "inherit" }, + ): Promise { + return this.subContainer.spawn(command, options) } } @@ -287,6 +399,10 @@ export type CommandOptions = { user?: string } +export type StdioOptions = { + stdio?: cp.IOType +} + export type MountOptions = | MountOptionsVolume | MountOptionsAssets @@ -318,3 +434,6 @@ export type MountOptionsBackup = { type: "backup" subpath: string | null } +function wait(time: number) { + return new Promise((resolve) => setTimeout(resolve, time)) +} diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts new file mode 100644 index 000000000..80e5b3564 --- /dev/null +++ b/sdk/package/lib/util/fileHelper.ts @@ -0,0 +1,246 @@ +import * as matches from "ts-matches" +import * as YAML from "yaml" +import * as TOML from "@iarna/toml" +import merge from "lodash.merge" +import * as T from "../../../base/lib/types" +import * as fs from "node:fs/promises" +import { asError } from "../../../base/lib/util" + +const previousPath = /(.+?)\/([^/]*)$/ + +const exists = (path: string) => + fs.access(path).then( + () => true, + () => false, + ) + +async function onCreated(path: string) { + if (path === "/") return + if (!path.startsWith("/")) path = `${process.cwd()}/${path}` + if (await exists(path)) { + return + } + const split = path.split("/") + const filename = split.pop() + const parent = split.join("/") + await onCreated(parent) + const ctrl = new AbortController() + const watch = fs.watch(parent, { persistent: false, signal: ctrl.signal }) + if ( + await fs.access(path).then( + () => true, + () => false, + ) + ) { + ctrl.abort("finished") + return + } + for await (let event of watch) { + if (event.filename === filename) { + ctrl.abort("finished") + return + } + } +} + +/** + * @description Use this class to read/write an underlying configuration file belonging to the upstream service. + * + * Using the static functions, choose between officially supported file formats (json, yaml, toml), or a custom format (raw). + * @example + * Below are a few examples + * + * ``` + * import { matches, FileHelper } from '@start9labs/start-sdk' + * const { arrayOf, boolean, literal, literals, object, oneOf, natural, string } = matches + * + * export const jsonFile = FileHelper.json('./inputSpec.json', object({ + * passwords: arrayOf(string) + * type: oneOf(literals('private', 'public')) + * })) + * + * export const tomlFile = FileHelper.toml('./inputSpec.toml', object({ + * url: literal('https://start9.com') + * public: boolean + * })) + * + * export const yamlFile = FileHelper.yaml('./inputSpec.yml', object({ + * name: string + * age: natural + * })) + * + * export const bitcoinConfFile = FileHelper.raw( + * './service.conf', + * (obj: CustomType) => customConvertObjToFormattedString(obj), + * (str) => customParseStringToTypedObj(str), + * ) + * ``` + */ +export class FileHelper { + protected constructor( + readonly path: string, + readonly writeData: (dataIn: A) => string, + readonly readData: (stringValue: string) => unknown, + readonly validate: (value: unknown) => A, + ) {} + + /** + * Accepts structured data and overwrites the existing file on disk. + */ + private async writeFile(data: A): Promise { + const parent = previousPath.exec(this.path) + if (parent) { + await fs.mkdir(parent[1], { recursive: true }) + } + + await fs.writeFile(this.path, this.writeData(data)) + + return null + } + + private async readFile(): Promise { + if (!(await exists(this.path))) { + return null + } + return this.readData( + await fs.readFile(this.path).then((data) => data.toString("utf-8")), + ) + } + + /** + * Reads the file from disk and converts it to structured data. + */ + private async readOnce(): Promise { + const data = await this.readFile() + if (!data) return null + return this.validate(data) + } + + private async readConst(effects: T.Effects): Promise { + const watch = this.readWatch() + const res = await watch.next() + watch.next().then(effects.constRetry) + return res.value + } + + private async *readWatch() { + let res + while (true) { + if (await exists(this.path)) { + const ctrl = new AbortController() + const watch = fs.watch(this.path, { + persistent: false, + signal: ctrl.signal, + }) + res = await this.readOnce() + const listen = Promise.resolve() + .then(async () => { + for await (const _ of watch) { + ctrl.abort("finished") + return null + } + }) + .catch((e) => console.error(asError(e))) + yield res + await listen + } else { + yield null + await onCreated(this.path).catch((e) => console.error(asError(e))) + } + } + return null + } + + get read() { + return { + once: () => this.readOnce(), + const: (effects: T.Effects) => this.readConst(effects), + watch: () => this.readWatch(), + } + } + + /** + * Accepts full structured data and performs a merge with the existing file on disk if it exists. + */ + async write(data: A) { + const fileData = (await this.readFile()) || {} + const mergeData = merge({}, fileData, data) + return await this.writeFile(this.validate(mergeData)) + } + + /** + * Accepts partial structured data and performs a merge with the existing file on disk. + */ + async merge(data: T.DeepPartial) { + const fileData = + (await this.readFile()) || + (() => { + throw new Error(`${this.path}: does not exist`) + })() + const mergeData = merge({}, fileData, data) + return await this.writeFile(this.validate(mergeData)) + } + + /** + * We wanted to be able to have a fileHelper, and just modify the path later in time. + * Like one behaviour of another dependency or something similar. + */ + withPath(path: string) { + return new FileHelper(path, this.writeData, this.readData, this.validate) + } + + /** + * Create a File Helper for an arbitrary file type. + * + * Provide custom functions for translating data to/from the file format. + */ + static raw( + path: string, + toFile: (dataIn: A) => string, + fromFile: (rawData: string) => unknown, + validate: (data: unknown) => A, + ) { + return new FileHelper(path, toFile, fromFile, validate) + } + /** + * Create a File Helper for a .json file. + */ + static json(path: string, shape: matches.Validator) { + return new FileHelper( + path, + (inData) => JSON.stringify(inData, null, 2), + (inString) => JSON.parse(inString), + (data) => shape.unsafeCast(data), + ) + } + /** + * Create a File Helper for a .toml file + */ + static toml>( + path: string, + shape: matches.Validator, + ) { + return new FileHelper( + path, + (inData) => TOML.stringify(inData as any), + (inString) => TOML.parse(inString), + (data) => shape.unsafeCast(data), + ) + } + /** + * Create a File Helper for a .yaml file + */ + static yaml>( + path: string, + shape: matches.Validator, + ) { + return new FileHelper( + path, + (inData) => YAML.stringify(inData, null, 2), + (inString) => YAML.parse(inString), + (data) => shape.unsafeCast(data), + ) + } +} + +export default FileHelper diff --git a/sdk/package/lib/util/index.ts b/sdk/package/lib/util/index.ts new file mode 100644 index 000000000..66c73503e --- /dev/null +++ b/sdk/package/lib/util/index.ts @@ -0,0 +1,4 @@ +export * from "../../../base/lib/util" +export { GetSslCertificate } from "./GetSslCertificate" + +export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname" diff --git a/sdk/lib/versionInfo/setupVersionGraph.ts b/sdk/package/lib/version/VersionGraph.ts similarity index 92% rename from sdk/lib/versionInfo/setupVersionGraph.ts rename to sdk/package/lib/version/VersionGraph.ts index 5f89a7e35..91c7a0cc0 100644 --- a/sdk/lib/versionInfo/setupVersionGraph.ts +++ b/sdk/package/lib/version/VersionGraph.ts @@ -1,8 +1,6 @@ -import { ExtendedVersion, VersionRange } from "../exver" - -import * as T from "../types" -import { Graph, Vertex } from "../util/graph" -import { once } from "../util/once" +import { ExtendedVersion, VersionRange } from "../../../base/lib/exver" +import * as T from "../../../base/lib/types" +import { Graph, Vertex, once } from "../util" import { IMPOSSIBLE, VersionInfo } from "./VersionInfo" export class VersionGraph { @@ -110,6 +108,11 @@ export class VersionGraph { currentVersion = once(() => ExtendedVersion.parse(this.current.options.version), ) + /** + * Each exported `VersionInfo.of()` should be imported and provided as an argument to this function. + * + * ** The current version must be the FIRST argument. ** + */ static of< CurrentVersion extends string, OtherVersions extends Array>, @@ -191,16 +194,6 @@ export class VersionGraph { ) } -export function setupVersionGraph< - CurrentVersion extends string, - OtherVersions extends Array>, ->( - current: VersionInfo, - ...other: EnsureUniqueId -) { - return VersionGraph.of(current, ...other) -} - // prettier-ignore export type EnsureUniqueId = B extends [] ? A : diff --git a/sdk/lib/versionInfo/VersionInfo.ts b/sdk/package/lib/version/VersionInfo.ts similarity index 68% rename from sdk/lib/versionInfo/VersionInfo.ts rename to sdk/package/lib/version/VersionInfo.ts index beea16019..952ae5352 100644 --- a/sdk/lib/versionInfo/VersionInfo.ts +++ b/sdk/package/lib/version/VersionInfo.ts @@ -1,29 +1,27 @@ -import { ValidateExVer } from "../exver" -import * as T from "../types" +import { ValidateExVer } from "../../../base/lib/exver" +import * as T from "../../../base/lib/types" export const IMPOSSIBLE = Symbol("IMPOSSIBLE") export type VersionOptions = { - /** The version being described */ + /** The exver-compliant version number */ version: Version & ValidateExVer /** The release notes for this version */ releaseNotes: string /** Data migrations for this version */ migrations: { /** - * A migration from the previous version - * Leave blank to indicate no migration is necessary - * Set to `IMPOSSIBLE` to indicate migrating from the previous version is not possible + * A migration from the previous version. Leave empty to indicate no migration is necessary. + * Set to `IMPOSSIBLE` to indicate migrating from the previous version is not possible. */ up?: ((opts: { effects: T.Effects }) => Promise) | typeof IMPOSSIBLE /** - * A migration to the previous version - * Leave blank to indicate no migration is necessary - * Set to `IMPOSSIBLE` to indicate downgrades are prohibited + * A migration to the previous version. Leave blank to indicate no migration is necessary. + * Set to `IMPOSSIBLE` to indicate downgrades are prohibited */ down?: ((opts: { effects: T.Effects }) => Promise) | typeof IMPOSSIBLE /** - * Additional migrations, such as fast-forward migrations, or migrations from other flavors + * Additional migrations, such as fast-forward migrations, or migrations from other flavors. */ other?: Record Promise> } @@ -34,6 +32,13 @@ export class VersionInfo { private constructor( readonly options: VersionOptions & { satisfies: string[] }, ) {} + /** + * @description Use this function to define a new version of the service. By convention, each version should receive its own file. + * @property {string} version + * @property {string} releaseNotes + * @property {object} migrations + * @returns A VersionInfo class instance that is exported, then imported into versions/index.ts. + */ static of(options: VersionOptions) { return new VersionInfo({ ...options, satisfies: [] }) } diff --git a/sdk/package/lib/version/index.ts b/sdk/package/lib/version/index.ts new file mode 100644 index 000000000..c7a47fc38 --- /dev/null +++ b/sdk/package/lib/version/index.ts @@ -0,0 +1,2 @@ +export * from "./VersionGraph" +export * from "./VersionInfo" diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json new file mode 100644 index 000000000..0b06e79db --- /dev/null +++ b/sdk/package/package-lock.json @@ -0,0 +1,4714 @@ +{ + "name": "@start9labs/start-sdk", + "version": "0.3.6-alpha.16", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@start9labs/start-sdk", + "version": "0.3.6-alpha.16", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime-types": "^2.1.35", + "ts-matches": "^6.0.0", + "yaml": "^2.2.2" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/lodash.merge": "^4.6.2", + "jest": "^29.4.3", + "peggy": "^3.0.2", + "prettier": "^3.2.5", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "ts-pegjs": "^4.2.1", + "tsx": "^4.7.1", + "typescript": "^5.0.4" + } + }, + "../base": { + "name": "@start9labs/start-sdk-base", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime": "^4.0.3", + "ts-matches": "^5.5.1", + "yaml": "^2.2.2" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/lodash.merge": "^4.6.2", + "jest": "^29.4.3", + "peggy": "^3.0.2", + "prettier": "^3.2.5", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "ts-pegjs": "^4.2.1", + "tsx": "^4.7.1", + "typescript": "^5.0.4" + } + }, + "../base/dist": { + "extraneous": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.19.0.tgz", + "integrity": "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.12", + "minimatch": "^7.4.3", + "mkdirp": "^2.1.6", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", + "dev": true + }, + "node_modules/@types/lodash.merge": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz", + "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "18.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", + "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001470", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", + "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", + "dev": true + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "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/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.341", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.341.tgz", + "integrity": "sha512-R4A8VfUBQY9WmAhuqY5tjHRf5fH2AAf6vqitBOE0y6u2PgHgqHSrhZmu78dIX3fVZtjqlwJNX1i2zwC3VpHtQQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "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/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "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/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/isomorphic-fetch/node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/peggy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/peggy/-/peggy-3.0.2.tgz", + "integrity": "sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==", + "dev": true, + "dependencies": { + "commander": "^10.0.0", + "source-map-generator": "0.8.0" + }, + "bin": { + "peggy": "bin/peggy.js" + }, + "engines": { + "node": ">=14" + } + }, + "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/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/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/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", + "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-generator": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map-generator/-/source-map-generator-0.8.0.tgz", + "integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-jest": { + "version": "29.0.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", + "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-matches": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.0.0.tgz", + "integrity": "sha512-vR4hhz9bYMW30qIJUuLaeAWlsR54vse6ZI2riVhVLMBE6/vss43jwrOvbHheiyU7e26ssT/yWx69aJHD2REJSA==", + "license": "MIT" + }, + "node_modules/ts-morph": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz", + "integrity": "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==", + "dev": true, + "dependencies": { + "@ts-morph/common": "~0.19.0", + "code-block-writer": "^12.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-pegjs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ts-pegjs/-/ts-pegjs-4.2.1.tgz", + "integrity": "sha512-mK/O2pu6lzWUeKpEMA/wsa0GdYblfjJI1y0s0GqH6xCTvugQDOWPJbm5rY6AHivpZICuXIriCb+a7Cflbdtc2w==", + "dev": true, + "dependencies": { + "prettier": "^2.8.8", + "ts-morph": "^18.0.0" + }, + "bin": { + "tspegjs": "dist/cli.mjs" + }, + "peerDependencies": { + "peggy": "^3.0.2" + } + }, + "node_modules/ts-pegjs/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz", + "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "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": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "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/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sdk/package.json b/sdk/package/package.json similarity index 75% rename from sdk/package.json rename to sdk/package/package.json index b2b2ceb44..bbcab9830 100644 --- a/sdk/package.json +++ b/sdk/package/package.json @@ -1,23 +1,23 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha7", + "version": "0.3.6-alpha.17", "description": "Software development kit to facilitate packaging services for StartOS", - "main": "./cjs/lib/index.js", - "types": "./cjs/lib/index.d.ts", - "module": "./mjs/lib/index.js", - "browser": "./mjs/lib/index.browser.js", + "main": "./package/lib/index.js", + "types": "./package/lib/index.d.ts", "sideEffects": true, "typesVersion": { ">=3.1": { "*": [ - "cjs/lib/*" + "package/lib/*", + "base/lib/*" ] } }, "scripts": { "test": "jest -c ./jest.config.js --coverage", - "buildOutput": "ts-node --project ./tsconfig-cjs.json ./lib/test/makeOutput.ts && npx prettier --write '**/*.ts'", - "check": "tsc --noEmit" + "buildOutput": "ts-node ./lib/test/makeOutput.ts && npx prettier --write \"**/*.ts\"", + "check": "tsc --noEmit", + "tsc": "tsc" }, "repository": { "type": "git", @@ -32,8 +32,8 @@ "dependencies": { "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", - "mime": "^4.0.3", - "ts-matches": "^5.5.1", + "mime-types": "^2.1.35", + "ts-matches": "^6.0.0", "yaml": "^2.2.2", "@iarna/toml": "^2.2.5", "@noble/curves": "^1.4.0", diff --git a/sdk/scripts/oldSpecToBuilder.ts b/sdk/package/scripts/oldSpecToBuilder.ts similarity index 85% rename from sdk/scripts/oldSpecToBuilder.ts rename to sdk/package/scripts/oldSpecToBuilder.ts index 6dd726e1b..11ef30340 100644 --- a/sdk/scripts/oldSpecToBuilder.ts +++ b/sdk/package/scripts/oldSpecToBuilder.ts @@ -33,18 +33,18 @@ export default async function makeFileContentFromOld( const outputLines: string[] = [] outputLines.push(` import { sdk } from "${StartSdk}" -const {Config, List, Value, Variants} = sdk +const {InputSpec, List, Value, Variants} = sdk `) const data = await inputData - const namedConsts = new Set(["Config", "Value", "List"]) - const configName = newConst("configSpec", convertInputSpec(data)) - const configMatcherName = newConst( - "matchConfigSpec", - `${configName}.validator`, + const namedConsts = new Set(["InputSpec", "Value", "List"]) + const inputSpecName = newConst("inputSpecSpec", convertInputSpec(data)) + const inputSpecMatcherName = newConst( + "matchInputSpecSpec", + `${inputSpecName}.validator`, ) outputLines.push( - `export type ConfigSpec = typeof ${configMatcherName}._TYPE;`, + `export type InputSpecSpec = typeof ${inputSpecMatcherName}._TYPE;`, ) return outputLines.join("\n") @@ -71,7 +71,7 @@ const {Config, List, Value, Variants} = sdk } function convertInputSpec(data: any) { - return `Config.of(${convertInputSpecInner(data)})` + return `InputSpec.of(${convertInputSpecInner(data)})` } function convertValueSpec(value: any): string { switch (value.type) { @@ -85,6 +85,7 @@ const {Config, List, Value, Variants} = sdk description: value.description || null, warning: value.warning || null, required: !(value.nullable || false), + default: value.default, placeholder: value.placeholder || null, maxLength: null, minLength: null, @@ -96,12 +97,8 @@ const {Config, List, Value, Variants} = sdk return `${rangeToTodoComment(value?.range)}Value.text(${JSON.stringify( { name: value.name || null, - // prettier-ignore - required: ( - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable - ), + default: value.default || null, + required: !value.nullable, description: value.description || null, warning: value.warning || null, masked: value.masked || false, @@ -130,12 +127,8 @@ const {Config, List, Value, Variants} = sdk name: value.name || null, description: value.description || null, warning: value.warning || null, - // prettier-ignore - required: ( - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable - ), + default: value.default || null, + required: !value.nullable, min: null, max: null, step: null, @@ -174,13 +167,7 @@ const {Config, List, Value, Variants} = sdk name: value.name || null, description: value.description || null, warning: value.warning || null, - - // prettier-ignore - required:( - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable - ), + default: value.default, values, }, null, @@ -195,7 +182,6 @@ const {Config, List, Value, Variants} = sdk return `Value.object({ name: ${JSON.stringify(value.name || null)}, description: ${JSON.stringify(value.description || null)}, - warning: ${JSON.stringify(value.warning || null)}, }, ${specName})` } case "union": { @@ -208,14 +194,7 @@ const {Config, List, Value, Variants} = sdk name: ${JSON.stringify(value.name || null)}, description: ${JSON.stringify(value.tag.description || null)}, warning: ${JSON.stringify(value.tag.warning || null)}, - - // prettier-ignore - required: ${JSON.stringify( - // prettier-ignore - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable, - )}, + default: ${JSON.stringify(value.default)}, }, ${variants})` } case "list": { @@ -319,7 +298,6 @@ const {Config, List, Value, Variants} = sdk maxLength: ${JSON.stringify(null)}, default: ${JSON.stringify(value.default || null)}, description: ${JSON.stringify(value.description || null)}, - warning: ${JSON.stringify(value.warning || null)}, }, { spec: ${specName}, displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)}, @@ -343,19 +321,14 @@ const {Config, List, Value, Variants} = sdk value?.spec?.tag?.description || null, )}, warning: ${JSON.stringify(value?.spec?.tag?.warning || null)}, - required: ${JSON.stringify( - // prettier-ignore - 'default' in value?.spec ? {default: value?.spec?.default} : - !!value?.spec?.tag?.nullable || false ? {default: null} : - false, - )}, + default: ${JSON.stringify(value?.spec?.default || null)}, }, ${variants}) `, ) - const listConfig = maybeNewConst( - value.name + "_list_config", + const listInputSpec = maybeNewConst( + value.name + "_list_inputSpec", ` - Config.of({ + InputSpec.of({ "union": ${unionValueName} }) `, @@ -368,7 +341,7 @@ const {Config, List, Value, Variants} = sdk description: ${JSON.stringify(value.description || null)}, warning: ${JSON.stringify(value.warning || null)}, }, { - spec: ${listConfig}, + spec: ${listInputSpec}, displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)}, uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)}, })` @@ -407,7 +380,7 @@ function rangeToTodoComment(range: string | undefined) { } // oldSpecToBuilder( -// "./config.ts", -// // Put config here +// "./inputSpec.ts", +// // Put inputSpec here // {}, // ) diff --git a/sdk/package/tsconfig.json b/sdk/package/tsconfig.json new file mode 100644 index 000000000..7b4d4f7d8 --- /dev/null +++ b/sdk/package/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "strict": true, + "preserveConstEnums": true, + "sourceMap": true, + "pretty": true, + "declaration": true, + "noImplicitAny": true, + "esModuleInterop": true, + "types": ["node", "jest"], + "moduleResolution": "node", + "skipLibCheck": true, + "module": "commonjs", + "outDir": "../dist", + "target": "es2018" + }, + "include": ["lib/**/*", "../base/lib/util/Hostname.ts"], + "exclude": ["lib/**/*.spec.ts", "lib/**/*.gen.ts", "list", "node_modules"] +} diff --git a/sdk/tsconfig-cjs.json b/sdk/tsconfig-cjs.json deleted file mode 100644 index 8413cf248..000000000 --- a/sdk/tsconfig-cjs.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "module": "commonjs", - "outDir": "dist/cjs", - "target": "es2018" - } -} diff --git a/sdk/tsconfig.json b/sdk/tsconfig.json deleted file mode 100644 index 8ae7d62a8..000000000 --- a/sdk/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "module": "esnext", - "outDir": "dist/mjs", - "target": "esnext" - } -} diff --git a/web/README.md b/web/README.md index 213113775..feddf713e 100644 --- a/web/README.md +++ b/web/README.md @@ -3,33 +3,37 @@ StartOS web UIs are written in [Angular/Typescript](https://angular.io/docs) and leverage the [Ionic Framework](https://ionicframework.com/) component library. StartOS conditionally serves one of four Web UIs, depending on the state of the system and user choice. + - **install-wizard** - UI for installing StartOS, served on localhost. - **setup-wizard** - UI for setting up StartOS, served on start.local. -- **diagnostic-ui** - UI to display any error during server initialization, served on start.local. - **ui** - primary UI for administering StartOS, served on various hosts unique to the instance. Additionally, there are two libraries for shared code: + - **marketplace** - library code shared between the StartOS UI and Start9's [brochure marketplace](https://github.com/Start9Labs/brochure-marketplace). - **shared** - library code shared between the various web UIs and marketplace lib. ## Environment Setup #### Install NodeJS and NPM + - [Install nodejs](https://nodejs.org/en/) - [Install npm](https://www.npmjs.com/get-npm) #### Check that your versions match the ones below + ```sh node --version -v18.15.0 +v20.17.0 npm --version -v8.0.0 +v10.8.2 ``` #### Install and enable the Prettier extension for your text editor #### Clone StartOS and load the PatchDB submodule if you have not already + ```sh git clone https://github.com/Start9Labs/start-os.git cd start-os @@ -37,13 +41,17 @@ git submodule update --init --recursive ``` #### Move to web directory and install dependencies + ```sh cd web npm i npm run build:deps ``` +> Note if you are on **Windows** you need to install `make` for these scripts to work. Easiest way to do so is to install [Chocolatey](https://chocolatey.org/install) and then run `choco install make`. + #### Copy `config-sample.json` to a new file `config.json`. + ```sh cp config-sample.json config.json ``` @@ -59,10 +67,10 @@ You can develop using mocks (recommended to start) or against a live server. Eit ### Using mocks #### Start the standard development server + ```sh npm run start:install-wiz npm run start:setup -npm run start:dui npm run start:ui ``` @@ -71,6 +79,7 @@ npm run start:ui #### In `config.json`, set "useMocks" to `false` #### Copy `proxy.conf-sample.json` to a new file `proxy.conf.json` + ```sh cp proxy.conf-sample.json proxy.conf.json ``` @@ -78,6 +87,7 @@ cp proxy.conf-sample.json proxy.conf.json #### Replace every instance of "\\" with the hostname of your remote server #### Start the proxy development server + ```sh npm run start:ui:proxy ``` diff --git a/web/ionic.config.json b/web/ionic.config.json index ee434f78a..c5810bc10 100644 --- a/web/ionic.config.json +++ b/web/ionic.config.json @@ -17,12 +17,6 @@ "integrations": {}, "type": "angular", "root": "projects/setup-wizard" - }, - "diagnostic-ui": { - "name": "diagnostic-ui", - "integrations": {}, - "type": "angular", - "root": "projects/diagnostic-ui" } }, "defaultProject": "ui" diff --git a/web/lint-staged.config.js b/web/lint-staged.config.js index 80ea7cf8b..731cc9d5e 100644 --- a/web/lint-staged.config.js +++ b/web/lint-staged.config.js @@ -4,7 +4,6 @@ module.exports = { 'projects/ui/**/*.ts': () => 'npm run check:ui', 'projects/shared/**/*.ts': () => 'npm run check:shared', 'projects/marketplace/**/*.ts': () => 'npm run check:marketplace', - 'projects/diagnostic-ui/**/*.ts': () => 'npm run check:dui', 'projects/install-wizard/**/*.ts': () => 'npm run check:install-wiz', 'projects/setup-wizard/**/*.ts': () => 'npm run check:setup', } diff --git a/web/package-lock.json b/web/package-lock.json index ac1f77a6b..764694127 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "startos-ui", - "version": "0.3.6", + "version": "0.3.6-alpha.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.3.6", + "version": "0.3.6-alpha.8", "license": "MIT", "dependencies": { "@angular/animations": "^14.1.0", @@ -21,20 +21,20 @@ "@angular/service-worker": "^14.2.2", "@ionic/angular": "^6.1.15", "@materia-ui/ngx-monaco-editor": "^6.0.0", - "@ng-web-apis/common": "^3.0.6", - "@ng-web-apis/mutation-observer": "^3.2.1", - "@ng-web-apis/resize-observer": "^3.2.1", + "@ng-web-apis/common": "^3.2.3", + "@ng-web-apis/mutation-observer": "^3.2.3", + "@ng-web-apis/resize-observer": "^3.2.3", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "@start9labs/argon2": "^0.2.2", "@start9labs/emver": "^0.1.5", - "@start9labs/start-sdk": "file:../sdk/dist", - "@taiga-ui/addon-charts": "3.86.0", - "@taiga-ui/cdk": "3.86.0", - "@taiga-ui/core": "3.86.0", - "@taiga-ui/experimental": "3.86.0", - "@taiga-ui/icons": "3.86.0", - "@taiga-ui/kit": "3.86.0", + "@start9labs/start-sdk": "file:../sdk/baseDist", + "@taiga-ui/addon-charts": "3.96.0", + "@taiga-ui/cdk": "3.96.0", + "@taiga-ui/core": "3.96.0", + "@taiga-ui/experimental": "3.96.0", + "@taiga-ui/icons": "3.96.0", + "@taiga-ui/kit": "3.96.0", "@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-event-plugins": "3.2.0", "angular-svg-round-progressbar": "^9.0.0", @@ -57,6 +57,7 @@ "ng-qrcode": "^7.0.0", "node-jose": "^2.2.0", "patch-db-client": "file:../patch-db/client", + "path-browserify": "^1.0.1", "pbkdf2": "^3.1.2", "rxjs": "^7.8.1", "swiper": "^8.2.4", @@ -114,9 +115,37 @@ "rxjs": ">=7.0.0" } }, + "../sdk/baseDist": { + "name": "@start9labs/start-sdk-base", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime-types": "^2.1.35", + "ts-matches": "^5.5.1", + "yaml": "^2.2.2" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/lodash.merge": "^4.6.2", + "@types/mime-types": "^2.1.4", + "jest": "^29.4.3", + "peggy": "^3.0.2", + "prettier": "^3.2.5", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "ts-pegjs": "^4.2.1", + "tsx": "^4.7.1", + "typescript": "^5.0.4" + } + }, "../sdk/dist": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha5", + "version": "0.3.6-alpha8", + "extraneous": true, "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -3732,10 +3761,11 @@ } }, "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 + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "devOptional": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -3757,6 +3787,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-1.9.0.tgz", "integrity": "sha512-Wa/9nM9Nv0oieVZ6yxQNXfDRA4obFDR15xO16o1GKF8i9W1IdQQn+tuMRjkmx6HhJDN9+x3k8OTJ1f80BIrhjA==", + "license": "Apache-2.0", "dependencies": { "tslib": "2.6.2" }, @@ -3771,17 +3802,20 @@ "node_modules/@maskito/angular/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" }, "node_modules/@maskito/core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@maskito/core/-/core-1.9.0.tgz", - "integrity": "sha512-WQIUrwkdIUg6PzAb4Apa0RjTPHB0EqZLc9/7kWCKVIixhkITRFXFg2BhiDVSRv0mIKVlAEJcOvvjHK5T3UaIig==" + "integrity": "sha512-WQIUrwkdIUg6PzAb4Apa0RjTPHB0EqZLc9/7kWCKVIixhkITRFXFg2BhiDVSRv0mIKVlAEJcOvvjHK5T3UaIig==", + "license": "Apache-2.0" }, "node_modules/@maskito/kit": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-1.9.0.tgz", "integrity": "sha512-LNNgOJ0tAfrPoPehvoP+ZyYF9giOYL02sOMKyDC3IcqDNA8BAU0PARmS7TNsVEBpvSuJhU6xVt40nxJaONgUdw==", + "license": "Apache-2.0", "peerDependencies": { "@maskito/core": "^1.9.0" } @@ -3799,9 +3833,10 @@ } }, "node_modules/@ng-web-apis/common": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-3.0.6.tgz", - "integrity": "sha512-ral+lzGpFS3aOCFB5DcHOI4lZhhp8GH4BnjSbngH2Xk8J0FKYdxRzvcPQVy7hS+TPUu0tW9uFVp6cC7odu3iyQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-3.2.3.tgz", + "integrity": "sha512-1ts2FkLRw6dE/uTuYFMf9VTbLJ9CS8dpfIXTpxFsPArs13mEuz0Yvpe0rl0tMAhfNoeN4e7V8wVSyqDNgfzgmw==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.2.0" }, @@ -3812,9 +3847,10 @@ } }, "node_modules/@ng-web-apis/intersection-observer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.0.tgz", - "integrity": "sha512-EhwqEZJFKR9pz55TWp82qyWTXdg8TZeMP6bUw26bVHz8CTkgrpzaXdtxurMTvJ/+gwuFy4JSJLjBeV9nfZ/SXA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.3.tgz", + "integrity": "sha512-0yp+rr6ZEyF2vz4zYlMZ1GNtTHQziKajCurqAycZkSXVUdon7MhfiY/PiTr8xklTr40DjrF4anXV7oUAaA0szQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.2.0" }, @@ -3824,9 +3860,10 @@ } }, "node_modules/@ng-web-apis/mutation-observer": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.2.1.tgz", - "integrity": "sha512-a7krkMx0e9cfnutClwDylWjbTQVRHUP3oUik/nBvUdKlk/Q4anNww9aIKJ64VgiXR+1ZF8OmHGl0+XUzN6xP9Q==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.2.3.tgz", + "integrity": "sha512-iFwxut1cw94lTXnloDMBRanqNTvEjbnigWkTowlPH3QY16ysTKuC51JeonRuARW4K3bbDGbXiLUYYZWKQaDfKw==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.2.0" }, @@ -3836,9 +3873,10 @@ } }, "node_modules/@ng-web-apis/resize-observer": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-3.2.1.tgz", - "integrity": "sha512-r1YaZUo6DIDeR+4/C/pM4Ar0eTQBxjK0FUhYYJ512EnN8RqAn8d3a0wM8ZYunucwDICwW1sx4IIV6PZ2G77xsg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-3.2.3.tgz", + "integrity": "sha512-x3KxBZSragzdQlkbY9tiHY0lVlkOFD7Y34rzXdBJ7PTnIvlq43X/tN31Mmb3R0Np4vsarWqnhO6Y42ljudsWdA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.2.0" }, @@ -4116,7 +4154,7 @@ "integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg==" }, "node_modules/@start9labs/start-sdk": { - "resolved": "../sdk/dist", + "resolved": "../sdk/baseDist", "link": true }, "node_modules/@stencil/core": { @@ -4132,60 +4170,63 @@ } }, "node_modules/@taiga-ui/addon-charts": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.86.0.tgz", - "integrity": "sha512-Du/85qqaj8hpFSI6hPuFeIhtE93Z6WSkYZLt0gvnsaCb2qSAg8D4oHSogrtF1rsWGGoM+fvXjD7UEUw9GzFIPg==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.96.0.tgz", + "integrity": "sha512-vU8fZhwdg+sWOPJNnIrEtuuVkoFjQKeMhvk4f68oHBXUa0XUESS+qNeAfstvow6xnAZJrhxYtMoDuH/cgC7kNg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "tslib": ">=2.7.0" }, "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", - "@ng-web-apis/common": "^3.0.6", - "@taiga-ui/cdk": "^3.86.0", - "@taiga-ui/core": "^3.86.0", - "@tinkoff/ng-polymorpheus": "^4.3.0" + "@ng-web-apis/common": ">=3.2.3 <4", + "@taiga-ui/cdk": ">=3.96.0 <4", + "@taiga-ui/core": ">=3.96.0 <4", + "@tinkoff/ng-polymorpheus": ">=4.3.0" } }, "node_modules/@taiga-ui/addon-commerce": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.86.0.tgz", - "integrity": "sha512-8QSB490ckI4jnU+1sQ3x8os2GVE162hbvzPVYIZ0TruoeXl076dAz6PT2WRaFwjcaCAIGsuaQgQ4Cv02NjkiYQ==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.96.0.tgz", + "integrity": "sha512-Y1MACB6KrQVnNjgeKQrNfbz51jMXbU7j83sL28+6q8DS9LNv4DVcv/r/UvK2VZ1PhESkBU85NJAIHf5iy9GN4g==", + "license": "Apache-2.0", "peer": true, "dependencies": { - "tslib": "^2.6.2" + "tslib": ">=2.7.0" }, "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@angular/forms": ">=12.0.0", - "@maskito/angular": "^1.9.0", - "@maskito/core": "^1.9.0", - "@maskito/kit": "^1.9.0", - "@ng-web-apis/common": "^3.0.6", - "@taiga-ui/cdk": "^3.86.0", - "@taiga-ui/core": "^3.86.0", - "@taiga-ui/i18n": "^3.86.0", - "@taiga-ui/kit": "^3.86.0", - "@tinkoff/ng-polymorpheus": "^4.3.0", + "@maskito/angular": ">=1.9.0 <2", + "@maskito/core": ">=1.9.0 <2", + "@maskito/kit": ">=1.9.0 <2", + "@ng-web-apis/common": ">=3.2.3 <4", + "@taiga-ui/cdk": ">=3.96.0 <4", + "@taiga-ui/core": ">=3.96.0 <4", + "@taiga-ui/i18n": ">=3.96.0 <4", + "@taiga-ui/kit": ">=3.96.0 <4", + "@tinkoff/ng-polymorpheus": ">=4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/cdk": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.86.0.tgz", - "integrity": "sha512-aVbnW01Oh0Er1sHKVGHP8W05mOSKxjSzFE3Qx4iF4T6KW7Rlz9HZoNx5ADMg0TATYChtWh9Kwjo8I4LSVj2ZUw==", - "dependencies": { - "@ng-web-apis/common": "3.0.6", - "@ng-web-apis/mutation-observer": "3.1.0", - "@ng-web-apis/resize-observer": "3.0.6", - "@tinkoff/ng-event-plugins": "3.2.0", - "@tinkoff/ng-polymorpheus": "4.3.0", - "tslib": "2.6.2" + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.96.0.tgz", + "integrity": "sha512-y1T4+Olhys370ePT8SvZpMlOConDG6bLxjo6jOvFI6D6w0nEgqRxxFDBcdoHxgMWJZdAg7lRLEtN9dHEwKaABA==", + "license": "Apache-2.0", + "dependencies": { + "@ng-web-apis/common": "^3.2.3", + "@ng-web-apis/mutation-observer": "^3.2.3", + "@ng-web-apis/resize-observer": "^3.2.3", + "@tinkoff/ng-event-plugins": "^3.2.0", + "@tinkoff/ng-polymorpheus": "^4.3.0", + "tslib": "^2.7.0" }, "optionalDependencies": { - "ng-morph": "4.0.5", - "parse5": "6.0.1" + "ng-morph": "^4.8.2", + "parse5": "^6.0.1" }, "peerDependencies": { "@angular/animations": ">=12.0.0", @@ -4195,42 +4236,166 @@ "rxjs": ">=6.0.0" } }, - "node_modules/@taiga-ui/cdk/node_modules/@ng-web-apis/mutation-observer": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.1.0.tgz", - "integrity": "sha512-MFN0TLLBMFJJPpXkGFe9ChRCSOKvMHZRRtBq5jHWS7tv5/CtdUkqW5CU7RC9KTzZjGeMzYe0cXO4JRkjL5aZ9g==", + "node_modules/@taiga-ui/cdk/node_modules/@angular-devkit/core": { + "version": "18.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.9.tgz", + "integrity": "sha512-bsVt//5E0ua7FZfO0dCF/qGGY6KQD34/bNGyRu5B6HedimpdU2/0PGDptksU5v3yKEc9gNw0xC6mT0UsY/R9pA==", + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "tslib": "^2.2.0" + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/core": ">=12.0.0", - "@ng-web-apis/common": ">=2.0.0" + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@taiga-ui/cdk/node_modules/@ng-web-apis/resize-observer": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-3.0.6.tgz", - "integrity": "sha512-QdGYdEdC0AzFonLfNOnyYyeCwnvK9jlskoeefvJN3Yyvds3ivBrrTjpeDOdiLsQpCPBp9/673imgq7355vkQow==", + "node_modules/@taiga-ui/cdk/node_modules/@angular-devkit/schematics": { + "version": "18.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.9.tgz", + "integrity": "sha512-aIY5/IomDOINGCtFYi77uo0acDpdQNNCighfBBUGEBNMQ1eE3oGNGpLAH/qWeuxJndgmxrdKsvws9DdT46kLig==", + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "tslib": "^2.2.0" + "@angular-devkit/core": "18.2.9", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.11", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@taiga-ui/cdk/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@taiga-ui/cdk/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "ajv": "^8.0.0" }, "peerDependencies": { - "@angular/core": ">=12.0.0", - "@ng-web-apis/common": ">=2.0.0" + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@taiga-ui/cdk/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "node_modules/@taiga-ui/cdk/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT", + "optional": true + }, + "node_modules/@taiga-ui/cdk/node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@taiga-ui/cdk/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@taiga-ui/cdk/node_modules/ng-morph": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.8.4.tgz", + "integrity": "sha512-XwL53wCOhyaAxvoekN74ONbWUK30huzp+GpZYyC01RfaG2AX9l7YlC1mGG/l7Rx7YXtFAk85VFnNJqn2e46K8g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "jsonc-parser": "3.3.1", + "minimatch": "10.0.1", + "multimatch": "5.0.0", + "ts-morph": "23.0.0" + }, + "peerDependencies": { + "@angular-devkit/core": ">=16.0.0", + "@angular-devkit/schematics": ">=16.0.0", + "tslib": "^2.7.0" + } + }, + "node_modules/@taiga-ui/cdk/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/@taiga-ui/core": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.86.0.tgz", - "integrity": "sha512-diQKOnPtDDfxPOMk6wLRq8nyDVfNSPSNy+1TeyqzUgOvJ6XAjfaBXGsL3iuR7AN8+sz/b3rJmBce+vdw6FjMLQ==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.96.0.tgz", + "integrity": "sha512-0w2Jpesb2oM+5aMTfSKFzzYdfPDHXq0fhE6TN4Eutc9LO4Lyh6Hg0KPfoHmw8Tj6wg4KxBcdeAj/opNehWwrVw==", + "license": "Apache-2.0", "dependencies": { - "@taiga-ui/i18n": "^3.86.0", - "tslib": "^2.6.2" + "@taiga-ui/i18n": "^3.96.0", + "tslib": ">=2.7.0" }, "peerDependencies": { "@angular/animations": ">=12.0.0", @@ -4239,81 +4404,85 @@ "@angular/forms": ">=12.0.0", "@angular/platform-browser": ">=12.0.0", "@angular/router": ">=12.0.0", - "@ng-web-apis/common": "^3.0.6", - "@ng-web-apis/mutation-observer": "^3.1.0", - "@taiga-ui/cdk": "^3.86.0", - "@taiga-ui/i18n": "^3.86.0", - "@tinkoff/ng-event-plugins": "^3.2.0", - "@tinkoff/ng-polymorpheus": "^4.3.0", + "@ng-web-apis/common": ">=3.2.3 <4", + "@ng-web-apis/mutation-observer": ">=3.2.3 <4", + "@taiga-ui/cdk": ">=3.96.0 <4", + "@taiga-ui/i18n": ">=3.96.0 <4", + "@tinkoff/ng-event-plugins": ">=3.2.0 <4", + "@tinkoff/ng-polymorpheus": ">=4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/experimental": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.86.0.tgz", - "integrity": "sha512-ACjoRVeX5MgsNJsiu2ukliXLD2mfEWm8Vtmk78vqcnkyPUmy1ZWK4sG3p5ybFN8AdIMHkblVq0l+x2qAwr/+LQ==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.96.0.tgz", + "integrity": "sha512-AxEaYieouK3NzkGzOVTojnHBAd/eRw5D2sVAK+SQdBFdBjQiRGk/FKzZhOWpDOQOcIN+lhpDGpth3KV30+AgFQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "tslib": ">=2.7.0" }, "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", - "@taiga-ui/addon-commerce": "^3.86.0", - "@taiga-ui/cdk": "^3.86.0", - "@taiga-ui/core": "^3.86.0", - "@taiga-ui/kit": "^3.86.0", - "@tinkoff/ng-polymorpheus": "^4.3.0", + "@taiga-ui/addon-commerce": ">=3.96.0 <4", + "@taiga-ui/cdk": ">=3.96.0 <4", + "@taiga-ui/core": ">=3.96.0 <4", + "@taiga-ui/kit": ">=3.96.0 <4", + "@tinkoff/ng-polymorpheus": ">=4.3.0", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/i18n": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.86.0.tgz", - "integrity": "sha512-8zkNhMo/QtxZ2Zp6EP/nxo4SOLwaIrX+P3X/Wt+1cjFNZUYWWfdvfHLLdNviKFPVl4RAOxvkhDfza/wkrwv+iQ==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.96.0.tgz", + "integrity": "sha512-SGO89mRhmdD3BldQJiSqDCftEjjaOKXLJFFEZWBtrhelxKX5hWJ2lL0O+WzVAMegbQqHoT60HoHrWZIlSy9tVQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "tslib": ">=2.7.0" }, "peerDependencies": { "@angular/core": ">=12.0.0", - "@ng-web-apis/common": "^3.0.6", + "@ng-web-apis/common": ">=3.2.3 <4", "rxjs": ">=6.0.0" } }, "node_modules/@taiga-ui/icons": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.86.0.tgz", - "integrity": "sha512-jVBEbvE/r9JG+knmXMTn/l/js3JjYi8nSGbrLCryJZZoS2izRnQARN2txABieUJm8H463CoF0rcdXlHKRuA4Ew==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.96.0.tgz", + "integrity": "sha512-bBuX8RGAqr2+9TRbTA4RAmq3v4pnE9ObDwoq/5CrUtE3Wr7idcl7hgfW3yeyhpdOdJJMGfbTauPBONLz2uFRCQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "tslib": ">=2.7.0" }, "peerDependencies": { - "@taiga-ui/cdk": "^3.86.0" + "@taiga-ui/cdk": ">=3.96.0 <4" } }, "node_modules/@taiga-ui/kit": { - "version": "3.86.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.86.0.tgz", - "integrity": "sha512-naAy4pyhCaQ9+vWxqSMjbV+9KwnMxT5ybrw+MAJgMn2evzRq0FjqzyFZFog7oiRbRvgVdoWPQfBNKaaLhJcpsw==", + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.96.0.tgz", + "integrity": "sha512-gHn0AZU1kiNZU2T/LnnxLGkHC3/XNroBWCGmhxupAmL/JcsmOXsl3L8aK1rZhjS4QgkmgaC5ueVWNwdXINRnXw==", + "license": "Apache-2.0", "dependencies": { - "@maskito/angular": "1.9.0", - "@maskito/core": "1.9.0", - "@maskito/kit": "1.9.0", - "@ng-web-apis/intersection-observer": "3.2.0", - "text-mask-core": "5.1.2", - "tslib": "^2.6.2" + "@maskito/angular": "^1.9.0", + "@maskito/core": "^1.9.0", + "@maskito/kit": "^1.9.0", + "@ng-web-apis/intersection-observer": "^3.2.3", + "text-mask-core": "^5.1.2", + "tslib": ">=2.7.0" }, "peerDependencies": { "@angular/common": ">=12.0.0", "@angular/core": ">=12.0.0", "@angular/forms": ">=12.0.0", "@angular/router": ">=12.0.0", - "@ng-web-apis/common": "3.0.6", - "@ng-web-apis/mutation-observer": "^3.1.0", - "@ng-web-apis/resize-observer": "^3.0.6", - "@taiga-ui/cdk": "^3.86.0", - "@taiga-ui/core": "^3.86.0", - "@taiga-ui/i18n": "^3.86.0", - "@tinkoff/ng-polymorpheus": "^4.3.0", + "@ng-web-apis/common": ">=3.2.3 <4", + "@ng-web-apis/mutation-observer": ">=3.2.3 <4", + "@ng-web-apis/resize-observer": ">=3.2.3 <4", + "@taiga-ui/cdk": ">=3.96.0 <4", + "@taiga-ui/core": ">=3.96.0 <4", + "@taiga-ui/i18n": ">=3.96.0 <4", + "@tinkoff/ng-polymorpheus": ">=4.3.0", "rxjs": ">=6.0.0" } }, @@ -4371,13 +4540,14 @@ } }, "node_modules/@ts-morph/common": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", - "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.24.0.tgz", + "integrity": "sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==", + "license": "MIT", "optional": true, "dependencies": { "fast-glob": "^3.3.2", - "minimatch": "^9.0.3", + "minimatch": "^9.0.4", "mkdirp": "^3.0.1", "path-browserify": "^1.0.1" } @@ -4386,6 +4556,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -4401,6 +4572,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", "optional": true, "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -4584,6 +4756,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "license": "MIT", "optional": true }, "node_modules/@types/mustache": { @@ -5208,6 +5381,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -5223,6 +5397,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -5232,6 +5407,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -6059,9 +6235,10 @@ } }, "node_modules/code-block-writer": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", - "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "license": "MIT", "optional": true }, "node_modules/color-convert": { @@ -7839,6 +8016,14 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause", + "optional": true, + "peer": true + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -10622,6 +10807,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "license": "MIT", "optional": true, "dependencies": { "@types/minimatch": "^3.0.3", @@ -10641,6 +10827,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "optional": true, "dependencies": { "balanced-match": "^1.0.0", @@ -10651,6 +10838,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "optional": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -10752,50 +10940,6 @@ "node": ">= 0.4.0" } }, - "node_modules/ng-morph": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.5.tgz", - "integrity": "sha512-5tnlb5WrGKeo2E7VRcV7ZHhScyNgliYqpbXqt103kynmfj6Ic8kzhJAhHu9iLkF1yRnKv2kyCE+O7UGZx5RraQ==", - "optional": true, - "dependencies": { - "jsonc-parser": "3.2.0", - "minimatch": "9.0.3", - "multimatch": "5.0.0", - "ts-morph": "21.0.1", - "tslib": "2.6.2" - }, - "peerDependencies": { - "@angular-devkit/core": ">=11.0.0", - "@angular-devkit/schematics": ">=11.0.0" - } - }, - "node_modules/ng-morph/node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "optional": true - }, - "node_modules/ng-morph/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "optional": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ng-morph/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "optional": true - }, "node_modules/ng-packagr": { "version": "14.2.2", "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-14.2.2.tgz", @@ -11704,7 +11848,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "optional": true + "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", @@ -14765,7 +14909,8 @@ "node_modules/text-mask-core": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/text-mask-core/-/text-mask-core-5.1.2.tgz", - "integrity": "sha512-VfkCMdmRRZqXgQZFlDMiavm3hzsMzBM23CxHZsaeAYg66ZhXCNJWrFmnJwNy8KF9f74YvAUAuQenxsMCfuvhUw==" + "integrity": "sha512-VfkCMdmRRZqXgQZFlDMiavm3hzsMzBM23CxHZsaeAYg66ZhXCNJWrFmnJwNy8KF9f74YvAUAuQenxsMCfuvhUw==", + "license": "Unlicense" }, "node_modules/text-table": { "version": "0.2.0", @@ -14851,13 +14996,14 @@ "integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg==" }, "node_modules/ts-morph": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", - "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-23.0.0.tgz", + "integrity": "sha512-FcvFx7a9E8TUe6T3ShihXJLiJOiqyafzFKUO4aqIHDUCIvADdGNShcbc2W5PMr3LerXRv7mafvFZ9lRENxJmug==", + "license": "MIT", "optional": true, "dependencies": { - "@ts-morph/common": "~0.22.0", - "code-block-writer": "^12.0.0" + "@ts-morph/common": "~0.24.0", + "code-block-writer": "^13.0.1" } }, "node_modules/ts-node": { @@ -14904,9 +15050,10 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" }, "node_modules/tslint": { "version": "6.1.3", diff --git a/web/package.json b/web/package.json index c46090544..e8aafc8c7 100644 --- a/web/package.json +++ b/web/package.json @@ -1,19 +1,18 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.4", + "version": "0.3.6-alpha.8", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", "scripts": { "ng": "ng", - "check": "npm run check:shared && npm run check:marketplace && npm run check:ui && npm run check:install-wiz && npm run check:setup && npm run check:dui", + "check": "npm run check:shared && npm run check:marketplace && npm run check:ui && npm run check:install-wiz && npm run check:setup", "check:shared": "tsc --project projects/shared/tsconfig.json --noEmit --skipLibCheck", "check:marketplace": "tsc --project projects/marketplace/tsconfig.json --noEmit --skipLibCheck", - "check:dui": "tsc --project projects/diagnostic-ui/tsconfig.json --noEmit --skipLibCheck", "check:install-wiz": "tsc --project projects/install-wizard/tsconfig.json --noEmit --skipLibCheck", "check:setup": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck", "check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck", - "build:deps": "rm -rf .angular/cache && (cd ../patch-db/client && npm ci && npm run build) && (cd ../sdk && make bundle)", + "build:deps": "rimraf .angular/cache && (cd ../sdk && make bundle) && (cd ../patch-db/client && npm ci && npm run build)", "build:install-wiz": "ng run install-wizard:build", "build:setup": "ng run setup-wizard:build", "build:ui": "ng run ui:build", @@ -44,20 +43,20 @@ "@angular/service-worker": "^14.2.2", "@ionic/angular": "^6.1.15", "@materia-ui/ngx-monaco-editor": "^6.0.0", - "@ng-web-apis/common": "^3.0.6", - "@ng-web-apis/mutation-observer": "^3.2.1", - "@ng-web-apis/resize-observer": "^3.2.1", + "@ng-web-apis/common": "^3.2.3", + "@ng-web-apis/mutation-observer": "^3.2.3", + "@ng-web-apis/resize-observer": "^3.2.3", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", "@start9labs/argon2": "^0.2.2", "@start9labs/emver": "^0.1.5", - "@start9labs/start-sdk": "file:../sdk/dist", - "@taiga-ui/addon-charts": "3.86.0", - "@taiga-ui/cdk": "3.86.0", - "@taiga-ui/core": "3.86.0", - "@taiga-ui/experimental": "3.86.0", - "@taiga-ui/icons": "3.86.0", - "@taiga-ui/kit": "3.86.0", + "@start9labs/start-sdk": "file:../sdk/baseDist", + "@taiga-ui/addon-charts": "3.96.0", + "@taiga-ui/cdk": "3.96.0", + "@taiga-ui/core": "3.96.0", + "@taiga-ui/experimental": "3.96.0", + "@taiga-ui/icons": "3.96.0", + "@taiga-ui/kit": "3.96.0", "@tinkoff/ng-dompurify": "4.0.0", "@tinkoff/ng-event-plugins": "3.2.0", "angular-svg-round-progressbar": "^9.0.0", @@ -80,6 +79,7 @@ "ng-qrcode": "^7.0.0", "node-jose": "^2.2.0", "patch-db-client": "file:../patch-db/client", + "path-browserify": "^1.0.1", "pbkdf2": "^3.1.2", "rxjs": "^7.8.1", "swiper": "^8.2.4", diff --git a/web/patchdb-ui-seed.json b/web/patchdb-ui-seed.json index 807483c3a..c6b967e29 100644 --- a/web/patchdb-ui-seed.json +++ b/web/patchdb-ui-seed.json @@ -21,5 +21,5 @@ "ackInstructions": {}, "theme": "Dark", "widgets": [], - "ack-welcome": "0.3.6-alpha.4" + "ack-welcome": "0.3.6-alpha.8" } diff --git a/web/projects/marketplace/src/pages/show/package/package.component.scss b/web/projects/marketplace/src/pages/show/package/package.component.scss index 9e75cfd41..d5f63c9e3 100644 --- a/web/projects/marketplace/src/pages/show/package/package.component.scss +++ b/web/projects/marketplace/src/pages/show/package/package.component.scss @@ -27,8 +27,8 @@ } .published { - margin: 0; - padding: 4px 0 12px 0; + margin: 0px; + padding: 8px 0 8px 0; font-style: italic; } diff --git a/web/projects/setup-wizard/src/app/pages/loading/loading.page.html b/web/projects/setup-wizard/src/app/pages/loading/loading.page.html index 94a666223..942d667a3 100644 --- a/web/projects/setup-wizard/src/app/pages/loading/loading.page.html +++ b/web/projects/setup-wizard/src/app/pages/loading/loading.page.html @@ -15,7 +15,7 @@

[style.margin]="'1rem auto'" [attr.value]="progress.total" > -

{{ progress.message }}

+

{{ progress.message }}

diff --git a/web/projects/setup-wizard/src/app/pages/success/success.page.ts b/web/projects/setup-wizard/src/app/pages/success/success.page.ts index dab0b44a6..aa0c7fc90 100644 --- a/web/projects/setup-wizard/src/app/pages/success/success.page.ts +++ b/web/projects/setup-wizard/src/app/pages/success/success.page.ts @@ -83,7 +83,7 @@ export class SuccessPage { await this.api.exit() } } catch (e: any) { - await this.errorService.handleError(e) + this.errorService.handleError(e) } } diff --git a/web/projects/setup-wizard/src/app/services/api/mock-api.service.ts b/web/projects/setup-wizard/src/app/services/api/mock-api.service.ts index a5757b010..a17f56a0c 100644 --- a/web/projects/setup-wizard/src/app/services/api/mock-api.service.ts +++ b/web/projects/setup-wizard/src/app/services/api/mock-api.service.ts @@ -137,6 +137,7 @@ export class MockApiService extends ApiService { return { status: 'complete', torAddress: 'https://asdafsadasdasasdasdfasdfasdf.onion', + hostname: 'adjective-noun', lanAddress: 'https://adjective-noun.local', rootCa: encodeBase64(rootCA), } @@ -283,6 +284,7 @@ export class MockApiService extends ApiService { await pauseFor(1000) return { torAddress: 'https://asdafsadasdasasdasdfasdfasdf.onion', + hostname: 'adjective-noun', lanAddress: 'https://adjective-noun.local', rootCa: encodeBase64(rootCA), } diff --git a/web/projects/shared/assets/icon/favicon.ico b/web/projects/shared/assets/icon/favicon.ico deleted file mode 100644 index 01b1348a1..000000000 Binary files a/web/projects/shared/assets/icon/favicon.ico and /dev/null differ diff --git a/web/projects/shared/assets/icons/apple-touch-icon.png b/web/projects/shared/assets/icons/apple-touch-icon.png new file mode 100644 index 000000000..1df19c2b4 Binary files /dev/null and b/web/projects/shared/assets/icons/apple-touch-icon.png differ diff --git a/web/projects/shared/assets/icons/favicon-96x96.png b/web/projects/shared/assets/icons/favicon-96x96.png new file mode 100644 index 000000000..4df833b3e Binary files /dev/null and b/web/projects/shared/assets/icons/favicon-96x96.png differ diff --git a/web/projects/shared/assets/icons/favicon.ico b/web/projects/shared/assets/icons/favicon.ico new file mode 100644 index 000000000..9622dd64c Binary files /dev/null and b/web/projects/shared/assets/icons/favicon.ico differ diff --git a/web/projects/shared/assets/icons/favicon.svg b/web/projects/shared/assets/icons/favicon.svg new file mode 100644 index 000000000..c310c86d3 --- /dev/null +++ b/web/projects/shared/assets/icons/favicon.svg @@ -0,0 +1,17 @@ + + + + + diff --git a/web/projects/shared/assets/icons/web-app-manifest-192x192.png b/web/projects/shared/assets/icons/web-app-manifest-192x192.png new file mode 100644 index 000000000..24d2c68eb Binary files /dev/null and b/web/projects/shared/assets/icons/web-app-manifest-192x192.png differ diff --git a/web/projects/shared/assets/icons/web-app-manifest-512x512.png b/web/projects/shared/assets/icons/web-app-manifest-512x512.png new file mode 100644 index 000000000..8d9af255a Binary files /dev/null and b/web/projects/shared/assets/icons/web-app-manifest-512x512.png differ diff --git a/web/projects/shared/src/pipes/unit-conversion/unit-conversion.pipe.ts b/web/projects/shared/src/pipes/unit-conversion/unit-conversion.pipe.ts index 266e7fe9a..afdc48c48 100644 --- a/web/projects/shared/src/pipes/unit-conversion/unit-conversion.pipe.ts +++ b/web/projects/shared/src/pipes/unit-conversion/unit-conversion.pipe.ts @@ -6,13 +6,17 @@ import { Pipe, PipeTransform } from '@angular/core' }) export class ConvertBytesPipe implements PipeTransform { transform(bytes: number): string { - if (bytes === 0) return '0 Bytes' + return convertBytes(bytes) + } +} - const k = 1024 - const i = Math.floor(Math.log(bytes) / Math.log(k)) +export function convertBytes(bytes: number): string { + if (bytes === 0) return '0 Bytes' - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i] - } + const k = 1024 + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i] } @Pipe({ diff --git a/web/projects/shared/src/services/exver.service.ts b/web/projects/shared/src/services/exver.service.ts index f3a28fae2..516879333 100644 --- a/web/projects/shared/src/services/exver.service.ts +++ b/web/projects/shared/src/services/exver.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { VersionRange, ExtendedVersion, Version } from '@start9labs/start-sdk' +import { ExtendedVersion, VersionRange } from '@start9labs/start-sdk' @Injectable({ providedIn: 'root', @@ -29,12 +29,8 @@ export class Exver { } } - compareOsVersion(current: string, other: string) { - return Version.parse(current).compare(Version.parse(other)) - } - satisfies(version: string, range: string): boolean { - return VersionRange.parse(range).satisfiedBy(ExtendedVersion.parse(version)) + return ExtendedVersion.parse(version).satisfies(VersionRange.parse(range)) } getFlavor(version: string): string | null { diff --git a/web/projects/shared/src/types/workspace-config.ts b/web/projects/shared/src/types/workspace-config.ts index 57d5e2a4c..d10c4b07e 100644 --- a/web/projects/shared/src/types/workspace-config.ts +++ b/web/projects/shared/src/types/workspace-config.ts @@ -2,7 +2,7 @@ export type WorkspaceConfig = { gitHash: string useMocks: boolean enableWidgets: boolean - // each key corresponds to a project and values adjust settings for that project, eg: ui, install-wizard, setup-wizard, diagnostic-ui + // each key corresponds to a project and values adjust settings for that project, eg: ui, install-wizard, setup-wizard ui: { api: { url: string diff --git a/web/projects/ui/src/app/app/preloader/preloader.component.ts b/web/projects/ui/src/app/app/preloader/preloader.component.ts index 177362222..70c5e2995 100644 --- a/web/projects/ui/src/app/app/preloader/preloader.component.ts +++ b/web/projects/ui/src/app/app/preloader/preloader.component.ts @@ -65,7 +65,6 @@ const ICONS = [ 'options-outline', 'pencil', 'phone-portrait-outline', - 'play-circle-outline', 'play-outline', 'power', 'pricetag-outline', diff --git a/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts b/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts index a4b272c46..859433077 100644 --- a/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts +++ b/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core' import { ActionSheetController, AlertController } from '@ionic/angular' import { ErrorService, LoadingService } from '@start9labs/shared' -import { CB } from '@start9labs/start-sdk' +import { ISB } from '@start9labs/start-sdk' import { CifsBackupTarget, DiskBackupTarget, @@ -261,32 +261,36 @@ export class BackupDrivesStatusComponent { @Input() hasAnyBackup!: boolean } -const cifsSpec = CB.Config.of({ - hostname: CB.Value.text({ +const cifsSpec = ISB.InputSpec.of({ + hostname: ISB.Value.text({ name: 'Hostname', description: 'The hostname of your target device on the Local Area Network.', warning: null, placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, - required: { default: null }, + required: true, + default: null, patterns: [], }), - path: CB.Value.text({ + path: ISB.Value.text({ name: 'Path', description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`, placeholder: 'e.g. my-shared-folder or /Desktop/my-folder', - required: { default: null }, + required: true, + default: null, }), - username: CB.Value.text({ + username: ISB.Value.text({ name: 'Username', description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`, - required: { default: null }, + required: true, + default: null, placeholder: 'My Network Folder', }), - password: CB.Value.text({ + password: ISB.Value.text({ name: 'Password', description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`, required: false, + default: null, masked: true, placeholder: 'My Network Folder', }), diff --git a/web/projects/ui/src/app/components/backup-drives/backup.service.ts b/web/projects/ui/src/app/components/backup-drives/backup.service.ts index 837bebf9b..ec42c8266 100644 --- a/web/projects/ui/src/app/components/backup-drives/backup.service.ts +++ b/web/projects/ui/src/app/components/backup-drives/backup.service.ts @@ -7,7 +7,7 @@ import { DiskBackupTarget, } from 'src/app/services/api/api.types' import { MappedBackupTarget } from 'src/app/types/mapped-backup-target' -import { Exver, getErrorMessage } from '@start9labs/shared' +import { getErrorMessage } from '@start9labs/shared' import { Version } from '@start9labs/start-sdk' @Injectable({ @@ -19,10 +19,7 @@ export class BackupService { loading = true loadingError: string | IonicSafeString = '' - constructor( - private readonly embassyApi: ApiService, - private readonly exver: Exver, - ) {} + constructor(private readonly embassyApi: ApiService) {} async getBackupTargets(): Promise { this.loading = true @@ -58,15 +55,16 @@ export class BackupService { hasAnyBackup(target: BackupTarget): boolean { return Object.values(target.startOs).some( - s => this.exver.compareOsVersion(s.version, '0.3.6') !== 'less', + s => Version.parse(s.version).compare(Version.parse('0.3.6')) !== 'less', ) } hasThisBackup(target: BackupTarget, id: string): boolean { return ( target.startOs[id] && - this.exver.compareOsVersion(target.startOs[id].version, '0.3.6') !== - 'less' + Version.parse(target.startOs[id].version).compare( + Version.parse('0.3.6'), + ) !== 'less' ) } } diff --git a/web/projects/ui/src/app/components/form.component.ts b/web/projects/ui/src/app/components/form.component.ts index 830e8de1e..93f65115a 100644 --- a/web/projects/ui/src/app/components/form.component.ts +++ b/web/projects/ui/src/app/components/form.component.ts @@ -8,8 +8,7 @@ import { } from '@angular/core' import { FormGroup, ReactiveFormsModule } from '@angular/forms' import { RouterModule } from '@angular/router' -import { CT } from '@start9labs/start-sdk' - +import { IST } from '@start9labs/start-sdk' import { tuiMarkControlAsTouchedAndValidate, TuiValueChangesModule, @@ -30,10 +29,10 @@ export interface ActionButton { } export interface FormContext { - spec: CT.InputSpec + spec: IST.InputSpec buttons: ActionButton[] value?: T - patch?: Operation[] + operations?: Operation[] } @Component({ @@ -112,7 +111,7 @@ export class FormComponent> implements OnInit { @Input() spec = this.context?.data.spec || {} @Input() buttons = this.context?.data.buttons || [] - @Input() patch = this.context?.data.patch || [] + @Input() operations = this.context?.data.operations || [] @Input() value?: T = this.context?.data.value form = new FormGroup({}) @@ -120,7 +119,7 @@ export class FormComponent> implements OnInit { ngOnInit() { this.dialogFormService.markAsPristine() this.form = this.formService.createForm(this.spec, this.value) - this.process(this.patch) + this.process(this.operations) } onReset() { @@ -149,15 +148,16 @@ export class FormComponent> implements OnInit { this.context?.$implicit.complete() } - private process(patch: Operation[]) { - patch.forEach(({ op, path }) => { - const control = this.form.get(path.substring(1).split('/')) + private process(operations: Operation[]) { + operations.forEach(operation => { + const control = this.form.get(operation.path.substring(1).split('/')) if (!control || !control.parent) return - if (op !== 'remove') { + if (operation.op === 'add' || operation.op === 'replace') { control.markAsDirty() control.markAsTouched() + control.setValue(operation.value) } control.parent.markAsDirty() diff --git a/web/projects/ui/src/app/components/form/control.ts b/web/projects/ui/src/app/components/form/control.ts index 476826194..c77c76ecf 100644 --- a/web/projects/ui/src/app/components/form/control.ts +++ b/web/projects/ui/src/app/components/form/control.ts @@ -1,8 +1,8 @@ import { inject } from '@angular/core' import { FormControlComponent } from './form-control/form-control.component' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' -export abstract class Control { +export abstract class Control, Value> { private readonly control: FormControlComponent = inject(FormControlComponent) diff --git a/web/projects/ui/src/app/components/form/filter-hidden.pipe.ts b/web/projects/ui/src/app/components/form/filter-hidden.pipe.ts new file mode 100644 index 000000000..84666a2c9 --- /dev/null +++ b/web/projects/ui/src/app/components/form/filter-hidden.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { IST } from '@start9labs/start-sdk' +import { KeyValue } from '@angular/common' + +@Pipe({ + name: 'filterHidden', +}) +export class FilterHiddenPipe implements PipeTransform { + transform(value: KeyValue[]) { + return value.filter(x => x.value.type !== 'hidden') as KeyValue< + string, + Exclude + >[] + } +} diff --git a/web/projects/ui/src/app/components/form/form-array/form-array.component.ts b/web/projects/ui/src/app/components/form/form-array/form-array.component.ts index 11495d510..25242f826 100644 --- a/web/projects/ui/src/app/components/form/form-array/form-array.component.ts +++ b/web/projects/ui/src/app/components/form/form-array/form-array.component.ts @@ -8,7 +8,7 @@ import { tuiHeightCollapse, } from '@taiga-ui/core' import { TUI_PROMPT } from '@taiga-ui/kit' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { filter, takeUntil } from 'rxjs' import { FormService } from 'src/app/services/form.service' import { ERRORS } from '../form-group/form-group.component' @@ -22,7 +22,7 @@ import { ERRORS } from '../form-group/form-group.component' }) export class FormArrayComponent { @Input() - spec!: CT.ValueSpecList + spec!: IST.ValueSpecList @HostBinding('@tuiParentAnimation') readonly animation = { value: '', ...inject(TUI_ANIMATION_OPTIONS) } diff --git a/web/projects/ui/src/app/components/form/form-color/form-color.component.ts b/web/projects/ui/src/app/components/form/form-color/form-color.component.ts index 32a7c1c04..0f65f06ce 100644 --- a/web/projects/ui/src/app/components/form/form-color/form-color.component.ts +++ b/web/projects/ui/src/app/components/form/form-color/form-color.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { Control } from '../control' import { MaskitoOptions } from '@maskito/core' @@ -8,7 +8,7 @@ import { MaskitoOptions } from '@maskito/core' templateUrl: './form-color.component.html', styleUrls: ['./form-color.component.scss'], }) -export class FormColorComponent extends Control { +export class FormColorComponent extends Control { readonly mask: MaskitoOptions = { mask: ['#', ...Array(6).fill(/[0-9a-f]/i)], } diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.component.html b/web/projects/ui/src/app/components/form/form-control/form-control.component.html index 731d64a63..dd3cf2b89 100644 --- a/web/projects/ui/src/app/components/form/form-control/form-control.component.html +++ b/web/projects/ui/src/app/components/form/form-control/form-control.component.html @@ -36,4 +36,4 @@ Accept - + \ No newline at end of file diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.component.ts b/web/projects/ui/src/app/components/form/form-control/form-control.component.ts index ec49bd084..9544188a6 100644 --- a/web/projects/ui/src/app/components/form/form-control/form-control.component.ts +++ b/web/projects/ui/src/app/components/form/form-control/form-control.component.ts @@ -13,7 +13,7 @@ import { TuiNotification, } from '@taiga-ui/core' import { filter, takeUntil } from 'rxjs' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { ERRORS } from '../form-group/form-group.component' import { FORM_CONTROL_PROVIDERS } from './form-control.providers' @@ -25,7 +25,7 @@ import { FORM_CONTROL_PROVIDERS } from './form-control.providers' providers: FORM_CONTROL_PROVIDERS, }) export class FormControlComponent< - T extends CT.ValueSpec, + T extends Exclude, V, > extends AbstractTuiNullableControl { @Input() diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts b/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts index f065f86cb..62e1ff6aa 100644 --- a/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts +++ b/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts @@ -1,6 +1,6 @@ import { forwardRef, Provider } from '@angular/core' import { TUI_VALIDATION_ERRORS } from '@taiga-ui/kit' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { FormControlComponent } from './form-control.component' interface ValidatorsPatternError { @@ -12,7 +12,7 @@ export const FORM_CONTROL_PROVIDERS: Provider[] = [ { provide: TUI_VALIDATION_ERRORS, deps: [forwardRef(() => FormControlComponent)], - useFactory: (control: FormControlComponent) => ({ + useFactory: (control: FormControlComponent, string>) => ({ required: 'Required', pattern: ({ requiredPattern }: ValidatorsPatternError) => ('patterns' in control.spec && diff --git a/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts b/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts index e09b22d24..fc3acecd0 100644 --- a/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts +++ b/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts @@ -6,7 +6,7 @@ import { tuiPure, TuiTime, } from '@taiga-ui/cdk' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { Control } from '../control' @Component({ @@ -14,7 +14,7 @@ import { Control } from '../control' templateUrl: './form-datetime.component.html', }) export class FormDatetimeComponent extends Control< - CT.ValueSpecDatetime, + IST.ValueSpecDatetime, string > { readonly min = TUI_FIRST_DAY diff --git a/web/projects/ui/src/app/components/form/form-group/form-group.component.html b/web/projects/ui/src/app/components/form/form-group/form-group.component.html index 1c4f8301a..65975e970 100644 --- a/web/projects/ui/src/app/components/form/form-group/form-group.component.html +++ b/web/projects/ui/src/app/components/form/form-group/form-group.component.html @@ -1,5 +1,5 @@ { private readonly inverted = invert(this.spec.values) diff --git a/web/projects/ui/src/app/components/form/form-number/form-number.component.ts b/web/projects/ui/src/app/components/form/form-number/form-number.component.ts index a930b1614..b07858207 100644 --- a/web/projects/ui/src/app/components/form/form-number/form-number.component.ts +++ b/web/projects/ui/src/app/components/form/form-number/form-number.component.ts @@ -1,11 +1,11 @@ import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { Control } from '../control' @Component({ selector: 'form-number', templateUrl: './form-number.component.html', }) -export class FormNumberComponent extends Control { +export class FormNumberComponent extends Control { protected readonly Infinity = Infinity } diff --git a/web/projects/ui/src/app/components/form/form-object/form-object.component.ts b/web/projects/ui/src/app/components/form/form-object/form-object.component.ts index b1aa507cf..a036c1e43 100644 --- a/web/projects/ui/src/app/components/form/form-object/form-object.component.ts +++ b/web/projects/ui/src/app/components/form/form-object/form-object.component.ts @@ -7,7 +7,7 @@ import { Output, } from '@angular/core' import { ControlContainer } from '@angular/forms' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' @Component({ selector: 'form-object', @@ -17,7 +17,7 @@ import { CT } from '@start9labs/start-sdk' }) export class FormObjectComponent { @Input() - spec!: CT.ValueSpecObject + spec!: IST.ValueSpecObject @Input() open = false diff --git a/web/projects/ui/src/app/components/form/form-select/form-select.component.html b/web/projects/ui/src/app/components/form/form-select/form-select.component.html index fe2b561c7..9149e8844 100644 --- a/web/projects/ui/src/app/components/form/form-select/form-select.component.html +++ b/web/projects/ui/src/app/components/form/form-select/form-select.component.html @@ -2,13 +2,12 @@ [tuiHintContent]="spec | hint" [disabled]="disabled" [readOnly]="readOnly" - [tuiTextfieldCleaner]="!spec.required" + [tuiTextfieldCleaner]="false" [pseudoInvalid]="invalid" [(ngModel)]="selected" (focusedChange)="onFocus($event)" > - {{ spec.name }} - * + {{ spec.name }}* + + + + + + + + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + @import '@taiga-ui/core/styles/taiga-ui-local'; + + .reveal { + @include center-all(); + } + + .qr { + position: relative; + text-align: center; + } + `, + ], + imports: [ + CommonModule, + FormsModule, + TuiInputModule, + TuiTextfieldControllerModule, + TuiButtonModule, + QrCodeModule, + TuiTitleModule, + ], +}) +export class ActionSuccessMemberComponent { + @ViewChild(TuiTextfieldComponent, { read: ElementRef }) + private readonly input!: ElementRef + private readonly dialogs = inject(TuiDialogService) + + @Input() + member!: T.ActionResultMember & { type: 'single' } + + masked = true + + get border(): number { + let border = 0 + + if (this.member.masked) border += 2 + if (this.member.copyable) border += 2 + if (this.member.qr) border += 2 + + return border + } + + show(template: TemplateRef) { + const masked = this.masked + + this.masked = this.member.masked + this.dialogs + .open(template, { label: 'Scan this QR', size: 's' }) + .subscribe({ + complete: () => (this.masked = masked), + }) + } + + copy() { + const el = this.input.nativeElement + + if (!el) { + return + } + + el.type = 'text' + el.focus() + el.select() + el.ownerDocument.execCommand('copy') + el.type = this.masked && this.member.masked ? 'password' : 'text' + } +} diff --git a/web/projects/ui/src/app/modals/action-success/action-success-single.component.ts b/web/projects/ui/src/app/modals/action-success/action-success-single.component.ts new file mode 100644 index 000000000..2902c3c88 --- /dev/null +++ b/web/projects/ui/src/app/modals/action-success/action-success-single.component.ts @@ -0,0 +1,145 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + ElementRef, + inject, + Input, + TemplateRef, + ViewChild, +} from '@angular/core' +import { FormsModule } from '@angular/forms' +import { + TuiDialogService, + TuiLabelModule, + TuiTextfieldComponent, + TuiTextfieldControllerModule, +} from '@taiga-ui/core' +import { TuiButtonModule } from '@taiga-ui/experimental' +import { TuiInputModule } from '@taiga-ui/kit' +import { QrCodeModule } from 'ng-qrcode' +import { SingleResult } from './types' + +@Component({ + standalone: true, + selector: 'app-action-success-single', + template: ` +

+ +

+ + + + + + + + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + @import '@taiga-ui/core/styles/taiga-ui-local'; + + .reveal { + @include center-all(); + } + + .qr { + position: relative; + text-align: center; + } + `, + ], + imports: [ + CommonModule, + FormsModule, + TuiInputModule, + TuiTextfieldControllerModule, + TuiButtonModule, + QrCodeModule, + TuiLabelModule, + ], +}) +export class ActionSuccessSingleComponent { + @ViewChild(TuiTextfieldComponent, { read: ElementRef }) + private readonly input!: ElementRef + private readonly dialogs = inject(TuiDialogService) + + @Input() + single!: SingleResult + + masked = true + + get border(): number { + let border = 0 + + if (this.single.masked) border += 2 + if (this.single.copyable) border += 2 + + return border + } + + copy() { + const el = this.input.nativeElement + + if (!el) { + return + } + + el.type = 'text' + el.focus() + el.select() + el.ownerDocument.execCommand('copy') + el.type = this.masked && this.single.masked ? 'password' : 'text' + } +} diff --git a/web/projects/ui/src/app/modals/action-success/action-success.module.ts b/web/projects/ui/src/app/modals/action-success/action-success.module.ts deleted file mode 100644 index 23c123081..000000000 --- a/web/projects/ui/src/app/modals/action-success/action-success.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { ActionSuccessPage } from './action-success.page' -import { QrCodeModule } from 'ng-qrcode' - -@NgModule({ - declarations: [ActionSuccessPage], - imports: [CommonModule, IonicModule, QrCodeModule], - exports: [ActionSuccessPage], -}) -export class ActionSuccessPageModule {} diff --git a/web/projects/ui/src/app/modals/action-success/action-success.page.html b/web/projects/ui/src/app/modals/action-success/action-success.page.html deleted file mode 100644 index da8cc7be5..000000000 --- a/web/projects/ui/src/app/modals/action-success/action-success.page.html +++ /dev/null @@ -1,35 +0,0 @@ - - - Execution Complete - - - - - - - - - -

{{ actionRes.message }}

- -
- diff --git a/web/projects/ui/src/app/modals/action-success/action-success.page.scss b/web/projects/ui/src/app/modals/action-success/action-success.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/modals/action-success/action-success.page.ts b/web/projects/ui/src/app/modals/action-success/action-success.page.ts index 30c5c02cd..7f390e2ab 100644 --- a/web/projects/ui/src/app/modals/action-success/action-success.page.ts +++ b/web/projects/ui/src/app/modals/action-success/action-success.page.ts @@ -1,39 +1,36 @@ -import { Component, Input } from '@angular/core' -import { ModalController, ToastController } from '@ionic/angular' -import { ActionResponse } from 'src/app/services/api/api.types' -import { copyToClipboard } from '@start9labs/shared' +import { CommonModule } from '@angular/common' +import { Component, inject } from '@angular/core' +import { TuiDialogContext } from '@taiga-ui/core' +import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' +import { ActionSuccessGroupComponent } from './action-success-group.component' +import { ActionSuccessSingleComponent } from './action-success-single.component' +import { ActionResponseWithResult } from './types' @Component({ - selector: 'action-success', - templateUrl: './action-success.page.html', - styleUrls: ['./action-success.page.scss'], + standalone: true, + template: ` +

{{ data.message }}

+ + + `, + imports: [ + CommonModule, + ActionSuccessGroupComponent, + ActionSuccessSingleComponent, + ], }) export class ActionSuccessPage { - @Input() - actionRes!: ActionResponse + readonly data = + inject>( + POLYMORPHEUS_CONTEXT, + ).data - constructor( - private readonly modalCtrl: ModalController, - private readonly toastCtrl: ToastController, - ) {} - - async copy(address: string) { - let message = '' - await copyToClipboard(address || '').then(success => { - message = success - ? 'Copied to clipboard!' - : 'Failed to copy to clipboard.' - }) - - const toast = await this.toastCtrl.create({ - header: message, - position: 'bottom', - duration: 1000, - }) - await toast.present() - } - - async dismiss() { - return this.modalCtrl.dismiss() - } + readonly single = this.data.result.type === 'single' ? this.data.result : null + readonly group = this.data.result.type === 'group' ? this.data.result : null } diff --git a/web/projects/ui/src/app/modals/action-success/types.ts b/web/projects/ui/src/app/modals/action-success/types.ts new file mode 100644 index 000000000..efc515195 --- /dev/null +++ b/web/projects/ui/src/app/modals/action-success/types.ts @@ -0,0 +1,7 @@ +import { RR } from 'src/app/services/api/api.types' + +type ActionResponse = NonNullable +type ActionResult = NonNullable +export type ActionResponseWithResult = ActionResponse & { result: ActionResult } +export type SingleResult = ActionResult & { type: 'single' } +export type GroupResult = ActionResult & { type: 'group' } diff --git a/web/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts b/web/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts index 1c2d99f53..206e97f71 100644 --- a/web/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts +++ b/web/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts @@ -5,6 +5,7 @@ import { ConfigService } from 'src/app/services/config.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { Observable } from 'rxjs' import { map } from 'rxjs/operators' +import { Version } from '@start9labs/start-sdk' export interface AppRecoverOption extends PackageBackupInfo { id: string @@ -34,7 +35,10 @@ export class ToOptionsPipe implements PipeTransform { id, installed: !!packageData[id], checked: false, - newerOS: this.compare(packageBackups[id].osVersion), + newerOS: + Version.parse(packageBackups[id].osVersion).compare( + Version.parse(this.config.version), + ) === 'greater', })) .sort((a, b) => b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1, @@ -42,11 +46,4 @@ export class ToOptionsPipe implements PipeTransform { ), ) } - - private compare(version: string): boolean { - // checks to see if backup was made on a newer version of startOS - return ( - this.exver.compareOsVersion(version, this.config.version) === 'greater' - ) - } } diff --git a/web/projects/ui/src/app/modals/backup-select/backup-select.page.ts b/web/projects/ui/src/app/modals/backup-select/backup-select.page.ts index 2b6934c39..32770af76 100644 --- a/web/projects/ui/src/app/modals/backup-select/backup-select.page.ts +++ b/web/projects/ui/src/app/modals/backup-select/backup-select.page.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core' import { ModalController } from '@ionic/angular' -import { map, take } from 'rxjs/operators' +import { map } from 'rxjs/operators' import { DataModel } from 'src/app/services/patch-db/data-model' import { PatchDB } from 'patch-db-client' import { firstValueFrom } from 'rxjs' @@ -13,7 +13,7 @@ import { getManifest } from 'src/app/util/get-package-data' }) export class BackupSelectPage { hasSelection = false - selectAll = false + selectAll = true pkgs: { id: string title: string diff --git a/web/projects/ui/src/app/modals/config.component.ts b/web/projects/ui/src/app/modals/config.component.ts deleted file mode 100644 index d0e8afa4d..000000000 --- a/web/projects/ui/src/app/modals/config.component.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { CommonModule } from '@angular/common' -import { Component, Inject, ViewChild } from '@angular/core' -import { - ErrorService, - getErrorMessage, - isEmptyObject, - LoadingService, -} from '@start9labs/shared' -import { CT, T } from '@start9labs/start-sdk' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { - TuiDialogContext, - TuiDialogService, - TuiLoaderModule, - TuiModeModule, - TuiNotificationModule, -} from '@taiga-ui/core' -import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { compare, Operation } from 'fast-json-patch' -import { PatchDB } from 'patch-db-client' -import { endWith, firstValueFrom, Subscription } from 'rxjs' -import { ActionButton, FormComponent } from 'src/app/components/form.component' -import { InvalidService } from 'src/app/components/form/invalid.service' -import { ConfigDepComponent } from 'src/app/modals/config-dep.component' -import { UiPipeModule } from 'src/app/pipes/ui/ui.module' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { - getAllPackages, - getManifest, - getPackage, -} from 'src/app/util/get-package-data' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { Breakages } from 'src/app/services/api/api.types' -import { DependentInfo } from 'src/app/types/dependent-info' - -export interface PackageConfigData { - readonly pkgId: string - readonly dependentInfo?: DependentInfo -} - -@Component({ - template: ` - - - -
-
- - - - {{ manifest.title }} has been automatically configured with recommended - defaults. Make whatever changes you want, then click "Save". - - - - - - No config options for {{ manifest.title }} {{ manifest.version }}. - - - - - - - `, - styles: [ - ` - tui-notification { - font-size: 1rem; - margin-bottom: 1rem; - } - `, - ], - standalone: true, - imports: [ - CommonModule, - FormComponent, - TuiLoaderModule, - TuiNotificationModule, - TuiButtonModule, - TuiModeModule, - ConfigDepComponent, - UiPipeModule, - ], - providers: [InvalidService], -}) -export class ConfigModal { - @ViewChild(FormComponent) - private readonly form?: FormComponent> - - readonly pkgId = this.context.data.pkgId - readonly dependentInfo = this.context.data.dependentInfo - - loadingError = '' - loadingText = this.dependentInfo - ? `Setting properties to accommodate ${this.dependentInfo.title}` - : 'Loading Config' - - pkg?: PackageDataEntry - spec: CT.InputSpec = {} - patch: Operation[] = [] - buttons: ActionButton[] = [ - { - text: 'Save', - handler: value => this.save(value), - }, - ] - - original: object | null = null - value: object | null = null - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly embassyApi: ApiService, - private readonly patchDb: PatchDB, - ) {} - - get success(): boolean { - return ( - !!this.form && - !this.form.form.dirty && - !this.original && - !this.pkg?.status?.configured - ) - } - - async ngOnInit() { - try { - this.pkg = await getPackage(this.patchDb, this.pkgId) - - if (!this.pkg) { - this.loadingError = 'This service does not exist' - - return - } - - if (this.dependentInfo) { - const depConfig = await this.embassyApi.dryConfigureDependency({ - dependencyId: this.pkgId, - dependentId: this.dependentInfo.id, - }) - - this.original = depConfig.oldConfig - this.value = depConfig.newConfig || this.original - this.spec = depConfig.spec - this.patch = compare(this.original, this.value) - } else { - const { config, spec } = await this.embassyApi.getPackageConfig({ - id: this.pkgId, - }) - - this.original = config - this.value = config - this.spec = spec - } - } catch (e: any) { - this.loadingError = String(getErrorMessage(e)) - } finally { - this.loadingText = '' - } - } - - private async save(config: any) { - const loader = new Subscription() - - try { - if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patchDb))) { - await this.configureDeps(config, loader) - } else { - await this.configure(config, loader) - } - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async configureDeps( - config: Record, - loader: Subscription, - ) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Checking dependent services...').subscribe()) - - const breakages = await this.embassyApi.drySetPackageConfig({ - id: this.pkgId, - config, - }) - - loader.unsubscribe() - loader.closed = false - - if (isEmptyObject(breakages) || (await this.approveBreakages(breakages))) { - await this.configure(config, loader) - } - } - - private async configure(config: Record, loader: Subscription) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Saving...').subscribe()) - - await this.embassyApi.setPackageConfig({ id: this.pkgId, config }) - this.context.$implicit.complete() - } - - private async approveBreakages(breakages: T.PackageId[]): Promise { - const packages = await getAllPackages(this.patchDb) - const message = - 'As a result of this change, the following services will no longer work properly and may crash:
    ' - const content = `${message}${breakages.map( - id => `
  • ${getManifest(packages[id]).title}
  • `, - )}
` - const data: TuiPromptData = { content, yes: 'Continue', no: 'Cancel' } - - return firstValueFrom( - this.dialogs.open(TUI_PROMPT, { data }).pipe(endWith(false)), - ) - } -} diff --git a/web/projects/ui/src/app/modals/marketplace-settings/marketplace-settings.page.ts b/web/projects/ui/src/app/modals/marketplace-settings/marketplace-settings.page.ts index dd8fe73eb..4c70d2837 100644 --- a/web/projects/ui/src/app/modals/marketplace-settings/marketplace-settings.page.ts +++ b/web/projects/ui/src/app/modals/marketplace-settings/marketplace-settings.page.ts @@ -7,7 +7,7 @@ import { sameUrl, toUrl, } from '@start9labs/shared' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core' import { TuiButtonModule, @@ -182,7 +182,7 @@ export const MARKETPLACE_REGISTRY = new PolymorpheusComponent( MarketplaceSettingsPage, ) -function getMarketplaceValueSpec(): CT.ValueSpecObject { +function getMarketplaceValueSpec(): IST.ValueSpecObject { return { type: 'object', name: 'Add Custom Registry', diff --git a/web/projects/ui/src/app/modals/os-welcome/os-welcome.page.html b/web/projects/ui/src/app/modals/os-welcome/os-welcome.page.html index 6c259a6d0..0b06c4a90 100644 --- a/web/projects/ui/src/app/modals/os-welcome/os-welcome.page.html +++ b/web/projects/ui/src/app/modals/os-welcome/os-welcome.page.html @@ -12,9 +12,12 @@

This Release

-

0.3.6-alpha.4

+

0.3.6-alpha.8

This is an ALPHA release! DO NOT use for production data!
-
Expect that any data you create or store on this version of the OS can be LOST FOREVER!
+
+ Expect that any data you create or store on this version of the OS can be + LOST FOREVER! +
- + +

{{ action.name }}

{{ action.description }}

+

+ {{ disabledText }} +

diff --git a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts index 84e52d15f..377504238 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.module.ts @@ -5,7 +5,6 @@ import { IonicModule } from '@ionic/angular' import { AppActionsPage, AppActionsItemComponent } from './app-actions.page' import { QRComponentModule } from 'src/app/components/qr/qr.component.module' import { SharedPipesModule } from '@start9labs/shared' -import { ActionSuccessPageModule } from 'src/app/modals/action-success/action-success.module' const routes: Routes = [ { @@ -21,7 +20,6 @@ const routes: Routes = [ RouterModule.forChild(routes), QRComponentModule, SharedPipesModule, - ActionSuccessPageModule, ], declarations: [AppActionsPage, AppActionsItemComponent], }) diff --git a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html index ec425f8bb..3532fcacc 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html @@ -8,32 +8,37 @@ - - - - Standard Actions - + + + Standard Actions + + - - - Actions for {{ pkg.stateInfo.manifest.title }} - - - - + + + Actions for {{ pkg.manifest.title }} + + + diff --git a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts index 99bd70e48..949a1adad 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts @@ -1,38 +1,19 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { AlertController, ModalController, NavController } from '@ionic/angular' import { - ErrorService, - getPkgId, - isEmptyObject, - LoadingService, -} from '@start9labs/shared' + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core' +import { ActivatedRoute } from '@angular/router' +import { getPkgId } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { PatchDB } from 'patch-db-client' -import { FormComponent } from 'src/app/components/form.component' -import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { getAllPackages, getManifest } from 'src/app/util/get-package-data' -import { hasCurrentDeps } from 'src/app/util/has-deps' - -const allowedStatuses = { - onlyRunning: new Set(['running']), - onlyStopped: new Set(['stopped']), - any: new Set([ - 'running', - 'stopped', - 'restarting', - 'restoring', - 'stopping', - 'starting', - 'backingUp', - ]), -} +import { ActionService } from 'src/app/services/action.service' +import { StandardActionsService } from 'src/app/services/standard-actions.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { getManifest } from 'src/app/util/get-package-data' +import { filter, map } from 'rxjs' @Component({ selector: 'app-actions', @@ -42,182 +23,47 @@ const allowedStatuses = { }) export class AppActionsPage { readonly pkgId = getPkgId(this.route) - readonly pkg$ = this.patch.watch$('packageData', this.pkgId) + readonly pkg$ = this.patch.watch$('packageData', this.pkgId).pipe( + filter(pkg => pkg.stateInfo.state === 'installed'), + map(pkg => ({ + mainStatus: pkg.status.main, + icon: pkg.icon, + manifest: getManifest(pkg), + actions: Object.keys(pkg.actions).map(id => ({ + id, + ...pkg.actions[id], + })), + })), + ) constructor( private readonly route: ActivatedRoute, - private readonly embassyApi: ApiService, - private readonly modalCtrl: ModalController, - private readonly alertCtrl: AlertController, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly navCtrl: NavController, private readonly patch: PatchDB, - private readonly formDialog: FormDialogService, + private readonly actionService: ActionService, + private readonly standardActionsService: StandardActionsService, ) {} async handleAction( - status: T.Status, - action: { key: string; value: T.ActionMetadata }, + mainStatus: T.MainStatus['main'], + icon: string, + manifest: T.Manifest, + action: T.ActionMetadata & { id: string }, ) { - if ( - status && - allowedStatuses[action.value.allowedStatuses].has(status.main.status) - ) { - if (!isEmptyObject(action.value.input || {})) { - this.formDialog.open(FormComponent, { - label: action.value.name, - data: { - spec: action.value.input, - buttons: [ - { - text: 'Execute', - handler: async (value: any) => - this.executeAction(action.key, value), - }, - ], - }, - }) - } else { - const alert = await this.alertCtrl.create({ - header: 'Confirm', - message: `Are you sure you want to execute action "${ - action.value.name - }"? ${action.value.warning || ''}`, - buttons: [ - { - text: 'Cancel', - role: 'cancel', - }, - { - text: 'Execute', - handler: () => { - this.executeAction(action.key) - }, - cssClass: 'enter-click', - }, - ], - }) - await alert.present() - } - } else { - const statuses = [...allowedStatuses[action.value.allowedStatuses]] - const last = statuses.pop() - let statusesStr = statuses.join(', ') - let error = '' - if (statuses.length) { - if (statuses.length > 1) { - // oxford comma - statusesStr += ',' - } - statusesStr += ` or ${last}` - } else if (last) { - statusesStr = `${last}` - } else { - error = `There is no status for which this action may be run. This is a bug. Please file an issue with the service maintainer.` - } - const alert = await this.alertCtrl.create({ - header: 'Forbidden', - message: - error || - `Action "${action.value.name}" can only be executed when service is ${statusesStr}`, - buttons: ['OK'], - cssClass: 'alert-error-message enter-click', - }) - await alert.present() - } - } - - async tryUninstall(pkg: PackageDataEntry): Promise { - const { title, alerts } = getManifest(pkg) - - let message = - alerts.uninstall || - `Uninstalling ${title} will permanently delete its data` - - if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patch))) { - message = `${message}. Services that depend on ${title} will no longer work properly and may crash` - } - - const alert = await this.alertCtrl.create({ - header: 'Warning', - message, - buttons: [ - { - text: 'Cancel', - role: 'cancel', - }, - { - text: 'Uninstall', - handler: () => { - this.uninstall() - }, - cssClass: 'enter-click', - }, - ], - cssClass: 'alert-warning-message', + this.actionService.present({ + pkgInfo: { id: manifest.id, title: manifest.title, icon, mainStatus }, + actionInfo: { id: action.id, metadata: action }, }) - - await alert.present() } - private async uninstall() { - const loader = this.loader.open(`Beginning uninstall...`).subscribe() - - try { - await this.embassyApi.uninstallPackage({ id: this.pkgId }) - this.embassyApi - .setDbValue(['ackInstructions', this.pkgId], false) - .catch(e => console.error('Failed to mark instructions as unseen', e)) - this.navCtrl.navigateRoot('/services') - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } + async rebuild(id: string) { + return this.standardActionsService.rebuild(id) } - private async executeAction( - actionId: string, - input?: object, - ): Promise { - const loader = this.loader.open('Executing action...').subscribe() - - try { - const res = await this.embassyApi.executePackageAction({ - id: this.pkgId, - actionId, - input, - }) - - const successModal = await this.modalCtrl.create({ - component: ActionSuccessPage, - componentProps: { - actionRes: res, - }, - }) - - setTimeout(() => successModal.present(), 500) - return true // needed to dismiss original modal/alert - } catch (e: any) { - this.errorService.handleError(e) - return false // don't dismiss original modal/alert - } finally { - loader.unsubscribe() - } - } - - asIsOrder() { - return 0 + async tryUninstall(manifest: T.Manifest) { + return this.standardActionsService.tryUninstall(manifest) } } -interface LocalAction { - name: string - description: string - icon: string -} - @Component({ selector: 'app-actions-item', templateUrl: './app-actions-item.component.html', @@ -225,5 +71,19 @@ interface LocalAction { changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppActionsItemComponent { - @Input() action!: LocalAction + @Input() action!: { + name: string + description: string + visibility: T.ActionVisibility + } + @Input() icon!: string + + @Output() onClick: EventEmitter = new EventEmitter() + + get disabledText() { + return ( + typeof this.action.visibility === 'object' && + this.action.visibility.disabled + ) + } } diff --git a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html index 8225d7e53..d4238bfbf 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html @@ -26,9 +26,7 @@

{{ manifest.title }}

fill="clear" color="primary" (click)="launchUi($event, pkg.entry.serviceInterfaces, pkg.entry.hosts)" - [disabled]=" - !(pkg.entry.stateInfo.state | isLaunchable : pkgMainStatus.status) - " + [disabled]="!(pkg.entry.stateInfo.state | isLaunchable : pkgMainStatus)" >
diff --git a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts index 1464ac8a2..fdc0a3b2a 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts @@ -15,16 +15,12 @@ export class AppListPkgComponent { constructor(private readonly launcherService: UiLauncherService) {} - get pkgMainStatus(): T.MainStatus { - return ( - this.pkg.entry.status.main || { - status: 'stopped', - } - ) + get pkgMainStatus(): T.MainStatus['main'] { + return this.pkg.entry.status.main } get sigtermTimeout(): string | null { - return this.pkgMainStatus.status === 'stopping' ? '30s' : null // @dr-bonez TODO + return this.pkgMainStatus === 'stopping' ? '30s' : null // @dr-bonez TODO } launchUi( diff --git a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts b/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts deleted file mode 100644 index 2c53d0fea..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppMetricsPage } from './app-metrics.page' -import { SharedPipesModule } from '@start9labs/shared' -import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module' - -const routes: Routes = [ - { - path: '', - component: AppMetricsPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - SharedPipesModule, - SkeletonListComponentModule, - ], - declarations: [AppMetricsPage], -}) -export class AppMetricsPageModule {} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html b/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html deleted file mode 100644 index cca899f46..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - Monitor - - - - - - - - - - - {{ metric.key }} - - - {{ metric.value.value }} {{ metric.value.unit }} - - - - - diff --git a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss b/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss deleted file mode 100644 index eea898305..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -.metric-note { - font-size: 16px; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts b/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts deleted file mode 100644 index 61b17b669..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Component } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { ErrorService, getPkgId, pauseFor } from '@start9labs/shared' -import { Metric } from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -@Component({ - selector: 'app-metrics', - templateUrl: './app-metrics.page.html', - styleUrls: ['./app-metrics.page.scss'], -}) -export class AppMetricsPage { - loading = true - readonly pkgId = getPkgId(this.route) - going = false - metrics?: Metric - - constructor( - private readonly route: ActivatedRoute, - private readonly errorService: ErrorService, - private readonly embassyApi: ApiService, - ) {} - - ngOnInit() { - this.startDaemon() - } - - ngOnDestroy() { - this.stopDaemon() - } - - async startDaemon(): Promise { - this.going = true - while (this.going) { - const startTime = Date.now() - await this.getMetrics() - await pauseFor(Math.max(4000 - (Date.now() - startTime), 0)) - } - } - - stopDaemon() { - this.going = false - } - - async getMetrics(): Promise { - try { - this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId }) - } catch (e: any) { - this.errorService.handleError(e) - this.stopDaemon() - } finally { - this.loading = false - } - } - - asIsOrder(a: any, b: any) { - return 0 - } -} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.module.ts b/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.module.ts deleted file mode 100644 index 2d8553017..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { Routes, RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { AppPropertiesPage } from './app-properties.page' -import { QRComponentModule } from 'src/app/components/qr/qr.component.module' -import { MaskPipeModule } from 'src/app/pipes/mask/mask.module' -import { - SharedPipesModule, - TextSpinnerComponentModule, -} from '@start9labs/shared' - -const routes: Routes = [ - { - path: '', - component: AppPropertiesPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - IonicModule, - RouterModule.forChild(routes), - QRComponentModule, - SharedPipesModule, - TextSpinnerComponentModule, - MaskPipeModule, - ], - declarations: [AppPropertiesPage], -}) -export class AppPropertiesPageModule {} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html b/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html deleted file mode 100644 index ca3cdd3be..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - Properties - - - - Refresh - - - - - - - - - - - - -

- - Service is stopped. Information on this page could be inaccurate. - -

-
-
- - - - -

No properties.

-
-
- - - -
- - - - - - -

{{ prop.key }}

-
-
- - - - - - -

{{ prop.key }}

-

- {{ prop.value.masked && !unmasked[prop.key] ? (prop.value.value | - mask : 64) : prop.value.value }} -

-
-
- - - - - - - - - -
-
-
-
-
-
diff --git a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.scss b/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.scss deleted file mode 100644 index eea898305..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.scss +++ /dev/null @@ -1,3 +0,0 @@ -.metric-note { - font-size: 16px; -} \ No newline at end of file diff --git a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts b/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts deleted file mode 100644 index af659c32b..000000000 --- a/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Component, ViewChild } from '@angular/core' -import { ActivatedRoute } from '@angular/router' -import { - AlertController, - IonBackButtonDelegate, - ModalController, - NavController, - ToastController, -} from '@ionic/angular' -import { copyToClipboard, ErrorService, getPkgId } from '@start9labs/shared' -import { TuiDestroyService } from '@taiga-ui/cdk' -import { getValueByPointer } from 'fast-json-patch' -import { PatchDB } from 'patch-db-client' -import { map, takeUntil } from 'rxjs/operators' -import { QRComponent } from 'src/app/components/qr/qr.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { DataModel } from 'src/app/services/patch-db/data-model' -import { PackageProperties } from 'src/app/util/properties.util' - -@Component({ - selector: 'app-properties', - templateUrl: './app-properties.page.html', - styleUrls: ['./app-properties.page.scss'], - providers: [TuiDestroyService], -}) -export class AppPropertiesPage { - loading = true - readonly pkgId = getPkgId(this.route) - - pointer = '' - node: PackageProperties = {} - - properties: PackageProperties = {} - unmasked: { [key: string]: boolean } = {} - - stopped$ = this.patch - .watch$('packageData', this.pkgId, 'status', 'main', 'status') - .pipe(map(status => status === 'stopped')) - - @ViewChild(IonBackButtonDelegate, { static: false }) - backButton?: IonBackButtonDelegate - - constructor( - private readonly route: ActivatedRoute, - private readonly embassyApi: ApiService, - private readonly errorService: ErrorService, - private readonly alertCtrl: AlertController, - private readonly toastCtrl: ToastController, - private readonly modalCtrl: ModalController, - private readonly navCtrl: NavController, - private readonly patch: PatchDB, - private readonly destroy$: TuiDestroyService, - ) {} - - ionViewDidEnter() { - if (!this.backButton) return - this.backButton.onClick = () => { - history.back() - } - } - - async ngOnInit() { - await this.getProperties() - - this.route.queryParams - .pipe(takeUntil(this.destroy$)) - .subscribe(queryParams => { - if (queryParams['pointer'] === this.pointer) return - this.pointer = queryParams['pointer'] || '' - this.node = getValueByPointer(this.properties, this.pointer) - }) - } - - async refresh() { - await this.getProperties() - } - - async presentDescription( - property: { key: string; value: PackageProperties[''] }, - e: Event, - ) { - e.stopPropagation() - - const alert = await this.alertCtrl.create({ - header: property.key, - message: property.value.description || undefined, - }) - await alert.present() - } - - async goToNested(key: string): Promise { - this.navCtrl.navigateForward(`/services/${this.pkgId}/properties`, { - queryParams: { - pointer: `${this.pointer}/${key}/value`, - }, - }) - } - - async copy(text: string): Promise { - let message = '' - await copyToClipboard(text).then(success => { - message = success - ? 'Copied to clipboard!' - : 'Failed to copy to clipboard.' - }) - - const toast = await this.toastCtrl.create({ - header: message, - position: 'bottom', - duration: 1000, - }) - await toast.present() - } - - async showQR(text: string): Promise { - const modal = await this.modalCtrl.create({ - component: QRComponent, - componentProps: { - text, - }, - cssClass: 'qr-modal', - }) - await modal.present() - } - - toggleMask(key: string) { - this.unmasked[key] = !this.unmasked[key] - } - - private async getProperties(): Promise { - this.loading = true - try { - this.properties = await this.embassyApi.getPackageProperties({ - id: this.pkgId, - }) - this.node = getValueByPointer(this.properties, this.pointer) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading = false - } - } - - asIsOrder(a: any, b: any) { - return 0 - } -} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts index 8db082f42..5787948d6 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.module.ts @@ -18,6 +18,8 @@ import { AppShowDependenciesComponent } from './components/app-show-dependencies import { AppShowMenuComponent } from './components/app-show-menu/app-show-menu.component' import { AppShowHealthChecksComponent } from './components/app-show-health-checks/app-show-health-checks.component' import { AppShowAdditionalComponent } from './components/app-show-additional/app-show-additional.component' +import { AppShowErrorComponent } from './components/app-show-error/app-show-error.component' +import { AppShowActionRequestsComponent } from './components/app-show-action-requests/app-show-action-requests.component' import { HealthColorPipe } from './pipes/health-color.pipe' import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe' import { ToButtonsPipe } from './pipes/to-buttons.pipe' @@ -43,6 +45,8 @@ const routes: Routes = [ AppShowMenuComponent, AppShowHealthChecksComponent, AppShowAdditionalComponent, + AppShowErrorComponent, + AppShowActionRequestsComponent, ], imports: [ CommonModule, @@ -56,5 +60,6 @@ const routes: Routes = [ StatusComponentModule, SharedPipesModule, ], + exports: [AppShowProgressComponent], }) export class AppShowPageModule {} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html b/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html index 390c6c642..286f3ef5b 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html @@ -16,14 +16,20 @@ - + + + + diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index 52855be3c..b48b49dd9 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -26,8 +26,7 @@ import { isUpdating, } from 'src/app/util/get-package-data' import { T } from '@start9labs/start-sdk' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { ConfigModal, PackageConfigData } from 'src/app/modals/config.component' +import { getDepDetails } from 'src/app/util/dep-info' export interface DependencyInfo { id: string @@ -35,7 +34,7 @@ export interface DependencyInfo { icon: string version: string errorText: string - actionText: string + actionText: string | null action: () => any } @@ -45,7 +44,7 @@ export interface DependencyInfo { changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppShowPage { - private readonly pkgId = getPkgId(this.route) + readonly pkgId = getPkgId(this.route) readonly pkgPlus$ = combineLatest([ this.patch.watch$('packageData'), @@ -58,9 +57,12 @@ export class AppShowPage { }), map(([allPkgs, depErrors]) => { const pkg = allPkgs[this.pkgId] + const manifest = getManifest(pkg) return { + allPkgs, pkg, - dependencies: this.getDepInfo(pkg, allPkgs, depErrors), + manifest, + dependencies: this.getDepInfo(pkg, manifest, allPkgs, depErrors), status: renderPkgStatus(pkg, depErrors), } }), @@ -73,7 +75,6 @@ export class AppShowPage { private readonly navCtrl: NavController, private readonly patch: PatchDB, private readonly depErrorService: DepErrorService, - private readonly formDialog: FormDialogService, ) {} showProgress( @@ -84,40 +85,13 @@ export class AppShowPage { private getDepInfo( pkg: PackageDataEntry, + manifest: T.Manifest, allPkgs: AllPackageData, depErrors: PkgDependencyErrors, ): DependencyInfo[] { - const manifest = getManifest(pkg) - - return Object.keys(pkg.currentDependencies) - .filter(id => !!manifest.dependencies[id]) - .map(id => this.getDepValues(pkg, allPkgs, manifest, id, depErrors)) - } - - private getDepDetails( - pkg: PackageDataEntry, - allPkgs: AllPackageData, - depId: string, - ) { - const { title, icon, versionRange } = pkg.currentDependencies[depId] - - if ( - allPkgs[depId] && - (allPkgs[depId].stateInfo.state === 'installed' || - allPkgs[depId].stateInfo.state === 'updating') - ) { - return { - title: allPkgs[depId].stateInfo.manifest!.title, - icon: allPkgs[depId].icon, - versionRange, - } - } else { - return { - title: title ? title : depId, - icon: icon ? icon : 'assets/img/service-icons/fallback.png', - versionRange, - } - } + return Object.keys(pkg.currentDependencies).map(id => + this.getDepValues(pkg, allPkgs, manifest, id, depErrors), + ) } private getDepValues( @@ -134,23 +108,16 @@ export class AppShowPage { depErrors, ) - const { title, icon, versionRange } = this.getDepDetails( - pkg, - allPkgs, - depId, - ) + const { title, icon, versionRange } = getDepDetails(pkg, allPkgs, depId) return { id: depId, version: versionRange, title, icon, - errorText: errorText - ? `${errorText}. ${manifest.title} will not work as expected.` - : '', - actionText: fixText || 'View', - action: - fixAction || (() => this.navCtrl.navigateForward(`/services/${depId}`)), + errorText: errorText ? errorText : '', + actionText: fixText, + action: fixAction, } } @@ -164,28 +131,31 @@ export class AppShowPage { let errorText: string | null = null let fixText: string | null = null - let fixAction: (() => any) | null = null + let fixAction: () => any = () => {} if (depError) { if (depError.type === 'notInstalled') { errorText = 'Not installed' fixText = 'Install' - fixAction = () => this.fixDep(pkg, manifest, 'install', depId) + fixAction = () => this.installDep(pkg, manifest, depId) } else if (depError.type === 'incorrectVersion') { errorText = 'Incorrect version' fixText = 'Update' - fixAction = () => this.fixDep(pkg, manifest, 'update', depId) - } else if (depError.type === 'configUnsatisfied') { - errorText = 'Config not satisfied' - fixText = 'Auto config' - fixAction = () => this.fixDep(pkg, manifest, 'configure', depId) + fixAction = () => this.installDep(pkg, manifest, depId) + } else if (depError.type === 'actionRequired') { + errorText = 'Action Required (see above)' } else if (depError.type === 'notRunning') { errorText = 'Not running' fixText = 'Start' + fixAction = () => this.navCtrl.navigateForward(`/services/${depId}`) } else if (depError.type === 'healthChecksFailed') { errorText = 'Required health check not passing' + fixText = 'View' + fixAction = () => this.navCtrl.navigateForward(`/services/${depId}`) } else if (depError.type === 'transitive') { errorText = 'Dependency has a dependency issue' + fixText = 'View' + fixAction = () => this.navCtrl.navigateForward(`/services/${depId}`) } } @@ -196,27 +166,6 @@ export class AppShowPage { } } - private async fixDep( - pkg: PackageDataEntry, - pkgManifest: T.Manifest, - action: 'install' | 'update' | 'configure', - id: string, - ): Promise { - switch (action) { - case 'install': - case 'update': - return this.installDep(pkg, pkgManifest, id) - case 'configure': - return this.formDialog.open(ConfigModal, { - label: `${pkgManifest.title} config`, - data: { - pkgId: id, - dependentInfo: pkgManifest, - }, - }) - } - } - private async installDep( pkg: PackageDataEntry, pkgManifest: T.Manifest, diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.html b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.html new file mode 100644 index 000000000..2aee18f05 --- /dev/null +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.html @@ -0,0 +1,45 @@ + + Required Actions + + + +

{{ request.actionName }}

+

+ Service: + + {{ request.dependency.title }} +

+

+ Reason: + {{ request.reason || 'no reason provided' }} +

+
+
+
+ + + Requested Actions + + + +

{{ request.actionName }}

+

+ Service: + + {{ request.dependency.title }} +

+

+ Reason: + {{ request.reason || 'no reason provided' }} +

+
+
+
diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.scss b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.scss new file mode 100644 index 000000000..c83e6f6a7 --- /dev/null +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.scss @@ -0,0 +1,16 @@ +.light { + color: var(--ion-color-dark); +} + +.highlighted { + color: var(--ion-color-dark); + font-weight: bold; +} + +.dependency { + display: inline-flex; + img { + max-width: 16px; + margin: 0 2px 0 5px; + } +} \ No newline at end of file diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.ts new file mode 100644 index 000000000..0fefe17be --- /dev/null +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.ts @@ -0,0 +1,94 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { T } from '@start9labs/start-sdk' +import { ActionService } from 'src/app/services/action.service' +import { getDepDetails } from 'src/app/util/dep-info' + +@Component({ + selector: 'app-show-action-requests', + templateUrl: './app-show-action-requests.component.html', + styleUrls: ['./app-show-action-requests.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowActionRequestsComponent { + @Input() + allPkgs!: Record + + @Input() + pkg!: T.PackageDataEntry + + @Input() + manifest!: T.Manifest + + get actionRequests() { + const critical: (T.ActionRequest & { + actionName: string + dependency: { + title: string + icon: string + } | null + })[] = [] + const important: (T.ActionRequest & { + actionName: string + dependency: { + title: string + icon: string + } | null + })[] = [] + + Object.values(this.pkg.requestedActions) + .filter(r => r.active) + .forEach(r => { + const self = r.request.packageId === this.manifest.id + const toReturn = { + ...r.request, + actionName: self + ? this.pkg.actions[r.request.actionId].name + : this.allPkgs[r.request.packageId]?.actions[r.request.actionId] + .name || 'Unknown Action', + dependency: self + ? null + : getDepDetails(this.pkg, this.allPkgs, r.request.packageId), + } + + if (r.request.severity === 'critical') { + critical.push(toReturn) + } else { + important.push(toReturn) + } + }) + + return { critical, important } + } + + constructor(private readonly actionService: ActionService) {} + + async handleAction(request: T.ActionRequest) { + const self = request.packageId === this.manifest.id + this.actionService.present({ + pkgInfo: { + id: request.packageId, + title: self + ? this.manifest.title + : getDepDetails(this.pkg, this.allPkgs, request.packageId).title, + mainStatus: self + ? this.pkg.status.main + : this.allPkgs[request.packageId].status.main, + icon: self + ? this.pkg.icon + : getDepDetails(this.pkg, this.allPkgs, request.packageId).icon, + }, + actionInfo: { + id: request.actionId, + metadata: + request.packageId === this.manifest.id + ? this.pkg.actions[request.actionId] + : this.allPkgs[request.packageId].actions[request.actionId], + }, + requestInfo: { + request, + dependentId: + request.packageId === this.manifest.id ? undefined : this.manifest.id, + }, + }) + } +} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html index b184b83ff..059dc208b 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html @@ -1,6 +1,10 @@ Dependencies - + diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.html b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.html new file mode 100644 index 000000000..c056f2977 --- /dev/null +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.html @@ -0,0 +1,31 @@ +Message +
+ + {{ error.message }} + +
+ +Actions +
+

+ Rebuild Container + is harmless action that and only takes a few seconds to complete. It will + likely resolve this issue. + Uninstall Service + is a dangerous action that will remove the service from StartOS and wipe all + its data. +

+ + Rebuild Container + + + Uninstall Service + +
+ + + Full Stack Trace +
+ {{ error.message }} +
+
diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.ts new file mode 100644 index 000000000..ef689f178 --- /dev/null +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.ts @@ -0,0 +1,45 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { ToastController } from '@ionic/angular' +import { copyToClipboard } from '@start9labs/shared' +import { T } from '@start9labs/start-sdk' +import { StandardActionsService } from 'src/app/services/standard-actions.service' + +@Component({ + selector: 'app-show-error', + templateUrl: 'app-show-error.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowErrorComponent { + @Input() + manifest!: T.Manifest + + @Input() + error!: T.MainStatus & { main: 'error' } + + constructor( + private readonly toastCtrl: ToastController, + private readonly standardActionsService: StandardActionsService, + ) {} + + async copy(text: string): Promise { + const success = await copyToClipboard(text) + const message = success + ? 'Copied to clipboard!' + : 'Failed to copy to clipboard.' + + const toast = await this.toastCtrl.create({ + header: message, + position: 'bottom', + duration: 1000, + }) + await toast.present() + } + + async rebuild() { + return this.standardActionsService.rebuild(this.manifest.id) + } + + async tryUninstall() { + return this.standardActionsService.tryUninstall(this.manifest) + } +} diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html index c5d18907c..ef8f9a44e 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html @@ -11,7 +11,14 @@
- + @@ -36,7 +43,7 @@ - - - Configure - - diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts index 308db67be..70b969411 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts @@ -3,10 +3,8 @@ import { AlertController } from '@ionic/angular' import { ErrorService, LoadingService } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { PatchDB } from 'patch-db-client' -import { ConfigModal, PackageConfigData } from 'src/app/modals/config.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ConnectionService } from 'src/app/services/connection.service' -import { FormDialogService } from 'src/app/services/form-dialog.service' import { DataModel, PackageDataEntry, @@ -46,7 +44,6 @@ export class AppShowStatusComponent { private readonly loader: LoadingService, private readonly embassyApi: ApiService, private readonly launcherService: UiLauncherService, - private readonly formDialog: FormDialogService, readonly connection$: ConnectionService, private readonly patch: PatchDB, ) {} @@ -59,7 +56,7 @@ export class AppShowStatusComponent { return this.pkg.hosts } - get pkgStatus(): T.Status { + get pkgStatus(): T.MainStatus { return this.pkg.status } @@ -75,12 +72,12 @@ export class AppShowStatusComponent { return ['running', 'starting', 'restarting'].includes(this.status.primary) } - get isStopped(): boolean { + get canStart(): boolean { return this.status.primary === 'stopped' } get sigtermTimeout(): string | null { - return this.pkgStatus?.main.status === 'stopping' ? '30s' : null // @dr-bonez TODO + return this.pkgStatus?.main === 'stopping' ? '30s' : null // @TODO Aiden } launchUi( @@ -90,12 +87,6 @@ export class AppShowStatusComponent { this.launcherService.launch(interfaces, hosts) } - async presentModalConfig(): Promise { - return this.formDialog.open(ConfigModal, { - data: { pkgId: this.manifest.id }, - }) - } - async tryStart(): Promise { if (this.status.dependency === 'warning') { const depErrMsg = `${this.manifest.title} has unmet dependencies. It will not work as expected.` @@ -212,6 +203,7 @@ export class AppShowStatusComponent { loader.unsubscribe() } } + private async presentAlertStart(message: string): Promise { return new Promise(async resolve => { const alert = await this.alertCtrl.create({ diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts index 87bb5423f..b2101ce29 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts @@ -10,8 +10,6 @@ import { import { ApiService } from 'src/app/services/api/embassy-api.service' import { from, map, Observable } from 'rxjs' import { PatchDB } from 'patch-db-client' -import { FormDialogService } from 'src/app/services/form-dialog.service' -import { ConfigModal, PackageConfigData } from 'src/app/modals/config.component' export interface Button { title: string @@ -33,7 +31,6 @@ export class ToButtonsPipe implements PipeTransform { private readonly apiService: ApiService, private readonly api: ApiService, private readonly patch: PatchDB, - private readonly formDialog: FormDialogService, ) {} transform(pkg: PackageDataEntry): Button[] { @@ -50,34 +47,12 @@ export class ToButtonsPipe implements PipeTransform { .watch$('ui', 'ackInstructions', manifest.id) .pipe(map(seen => !seen)), }, - // config - { - action: async () => - this.formDialog.open(ConfigModal, { - label: `${manifest.title} configuration`, - data: { pkgId: manifest.id }, - }), - title: 'Config', - description: `Customize ${manifest.title}`, - icon: 'options-outline', - }, - // properties - { - action: () => - this.navCtrl.navigateForward(['properties'], { - relativeTo: this.route, - }), - title: 'Properties', - description: - 'Runtime information, credentials, and other values of interest', - icon: 'briefcase-outline', - }, // actions { action: () => this.navCtrl.navigateForward(['actions'], { relativeTo: this.route }), title: 'Actions', - description: `Uninstall and other commands specific to ${manifest.title}`, + description: `All actions for ${manifest.title}`, icon: 'flash-outline', }, // interfaces diff --git a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts index 24153caf9..12d731010 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts +++ b/web/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts @@ -15,10 +15,10 @@ export class ToHealthChecksPipe implements PipeTransform { transform( manifest: T.Manifest, ): Observable | null> { - return this.patch.watch$('packageData', manifest.id, 'status', 'main').pipe( - map(main => { - return main.status === 'running' && !isEmptyObject(main.health) - ? main.health + return this.patch.watch$('packageData', manifest.id, 'status').pipe( + map(status => { + return status.main === 'running' && !isEmptyObject(status.health) + ? status.health : null }), startWith(null), diff --git a/web/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts b/web/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts index 9dfbddcad..06f4b45fe 100644 --- a/web/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts +++ b/web/projects/ui/src/app/pages/apps-routes/apps-routing.module.ts @@ -36,20 +36,6 @@ const routes: Routes = [ loadChildren: () => import('./app-logs/app-logs.module').then(m => m.AppLogsPageModule), }, - { - path: ':pkgId/metrics', - loadChildren: () => - import('./app-metrics/app-metrics.module').then( - m => m.AppMetricsPageModule, - ), - }, - { - path: ':pkgId/properties', - loadChildren: () => - import('./app-properties/app-properties.module').then( - m => m.AppPropertiesPageModule, - ), - }, ] @NgModule({ diff --git a/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.html b/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.html index 69a58a3aa..7605d2a8e 100644 --- a/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.html +++ b/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.html @@ -1,12 +1,10 @@
-

- StartOS - Diagnostic Mode -

+
+

StartOS - Diagnostic Mode

+

StartOS version: {{ config.version }}

+

Logs + + Download + + diff --git a/web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.ts b/web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.ts index 1a6cf6a4f..d8e6b2253 100644 --- a/web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.ts +++ b/web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.ts @@ -1,6 +1,12 @@ import { Component, ViewChild } from '@angular/core' import { IonContent } from '@ionic/angular' -import { ErrorService, toLocalIsoString } from '@start9labs/shared' +import { + DownloadHTMLService, + ErrorService, + LoadingService, + Log, + toLocalIsoString, +} from '@start9labs/shared' import { ApiService } from 'src/app/services/api/embassy-api.service' var Convert = require('ansi-to-html') @@ -24,6 +30,8 @@ export class LogsPage { constructor( private readonly api: ApiService, private readonly errorService: ErrorService, + private readonly loader: LoadingService, + private readonly downloadHtml: DownloadHTMLService, ) {} async ngOnInit() { @@ -47,6 +55,30 @@ export class LogsPage { e.target.complete() } + async download() { + const loader = this.loader.open('Processing 10,000 logs...').subscribe() + + try { + const { entries } = await this.api.diagnosticGetLogs({ + before: true, + limit: 10000, + }) + + const styles = { + 'background-color': '#222428', + color: '#e0e0e0', + 'font-family': 'monospace', + } + const html = this.convertToAnsi(entries) + + this.downloadHtml.download('diagnostic-logs.html', html, styles) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + } + private async getLogs() { try { const { startCursor, entries } = await this.api.diagnosticGetLogs({ @@ -92,4 +124,15 @@ export class LogsPage { this.errorService.handleError(e) } } + + private convertToAnsi(entries: Log[]) { + return entries + .map( + entry => + `${toLocalIsoString( + new Date(entry.timestamp), + )}  ${convert.toHtml(entry.message)}`, + ) + .join('
') + } } diff --git a/web/projects/ui/src/app/pages/init/init.service.ts b/web/projects/ui/src/app/pages/init/init.service.ts index c98c0b11c..c58e7777e 100644 --- a/web/projects/ui/src/app/pages/init/init.service.ts +++ b/web/projects/ui/src/app/pages/init/init.service.ts @@ -29,9 +29,7 @@ export class InitService extends Observable { from(this.api.initGetProgress()), ).pipe( switchMap(({ guid, progress }) => - this.api - .openWebsocket$(guid, {}) - .pipe(startWith(progress)), + this.api.openWebsocket$(guid).pipe(startWith(progress)), ), map(({ phases, overall }) => { return { diff --git a/web/projects/ui/src/app/pages/init/logs/logs.service.ts b/web/projects/ui/src/app/pages/init/logs/logs.service.ts index 2553e9872..7ff3cecd1 100644 --- a/web/projects/ui/src/app/pages/init/logs/logs.service.ts +++ b/web/projects/ui/src/app/pages/init/logs/logs.service.ts @@ -38,7 +38,7 @@ export class LogsService extends Observable { private readonly log$ = defer(() => this.api.initFollowLogs({ boot: 0 }), ).pipe( - switchMap(({ guid }) => this.api.openWebsocket$(guid, {})), + switchMap(({ guid }) => this.api.openWebsocket$(guid)), bufferTime(500), filter(logs => !!logs.length), map(convertAnsi), diff --git a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html index 797a2fb13..bdfe8e3ed 100644 --- a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html +++ b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html @@ -11,49 +11,51 @@ : 'View Installing' }} - - - - Update - - + + - Downgrade - - - Reinstall + Update + + Downgrade + + + + Reinstall + + + + + + {{ localFlavor ? 'Switch' : 'Install' }} + + - - - - {{ localFlavor ? 'Switch' : 'Install' }} - -

diff --git a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts index 0b76579bc..40b443b27 100644 --- a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts +++ b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.ts @@ -47,6 +47,9 @@ export class MarketplaceShowControlsComponent { @Input() localFlavor!: boolean + @Input() + conflict?: string | null + readonly showDevTools$ = this.ClientStorageService.showDevTools$ constructor( diff --git a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html index a0f7622ad..f18a22968 100644 --- a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html +++ b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.html @@ -13,12 +13,20 @@

{{ pkgId }} not found in this registry

- + + + + +

+
+
+
diff --git a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts index 018ddbb64..9b2562395 100644 --- a/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts +++ b/web/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show.page.ts @@ -1,15 +1,24 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' -import { Exver, getPkgId } from '@start9labs/shared' +import { convertBytes, Exver, getPkgId } from '@start9labs/shared' import { AbstractMarketplaceService, MarketplacePkg, } from '@start9labs/marketplace' import { PatchDB } from 'patch-db-client' import { combineLatest, Observable } from 'rxjs' -import { filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators' +import { + filter, + first, + map, + pairwise, + shareReplay, + startWith, + switchMap, +} from 'rxjs/operators' import { DataModel } from 'src/app/services/patch-db/data-model' import { getManifest } from 'src/app/util/get-package-data' +import { Version, VersionRange } from '@start9labs/start-sdk' @Component({ selector: 'marketplace-show', @@ -25,9 +34,10 @@ export class MarketplaceShowPage { this.patch.watch$('packageData', this.pkgId).pipe(filter(Boolean)), this.route.queryParamMap, ]).pipe( - map(([pkg, paramMap]) => - this.exver.getFlavor(getManifest(pkg).version) === paramMap.get('flavor') - ? pkg + map(([localPkg, paramMap]) => + this.exver.getFlavor(getManifest(localPkg).version) === + paramMap.get('flavor') + ? localPkg : null, ), shareReplay({ bufferSize: 1, refCount: true }), @@ -49,17 +59,93 @@ export class MarketplaceShowPage { ), ) + readonly conflict$: Observable = combineLatest([ + this.pkg$, + this.patch.watch$('packageData', this.pkgId).pipe( + map(pkg => getManifest(pkg).version), + pairwise(), + filter(([prev, curr]) => prev !== curr), + map(([_, curr]) => curr), + ), + this.patch.watch$('serverInfo').pipe(first()), + ]).pipe( + map(([pkg, localVersion, server]) => { + let conflicts: string[] = [] + + // OS version + if ( + !Version.parse(pkg.osVersion).satisfies( + VersionRange.parse(server.packageVersionCompat), + ) + ) { + const compare = Version.parse(pkg.osVersion).compare( + Version.parse(server.version), + ) + conflicts.push( + compare === 'greater' + ? `Minimum StartOS version ${pkg.osVersion}. Detected ${server.version}` + : `Version ${pkg.version} is outdated and cannot run newer versions of StartOS`, + ) + } + + // package version + if ( + localVersion && + pkg.sourceVersion && + !this.exver.satisfies(localVersion, pkg.sourceVersion) + ) { + conflicts.push( + `Currently installed version ${localVersion} cannot be upgraded to version ${pkg.version}. Try installing an older version first.`, + ) + } + + const { arch, ram, device } = pkg.hardwareRequirements + + // arch + if (arch && !arch.includes(server.arch)) { + conflicts.push( + `Arch ${server.arch} is not supported. Supported: ${arch.join( + ', ', + )}.`, + ) + } + + // ram + if (ram && ram > server.ram) { + conflicts.push( + `Minimum ${convertBytes( + ram, + )} of RAM required, detected ${convertBytes(server.ram)}.`, + ) + } + + // devices + conflicts.concat( + device + .filter(d => + server.devices.some( + sd => + d.class === sd.class && !new RegExp(d.pattern).test(sd.product), + ), + ) + .map(d => d.patternDescription), + ) + + return conflicts.join(' ') + }), + shareReplay({ bufferSize: 1, refCount: true }), + ) + readonly flavors$ = this.route.queryParamMap.pipe( switchMap(paramMap => - this.marketplaceService - .getSelectedStore$() - .pipe( - map(s => - s.packages.filter( - p => p.id === this.pkgId && p.flavor !== paramMap.get('flavor'), - ), + this.marketplaceService.getSelectedStore$().pipe( + map(s => + s.packages.filter( + p => p.id === this.pkgId && p.flavor !== paramMap.get('flavor'), ), ), + filter(p => p.length > 0), + ), ), ) diff --git a/web/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts b/web/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts index d88ab8838..7c7f381a1 100644 --- a/web/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts +++ b/web/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts @@ -30,8 +30,8 @@ export class BackingUpComponent { name: 'pkgMainStatus', }) export class PkgMainStatusPipe implements PipeTransform { - transform(pkgId: string): Observable { - return this.patch.watch$('packageData', pkgId, 'status', 'main', 'status') + transform(pkgId: string): Observable { + return this.patch.watch$('packageData', pkgId, 'status', 'main') } constructor(private readonly patch: PatchDB) {} diff --git a/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts index 8ddc1c8ca..3b89deba3 100644 --- a/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/web/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -9,7 +9,7 @@ import { import { WINDOW } from '@ng-web-apis/common' import * as argon2 from '@start9labs/argon2' import { ErrorService, LoadingService } from '@start9labs/shared' -import { CB } from '@start9labs/start-sdk' +import { ISB } from '@start9labs/start-sdk' import { TuiAlertService, TuiDialogService } from '@taiga-ui/core' import { TUI_PROMPT } from '@taiga-ui/kit' import { PatchDB } from 'patch-db-client' @@ -694,26 +694,23 @@ interface SettingBtn { disabled$: Observable } -const passwordSpec = CB.Config.of({ - currentPassword: CB.Value.text({ +const passwordSpec = ISB.InputSpec.of({ + currentPassword: ISB.Value.text({ name: 'Current Password', - required: { - default: null, - }, + required: true, + default: null, masked: true, }), - newPassword1: CB.Value.text({ + newPassword1: ISB.Value.text({ name: 'New Password', - required: { - default: null, - }, + required: true, + default: null, masked: true, }), - newPassword2: CB.Value.text({ + newPassword2: ISB.Value.text({ name: 'Retype New Password', - required: { - default: null, - }, + required: true, + default: null, masked: true, }), }) diff --git a/web/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html b/web/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html index 2473e6041..f1e7dda6b 100644 --- a/web/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html +++ b/web/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html @@ -85,14 +85,14 @@

{{ agent }}

>

{{ getPlatformName(session.metadata.platforms) }}

-

{{ agent }}

+

{{ agent }}

First Seen - : {{ currentSession.loggedIn| date : 'medium' }} + : {{ session.loggedIn| date : 'medium' }}

Last Active - : {{ currentSession.lastActive| date : 'medium' }} + : {{ session.lastActive| date : 'medium' }}

- + + + +

+ {{ phase.name }} + + : {{ progress }}% + +

+ +
+
+ -
- -

Upload .s9pk package file

-

- - Tip: switch to LAN for faster uploads. - -

- - - - -
- - -
-

- - - {{ uploadState?.message }} -

-
-
-
- - - -
-
- -

{{ toUpload.manifest.title }}

-

{{ toUpload.manifest.version }}

+ +
+ +

Upload .s9pk package file

+

+ + Tip: switch to LAN for faster uploads. + +

+ + + + +
+ + +
+

+ + + {{ uploadState?.message }} +

+
+
+
+ + + +
+
+ +

{{ toUpload.manifest.title }}

+

{{ toUpload.manifest.version }}

+
-
- - Try again - - - - Upload & Install + + Try again - -
+ + + Upload & Install + + +
+ diff --git a/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts index 9e09aca62..d9bc2dff3 100644 --- a/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts +++ b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.ts @@ -1,10 +1,13 @@ import { Component } from '@angular/core' -import { isPlatform, NavController } from '@ionic/angular' +import { isPlatform } from '@ionic/angular' import { ErrorService, LoadingService } from '@start9labs/shared' import { S9pk, T } from '@start9labs/start-sdk' import cbor from 'cbor' import { ApiService } from 'src/app/services/api/embassy-api.service' import { ConfigService } from 'src/app/services/config.service' +import { SideloadService } from './sideload.service' +import { firstValueFrom } from 'rxjs' +import mime from 'mime' interface Positions { [key: string]: [bigint, bigint] // [position, length] @@ -22,7 +25,7 @@ const VERSION_2 = new Uint8Array([2]) export class SideloadPage { isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android') toUpload: { - manifest: T.Manifest | null + manifest: { title: string; version: string } | null icon: string | null file: File | null } = { @@ -36,12 +39,14 @@ export class SideloadPage { message: string } + readonly progress$ = this.sideloadService.progress$ + constructor( private readonly loader: LoadingService, private readonly api: ApiService, - private readonly navCtrl: NavController, private readonly errorService: ErrorService, private readonly config: ConfigService, + private readonly sideloadService: SideloadService, ) {} handleFileDrop(e: any) { @@ -111,15 +116,15 @@ export class SideloadPage { } async handleUpload() { - const loader = this.loader.open('Uploading package').subscribe() + const loader = this.loader.open('Starting upload').subscribe() try { const res = await this.api.sideloadPackage() + this.sideloadService.followProgress(res.progress) this.api .uploadPackage(res.upload, this.toUpload.file!) .catch(e => console.error(e)) - - this.navCtrl.navigateRoot('/services') + await firstValueFrom(this.sideloadService.websocketConnected$) } catch (e: any) { this.errorService.handleError(e) } finally { diff --git a/web/projects/ui/src/app/pages/server-routes/sideload/sideload.service.ts b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.service.ts new file mode 100644 index 000000000..79f871bba --- /dev/null +++ b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core' +import { T } from '@start9labs/start-sdk' +import { endWith, ReplaySubject, shareReplay, Subject, switchMap } from 'rxjs' +import { ApiService } from 'src/app/services/api/embassy-api.service' + +@Injectable({ + providedIn: 'root', +}) +export class SideloadService { + private readonly guid$ = new Subject() + + readonly websocketConnected$ = new ReplaySubject() + + readonly progress$ = this.guid$.pipe( + switchMap(guid => + this.api + .openWebsocket$(guid, { + openObserver: { + next: () => this.websocketConnected$.next(''), + }, + }) + .pipe(endWith(null)), + ), + shareReplay(1), + ) + + constructor(private readonly api: ApiService) {} + + followProgress(guid: string) { + this.guid$.next(guid) + } +} diff --git a/web/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts b/web/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts index 5d722b482..7d59a4d30 100644 --- a/web/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts +++ b/web/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts @@ -7,7 +7,7 @@ import { import { ActionSheetButton, AlertInput } from '@ionic/core' import { WINDOW } from '@ng-web-apis/common' import { ErrorService, LoadingService, pauseFor } from '@start9labs/shared' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { TuiDialogOptions } from '@taiga-ui/core' import { PatchDB } from 'patch-db-client' import { FormComponent, FormContext } from 'src/app/components/form.component' @@ -343,7 +343,7 @@ export class WifiPage { function getWifiValueSpec( ssid: string | null = null, needsPW: boolean = true, -): CT.ValueSpecObject { +): IST.ValueSpecObject { return { warning: null, type: 'object', diff --git a/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts b/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts index 9077c5215..405d61019 100644 --- a/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts +++ b/web/projects/ui/src/app/pipes/launchable/launchable.pipe.ts @@ -10,7 +10,7 @@ export class LaunchablePipe implements PipeTransform { transform( state: T.PackageState['state'], - status: T.MainStatus['status'], + status: T.MainStatus['main'], ): boolean { return this.configService.isLaunchable(state, status) } diff --git a/web/projects/ui/src/app/services/action.service.ts b/web/projects/ui/src/app/services/action.service.ts new file mode 100644 index 000000000..298b9facc --- /dev/null +++ b/web/projects/ui/src/app/services/action.service.ts @@ -0,0 +1,141 @@ +import { Injectable } from '@angular/core' +import { AlertController } from '@ionic/angular' +import { ErrorService, LoadingService } from '@start9labs/shared' +import { TuiDialogService } from '@taiga-ui/core' +import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' +import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { FormDialogService } from 'src/app/services/form-dialog.service' +import { + ActionInputModal, + PackageActionData, +} from '../modals/action-input.component' + +const allowedStatuses = { + 'only-running': new Set(['running']), + 'only-stopped': new Set(['stopped']), + any: new Set([ + 'running', + 'stopped', + 'restarting', + 'restoring', + 'stopping', + 'starting', + 'backingUp', + ]), +} + +@Injectable({ + providedIn: 'root', +}) +export class ActionService { + constructor( + private readonly api: ApiService, + private readonly dialogs: TuiDialogService, + private readonly alertCtrl: AlertController, + private readonly errorService: ErrorService, + private readonly loader: LoadingService, + private readonly formDialog: FormDialogService, + ) {} + + async present(data: PackageActionData) { + const { pkgInfo, actionInfo } = data + + if ( + allowedStatuses[actionInfo.metadata.allowedStatuses].has( + pkgInfo.mainStatus, + ) + ) { + if (actionInfo.metadata.hasInput) { + this.formDialog.open(ActionInputModal, { + label: actionInfo.metadata.name, + data, + }) + } else { + if (actionInfo.metadata.warning) { + const alert = await this.alertCtrl.create({ + header: 'Warning', + message: actionInfo.metadata.warning, + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Run', + handler: () => { + this.execute(pkgInfo.id, actionInfo.id) + }, + cssClass: 'enter-click', + }, + ], + cssClass: 'alert-warning-message', + }) + await alert.present() + } else { + this.execute(pkgInfo.id, actionInfo.id) + } + } + } else { + const statuses = [...allowedStatuses[actionInfo.metadata.allowedStatuses]] + const last = statuses.pop() + let statusesStr = statuses.join(', ') + let error = '' + if (statuses.length) { + if (statuses.length > 1) { + // oxford comma + statusesStr += ',' + } + statusesStr += ` or ${last}` + } else if (last) { + statusesStr = `${last}` + } else { + error = `There is no status for which this action may be run. This is a bug. Please file an issue with the service maintainer.` + } + const alert = await this.alertCtrl.create({ + header: 'Forbidden', + message: + error || + `Action "${actionInfo.metadata.name}" can only be executed when service is ${statusesStr}`, + buttons: ['OK'], + cssClass: 'alert-error-message enter-click', + }) + await alert.present() + } + } + + async execute( + packageId: string, + actionId: string, + input?: object, + ): Promise { + const loader = this.loader.open('Loading...').subscribe() + + try { + const res = await this.api.runAction({ + packageId, + actionId, + input: input || null, + }) + + if (!res) return true + + if (res.result) { + this.dialogs + .open(new PolymorpheusComponent(ActionSuccessPage), { + label: res.title, + data: res, + }) + .subscribe() + } else if (res.message) { + this.dialogs.open(res.message, { label: res.title }).subscribe() + } + return true // needed to dismiss original modal/alert + } catch (e: any) { + this.errorService.handleError(e) + return false // don't dismiss original modal/alert + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index b093ad29e..2f93b3e28 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -2,18 +2,13 @@ import { InstalledState, PackageDataEntry, } from 'src/app/services/patch-db/data-model' -import { Metric, NotificationLevel, RR, ServerNotifications } from './api.types' +import { NotificationLevel, RR, ServerNotifications } from './api.types' import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons' import { Log } from '@start9labs/shared' import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec' -import { T, CB } from '@start9labs/start-sdk' +import { T, ISB, IST } from '@start9labs/start-sdk' import { GetPackagesRes } from '@start9labs/marketplace' -const mockBlake3Commitment: T.Blake3Commitment = { - hash: 'fakehash', - size: 0, -} - const mockMerkleArchiveCommitment: T.MerkleArchiveCommitment = { rootSighash: 'fakehash', rootMaxsize: 0, @@ -112,7 +107,6 @@ export module Mock { }, osVersion: '0.2.12', dependencies: {}, - hasConfig: true, images: { main: { source: 'packed', @@ -123,7 +117,7 @@ export module Mock { assets: [], volumes: ['main'], hardwareRequirements: { - device: {}, + device: [], arch: null, ram: null, }, @@ -170,7 +164,6 @@ export module Mock { s9pk: '', }, }, - hasConfig: true, images: { main: { source: 'packed', @@ -181,7 +174,7 @@ export module Mock { assets: [], volumes: ['main'], hardwareRequirements: { - device: {}, + device: [], arch: null, ram: null, }, @@ -221,7 +214,6 @@ export module Mock { s9pk: '', }, }, - hasConfig: false, images: { main: { source: 'packed', @@ -232,7 +224,7 @@ export module Mock { assets: [], volumes: ['main'], hardwareRequirements: { - device: {}, + device: [], arch: null, ram: null, }, @@ -261,7 +253,7 @@ export module Mock { '26.1.0:0.1.0': { title: 'Bitcoin Core', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/bitcoind-startos', upstreamRepo: 'https://github.com/bitcoin/bitcoin', @@ -294,7 +286,7 @@ export module Mock { short: 'An alternate fully verifying implementation of Bitcoin', long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.', }, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos', upstreamRepo: 'https://github.com/bitcoinknots/bitcoin', @@ -337,7 +329,7 @@ export module Mock { '26.1.0:0.1.0': { title: 'Bitcoin Core', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/bitcoind-startos', upstreamRepo: 'https://github.com/bitcoin/bitcoin', @@ -370,7 +362,7 @@ export module Mock { short: 'An alternate fully verifying implementation of Bitcoin', long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.', }, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos', upstreamRepo: 'https://github.com/bitcoinknots/bitcoin', @@ -415,7 +407,7 @@ export module Mock { '0.17.5:0': { title: 'LND', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/lnd-startos', upstreamRepo: 'https://github.com/lightningnetwork/lnd', @@ -471,7 +463,7 @@ export module Mock { '0.17.4-beta:1.0-alpha': { title: 'LND', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/lnd-startos', upstreamRepo: 'https://github.com/lightningnetwork/lnd', @@ -529,7 +521,7 @@ export module Mock { '0.3.2.6:0': { title: 'Bitcoin Proxy', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/Start9Labs/btc-rpc-proxy-wrappers', upstreamRepo: 'https://github.com/Kixunil/btc-rpc-proxy', @@ -573,7 +565,7 @@ export module Mock { '27.0.0:1.0.0': { title: 'Bitcoin Core', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/bitcoind-startos', upstreamRepo: 'https://github.com/bitcoin/bitcoin', @@ -606,7 +598,7 @@ export module Mock { short: 'An alternate fully verifying implementation of Bitcoin', long: 'Bitcoin Knots is a combined Bitcoin node and wallet. Not only is it easy to use, but it also ensures bitcoins you receive are both real bitcoins and really yours.', }, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/bitcoinknots-startos', upstreamRepo: 'https://github.com/bitcoinknots/bitcoin', @@ -649,7 +641,7 @@ export module Mock { '0.18.0:0.0.1': { title: 'LND', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/start9labs/lnd-startos', upstreamRepo: 'https://github.com/lightningnetwork/lnd', @@ -705,7 +697,7 @@ export module Mock { '0.3.2.7:0': { title: 'Bitcoin Proxy', description: mockDescription, - hardwareRequirements: { arch: null, device: {}, ram: null }, + hardwareRequirements: { arch: null, device: [], ram: null }, license: 'mit', wrapperRepo: 'https://github.com/Start9Labs/btc-rpc-proxy-wrappers', upstreamRepo: 'https://github.com/Kixunil/btc-rpc-proxy', @@ -883,25 +875,6 @@ export module Mock { } } - export function getAppMetrics() { - const metr: Metric = { - Metric1: { - value: Math.random(), - unit: 'mi/b', - }, - Metric2: { - value: Math.random(), - unit: '%', - }, - Metric3: { - value: 10.1, - unit: '%', - }, - } - - return metr - } - export const ServerLogs: Log[] = [ { timestamp: '2022-07-28T03:52:54.808769Z', @@ -949,14 +922,6 @@ export module Mock { }, } - export const ActionResponse: RR.ExecutePackageActionRes = { - message: - 'Password changed successfully. If you lose your new password, you will be lost forever.', - value: 'NewPassword1234!', - copyable: true, - qr: true, - } - export const SshKeys: RR.GetSSHKeysRes = [ { createdAt: new Date().toISOString(), @@ -1084,98 +1049,128 @@ export module Mock { }, } - export const PackageProperties: RR.GetPackagePropertiesRes<2> = { - version: 2, - data: { - lndconnect: { - type: 'string', - description: 'This is some information about the thing.', - copyable: true, - qr: true, - masked: true, - value: - 'lndconnect://udlyfq2mxa4355pt7cqlrdipnvk2tsl4jtsdw7zaeekenufwcev2wlad.onion:10009?cert=MIICJTCCAcugAwIBAgIRAOyq85fqAiA3U3xOnwhH678wCgYIKoZIzj0EAwIwODEfMB0GAkUEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMB4XDTIwMTAyNjA3MzEyN1oXDTIxMTIyMTA3MzEyN1owODEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKqfhAMMZdY-eFnU5P4bGrQTSx0lo7m8u4V0yYkzUM6jlql_u31_mU2ovLTj56wnZApkEjoPl6fL2yasZA2wiy6OBtTCBsjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH_BAUwAwEB_zAdBgNVHQ4EFgQUYQ9uIO6spltnVCx4rLFL5BvBF9IwWwYDVR0RBFQwUoIMNTc0OTkwMzIyYzZlgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwSAAswCgYIKoZIzj0EAwIDSAAwRQIgVZH2Z2KlyAVY2Q2aIQl0nsvN-OEN49wreFwiBqlxNj4CIQD5_JbpuBFJuf81I5J0FQPtXY-4RppWOPZBb-y6-rkIUQ&macaroon=AgEDbG5kAusBAwoQuA8OUMeQ8Fr2h-f65OdXdRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFAoIbWFjYXJvb24SCGdlbmVyYXRlGhYKB21lc3NhZ2USBHJlYWQSBXdyaXRlGhcKCG9mZmNoYWluEgRyZWFkEgV3cml0ZRoWCgdvbmNoYWluEgRyZWFkEgV3cml0ZRoUCgVwZWVycxIEcmVhZBIFd3JpdGUaGAoGc2lnbmVyEghnZW5lcmF0ZRIEcmVhZAAABiCYsRUoUWuAHAiCSLbBR7b_qULDSl64R8LIU2aqNIyQfA', - }, - Nested: { - type: 'object', - description: 'This is a nested thing metric', - value: { - 'Last Name': { - type: 'string', - description: 'The last name of the user', - copyable: true, - qr: true, - masked: false, - value: 'Hill', - }, - Age: { - type: 'string', - description: 'The age of the user', - copyable: false, - qr: false, - masked: false, - value: '35', - }, - Password: { - type: 'string', - description: 'A secret password', - copyable: true, - qr: false, - masked: true, - value: 'password123', - }, + export const ActionResMessage: RR.ActionRes = { + version: '1', + title: 'New Password', + message: + 'Action was run successfully and smoothly and fully and all is good on the western front.', + result: null, + } + + export const ActionResSingle: RR.ActionRes = { + version: '1', + title: 'New Password', + message: + 'Action was run successfully and smoothly and fully and all is good on the western front.', + result: { + type: 'single', + copyable: true, + qr: true, + masked: true, + value: 'iwejdoiewdhbew', + }, + } + + export const ActionResGroup: RR.ActionRes = { + version: '1', + title: 'Properties', + message: + 'Successfully retrieved properties. Here is a bunch of useful information about this service.', + result: { + type: 'group', + value: [ + { + type: 'single', + name: 'LND Connect', + description: 'This is some information about the thing.', + copyable: true, + qr: true, + masked: true, + value: + 'lndconnect://udlyfq2mxa4355pt7cqlrdipnvk2tsl4jtsdw7zaeekenufwcev2wlad.onion:10009?cert=MIICJTCCAcugAwIBAgIRAOyq85fqAiA3U3xOnwhH678wCgYIKoZIzj0EAwIwODEfMB0GAkUEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMB4XDTIwMTAyNjA3MzEyN1oXDTIxMTIyMTA3MzEyN1owODEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKqfhAMMZdY-eFnU5P4bGrQTSx0lo7m8u4V0yYkzUM6jlql_u31_mU2ovLTj56wnZApkEjoPl6fL2yasZA2wiy6OBtTCBsjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH_BAUwAwEB_zAdBgNVHQ4EFgQUYQ9uIO6spltnVCx4rLFL5BvBF9IwWwYDVR0RBFQwUoIMNTc0OTkwMzIyYzZlgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwSAAswCgYIKoZIzj0EAwIDSAAwRQIgVZH2Z2KlyAVY2Q2aIQl0nsvN-OEN49wreFwiBqlxNj4CIQD5_JbpuBFJuf81I5J0FQPtXY-4RppWOPZBb-y6-rkIUQ&macaroon=AgEDbG5kAusBAwoQuA8OUMeQ8Fr2h-f65OdXdRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFAoIbWFjYXJvb24SCGdlbmVyYXRlGhYKB21lc3NhZ2USBHJlYWQSBXdyaXRlGhcKCG9mZmNoYWluEgRyZWFkEgV3cml0ZRoWCgdvbmNoYWluEgRyZWFkEgV3cml0ZRoUCgVwZWVycxIEcmVhZBIFd3JpdGUaGAoGc2lnbmVyEghnZW5lcmF0ZRIEcmVhZAAABiCYsRUoUWuAHAiCSLbBR7b_qULDSl64R8LIU2aqNIyQfA', }, - }, - 'Another Value': { - type: 'string', - description: 'Some more information about the service.', - copyable: false, - qr: true, - masked: false, - value: 'https://guessagain.com', - }, + { + type: 'group', + name: 'Nested Stuff', + description: 'This is a nested thing metric', + value: [ + { + type: 'single', + name: 'Last Name', + description: 'The last name of the user', + copyable: true, + qr: true, + masked: false, + value: 'Hill', + }, + { + type: 'single', + name: 'Age', + description: 'The age of the user', + copyable: false, + qr: false, + masked: false, + value: '35', + }, + { + type: 'single', + name: 'Password', + description: 'A secret password', + copyable: true, + qr: false, + masked: true, + value: 'password123', + }, + ], + }, + { + type: 'single', + name: 'Another Value', + description: 'Some more information about the service.', + copyable: false, + qr: true, + masked: false, + value: 'https://guessagain.com', + }, + ], }, } - export const getInputSpec = async (): Promise< - RR.GetPackageConfigRes['spec'] - > => + export const getActionInputSpec = async (): Promise => configBuilderToSpec( - CB.Config.of({ - bitcoin: CB.Value.object( + ISB.InputSpec.of({ + bitcoin: ISB.Value.object( { name: 'Bitcoin Settings', description: 'RPC and P2P interface configuration options for Bitcoin Core', }, - CB.Config.of({ - 'bitcoind-p2p': CB.Value.union( + ISB.InputSpec.of({ + 'bitcoind-p2p': ISB.Value.union( { name: 'P2P Settings', description: '

The Bitcoin Core node to connect to over the peer-to-peer (P2P) interface:

  • Bitcoin Core: The Bitcoin Core service installed on this device
  • External Node: A Bitcoin node running on a different device
', - required: { default: 'internal' }, + default: 'internal', }, - CB.Variants.of({ - internal: { name: 'Bitcoin Core', spec: CB.Config.of({}) }, + ISB.Variants.of({ + internal: { name: 'Bitcoin Core', spec: ISB.InputSpec.of({}) }, external: { name: 'External Node', - spec: CB.Config.of({ - 'p2p-host': CB.Value.text({ + spec: ISB.InputSpec.of({ + 'p2p-host': ISB.Value.text({ name: 'Public Address', - required: { - default: null, - }, + required: false, + default: null, description: 'The public address of your Bitcoin Core server', }), - 'p2p-port': CB.Value.number({ + 'p2p-port': ISB.Value.number({ name: 'P2P Port', description: 'The port that your Bitcoin Core P2P server is bound to', - required: { - default: 8333, - }, + required: true, + default: 8333, min: 0, max: 65535, integer: true, @@ -1186,24 +1181,25 @@ export module Mock { ), }), ), - color: CB.Value.color({ + color: ISB.Value.color({ name: 'Color', required: false, + default: null, }), - datetime: CB.Value.datetime({ + datetime: ISB.Value.datetime({ name: 'Datetime', required: false, + default: null, }), - file: CB.Value.file({ - name: 'File', - required: false, - extensions: ['png', 'pdf'], - }), - users: CB.Value.multiselect({ + // file: ISB.Value.file({ + // name: 'File', + // required: false, + // extensions: ['png', 'pdf'], + // }), + users: ISB.Value.multiselect({ name: 'Users', default: [], maxLength: 2, - disabled: ['matt'], values: { matt: 'Matt Hill', alex: 'Alex Inkin', @@ -1211,25 +1207,22 @@ export module Mock { lucy: 'Lucy', }, }), - advanced: CB.Value.object( + advanced: ISB.Value.object( { name: 'Advanced', description: 'Advanced settings', }, - CB.Config.of({ - rpcsettings: CB.Value.object( + ISB.InputSpec.of({ + rpcsettings: ISB.Value.object( { name: 'RPC Settings', description: 'rpc username and password', - warning: - 'Adding RPC users gives them special permissions on your node.', }, - CB.Config.of({ - rpcuser2: CB.Value.text({ + ISB.InputSpec.of({ + rpcuser2: ISB.Value.text({ name: 'RPC Username', - required: { - default: 'defaultrpcusername', - }, + required: false, + default: 'defaultrpcusername', description: 'rpc username', patterns: [ { @@ -1238,11 +1231,10 @@ export module Mock { }, ], }), - rpcuser: CB.Value.text({ + rpcuser: ISB.Value.text({ name: 'RPC Username', - required: { - default: 'defaultrpcusername', - }, + required: true, + default: 'defaultrpcusername', description: 'rpc username', patterns: [ { @@ -1251,23 +1243,21 @@ export module Mock { }, ], }), - rpcpass: CB.Value.text({ + rpcpass: ISB.Value.text({ name: 'RPC User Password', - required: { - default: { - charset: 'a-z,A-Z,2-9', - len: 20, - }, + required: true, + default: { + charset: 'a-z,A-Z,2-9', + len: 20, }, description: 'rpc password', }), - rpcpass2: CB.Value.text({ + rpcpass2: ISB.Value.text({ name: 'RPC User Password', - required: { - default: { - charset: 'a-z,A-Z,2-9', - len: 20, - }, + required: true, + default: { + charset: 'a-z,A-Z,2-9', + len: 20, }, description: 'rpc password', }), @@ -1275,15 +1265,15 @@ export module Mock { ), }), ), - testnet: CB.Value.toggle({ + testnet: ISB.Value.toggle({ name: 'Testnet', default: true, description: '
  • determines whether your node is running on testnet or mainnet
', warning: 'Chain will have to resync!', }), - 'object-list': CB.Value.list( - CB.List.obj( + 'object-list': ISB.Value.list( + ISB.List.obj( { name: 'Object List', minLength: 0, @@ -1295,19 +1285,19 @@ export module Mock { description: 'This is a list of objects, like users or something', }, { - spec: CB.Config.of({ - 'first-name': CB.Value.text({ + spec: ISB.InputSpec.of({ + 'first-name': ISB.Value.text({ name: 'First Name', required: false, description: 'User first name', + default: 'Matt', }), - 'last-name': CB.Value.text({ + 'last-name': ISB.Value.text({ name: 'Last Name', - required: { - default: { - charset: 'a-g,2-9', - len: 12, - }, + required: true, + default: { + charset: 'a-g,2-9', + len: 12, }, description: 'User first name', patterns: [ @@ -1317,11 +1307,12 @@ export module Mock { }, ], }), - age: CB.Value.number({ + age: ISB.Value.number({ name: 'Age', description: 'The age of the user', warning: 'User must be at least 18.', required: false, + default: null, min: 18, integer: false, }), @@ -1331,8 +1322,8 @@ export module Mock { }, ), ), - 'union-list': CB.Value.list( - CB.List.obj( + 'union-list': ISB.Value.list( + ISB.List.obj( { name: 'Union List', minLength: 0, @@ -1342,32 +1333,29 @@ export module Mock { warning: 'If you change this, things may work.', }, { - spec: CB.Config.of({ + spec: ISB.InputSpec.of({ /* TODO: Convert range for this value ([0, 2])*/ - union: CB.Value.union( + union: ISB.Value.union( { name: 'Preference', description: null, warning: null, - required: { default: 'summer' }, + default: 'summer', }, - CB.Variants.of({ + ISB.Variants.of({ summer: { name: 'summer', - spec: CB.Config.of({ - 'favorite-tree': CB.Value.text({ + spec: ISB.InputSpec.of({ + 'favorite-tree': ISB.Value.text({ name: 'Favorite Tree', - required: { - default: 'Maple', - }, + required: true, + default: 'Maple', description: 'What is your favorite tree?', }), - 'favorite-flower': CB.Value.select({ + 'favorite-flower': ISB.Value.select({ name: 'Favorite Flower', description: 'Select your favorite flower', - required: { - default: 'none', - }, + default: 'none', values: { none: 'none', red: 'red', @@ -1379,8 +1367,8 @@ export module Mock { }, winter: { name: 'winter', - spec: CB.Config.of({ - 'like-snow': CB.Value.toggle({ + spec: ISB.InputSpec.of({ + 'like-snow': ISB.Value.toggle({ name: 'Like Snow?', default: true, description: 'Do you like snow or not?', @@ -1394,60 +1382,59 @@ export module Mock { }, ), ), - 'random-select': CB.Value.select({ + 'random-select': ISB.Value.dynamicSelect(() => ({ name: 'Random select', description: 'This is not even real.', warning: 'Be careful changing this!', - required: { - default: null, - }, + default: 'option1', values: { option1: 'option1', option2: 'option2', option3: 'option3', }, disabled: ['option2'], - }), + })), 'favorite-number': - /* TODO: Convert range for this value ((-100,100])*/ CB.Value.number({ - name: 'Favorite Number', - description: 'Your favorite number of all time', - warning: - 'Once you set this number, it can never be changed without severe consequences.', - required: { + /* TODO: Convert range for this value ((-100,100])*/ ISB.Value.number( + { + name: 'Favorite Number', + description: 'Your favorite number of all time', + warning: + 'Once you set this number, it can never be changed without severe consequences.', + required: false, default: 7, + integer: false, + units: 'BTC', }, - integer: false, - units: 'BTC', - }), - rpcsettings: CB.Value.object( + ), + rpcsettings: ISB.Value.object( { name: 'RPC Settings', description: 'rpc username and password', - warning: - 'Adding RPC users gives them special permissions on your node.', }, - CB.Config.of({ - laws: CB.Value.object( + ISB.InputSpec.of({ + laws: ISB.Value.object( { name: 'Laws', description: 'the law of the realm', }, - CB.Config.of({ - law1: CB.Value.text({ + ISB.InputSpec.of({ + law1: ISB.Value.text({ name: 'First Law', required: false, description: 'the first law', + default: null, }), - law2: CB.Value.text({ + law2: ISB.Value.text({ name: 'Second Law', required: false, description: 'the second law', + default: null, }), }), ), - rulemakers: CB.Value.list( - CB.List.obj( + rulemakers: ISB.Value.list( + ISB.List.obj( { name: 'Rule Makers', minLength: 0, @@ -1455,22 +1442,20 @@ export module Mock { description: 'the people who make the rules', }, { - spec: CB.Config.of({ - rulemakername: CB.Value.text({ + spec: ISB.InputSpec.of({ + rulemakername: ISB.Value.text({ name: 'Rulemaker Name', - required: { - default: { - charset: 'a-g,2-9', - len: 12, - }, + required: true, + default: { + charset: 'a-g,2-9', + len: 12, }, description: 'the name of the rule maker', }), - rulemakerip: CB.Value.text({ + rulemakerip: ISB.Value.text({ name: 'Rulemaker IP', - required: { - default: '192.168.1.0', - }, + required: true, + default: '192.168.1.0', description: 'the ip of the rule maker', patterns: [ { @@ -1484,11 +1469,10 @@ export module Mock { }, ), ), - rpcuser: CB.Value.text({ + rpcuser: ISB.Value.text({ name: 'RPC Username', - required: { - default: 'defaultrpcusername', - }, + required: true, + default: 'defaultrpcusername', description: 'rpc username', patterns: [ { @@ -1497,50 +1481,47 @@ export module Mock { }, ], }), - rpcpass: CB.Value.text({ + rpcpass: ISB.Value.text({ name: 'RPC User Password', - required: { - default: { - charset: 'a-z,A-Z,2-9', - len: 20, - }, + required: true, + default: { + charset: 'a-z,A-Z,2-9', + len: 20, }, description: 'rpc password', masked: true, }), }), ), - 'bitcoin-node': CB.Value.union( + 'bitcoin-node': ISB.Value.union( { name: 'Bitcoin Node', description: 'Options
  • Item 1
  • Item 2
', warning: 'Careful changing this', - required: { default: 'internal' }, - disabled: ['fake'], + default: 'internal', }, - CB.Variants.of({ + ISB.Variants.of({ fake: { name: 'Fake', - spec: CB.Config.of({}), + spec: ISB.InputSpec.of({}), }, internal: { name: 'Internal', - spec: CB.Config.of({}), + spec: ISB.InputSpec.of({}), }, external: { name: 'External', - spec: CB.Config.of({ - 'emergency-contact': CB.Value.object( + spec: ISB.InputSpec.of({ + 'emergency-contact': ISB.Value.object( { name: 'Emergency Contact', description: 'The person to contact in case of emergency.', }, - CB.Config.of({ - name: CB.Value.text({ + ISB.InputSpec.of({ + name: ISB.Value.text({ name: 'Name', - required: { - default: null, - }, + required: false, + default: null, patterns: [ { regex: '^[a-zA-Z]+$', @@ -1548,20 +1529,18 @@ export module Mock { }, ], }), - email: CB.Value.text({ + email: ISB.Value.text({ name: 'Email', inputmode: 'email', - required: { - default: null, - }, + required: false, + default: null, }), }), ), - 'public-domain': CB.Value.text({ + 'public-domain': ISB.Value.text({ name: 'Public Domain', - required: { - default: 'bitcoinnode.com', - }, + required: true, + default: 'bitcoinnode.com', description: 'the public address of the node', patterns: [ { @@ -1570,11 +1549,10 @@ export module Mock { }, ], }), - 'private-domain': CB.Value.text({ + 'private-domain': ISB.Value.text({ name: 'Private Domain', - required: { - default: null, - }, + required: false, + default: null, description: 'the private address of the node', masked: true, inputmode: 'url', @@ -1583,31 +1561,31 @@ export module Mock { }, }), ), - port: CB.Value.number({ + port: ISB.Value.number({ name: 'Port', description: 'the default port for your Bitcoin node. default: 8333, testnet: 18333, regtest: 18444', - required: { - default: 8333, - }, + required: true, + default: 8333, min: 1, max: 9998, step: 1, integer: true, }), - 'favorite-slogan': CB.Value.text({ + 'favorite-slogan': ISB.Value.text({ name: 'Favorite Slogan', generate: { charset: 'a-z,A-Z,2-9', len: 20, }, required: false, + default: null, description: 'You most favorite slogan in the whole world, used for paying you.', masked: true, }), - rpcallowip: CB.Value.list( - CB.List.text( + rpcallowip: ISB.Value.list( + ISB.List.text( { name: 'RPC Allowed IPs', minLength: 1, @@ -1629,8 +1607,8 @@ export module Mock { }, ), ), - rpcauth: CB.Value.list( - CB.List.text( + rpcauth: ISB.Value.list( + ISB.List.text( { name: 'RPC Auth', description: @@ -1694,14 +1672,40 @@ export module Mock { icon: '/assets/img/service-icons/bitcoind.svg', lastBackup: null, status: { - configured: true, - main: { - status: 'running', - started: new Date().toISOString(), - health: {}, + main: 'running', + started: new Date().toISOString(), + health: {}, + }, + actions: { + config: { + name: 'Set Config', + description: 'edit bitcoin.conf', + warning: null, + visibility: 'enabled', + allowedStatuses: 'any', + hasInput: true, + group: null, + }, + properties: { + name: 'View Properties', + description: 'view important information about Bitcoin', + warning: null, + visibility: 'enabled', + allowedStatuses: 'any', + hasInput: false, + group: null, + }, + test: { + name: 'Do Another Thing', + description: + 'An example of an action that shows a warning and takes no input', + warning: 'careful running this action', + visibility: { disabled: 'This is temporarily disabled' }, + allowedStatuses: 'only-running', + hasInput: false, + group: null, }, }, - actions: {}, serviceInterfaces: { ui: { id: 'ui', @@ -1860,6 +1864,27 @@ export module Mock { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: { + 'bitcoind-config': { + request: { + packageId: 'bitcoind', + actionId: 'config', + severity: 'critical', + reason: + 'You must run Config before starting Bitcoin for the first time', + }, + active: true, + }, + 'bitcoind-properties': { + request: { + packageId: 'bitcoind', + actionId: 'properties', + severity: 'important', + reason: 'Check out all the info about your Bitcoin node', + }, + active: true, + }, + }, } export const bitcoinProxy: PackageDataEntry = { @@ -1871,10 +1896,7 @@ export module Mock { icon: '/assets/img/service-icons/btc-rpc-proxy.png', lastBackup: null, status: { - configured: false, - main: { - status: 'stopped', - }, + main: 'stopped', }, actions: {}, serviceInterfaces: { @@ -1902,13 +1924,13 @@ export module Mock { kind: 'running', versionRange: '>=26.0.0', healthChecks: [], - configSatisfied: true, }, }, hosts: {}, storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: {}, } export const lnd: PackageDataEntry = { @@ -1920,10 +1942,7 @@ export module Mock { icon: '/assets/img/service-icons/lnd.png', lastBackup: null, status: { - configured: true, - main: { - status: 'stopped', - }, + main: 'stopped', }, actions: {}, serviceInterfaces: { @@ -1986,20 +2005,39 @@ export module Mock { kind: 'running', versionRange: '>=26.0.0', healthChecks: [], - configSatisfied: true, }, 'btc-rpc-proxy': { title: Mock.MockManifestBitcoinProxy.title, icon: 'assets/img/service-icons/btc-rpc-proxy.png', kind: 'exists', versionRange: '>2.0.0', - configSatisfied: false, }, }, hosts: {}, storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: { + 'bitcoind/config': { + active: true, + request: { + packageId: 'bitcoind', + actionId: 'config', + severity: 'critical', + reason: 'LND likes BTC a certain way', + input: { + kind: 'partial', + value: { + color: '#ffffff', + rpcsettings: { + rpcuser: 'lnd', + }, + testnet: false, + }, + }, + }, + }, + }, } export const LocalPkgs: { [key: string]: PackageDataEntry } = diff --git a/web/projects/ui/src/app/services/api/api.types.ts b/web/projects/ui/src/app/services/api/api.types.ts index ef0e80d20..fcf375913 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -1,8 +1,7 @@ import { Dump } from 'patch-db-client' -import { PackagePropertiesVersioned } from 'src/app/util/properties.util' import { DataModel } from 'src/app/services/patch-db/data-model' import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared' -import { CT, T } from '@start9labs/start-sdk' +import { IST, T } from '@start9labs/start-sdk' import { WebSocketSubjectConfig } from 'rxjs/webSocket' export module RR { @@ -72,11 +71,10 @@ export module RR { export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs export type GetServerLogsRes = LogsRes - // @param limit: BE default is 50 - // @param boot: number is offset (0: current, -1 prev, +1 first), string is a specific boot id, and null is all export type FollowServerLogsReq = { - limit?: number - boot?: number | string | null + limit?: number // (optional) default is 50. Ignored if cursor provided + boot?: number | string | null // (optional) number is offset (0: current, -1 prev, +1 first), string is a specific boot id, null is all. Default is undefined + cursor?: string // the last known log. Websocket will return all logs since this log } // server.logs.follow & server.kernel-logs.follow export type FollowServerLogsRes = { startCursor: string @@ -210,30 +208,27 @@ export module RR { // package - export type GetPackagePropertiesReq = { id: string } // package.properties - export type GetPackagePropertiesRes = - PackagePropertiesVersioned - export type GetPackageLogsReq = ServerLogsReq & { id: string } // package.logs export type GetPackageLogsRes = LogsRes export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow export type FollowPackageLogsRes = FollowServerLogsRes - export type GetPackageMetricsReq = { id: string } // package.metrics - export type GetPackageMetricsRes = Metric - export type InstallPackageReq = T.InstallParams export type InstallPackageRes = null - export type GetPackageConfigReq = { id: string } // package.config.get - export type GetPackageConfigRes = { spec: CT.InputSpec; config: object } - - export type DrySetPackageConfigReq = { id: string; config: object } // package.config.set.dry - export type DrySetPackageConfigRes = T.PackageId[] + export type GetActionInputReq = { packageId: string; actionId: string } // package.action.get-input + export type GetActionInputRes = { + spec: IST.InputSpec + value: object | null + } - export type SetPackageConfigReq = DrySetPackageConfigReq // package.config.set - export type SetPackageConfigRes = null + export type ActionReq = { + packageId: string + actionId: string + input: object | null + } // package.action.run + export type ActionRes = (T.ActionResult & { version: '1' }) | null export type RestorePackagesReq = { // package.backup.restore @@ -244,13 +239,6 @@ export module RR { } export type RestorePackagesRes = null - export type ExecutePackageActionReq = { - id: string - actionId: string - input?: object - } // package.action - export type ExecutePackageActionRes = ActionResponse - export type StartPackageReq = { id: string } // package.start export type StartPackageRes = null @@ -260,19 +248,12 @@ export module RR { export type StopPackageReq = { id: string } // package.stop export type StopPackageRes = null + export type RebuildPackageReq = { id: string } // package.rebuild + export type RebuildPackageRes = null + export type UninstallPackageReq = { id: string } // package.uninstall export type UninstallPackageRes = null - export type DryConfigureDependencyReq = { - dependencyId: string - dependentId: string - } // package.dependency.configure.dry - export type DryConfigureDependencyRes = { - oldConfig: object - newConfig: object - spec: CT.InputSpec - } - export type SideloadPackageReq = { manifest: T.Manifest icon: string // base64 @@ -306,13 +287,6 @@ export interface TaggedDependencyError { error: DependencyError } -export interface ActionResponse { - message: string - value: string | null - copyable: boolean - qr: boolean -} - interface MetricData { value: string unit: string @@ -511,7 +485,7 @@ export type DependencyError = | DependencyErrorNotInstalled | DependencyErrorNotRunning | DependencyErrorIncorrectVersion - | DependencyErrorConfigUnsatisfied + | DependencyErrorActionRequired | DependencyErrorHealthChecksFailed | DependencyErrorTransitive @@ -529,8 +503,8 @@ export interface DependencyErrorIncorrectVersion { received: string // version } -export interface DependencyErrorConfigUnsatisfied { - type: 'configUnsatisfied' +export interface DependencyErrorActionRequired { + type: 'actionRequired' } export interface DependencyErrorHealthChecksFailed { diff --git a/web/projects/ui/src/app/services/api/embassy-api.service.ts b/web/projects/ui/src/app/services/api/embassy-api.service.ts index acd6780d1..280ac79f3 100644 --- a/web/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-api.service.ts @@ -12,7 +12,7 @@ export abstract class ApiService { // http // for sideloading packages - abstract uploadPackage(guid: string, body: Blob): Promise + abstract uploadPackage(guid: string, body: Blob): Promise // for getting static files: ex icons, instructions, licenses abstract getStaticProxy( @@ -29,7 +29,7 @@ export abstract class ApiService { abstract openWebsocket$( guid: string, - config: RR.WebsocketConfig, + config?: RR.WebsocketConfig, ): Observable // state @@ -113,10 +113,6 @@ export abstract class ApiService { params: RR.GetServerMetricsReq, ): Promise - abstract getPkgMetrics( - params: RR.GetPackageMetricsReq, - ): Promise - abstract updateServer(url?: string): Promise abstract restartServer( @@ -215,10 +211,6 @@ export abstract class ApiService { // package - abstract getPackageProperties( - params: RR.GetPackagePropertiesReq, - ): Promise['data']> - abstract getPackageLogs( params: RR.GetPackageLogsReq, ): Promise @@ -231,26 +223,16 @@ export abstract class ApiService { params: RR.InstallPackageReq, ): Promise - abstract getPackageConfig( - params: RR.GetPackageConfigReq, - ): Promise - - abstract drySetPackageConfig( - params: RR.DrySetPackageConfigReq, - ): Promise + abstract getActionInput( + params: RR.GetActionInputReq, + ): Promise - abstract setPackageConfig( - params: RR.SetPackageConfigReq, - ): Promise + abstract runAction(params: RR.ActionReq): Promise abstract restorePackages( params: RR.RestorePackagesReq, ): Promise - abstract executePackageAction( - params: RR.ExecutePackageActionReq, - ): Promise - abstract startPackage(params: RR.StartPackageReq): Promise abstract restartPackage( @@ -259,13 +241,13 @@ export abstract class ApiService { abstract stopPackage(params: RR.StopPackageReq): Promise + abstract rebuildPackage( + params: RR.RebuildPackageReq, + ): Promise + abstract uninstallPackage( params: RR.UninstallPackageReq, ): Promise - abstract dryConfigureDependency( - params: RR.DryConfigureDependencyReq, - ): Promise - abstract sideloadPackage(): Promise } diff --git a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts index 324936e8d..6241eaad7 100644 --- a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -10,7 +10,6 @@ import { import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source' import { ApiService } from './embassy-api.service' import { RR } from './api.types' -import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { ConfigService } from '../config.service' import { webSocket } from 'rxjs/webSocket' import { Observable, filter, firstValueFrom } from 'rxjs' @@ -43,12 +42,11 @@ export class LiveApiService extends ApiService { // for sideloading packages - async uploadPackage(guid: string, body: Blob): Promise { - return this.httpRequest({ + async uploadPackage(guid: string, body: Blob): Promise { + await this.httpRequest({ method: Method.POST, body, url: `/rest/rpc/${guid}`, - responseType: 'text', }) } @@ -86,7 +84,7 @@ export class LiveApiService extends ApiService { openWebsocket$( guid: string, - config: RR.WebsocketConfig, + config: RR.WebsocketConfig = {}, ): Observable { const { location } = this.document.defaultView! const protocol = location.protocol === 'http:' ? 'ws' : 'wss' @@ -437,14 +435,6 @@ export class LiveApiService extends ApiService { // package - async getPackageProperties( - params: RR.GetPackagePropertiesReq, - ): Promise['data']> { - return this.rpcRequest({ method: 'package.properties', params }).then( - parsePropertiesPermissive, - ) - } - async getPackageLogs( params: RR.GetPackageLogsReq, ): Promise { @@ -457,34 +447,20 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.logs.follow', params }) } - async getPkgMetrics( - params: RR.GetPackageMetricsReq, - ): Promise { - return this.rpcRequest({ method: 'package.metrics', params }) - } - async installPackage( params: RR.InstallPackageReq, ): Promise { return this.rpcRequest({ method: 'package.install', params }) } - async getPackageConfig( - params: RR.GetPackageConfigReq, - ): Promise { - return this.rpcRequest({ method: 'package.config.get', params }) - } - - async drySetPackageConfig( - params: RR.DrySetPackageConfigReq, - ): Promise { - return this.rpcRequest({ method: 'package.config.set.dry', params }) + async getActionInput( + params: RR.GetActionInputReq, + ): Promise { + return this.rpcRequest({ method: 'package.action.get-input', params }) } - async setPackageConfig( - params: RR.SetPackageConfigReq, - ): Promise { - return this.rpcRequest({ method: 'package.config.set', params }) + async runAction(params: RR.ActionReq): Promise { + return this.rpcRequest({ method: 'package.action.run', params }) } async restorePackages( @@ -493,12 +469,6 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.backup.restore', params }) } - async executePackageAction( - params: RR.ExecutePackageActionReq, - ): Promise { - return this.rpcRequest({ method: 'package.action', params }) - } - async startPackage(params: RR.StartPackageReq): Promise { return this.rpcRequest({ method: 'package.start', params }) } @@ -513,21 +483,18 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.stop', params }) } + async rebuildPackage( + params: RR.RebuildPackageReq, + ): Promise { + return this.rpcRequest({ method: 'package.rebuild', params }) + } + async uninstallPackage( params: RR.UninstallPackageReq, ): Promise { return this.rpcRequest({ method: 'package.uninstall', params }) } - async dryConfigureDependency( - params: RR.DryConfigureDependencyReq, - ): Promise { - return this.rpcRequest({ - method: 'package.dependency.configure.dry', - params, - }) - } - async sideloadPackage(): Promise { return this.rpcRequest({ method: 'package.sideload', diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index d44ef809a..93925cc73 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -2,10 +2,12 @@ import { Injectable } from '@angular/core' import { Log, RPCErrorDetails, RPCOptions, pauseFor } from '@start9labs/shared' import { ApiService } from './embassy-api.service' import { + AddOperation, Operation, PatchOp, pathFromArray, RemoveOperation, + ReplaceOperation, Revision, } from 'patch-db-client' import { @@ -15,7 +17,6 @@ import { UpdatingState, } from 'src/app/services/patch-db/data-model' import { CifsBackupTarget, RR } from './api.types' -import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { Mock } from './api.fixures' import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md' import { @@ -81,9 +82,8 @@ export class MockApiService extends ApiService { .subscribe() } - async uploadPackage(guid: string, body: Blob): Promise { + async uploadPackage(guid: string, body: Blob): Promise { await pauseFor(2000) - return 'success' } async getStaticProxy( @@ -106,7 +106,7 @@ export class MockApiService extends ApiService { openWebsocket$( guid: string, - config: RR.WebsocketConfig, + config: RR.WebsocketConfig = {}, ): Observable { if (guid === 'db-guid') { return this.mockWsSource$.pipe( @@ -125,6 +125,11 @@ export class MockApiService extends ApiService { return from(this.initProgress()).pipe( startWith(PROGRESS), ) as Observable + } else if (guid === 'sideload-progress-guid') { + config.openObserver?.next(new Event('')) + return from(this.initProgress()).pipe( + startWith(PROGRESS), + ) as Observable } else { throw new Error('invalid guid type') } @@ -362,13 +367,6 @@ export class MockApiService extends ApiService { return Mock.getServerMetrics() } - async getPkgMetrics( - params: RR.GetServerMetricsReq, - ): Promise { - await pauseFor(2000) - return Mock.getAppMetrics() - } - async updateServer(url?: string): Promise { await pauseFor(2000) const initialProgress = { @@ -632,14 +630,14 @@ export class MockApiService extends ApiService { async createBackup(params: RR.CreateBackupReq): Promise { await pauseFor(2000) - const path = '/serverInfo/statusInfo/backupProgress' + const serverPath = '/serverInfo/statusInfo/backupProgress' const ids = params.packageIds setTimeout(async () => { for (let i = 0; i < ids.length; i++) { const id = ids[i] - const appPath = `/packageData/${id}/status/main/status` - const appPatch = [ + const appPath = `/packageData/${id}/status/main/` + const appPatch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, path: appPath, @@ -656,40 +654,43 @@ export class MockApiService extends ApiService { value: 'stopped', }, ]) - this.mockRevision([ + + const serverPatch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: `${path}/${id}/complete`, + path: `${serverPath}/${id}/complete`, value: true, }, - ]) + ] + this.mockRevision(serverPatch) } await pauseFor(1000) - // set server back to running - const lastPatch = [ + // remove backupProgress + const lastPatch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path, + path: serverPath, value: null, }, ] this.mockRevision(lastPatch) }, 500) - const originalPatch = [ - { - op: PatchOp.REPLACE, - path, - value: ids.reduce((acc, val) => { - return { - ...acc, - [val]: { complete: false }, - } - }, {}), - }, - ] + const originalPatch: ReplaceOperation[] = + [ + { + op: PatchOp.REPLACE, + path: serverPath, + value: ids.reduce((acc, val) => { + return { + ...acc, + [val]: { complete: false }, + } + }, {}), + }, + ] this.mockRevision(originalPatch) @@ -698,13 +699,6 @@ export class MockApiService extends ApiService { // package - async getPackageProperties( - params: RR.GetPackagePropertiesReq, - ): Promise['data']> { - await pauseFor(2000) - return parsePropertiesPermissive(Mock.PackageProperties) - } - async getPackageLogs( params: RR.GetPackageLogsReq, ): Promise { @@ -746,7 +740,7 @@ export class MockApiService extends ApiService { this.installProgress(params.id) }, 1000) - const patch: Operation< + const patch: AddOperation< PackageDataEntry >[] = [ { @@ -776,44 +770,43 @@ export class MockApiService extends ApiService { return null } - async getPackageConfig( - params: RR.GetPackageConfigReq, - ): Promise { + async getActionInput( + params: RR.GetActionInputReq, + ): Promise { await pauseFor(2000) return { - config: Mock.MockConfig, - spec: await Mock.getInputSpec(), + value: Mock.MockConfig, + spec: await Mock.getActionInputSpec(), } } - async drySetPackageConfig( - params: RR.DrySetPackageConfigReq, - ): Promise { + async runAction(params: RR.ActionReq): Promise { await pauseFor(2000) - return [] - } - - async setPackageConfig( - params: RR.SetPackageConfigReq, - ): Promise { - await pauseFor(2000) - const patch = [ - { - op: PatchOp.REPLACE, - path: `/packageData/${params.id}/status/configured`, - value: true, - }, - ] - this.mockRevision(patch) - return null + if (params.actionId === 'properties') { + // return Mock.ActionResGroup + return Mock.ActionResMessage + // return Mock.ActionResSingle + } else if (params.actionId === 'config') { + const patch: RemoveOperation[] = [ + { + op: PatchOp.REMOVE, + path: `/packageData/${params.packageId}/requestedActions/${params.packageId}-config`, + }, + ] + this.mockRevision(patch) + return null + } else { + return Mock.ActionResMessage + // return Mock.ActionResSingle + } } async restorePackages( params: RR.RestorePackagesReq, ): Promise { await pauseFor(2000) - const patch: Operation[] = params.ids.map(id => { + const patch: AddOperation[] = params.ids.map(id => { setTimeout(async () => { this.installProgress(id) }, 2000) @@ -839,84 +832,62 @@ export class MockApiService extends ApiService { return null } - async executePackageAction( - params: RR.ExecutePackageActionReq, - ): Promise { - await pauseFor(2000) - return Mock.ActionResponse - } - async startPackage(params: RR.StartPackageReq): Promise { - const path = `/packageData/${params.id}/status/main` + const path = `/packageData/${params.id}/status` await pauseFor(2000) setTimeout(async () => { - const patch2 = [ - { - op: PatchOp.REPLACE, - path: path + '/status', - value: 'running', - }, - { - op: PatchOp.REPLACE, - path: path + '/started', - value: new Date().toISOString(), - }, - ] - this.mockRevision(patch2) - - const patch3 = [ - { - op: PatchOp.REPLACE, - path: path + '/health', - value: { - 'ephemeral-health-check': { - result: 'starting', - }, - 'unnecessary-health-check': { - result: 'disabled', - }, - }, - }, - ] - this.mockRevision(patch3) - - await pauseFor(2000) - - const patch4 = [ + const patch2: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: path + '/health', + path, value: { - 'ephemeral-health-check': { - result: 'starting', - }, - 'unnecessary-health-check': { - result: 'disabled', - }, - 'chain-state': { - result: 'loading', - message: 'Bitcoin is syncing from genesis', - }, - 'p2p-interface': { - result: 'success', - }, - 'rpc-interface': { - result: 'failure', - error: 'RPC interface unreachable.', + main: 'running', + started: new Date().toISOString(), + health: { + 'ephemeral-health-check': { + name: 'Ephemeral Health Check', + result: 'starting', + message: null, + }, + 'unnecessary-health-check': { + name: 'Unnecessary Health Check', + result: 'disabled', + message: 'Custom disabled message', + }, + 'chain-state': { + name: 'Chain State', + result: 'loading', + message: 'Bitcoin is syncing from genesis', + }, + 'p2p-interface': { + name: 'P2P Interface', + result: 'success', + message: null, + }, + 'rpc-interface': { + name: 'RPC Interface', + result: 'failure', + message: 'Custom failure message', + }, }, }, }, ] - this.mockRevision(patch4) + this.mockRevision(patch2) }, 2000) - const originalPatch = [ + const originalPatch: ReplaceOperation< + T.MainStatus & { main: 'starting' } + >[] = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: 'starting', + path, + value: { + main: 'starting', + health: {}, + }, }, ] @@ -928,74 +899,57 @@ export class MockApiService extends ApiService { async restartPackage( params: RR.RestartPackageReq, ): Promise { - // first enact stop await pauseFor(2000) - const path = `/packageData/${params.id}/status/main` + const path = `/packageData/${params.id}/status` setTimeout(async () => { - const patch2: Operation[] = [ - { - op: PatchOp.REPLACE, - path: path + '/status', - value: 'starting', - }, - { - op: PatchOp.ADD, - path: path + '/restarting', - value: true, - }, - ] - this.mockRevision(patch2) - - await pauseFor(2000) - - const patch3: Operation[] = [ - { - op: PatchOp.REPLACE, - path: path + '/status', - value: 'running', - }, - { - op: PatchOp.REMOVE, - path: path + '/restarting', - }, + const patch2: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: path + '/health', + path, value: { - 'ephemeral-health-check': { - result: 'starting', - }, - 'unnecessary-health-check': { - result: 'disabled', - }, - 'chain-state': { - result: 'loading', - message: 'Bitcoin is syncing from genesis', - }, - 'p2p-interface': { - result: 'success', - }, - 'rpc-interface': { - result: 'failure', - error: 'RPC interface unreachable.', + main: 'running', + started: new Date().toISOString(), + health: { + 'ephemeral-health-check': { + name: 'Ephemeral Health Check', + result: 'starting', + message: null, + }, + 'unnecessary-health-check': { + name: 'Unnecessary Health Check', + result: 'disabled', + message: 'Custom disabled message', + }, + 'chain-state': { + name: 'Chain State', + result: 'loading', + message: 'Bitcoin is syncing from genesis', + }, + 'p2p-interface': { + name: 'P2P Interface', + result: 'success', + message: null, + }, + 'rpc-interface': { + name: 'RPC Interface', + result: 'failure', + message: 'Custom failure message', + }, }, }, - } as any, + }, ] - this.mockRevision(patch3) + this.mockRevision(patch2) }, this.revertTime) - const patch = [ - { - op: PatchOp.REPLACE, - path: path + '/status', - value: 'restarting', - }, + const patch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: path + '/health', - value: {}, + path, + value: { + main: 'restarting', + }, }, ] @@ -1006,29 +960,24 @@ export class MockApiService extends ApiService { async stopPackage(params: RR.StopPackageReq): Promise { await pauseFor(2000) - const path = `/packageData/${params.id}/status/main` + const path = `/packageData/${params.id}/status` setTimeout(() => { - const patch2 = [ + const patch2: ReplaceOperation[] = [ { op: PatchOp.REPLACE, path: path, - value: { - status: 'stopped', - }, + value: { main: 'stopped' }, }, ] this.mockRevision(patch2) }, this.revertTime) - const patch = [ + const patch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, path: path, - value: { - status: 'stopping', - timeout: '35s', - }, + value: { main: 'stopping' }, }, ] @@ -1037,6 +986,12 @@ export class MockApiService extends ApiService { return null } + async rebuildPackage( + params: RR.RebuildPackageReq, + ): Promise { + return this.restartPackage(params) + } + async uninstallPackage( params: RR.UninstallPackageReq, ): Promise { @@ -1052,7 +1007,7 @@ export class MockApiService extends ApiService { this.mockRevision(patch2) }, this.revertTime) - const patch = [ + const patch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, path: `/packageData/${params.id}/stateInfo/state`, @@ -1065,22 +1020,11 @@ export class MockApiService extends ApiService { return null } - async dryConfigureDependency( - params: RR.DryConfigureDependencyReq, - ): Promise { - await pauseFor(2000) - return { - oldConfig: Mock.MockConfig, - newConfig: Mock.MockDependencyConfig, - spec: await Mock.getInputSpec(), - } - } - async sideloadPackage(): Promise { await pauseFor(2000) return { - upload: '4120e092-05ab-4de2-9fbd-c3f1f4b1df9e', // no significance, randomly generated - progress: '5120e092-05ab-4de2-9fbd-c3f1f4b1df9e', // no significance, randomly generated + upload: 'sideload-upload-guid', // no significance, randomly generated + progress: 'sideload-progress-guid', // no significance, randomly generated } } diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index ba2966b7f..aea6b5828 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -1,6 +1,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model' import { Mock } from './api.fixures' import { BUILT_IN_WIDGETS } from '../../pages/widgets/built-in/widgets' +const version = require('../../../../../../package.json').version export const mockPatchData: DataModel = { ui: { @@ -37,7 +38,7 @@ export const mockPatchData: DataModel = { arch: 'x86_64', onionAddress: 'myveryownspecialtoraddress', id: 'abcdefgh', - version: '0.3.6', + version, lastBackup: new Date(new Date().valueOf() - 604800001).toISOString(), lanAddress: 'https://adjective-noun.local', torAddress: 'https://myveryownspecialtoraddress.onion', @@ -55,11 +56,13 @@ export const mockPatchData: DataModel = { ipv6Range: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD/64', }, }, + acme: null, unreadNotificationCount: 4, // password is asdfasdf passwordHash: '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ', - eosVersionCompat: '>=0.3.0 <=0.3.6', + packageVersionCompat: '>=0.3.0 <=0.3.6', + postInitMigrationTodos: [], statusInfo: { backupProgress: null, updated: false, @@ -81,6 +84,8 @@ export const mockPatchData: DataModel = { selected: null, lastRegion: null, }, + ram: 8 * 1024 * 1024 * 1024, + devices: [], }, packageData: { bitcoind: { @@ -95,40 +100,44 @@ export const mockPatchData: DataModel = { icon: '/assets/img/service-icons/bitcoind.svg', lastBackup: null, status: { - configured: true, - main: { - status: 'running', - started: '2021-06-14T20:49:17.774Z', - health: { - 'ephemeral-health-check': { - name: 'Ephemeral Health Check', - result: 'starting', - message: null, - }, - 'chain-state': { - name: 'Chain State', - result: 'loading', - message: 'Bitcoin is syncing from genesis', - }, - 'p2p-interface': { - name: 'P2P', - result: 'success', - message: 'Health check successful', - }, - 'rpc-interface': { - name: 'RPC', - result: 'failure', - message: 'RPC interface unreachable.', - }, - 'unnecessary-health-check': { - name: 'Unnecessary Health Check', - result: 'disabled', - message: null, - }, - }, + main: 'stopped', + }, + // status: { + // main: 'error', + // message: 'Bitcoin is erroring out', + // debug: 'This is a complete stack trace for bitcoin', + // onRebuild: 'start', + // }, + actions: { + config: { + name: 'Set Config', + description: 'edit bitcoin.conf', + warning: null, + visibility: 'enabled', + allowedStatuses: 'any', + hasInput: true, + group: null, + }, + properties: { + name: 'View Properties', + description: 'view important information about Bitcoin', + warning: null, + visibility: 'enabled', + allowedStatuses: 'any', + hasInput: false, + group: null, + }, + test: { + name: 'Do Another Thing', + description: + 'An example of an action that shows a warning and takes no input', + warning: 'careful running this action', + visibility: { disabled: 'This is temporarily disabled' }, + allowedStatuses: 'only-running', + hasInput: false, + group: null, }, }, - actions: {}, serviceInterfaces: { ui: { id: 'ui', @@ -287,6 +296,27 @@ export const mockPatchData: DataModel = { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: { + 'bitcoind-config': { + request: { + packageId: 'bitcoind', + actionId: 'config', + severity: 'critical', + reason: + 'You must run Config before starting Bitcoin for the first time', + }, + active: true, + }, + 'bitcoind-properties': { + request: { + packageId: 'bitcoind', + actionId: 'properties', + severity: 'important', + reason: 'Check out all the info about your Bitcoin node', + }, + active: true, + }, + }, }, lnd: { stateInfo: { @@ -300,10 +330,7 @@ export const mockPatchData: DataModel = { icon: '/assets/img/service-icons/lnd.png', lastBackup: null, status: { - configured: true, - main: { - status: 'stopped', - }, + main: 'stopped', }, actions: {}, serviceInterfaces: { @@ -366,7 +393,6 @@ export const mockPatchData: DataModel = { kind: 'running', versionRange: '>=26.0.0', healthChecks: [], - configSatisfied: true, }, 'btc-rpc-proxy': { title: 'Bitcoin Proxy', @@ -374,13 +400,33 @@ export const mockPatchData: DataModel = { kind: 'running', versionRange: '>2.0.0', healthChecks: [], - configSatisfied: false, }, }, hosts: {}, storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: { + 'bitcoind/config': { + active: true, + request: { + packageId: 'bitcoind', + actionId: 'config', + severity: 'critical', + reason: 'LND likes BTC a certain way', + input: { + kind: 'partial', + value: { + color: '#ffffff', + rpcsettings: { + rpcuser: 'lnd', + }, + testnet: false, + }, + }, + }, + }, + }, }, }, } diff --git a/web/projects/ui/src/app/services/config.service.ts b/web/projects/ui/src/app/services/config.service.ts index 69fafe815..c6d089dbd 100644 --- a/web/projects/ui/src/app/services/config.service.ts +++ b/web/projects/ui/src/app/services/config.service.ts @@ -51,7 +51,7 @@ export class ConfigService { isLaunchable( state: T.PackageState['state'], - status: T.MainStatus['status'], + status: T.MainStatus['main'], ): boolean { return state === 'installed' && status === 'running' } diff --git a/web/projects/ui/src/app/services/dep-error.service.ts b/web/projects/ui/src/app/services/dep-error.service.ts index a46a5123e..cd15ebff6 100644 --- a/web/projects/ui/src/app/services/dep-error.service.ts +++ b/web/projects/ui/src/app/services/dep-error.service.ts @@ -101,14 +101,21 @@ export class DepErrorService { } } - // invalid config - if (!currentDep.configSatisfied) { + // action required + if ( + Object.values(pkg.requestedActions).some( + a => + a.active && + a.request.packageId === depId && + a.request.severity === 'critical', + ) + ) { return { - type: 'configUnsatisfied', + type: 'actionRequired', } } - const depStatus = dep.status.main.status + const depStatus = dep.status.main // not running if (depStatus !== 'running' && depStatus !== 'starting') { @@ -120,7 +127,7 @@ export class DepErrorService { // health check failure if (depStatus === 'running' && currentDep.kind === 'running') { for (let id of currentDep.healthChecks) { - const check = dep.status.main.health[id] + const check = dep.status.health[id] if (check?.result !== 'success') { return { type: 'healthChecksFailed', diff --git a/web/projects/ui/src/app/services/eos.service.ts b/web/projects/ui/src/app/services/eos.service.ts index c61c667e3..5c1b2c5ee 100644 --- a/web/projects/ui/src/app/services/eos.service.ts +++ b/web/projects/ui/src/app/services/eos.service.ts @@ -6,7 +6,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service' import { PatchDB } from 'patch-db-client' import { getServerInfo } from 'src/app/util/get-server-info' import { DataModel } from './patch-db/data-model' -import { Exver } from '@start9labs/shared' +import { Version } from '@start9labs/start-sdk' @Injectable({ providedIn: 'root', @@ -48,14 +48,14 @@ export class EOSService { constructor( private readonly api: ApiService, private readonly patch: PatchDB, - private readonly exver: Exver, ) {} async loadEos(): Promise { const { version, id } = await getServerInfo(this.patch) this.osUpdate = await this.api.checkOSUpdate({ serverId: id }) const updateAvailable = - this.exver.compareOsVersion(this.osUpdate.version, version) === 'greater' + Version.parse(this.osUpdate.version).compare(Version.parse(version)) === + 'greater' this.updateAvailable$.next(updateAvailable) } } diff --git a/web/projects/ui/src/app/services/form.service.ts b/web/projects/ui/src/app/services/form.service.ts index 779fb1f53..7622cd05a 100644 --- a/web/projects/ui/src/app/services/form.service.ts +++ b/web/projects/ui/src/app/services/form.service.ts @@ -7,7 +7,7 @@ import { ValidatorFn, Validators, } from '@angular/forms' -import { CT, utils } from '@start9labs/start-sdk' +import { IST, utils } from '@start9labs/start-sdk' const Mustache = require('mustache') @Injectable({ @@ -17,16 +17,16 @@ export class FormService { constructor(private readonly formBuilder: UntypedFormBuilder) {} createForm( - spec: CT.InputSpec, + spec: IST.InputSpec, current: Record = {}, ): UntypedFormGroup { return this.getFormGroup(spec, [], current) } getUnionSelectSpec( - spec: CT.ValueSpecUnion, + spec: IST.ValueSpecUnion, selection: string | null, - ): CT.ValueSpecSelect { + ): IST.ValueSpecSelect { return { ...spec, type: 'select', @@ -38,7 +38,7 @@ export class FormService { } getUnionObject( - spec: CT.ValueSpecUnion, + spec: IST.ValueSpecUnion, selected: string | null, ): UntypedFormGroup { const group = this.getFormGroup({ @@ -53,16 +53,16 @@ export class FormService { return group } - getListItem(spec: CT.ValueSpecList, entry?: any) { - if (CT.isValueSpecListOf(spec, 'text')) { + getListItem(spec: IST.ValueSpecList, entry?: any) { + if (IST.isValueSpecListOf(spec, 'text')) { return this.formBuilder.control(entry, stringValidators(spec.spec)) - } else if (CT.isValueSpecListOf(spec, 'object')) { + } else if (IST.isValueSpecListOf(spec, 'object')) { return this.getFormGroup(spec.spec.spec, [], entry) } } getFormGroup( - config: CT.InputSpec, + config: IST.InputSpec, validators: ValidatorFn[] = [], current?: Record | null, ): UntypedFormGroup { @@ -77,7 +77,7 @@ export class FormService { } private getFormEntry( - spec: CT.ValueSpec, + spec: IST.ValueSpec, currentValue?: any, ): UntypedFormGroup | UntypedFormArray | UntypedFormControl { let value: any @@ -135,7 +135,7 @@ export class FormService { return this.formBuilder.control(value) case 'select': value = currentValue === undefined ? spec.default : currentValue - return this.formBuilder.control(value, selectValidators(spec)) + return this.formBuilder.control(value) case 'multiselect': value = currentValue === undefined ? spec.default : currentValue return this.formBuilder.control(value, multiselectValidators(spec)) @@ -145,18 +145,18 @@ export class FormService { } } -// function getListItemValidators(spec: CT.ValueSpecList) { -// if (CT.isValueSpecListOf(spec, 'text')) { +// function getListItemValidators(spec: IST.ValueSpecList) { +// if (IST.isValueSpecListOf(spec, 'text')) { // return stringValidators(spec.spec) // } // } function stringValidators( - spec: CT.ValueSpecText | CT.ListValueSpecText, + spec: IST.ValueSpecText | IST.ListValueSpecText, ): ValidatorFn[] { const validators: ValidatorFn[] = [] - if ((spec as CT.ValueSpecText).required) { + if ((spec as IST.ValueSpecText).required) { validators.push(Validators.required) } @@ -169,7 +169,7 @@ function stringValidators( return validators } -function textareaValidators(spec: CT.ValueSpecTextarea): ValidatorFn[] { +function textareaValidators(spec: IST.ValueSpecTextarea): ValidatorFn[] { const validators: ValidatorFn[] = [] if (spec.required) { @@ -181,7 +181,7 @@ function textareaValidators(spec: CT.ValueSpecTextarea): ValidatorFn[] { return validators } -function colorValidators({ required }: CT.ValueSpecColor): ValidatorFn[] { +function colorValidators({ required }: IST.ValueSpecColor): ValidatorFn[] { const validators: ValidatorFn[] = [Validators.pattern(/^#[0-9a-f]{6}$/i)] if (required) { @@ -195,7 +195,7 @@ function datetimeValidators({ required, min, max, -}: CT.ValueSpecDatetime): ValidatorFn[] { +}: IST.ValueSpecDatetime): ValidatorFn[] { const validators: ValidatorFn[] = [] if (required) { @@ -213,12 +213,12 @@ function datetimeValidators({ return validators } -function numberValidators(spec: CT.ValueSpecNumber): ValidatorFn[] { +function numberValidators(spec: IST.ValueSpecNumber): ValidatorFn[] { const validators: ValidatorFn[] = [] validators.push(isNumber()) - if ((spec as CT.ValueSpecNumber).required) { + if ((spec as IST.ValueSpecNumber).required) { validators.push(Validators.required) } @@ -231,23 +231,13 @@ function numberValidators(spec: CT.ValueSpecNumber): ValidatorFn[] { return validators } -function selectValidators(spec: CT.ValueSpecSelect): ValidatorFn[] { - const validators: ValidatorFn[] = [] - - if (spec.required) { - validators.push(Validators.required) - } - - return validators -} - -function multiselectValidators(spec: CT.ValueSpecMultiselect): ValidatorFn[] { +function multiselectValidators(spec: IST.ValueSpecMultiselect): ValidatorFn[] { const validators: ValidatorFn[] = [] validators.push(listInRange(spec.minLength, spec.maxLength)) return validators } -function listValidators(spec: CT.ValueSpecList): ValidatorFn[] { +function listValidators(spec: IST.ValueSpecList): ValidatorFn[] { const validators: ValidatorFn[] = [] validators.push(listInRange(spec.minLength, spec.maxLength)) validators.push(listItemIssue()) @@ -352,7 +342,7 @@ export function listItemIssue(): ValidatorFn { } } -export function listUnique(spec: CT.ValueSpecList): ValidatorFn { +export function listUnique(spec: IST.ValueSpecList): ValidatorFn { return control => { const list = control.value for (let idx = 0; idx < list.length; idx++) { @@ -389,7 +379,11 @@ export function listUnique(spec: CT.ValueSpecList): ValidatorFn { } } -function listItemEquals(spec: CT.ValueSpecList, val1: any, val2: any): boolean { +function listItemEquals( + spec: IST.ValueSpecList, + val1: any, + val2: any, +): boolean { // TODO: fix types switch (spec.spec.type) { case 'text': @@ -402,7 +396,7 @@ function listItemEquals(spec: CT.ValueSpecList, val1: any, val2: any): boolean { } } -function itemEquals(spec: CT.ValueSpec, val1: any, val2: any): boolean { +function itemEquals(spec: IST.ValueSpec, val1: any, val2: any): boolean { switch (spec.type) { case 'text': case 'textarea': @@ -414,15 +408,15 @@ function itemEquals(spec: CT.ValueSpec, val1: any, val2: any): boolean { // TODO: 'unique-by' does not exist on ValueSpecObject, fix types return objEquals( (spec as any)['unique-by'], - spec as CT.ValueSpecObject, + spec as IST.ValueSpecObject, val1, val2, ) case 'union': - // TODO: 'unique-by' does not exist on CT.ValueSpecUnion, fix types + // TODO: 'unique-by' does not exist onIST.ValueSpecUnion, fix types return unionEquals( (spec as any)['unique-by'], - spec as CT.ValueSpecUnion, + spec as IST.ValueSpecUnion, val1, val2, ) @@ -442,8 +436,8 @@ function itemEquals(spec: CT.ValueSpec, val1: any, val2: any): boolean { } function listObjEquals( - uniqueBy: CT.UniqueBy, - spec: CT.ListValueSpecObject, + uniqueBy: IST.UniqueBy, + spec: IST.ListValueSpecObject, val1: any, val2: any, ): boolean { @@ -470,8 +464,8 @@ function listObjEquals( } function objEquals( - uniqueBy: CT.UniqueBy, - spec: CT.ValueSpecObject, + uniqueBy: IST.UniqueBy, + spec: IST.ValueSpecObject, val1: any, val2: any, ): boolean { @@ -499,8 +493,8 @@ function objEquals( } function unionEquals( - uniqueBy: CT.UniqueBy, - spec: CT.ValueSpecUnion, + uniqueBy: IST.UniqueBy, + spec: IST.ValueSpecUnion, val1: any, val2: any, ): boolean { @@ -532,8 +526,8 @@ function unionEquals( } function uniqueByMessageWrapper( - uniqueBy: CT.UniqueBy, - spec: CT.ListValueSpecObject, + uniqueBy: IST.UniqueBy, + spec: IST.ListValueSpecObject, ) { let configSpec = spec.spec @@ -544,8 +538,8 @@ function uniqueByMessageWrapper( } function uniqueByMessage( - uniqueBy: CT.UniqueBy, - configSpec: CT.InputSpec, + uniqueBy: IST.UniqueBy, + configSpec: IST.InputSpec, outermost = true, ): string { let joinFunc @@ -554,7 +548,7 @@ function uniqueByMessage( return '' } else if (typeof uniqueBy === 'string') { return configSpec[uniqueBy] - ? (configSpec[uniqueBy] as CT.ValueSpecObject).name + ? (configSpec[uniqueBy] as IST.ValueSpecObject).name : uniqueBy } else if ('any' in uniqueBy) { joinFunc = ' OR ' @@ -574,14 +568,14 @@ function uniqueByMessage( } function isObject( - spec: CT.ListValueSpecOf, -): spec is CT.ListValueSpecObject { + spec: IST.ListValueSpecOf, +): spec is IST.ListValueSpecObject { // only lists of objects have uniqueBy return 'uniqueBy' in spec } export function convertValuesRecursive( - configSpec: CT.InputSpec, + configSpec: IST.InputSpec, group: UntypedFormGroup, ) { Object.entries(configSpec).forEach(([key, valueSpec]) => { @@ -611,7 +605,7 @@ export function convertValuesRecursive( }) } else if (valueSpec.spec.type === 'object') { controls.forEach(formGroup => { - const objectSpec = valueSpec.spec as CT.ListValueSpecObject + const objectSpec = valueSpec.spec as IST.ListValueSpecObject convertValuesRecursive(objectSpec.spec, formGroup as UntypedFormGroup) }) } diff --git a/web/projects/ui/src/app/services/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts index bef380aea..a080f051d 100644 --- a/web/projects/ui/src/app/services/marketplace.service.ts +++ b/web/projects/ui/src/app/services/marketplace.service.ts @@ -81,31 +81,35 @@ export class MarketplaceService implements AbstractMarketplaceService { shareReplay({ bufferSize: 1, refCount: true }), ) - private readonly marketplace$ = this.knownHosts$.pipe( - startWith([]), - pairwise(), - mergeMap(([prev, curr]) => - curr.filter(c => !prev.find(p => sameUrl(c.url, p.url))), - ), - mergeMap(({ url, name }) => - this.fetchStore$(url).pipe( - tap(data => { - if (data?.info.name) this.updateStoreName(url, name, data.info.name) - }), - map(data => [url, data]), - startWith<[string, StoreData | null]>([url, null]), + private readonly marketplace$: Observable = + this.knownHosts$.pipe( + startWith([]), + pairwise(), + mergeMap(([prev, curr]) => + curr.filter(c => !prev.find(p => sameUrl(c.url, p.url))), ), - ), - scan<[string, StoreData | null], Record>( - (requests, [url, store]) => { - requests[url] = store + mergeMap(({ url, name }) => + this.fetchStore$(url).pipe( + tap(data => { + if (data?.info.name) this.updateStoreName(url, name, data.info.name) + }), + map(data => [ + url, + data, + ]), + startWith<[string, StoreData | null]>([url, null]), + ), + ), + scan<[string, StoreData | null], Record>( + (requests, [url, store]) => { + requests[url] = store - return requests - }, - {}, - ), - shareReplay({ bufferSize: 1, refCount: true }), - ) + return requests + }, + {}, + ), + shareReplay({ bufferSize: 1, refCount: true }), + ) private readonly filteredMarketplace$ = combineLatest([ this.clientStorageService.showDevTools$, @@ -285,7 +289,12 @@ export class MarketplaceService implements AbstractMarketplaceService { this.api.getRegistryPackage(url, id, version ? `=${version}` : null), ).pipe( map(pkgInfo => - this.convertToMarketplacePkg(id, version, flavor, pkgInfo), + this.convertToMarketplacePkg( + id, + version === '*' ? null : version, + flavor, + pkgInfo, + ), ), ) } diff --git a/web/projects/ui/src/app/services/patch-db/patch-db-source.ts b/web/projects/ui/src/app/services/patch-db/patch-db-source.ts index 00ab3d43e..5d1f1ed51 100644 --- a/web/projects/ui/src/app/services/patch-db/patch-db-source.ts +++ b/web/projects/ui/src/app/services/patch-db/patch-db-source.ts @@ -33,7 +33,7 @@ export class PatchDbSource extends Observable[]> { private readonly stream$ = inject(AuthService).isVerified$.pipe( switchMap(verified => (verified ? this.api.subscribeToPatchDB({}) : EMPTY)), switchMap(({ dump, guid }) => - this.api.openWebsocket$(guid, {}).pipe( + this.api.openWebsocket$(guid).pipe( bufferTime(250), filter(revisions => !!revisions.length), startWith([dump]), diff --git a/web/projects/ui/src/app/services/pkg-status-rendering.service.ts b/web/projects/ui/src/app/services/pkg-status-rendering.service.ts index d73a9afe1..44543b15c 100644 --- a/web/projects/ui/src/app/services/pkg-status-rendering.service.ts +++ b/web/projects/ui/src/app/services/pkg-status-rendering.service.ts @@ -17,7 +17,7 @@ export function renderPkgStatus( let health: T.HealthStatus | null = null if (pkg.stateInfo.state === 'installed') { - primary = getInstalledPrimaryStatus(pkg.status) + primary = getInstalledPrimaryStatus(pkg) dependency = getDependencyStatus(depErrors) health = getHealthStatus(pkg.status) } else { @@ -27,11 +27,15 @@ export function renderPkgStatus( return { primary, dependency, health } } -function getInstalledPrimaryStatus(status: T.Status): PrimaryStatus { - if (!status.configured) { - return 'needsConfig' +function getInstalledPrimaryStatus(pkg: T.PackageDataEntry): PrimaryStatus { + if ( + Object.values(pkg.requestedActions).some( + r => r.active && r.request.severity === 'critical', + ) + ) { + return 'actionRequired' } else { - return status.main.status as any as PrimaryStatus + return pkg.status.main } } @@ -39,12 +43,12 @@ function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus { return Object.values(depErrors).some(err => !!err) ? 'warning' : 'satisfied' } -function getHealthStatus(status: T.Status): T.HealthStatus | null { - if (status.main.status !== 'running' || !status.main.health) { +function getHealthStatus(status: T.MainStatus): T.HealthStatus | null { + if (status.main !== 'running' || !status.main) { return null } - const values = Object.values(status.main.health) + const values = Object.values(status.health) if (values.some(h => h.result === 'failure')) { return 'failure' @@ -78,7 +82,8 @@ export type PrimaryStatus = | 'restarting' | 'stopped' | 'backingUp' - | 'needsConfig' + | 'actionRequired' + | 'error' export type DependencyStatus = 'warning' | 'satisfied' @@ -133,11 +138,16 @@ export const PrimaryRendering: Record = { color: 'success', showDots: false, }, - needsConfig: { - display: 'Needs Config', + actionRequired: { + display: 'Action Required', color: 'warning', showDots: false, }, + error: { + display: 'Service Launch Error', + color: 'danger', + showDots: false, + }, } export const DependencyRendering: Record = { diff --git a/web/projects/ui/src/app/services/standard-actions.service.ts b/web/projects/ui/src/app/services/standard-actions.service.ts new file mode 100644 index 000000000..664db822c --- /dev/null +++ b/web/projects/ui/src/app/services/standard-actions.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@angular/core' +import { T } from '@start9labs/start-sdk' +import { hasCurrentDeps } from '../util/has-deps' +import { getAllPackages } from '../util/get-package-data' +import { PatchDB } from 'patch-db-client' +import { DataModel } from './patch-db/data-model' +import { AlertController, NavController } from '@ionic/angular' +import { ApiService } from './api/embassy-api.service' +import { ErrorService, LoadingService } from '@start9labs/shared' + +@Injectable({ + providedIn: 'root', +}) +export class StandardActionsService { + constructor( + private readonly patch: PatchDB, + private readonly api: ApiService, + private readonly alertCtrl: AlertController, + private readonly errorService: ErrorService, + private readonly loader: LoadingService, + private readonly navCtrl: NavController, + ) {} + + async rebuild(id: string) { + const loader = this.loader.open(`Rebuilding Container...`).subscribe() + + try { + await this.api.rebuildPackage({ id }) + this.navCtrl.navigateBack('/services/' + id) + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + } + + async tryUninstall(manifest: T.Manifest): Promise { + const { id, title, alerts } = manifest + + let message = + alerts.uninstall || + `Uninstalling ${title} will permanently delete its data` + + if (hasCurrentDeps(id, await getAllPackages(this.patch))) { + message = `${message}. Services that depend on ${title} will no longer work properly and may crash` + } + + const alert = await this.alertCtrl.create({ + header: 'Warning', + message, + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Uninstall', + handler: () => { + this.uninstall(id) + }, + cssClass: 'enter-click', + }, + ], + cssClass: 'alert-warning-message', + }) + + await alert.present() + } + + private async uninstall(id: string) { + const loader = this.loader.open(`Beginning uninstall...`).subscribe() + + try { + await this.api.uninstallPackage({ id }) + this.api + .setDbValue(['ackInstructions', id], false) + .catch(e => console.error('Failed to mark instructions as unseen', e)) + this.navCtrl.navigateRoot('/services') + } catch (e: any) { + this.errorService.handleError(e) + } finally { + loader.unsubscribe() + } + } +} diff --git a/web/projects/ui/src/app/util/configBuilderToSpec.ts b/web/projects/ui/src/app/util/configBuilderToSpec.ts index 1f75329c5..108bf9468 100644 --- a/web/projects/ui/src/app/util/configBuilderToSpec.ts +++ b/web/projects/ui/src/app/util/configBuilderToSpec.ts @@ -1,9 +1,9 @@ -import { CB } from '@start9labs/start-sdk' +import { ISB } from '@start9labs/start-sdk' export async function configBuilderToSpec( builder: - | CB.Config, unknown> - | CB.Config, never>, + | ISB.InputSpec, unknown> + | ISB.InputSpec, never>, ) { return builder.build({} as any) } diff --git a/web/projects/ui/src/app/util/dep-info.ts b/web/projects/ui/src/app/util/dep-info.ts new file mode 100644 index 000000000..cd98c5d4b --- /dev/null +++ b/web/projects/ui/src/app/util/dep-info.ts @@ -0,0 +1,30 @@ +import { + AllPackageData, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' + +export function getDepDetails( + pkg: PackageDataEntry, + allPkgs: AllPackageData, + depId: string, +) { + const { title, icon, versionRange } = pkg.currentDependencies[depId] + + if ( + allPkgs[depId] && + (allPkgs[depId].stateInfo.state === 'installed' || + allPkgs[depId].stateInfo.state === 'updating') + ) { + return { + title: allPkgs[depId].stateInfo.manifest!.title, + icon: allPkgs[depId].icon, + versionRange, + } + } else { + return { + title: title || depId, + icon: icon || 'assets/img/service-icons/fallback.png', + versionRange, + } + } +} diff --git a/web/projects/ui/src/app/util/get-package-info.ts b/web/projects/ui/src/app/util/get-package-info.ts index 3e506ea61..b21b42a37 100644 --- a/web/projects/ui/src/app/util/get-package-info.ts +++ b/web/projects/ui/src/app/util/get-package-info.ts @@ -20,7 +20,7 @@ export function getPackageInfo( primaryRendering, primaryStatus: statuses.primary, error: statuses.health === 'failure' || statuses.dependency === 'warning', - warning: statuses.primary === 'needsConfig', + warning: statuses.primary === 'actionRequired', transitioning: primaryRendering.showDots || statuses.health === 'loading' || diff --git a/web/projects/ui/src/app/util/properties.util.ts b/web/projects/ui/src/app/util/properties.util.ts deleted file mode 100644 index 5afb4bd8b..000000000 --- a/web/projects/ui/src/app/util/properties.util.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { applyOperation } from 'fast-json-patch' -import matches, { - Parser, - shape, - string, - literal, - boolean, - deferred, - dictionary, - anyOf, - number, - arrayOf, -} from 'ts-matches' - -type PropertiesV1 = typeof matchPropertiesV1._TYPE -type PackagePropertiesV1 = PropertiesV1[] -type PackagePropertiesV2 = { - [name: string]: PackagePropertyString | PackagePropertyObject -} -type PackagePropertiesVersionedData = T extends 1 - ? PackagePropertiesV1 - : T extends 2 - ? PackagePropertiesV2 - : never - -type PackagePropertyString = typeof matchPackagePropertyString._TYPE - -export type PackagePropertiesVersioned = { - version: T - data: PackagePropertiesVersionedData -} -export type PackageProperties = PackagePropertiesV2 - -const matchPropertiesV1 = shape( - { - name: string, - value: string, - description: string, - copyable: boolean, - qr: boolean, - }, - ['description', 'copyable', 'qr'], - { copyable: false, qr: false } as const, -) - -const [matchPackagePropertiesV2, setPPV2] = deferred() -const matchPackagePropertyString = shape( - { - type: literal('string'), - description: string, - value: string, - copyable: boolean, - qr: boolean, - masked: boolean, - }, - ['description', 'copyable', 'qr', 'masked'], - { - copyable: false, - qr: false, - masked: false, - } as const, -) -const matchPackagePropertyObject = shape( - { - type: literal('object'), - value: matchPackagePropertiesV2, - description: string, - }, - ['description'], -) - -const matchPropertyV2 = anyOf( - matchPackagePropertyString, - matchPackagePropertyObject, -) -type PackagePropertyObject = typeof matchPackagePropertyObject._TYPE -setPPV2(dictionary([string, matchPropertyV2])) - -const matchPackagePropertiesVersionedV1 = shape({ - version: number, - data: arrayOf(matchPropertiesV1), -}) -const matchPackagePropertiesVersionedV2 = shape({ - version: number, - data: dictionary([string, matchPropertyV2]), -}) - -export function parsePropertiesPermissive( - properties: unknown, - errorCallback: (err: Error) => any = console.warn, -): PackageProperties { - return matches(properties) - .when(matchPackagePropertiesVersionedV1, prop => - parsePropertiesV1Permissive(prop.data, errorCallback), - ) - .when(matchPackagePropertiesVersionedV2, prop => prop.data) - .when(matches.nill, {}) - .defaultToLazy(() => { - errorCallback(new TypeError(`value is not valid`)) - return {} - }) -} - -function parsePropertiesV1Permissive( - properties: unknown, - errorCallback: (err: Error) => any, -): PackageProperties { - if (!Array.isArray(properties)) { - errorCallback(new TypeError(`${properties} is not an array`)) - return {} - } - return properties.reduce( - (prev: PackagePropertiesV2, cur: unknown, idx: number) => { - const result = matchPropertiesV1.enumParsed(cur) - if ('value' in result) { - const value = result.value - prev[value.name] = { - type: 'string', - value: value.value, - description: value.description, - copyable: value.copyable, - qr: value.qr, - masked: false, - } - } else { - const error = result.error - const message = Parser.validatorErrorAsString(error) - const dataPath = error.keys.map(removeQuotes).join('/') - errorCallback(new Error(`/data/${idx}: ${message}`)) - if (dataPath) { - applyOperation(cur, { - op: 'replace', - path: `/${dataPath}`, - value: undefined, - }) - } - } - return prev - }, - {}, - ) -} - -const removeRegex = /('|")/ -function removeQuotes(x: string) { - while (removeRegex.test(x)) { - x = x.replace(removeRegex, '') - } - return x -} diff --git a/web/projects/ui/src/index.html b/web/projects/ui/src/index.html index 9c0cc2e89..f3d8d40e3 100644 --- a/web/projects/ui/src/index.html +++ b/web/projects/ui/src/index.html @@ -12,13 +12,21 @@ /> - - + + + + +