From 66b018a355d218ba5fb1b492920313743b6f0d1f Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:29:27 -0600 Subject: [PATCH 01/46] Fix/health check error (#2731) * fix: No error's with an error code * fix dns query * await resolv.conf copy * use tty in subcontainer exec if parent is tty * Fix: Home=root for inject services * fix: Add the action inject too --------- Co-authored-by: Aiden McClelland --- .../Systems/SystemForEmbassy/MainLoop.ts | 20 ++++++++++- .../Systems/SystemForEmbassy/index.ts | 7 ++++ core/Cargo.lock | 12 +++++++ core/startos/Cargo.toml | 5 +-- core/startos/src/net/dns.rs | 12 ++----- .../src/service/effects/subcontainer/sync.rs | 34 ++++++++++++++++++- sdk/lib/util/SubContainer.ts | 3 +- 7 files changed, 78 insertions(+), 15 deletions(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts index e5aaacfdb..cae3405b9 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts @@ -173,10 +173,16 @@ export class MainLoop { subcontainer, } ) + const env: Record = actionProcedure.inject + ? { + HOME: "/root", + } + : {} const executed = await container.exec( [actionProcedure.entrypoint, ...actionProcedure.args], - { input: JSON.stringify(timeChanged) }, + { input: JSON.stringify(timeChanged), env }, ) + if (executed.exitCode === 0) { await effects.setHealth({ id: healthId, @@ -227,6 +233,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/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 1e0c34189..9e6382ca7 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -750,6 +750,12 @@ export class SystemForEmbassy implements System { const subcontainer = actionProcedure.inject ? this.currentRunning?.mainSubContainerHandle : undefined + + const env: Record = actionProcedure.inject + ? { + HOME: "/root", + } + : {} const container = await DockerProcedureContainer.of( effects, this.manifest.id, @@ -769,6 +775,7 @@ export class SystemForEmbassy implements System { JSON.stringify(formData), ], timeoutMs, + { env }, ) ).stdout.toString(), ), diff --git a/core/Cargo.lock b/core/Cargo.lock index 1fccce36d..e9b9330e3 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5144,6 +5144,7 @@ dependencies = [ "tracing-subscriber", "trust-dns-server", "ts-rs", + "tty-spawn", "typed-builder", "unix-named-pipe", "unshare", @@ -5886,6 +5887,17 @@ dependencies = [ "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" diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index d0228ba31..9b3e7a048 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -39,10 +39,10 @@ path = "src/main.rs" [features] cli = [] -container-runtime = ["procfs", "unshare"] +container-runtime = ["procfs", "unshare", "tty-spawn"] daemon = [] registry = [] -default = ["cli", "daemon"] +default = ["cli", "daemon", "registry", "container-runtime"] dev = [] unstable = ["console-subscriber", "tokio/tracing"] docker = [] @@ -205,6 +205,7 @@ 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" which = "6.0.3" unix-named-pipe = "0.2.0" 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/service/effects/subcontainer/sync.rs b/core/startos/src/service/effects/subcontainer/sync.rs index e18586a54..f31eb8f62 100644 --- a/core/startos/src/service/effects/subcontainer/sync.rs +++ b/core/startos/src/service/effects/subcontainer/sync.rs @@ -2,15 +2,16 @@ use std::borrow::Cow; 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 rpc_toolkit::Context; use signal_hook::consts::signal::*; use tokio::sync::oneshot; +use tty_spawn::TtySpawn; use unshare::Command as NSCommand; use crate::service::effects::prelude::*; @@ -294,6 +295,37 @@ pub fn exec( command, }: ExecParams, ) -> Result<(), Error> { + if std::io::stdin().is_terminal() { + 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 || { diff --git a/sdk/lib/util/SubContainer.ts b/sdk/lib/util/SubContainer.ts index 0d5982886..bbc5c5f64 100644 --- a/sdk/lib/util/SubContainer.ts +++ b/sdk/lib/util/SubContainer.ts @@ -97,7 +97,8 @@ export class SubContainer implements ExecSpawnable { 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}` From 9981ee76017ae533d552e606a631fe1a4d42f481 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 3 Sep 2024 09:23:47 -0600 Subject: [PATCH 02/46] follow sideload progress (#2718) * follow sideload progress * small bugfix * shareReplay with no refcount false * don't wrap sideload progress in RPCResult * dont present toast --------- Co-authored-by: Aiden McClelland --- core/startos/src/install/mod.rs | 39 ++-- .../apps-routes/app-show/app-show.module.ts | 1 + .../ui/src/app/pages/init/init.service.ts | 4 +- .../src/app/pages/init/logs/logs.service.ts | 2 +- .../server-routes/sideload/sideload.module.ts | 2 + .../server-routes/sideload/sideload.page.html | 195 ++++++++++-------- .../server-routes/sideload/sideload.page.ts | 16 +- .../sideload/sideload.service.ts | 32 +++ .../app/services/api/embassy-api.service.ts | 4 +- .../services/api/embassy-live-api.service.ts | 7 +- .../services/api/embassy-mock-api.service.ts | 14 +- .../src/app/services/marketplace.service.ts | 7 +- .../app/services/patch-db/patch-db-source.ts | 2 +- 13 files changed, 200 insertions(+), 125 deletions(-) create mode 100644 web/projects/ui/src/app/pages/server-routes/sideload/sideload.service.ts diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index 7a545a3aa..eefd6eb66 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -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; @@ -188,7 +189,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 +203,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 +220,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 +259,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 +406,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/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..a3c6cc584 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 @@ -56,5 +56,6 @@ const routes: Routes = [ StatusComponentModule, SharedPipesModule, ], + exports: [AppShowProgressComponent], }) export class AppShowPageModule {} 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/server-routes/sideload/sideload.module.ts b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.module.ts index 27a3757dd..27574da83 100644 --- a/web/projects/ui/src/app/pages/server-routes/sideload/sideload.module.ts +++ b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.module.ts @@ -5,6 +5,7 @@ import { SideloadPage } from './sideload.page' import { Routes, RouterModule } from '@angular/router' import { ExverPipesModule, SharedPipesModule } from '@start9labs/shared' import { DragNDropDirective } from './dnd.directive' +import { InstallingProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module' const routes: Routes = [ { @@ -20,6 +21,7 @@ const routes: Routes = [ RouterModule.forChild(routes), SharedPipesModule, ExverPipesModule, + InstallingProgressPipeModule, ], declarations: [SideloadPage, DragNDropDirective], }) diff --git a/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html index 0d413075e..abbb9128a 100644 --- a/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html +++ b/web/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html @@ -7,92 +7,121 @@ - + + + +

+ {{ 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 b95c4599f..b5f684103 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,12 @@ 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 { S9pk } 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' interface Positions { [key: string]: [bigint, bigint] // [position, length] @@ -36,12 +38,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 +115,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/services/api/embassy-api.service.ts b/web/projects/ui/src/app/services/api/embassy-api.service.ts index acd6780d1..31e3ac86e 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 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..48cf6edd3 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 @@ -43,12 +43,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 +85,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' 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..a40f29a0a 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 @@ -81,9 +81,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 +105,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 +124,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') } @@ -1079,8 +1083,8 @@ export class MockApiService extends ApiService { 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/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts index bef380aea..bc2dcc7ab 100644 --- a/web/projects/ui/src/app/services/marketplace.service.ts +++ b/web/projects/ui/src/app/services/marketplace.service.ts @@ -285,7 +285,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]), From ae88f7d181a3dd4a4ff333e6a87c06bc5737cbf8 Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:51:19 -0400 Subject: [PATCH 03/46] add types --- .../src/app/services/marketplace.service.ts | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/web/projects/ui/src/app/services/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts index bc2dcc7ab..4c8ab98a6 100644 --- a/web/projects/ui/src/app/services/marketplace.service.ts +++ b/web/projects/ui/src/app/services/marketplace.service.ts @@ -42,7 +42,7 @@ export class MarketplaceService implements AbstractMarketplaceService { private readonly knownHosts$: Observable = this.patch .watch$('ui', 'marketplace', 'knownHosts') .pipe( - map(hosts => { + map((hosts: { [url: string]: UIStore }) => { const { start9, community } = this.config.marketplace let arr = [ toStoreIdentity(start9, hosts[start9]), @@ -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$, From 21dd08544b3454f65675d49a4bc5a7a3787f8e4f Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:55:10 -0400 Subject: [PATCH 04/46] update version to clear refresh alert --- web/projects/ui/src/app/services/api/mock-patch.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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..34804fbc6 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', From 27416efb6df06c44e22ccf668a6ccc01f80e969c Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:55:54 -0400 Subject: [PATCH 05/46] only display alt implementations if no flavors --- .../marketplace-show/marketplace-show.page.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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..d2af27efc 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 @@ -51,15 +51,14 @@ export class MarketplaceShowPage { 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), + ), ), ) From dbbc42c5fdab7f729b6454f628206a0949483d20 Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:03:01 -0400 Subject: [PATCH 06/46] update packages --- web/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index ac1f77a6b..7ceeb4399 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.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.3.6", + "version": "0.3.6-alpha.5", "license": "MIT", "dependencies": { "@angular/animations": "^14.1.0", @@ -116,7 +116,7 @@ }, "../sdk/dist": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha5", + "version": "0.3.6-alpha8", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", From 47855dc78bc68856bce8caa52a8110bebaca75ee Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:46:09 -0400 Subject: [PATCH 07/46] remove explicit type --- web/projects/ui/src/app/services/marketplace.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/projects/ui/src/app/services/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts index 4c8ab98a6..a080f051d 100644 --- a/web/projects/ui/src/app/services/marketplace.service.ts +++ b/web/projects/ui/src/app/services/marketplace.service.ts @@ -42,7 +42,7 @@ export class MarketplaceService implements AbstractMarketplaceService { private readonly knownHosts$: Observable = this.patch .watch$('ui', 'marketplace', 'knownHosts') .pipe( - map((hosts: { [url: string]: UIStore }) => { + map(hosts => { const { start9, community } = this.config.marketplace let arr = [ toStoreIdentity(start9, hosts[start9]), From eec5cf6b65649006698b8e15b696d12e14624218 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:38:16 -0600 Subject: [PATCH 08/46] add support for remote attaching to container (#2732) * add support for remote attaching to container * feature: Add in the subcontainer searching * feat: Add in the name/ imageId filtering * Feat: Fix the env and the workdir * chore: Make the sigkill first? * add some extra guard on term * fix: Health during error doesnt return what we need * chore: Cleanup for pr * fix build * fix build * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * Update startos-iso.yaml * check status during build --------- Co-authored-by: J H --- .github/workflows/startos-iso.yaml | 21 +- Makefile | 67 +-- build/dpkg-deps/depends | 1 + .../src/Adapters/EffectCreator.ts | 2 +- container-runtime/src/Adapters/RpcListener.ts | 6 +- .../DockerProcedureContainer.ts | 9 +- .../Systems/SystemForEmbassy/MainLoop.ts | 34 +- .../Systems/SystemForEmbassy/index.ts | 81 ++- .../SystemForEmbassy/polyfillEffects.ts | 1 + core/Cargo.lock | 44 +- core/startos/Cargo.toml | 4 +- core/startos/src/auth.rs | 8 +- core/startos/src/context/rpc.rs | 6 +- core/startos/src/db/mod.rs | 2 +- core/startos/src/install/mod.rs | 2 +- core/startos/src/lib.rs | 7 + core/startos/src/middleware/auth.rs | 2 +- .../src/service/effects/subcontainer/mod.rs | 21 +- .../src/service/effects/subcontainer/sync.rs | 192 ++++--- core/startos/src/service/mod.rs | 517 +++++++++++++++++- .../src/service/persistent_container.rs | 17 +- core/startos/src/upload.rs | 2 +- core/startos/src/util/io.rs | 34 +- sdk/lib/StartSdk.ts | 1 + sdk/lib/mainFn/CommandController.ts | 17 +- sdk/lib/mainFn/Daemon.ts | 1 + sdk/lib/mainFn/Daemons.ts | 1 + .../osBindings/CreateSubcontainerFsParams.ts | 5 +- sdk/lib/osBindings/SessionList.ts | 2 +- sdk/lib/types.ts | 5 +- sdk/lib/util/SubContainer.ts | 9 +- 31 files changed, 852 insertions(+), 269 deletions(-) diff --git a/.github/workflows/startos-iso.yaml b/.github/workflows/startos-iso.yaml index bc0fb626e..3497624e7 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,24 @@ jobs: run: | mkdir -p web/node_modules mkdir -p web/dist/raw - touch core/startos/bindings - touch sdk/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 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/Makefile b/Makefile index a714cd389..d085117b2 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 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) @@ -57,7 +58,7 @@ touch: metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) sudo: - sudo true + sudo -v clean: rm -f system-images/**/*.tar @@ -94,10 +95,10 @@ test: | test-core test-sdk test-container-runtime test-core: $(CORE_SRC) $(ENVIRONMENT_FILE) ./core/run-tests.sh -test-sdk: $(shell git ls-files sdk) sdk/lib/osBindings +test-sdk: $(shell git ls-files sdk) sdk/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: @@ -218,34 +219,34 @@ 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 +sdk/lib/osBindings/index.ts: core/startos/bindings/index.ts rsync -ac --delete core/startos/bindings/ sdk/lib/osBindings/ - touch sdk/lib/osBindings + touch sdk/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 ./core/build-ts.sh - touch core/startos/bindings + 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 + touch core/startos/bindings/index.ts -sdk/dist: $(shell git ls-files sdk) sdk/lib/osBindings +sdk/dist/package.json: $(shell git ls-files sdk) sdk/lib/osBindings/index.ts (cd sdk && make bundle) + touch sdk/dist/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 +264,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 +272,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/dist/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/dist/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 +303,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) diff --git a/build/dpkg-deps/depends b/build/dpkg-deps/depends index cd29714b2..f495df85d 100644 --- a/build/dpkg-deps/depends +++ b/build/dpkg-deps/depends @@ -9,6 +9,7 @@ ca-certificates cifs-utils cryptsetup curl +dnsutils dmidecode dosfstools e2fsprogs diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index e0390b1e1..3338560c9 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -127,7 +127,7 @@ function makeEffects(context: EffectContext): Effects { > }, subcontainer: { - createFs(options: { imageId: string }) { + createFs(options: { imageId: string; name: string }) { return rpcRound("subcontainer.create-fs", options) as ReturnType< T.Effects["subcontainer"]["createFs"] > diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index 860f1c066..458b1af0f 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -198,7 +198,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()}`) + }), ) }) } diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 805f9b531..0c1f9d07a 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -20,6 +20,7 @@ export class DockerProcedureContainer { packageId: string, data: DockerProcedure, volumes: { [id: VolumeId]: Volume }, + name: string, options: { subcontainer?: ExecSpawnable } = {}, ) { const subcontainer = @@ -29,6 +30,7 @@ export class DockerProcedureContainer { packageId, data, volumes, + name, )) return new DockerProcedureContainer(subcontainer) } @@ -37,8 +39,13 @@ export class DockerProcedureContainer { packageId: string, data: DockerProcedure, volumes: { [id: VolumeId]: Volume }, + name: string, ) { - const subcontainer = await SubContainer.of(effects, { id: data.image }) + const subcontainer = await SubContainer.of( + effects, + { id: data.image }, + name, + ) if (data.mounts) { const mounts = data.mounts diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts index cae3405b9..91b0177eb 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts @@ -62,6 +62,7 @@ export class MainLoop { this.system.manifest.id, this.system.manifest.main, this.system.manifest.volumes, + `Main - ${currentCommand.join(" ")}`, ) return CommandController.of()( this.effects, @@ -162,26 +163,29 @@ export class MainLoop { const subcontainer = actionProcedure.inject ? this.mainSubContainerHandle : undefined - // prettier-ignore - const container = - await DockerProcedureContainer.of( - effects, - manifest.id, - actionProcedure, - manifest.volumes, - { - subcontainer, - } - ) + 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( - [actionProcedure.entrypoint, ...actionProcedure.args], - { input: JSON.stringify(timeChanged), env }, - ) + const executed = await container.exec(commands, { + input: JSON.stringify(timeChanged), + env, + }) if (executed.exitCode === 0) { await effects.setHealth({ diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 9e6382ca7..5b0380a1b 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -443,6 +443,7 @@ export class SystemForEmbassy implements System { ): 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, @@ -451,8 +452,9 @@ 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)) @@ -464,6 +466,7 @@ export class SystemForEmbassy implements System { ): 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, @@ -472,11 +475,9 @@ 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)) @@ -495,20 +496,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 @@ -543,24 +541,21 @@ 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 ?? {} @@ -652,23 +647,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 @@ -695,20 +687,17 @@ export class SystemForEmbassy implements System { 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) @@ -761,6 +750,7 @@ export class SystemForEmbassy implements System { this.manifest.id, actionProcedure, this.manifest.volumes, + `Action ${actionId}`, { subcontainer, }, @@ -801,23 +791,20 @@ export class SystemForEmbassy implements System { 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( - [ - actionProcedure.entrypoint, - ...actionProcedure.args, - JSON.stringify(oldConfig), - ], - timeoutMs, - ) - ).stdout.toString(), + (await container.execFail(commands, timeoutMs)).stdout.toString(), ) } else if (actionProcedure.type === "script") { const moduleCode = await this.moduleCode diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts index c212722e6..675cb2e9a 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts @@ -129,6 +129,7 @@ export const polyfillEffects = ( manifest.id, manifest.main, manifest.volumes, + [input.command, ...(input.args || [])].join(" "), ) const daemon = promiseSubcontainer.then((subcontainer) => daemons.runCommand()( diff --git a/core/Cargo.lock b/core/Cargo.lock index e9b9330e3..ebbcf6b5d 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -533,7 +533,7 @@ dependencies = [ "rustc-hash", "shlex", "syn 2.0.74", - "which 4.4.2", + "which", ] [[package]] @@ -3055,18 +3055,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "nix" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" -dependencies = [ - "bitflags 1.3.2", - "cc", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.24.3" @@ -5147,11 +5135,9 @@ dependencies = [ "tty-spawn", "typed-builder", "unix-named-pipe", - "unshare", "url", "urlencoding", "uuid", - "which 6.0.3", "zeroize", ] @@ -6051,16 +6037,6 @@ dependencies = [ "libc", ] -[[package]] -name = "unshare" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceda295552a1eda89f8a748237654ad76b9c87e383fc07af5c4e423eb8e7b9b" -dependencies = [ - "libc", - "nix 0.20.0", -] - [[package]] name = "untrusted" version = "0.9.0" @@ -6278,18 +6254,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "which" -version = "6.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" -dependencies = [ - "either", - "home", - "rustix", - "winsafe", -] - [[package]] name = "whoami" version = "1.5.1" @@ -6516,12 +6480,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "wyz" version = "0.2.0" diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 9b3e7a048..f2b780b7b 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -39,7 +39,7 @@ path = "src/main.rs" [features] cli = [] -container-runtime = ["procfs", "unshare", "tty-spawn"] +container-runtime = ["procfs", "tty-spawn"] daemon = [] registry = [] default = ["cli", "daemon", "registry", "container-runtime"] @@ -207,9 +207,7 @@ 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" -which = "6.0.3" unix-named-pipe = "0.2.0" -unshare = { version = "0.7.0", optional = true } url = { version = "2.4.1", features = ["serde"] } urlencoding = "2.1.3" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index d998e9897..9a881d0e6 100644 --- a/core/startos/src/auth.rs +++ b/core/startos/src/auth.rs @@ -275,8 +275,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, } @@ -323,7 +323,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 +340,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/context/rpc.rs b/core/startos/src/context/rpc.rs index 5330c58bc..c78f118ea 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -58,7 +58,7 @@ 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>>, @@ -431,8 +431,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/db/mod.rs b/core/startos/src/db/mod.rs index ef35bd30d..ff42d6a5c 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -115,7 +115,7 @@ pub struct SubscribeParams { pointer: Option, #[ts(skip)] #[serde(rename = "__auth_session")] - session: InternedString, + session: Option, } #[derive(Deserialize, Serialize, TS)] diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index eefd6eb66..32b0f6ce4 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -173,7 +173,7 @@ pub async fn install( pub struct SideloadParams { #[ts(skip)] #[serde(rename = "__auth_session")] - session: InternedString, + session: Option, } #[derive(Deserialize, Serialize, TS)] diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index feeb5a647..b5629aa04 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -323,6 +323,13 @@ pub fn package() -> ParentHandler { "connect", from_fn_async(service::connect_rpc_cli).no_display(), ) + .subcommand( + "attach", + from_fn_async(service::attach) + .with_metadata("get_session", Value::Bool(true)) + .no_cli(), + ) + .subcommand("attach", from_fn_async(service::cli_attach).no_display()) } pub fn diagnostic_api() -> ParentHandler { 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/service/effects/subcontainer/mod.rs b/core/startos/src/service/effects/subcontainer/mod.rs index 0375ef6c2..65fcbd387 100644 --- a/core/startos/src/service/effects/subcontainer/mod.rs +++ b/core/startos/src/service/effects/subcontainer/mod.rs @@ -1,12 +1,15 @@ use std::path::{Path, PathBuf}; +use imbl_value::InternedString; use models::ImageId; 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; +use crate::{ + disk::mount::filesystem::overlayfs::OverlayGuard, service::persistent_container::Subcontainer, +}; #[cfg(feature = "container-runtime")] mod sync; @@ -38,7 +41,7 @@ pub async fn destroy_subcontainer_fs( .await .remove(&guid) { - overlay.unmount(true).await?; + 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"); } @@ -50,11 +53,13 @@ pub async fn destroy_subcontainer_fs( #[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 }: CreateSubcontainerFsParams, + CreateSubcontainerFsParams { image_id, name }: CreateSubcontainerFsParams, ) -> Result<(PathBuf, Guid), Error> { let context = context.deref()?; if let Some(image) = context @@ -87,7 +92,13 @@ pub async fn create_subcontainer_fs( .with_kind(ErrorKind::Incoherent)?, ); tracing::info!("Mounting overlay {guid} for {image_id}"); - let guard = OverlayGuard::mount(image, &mountpoint).await?; + 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) @@ -100,7 +111,7 @@ pub async fn create_subcontainer_fs( .subcontainers .lock() .await - .insert(guid.clone(), guard); + .insert(guid.clone(), subcontainer_wrapper); Ok((container_mountpoint, guid)) } else { Err(Error::new( diff --git a/core/startos/src/service/effects/subcontainer/sync.rs b/core/startos/src/service/effects/subcontainer/sync.rs index f31eb8f62..702f34bbe 100644 --- a/core/startos/src/service/effects/subcontainer/sync.rs +++ b/core/startos/src/service/effects/subcontainer/sync.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::collections::BTreeMap; use std::ffi::{c_int, OsStr, OsString}; use std::fs::File; @@ -12,7 +11,6 @@ use nix::unistd::Pid; use signal_hook::consts::signal::*; use tokio::sync::oneshot; use tty_spawn::TtySpawn; -use unshare::Command as NSCommand; use crate::service::effects::prelude::*; use crate::service::effects::ContainerCliContext; @@ -50,11 +48,13 @@ fn open_file_read(path: impl AsRef) -> Result { #[derive(Debug, Clone, Serialize, Deserialize, Parser)] pub struct ExecParams { - #[arg(short = 'e', long = "env")] + #[arg(long)] + force_tty: bool, + #[arg(short, long)] env: Option, - #[arg(short = 'w', long = "workdir")] + #[arg(short, long)] workdir: Option, - #[arg(short = 'u', long = "user")] + #[arg(short, long)] user: Option, chroot: PathBuf, #[arg(trailing_var_arg = true)] @@ -68,6 +68,7 @@ impl ExecParams { user, chroot, command, + .. } = self; let Some(([command], args)) = command.split_at_checked(1) else { return Err(Error::new( @@ -88,16 +89,6 @@ impl ExecParams { .collect::>(); std::os::unix::fs::chroot(chroot) .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("chroot {chroot:?}")))?; - let command = which::which_in( - command, - env.get("PATH") - .copied() - .map(Cow::Borrowed) - .or_else(|| std::env::var("PATH").ok().map(Cow::Owned)) - .as_deref(), - workdir.as_deref().unwrap_or(Path::new("/")), - ) - .with_kind(ErrorKind::Filesystem)?; let mut cmd = StdCommand::new(command); cmd.args(args); for (k, v) in env { @@ -135,6 +126,7 @@ impl ExecParams { pub fn launch( _: ContainerCliContext, ExecParams { + force_tty, env, workdir, user, @@ -142,47 +134,8 @@ pub fn launch( command, }: ExecParams, ) -> Result<(), Error> { - use unshare::{Namespace, Stdio}; - - use crate::service::cli::ContainerCliContext; - let mut sig = signal_hook::iterator::Signals::new(FWD_SIGNALS)?; - let mut cmd = NSCommand::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.unshare(&[Namespace::Pid, Namespace::Cgroup, Namespace::Ipc]); - 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(); - } - }); if chroot.join("proc/1").exists() { - let ns_id = procfs::process::Process::new_with_root(chroot.join("proc")) + 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"))? @@ -225,20 +178,92 @@ pub fn launch( 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"))?; - let pid = child.pid(); - std::thread::spawn(move || { - for sig in sig.forever() { - nix::sys::signal::kill( - Pid::from_raw(pid), - Some(nix::sys::signal::Signal::try_from(sig).unwrap()), - ) - .unwrap(); - } - }); + send_pid.send(child.id() as i32).unwrap_or_default(); stdin_send .send(child.stdin.take().unwrap()) .unwrap_or_default(); @@ -253,16 +278,16 @@ pub fn launch( .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 { - if exit.success() { - Ok(()) - } else { - Err(Error::new( - color_eyre::eyre::Report::msg(exit), - ErrorKind::Unknown, - )) - } + Err(Error::new( + color_eyre::eyre::Report::msg(exit), + ErrorKind::Unknown, + )) } } @@ -288,6 +313,7 @@ pub fn launch_init(_: ContainerCliContext, params: ExecParams) -> Result<(), Err pub fn exec( _: ContainerCliContext, ExecParams { + force_tty, env, workdir, user, @@ -295,7 +321,11 @@ pub fn exec( command, }: ExecParams, ) -> Result<(), Error> { - if std::io::stdin().is_terminal() { + 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 { @@ -407,15 +437,13 @@ pub fn exec( .with_ctx(|_| (ErrorKind::Filesystem, "waiting on child process"))?; if let Some(code) = exit.code() { std::process::exit(code); + } else if exit.success() { + Ok(()) } else { - if exit.success() { - Ok(()) - } else { - Err(Error::new( - color_eyre::eyre::Report::msg(exit), - ErrorKind::Unknown, - )) - } + Err(Error::new( + color_eyre::eyre::Report::msg(exit), + ErrorKind::Unknown, + )) } } diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 5eae62756..55f76fd14 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -1,18 +1,31 @@ +use std::io::IsTerminal; use std::ops::Deref; +use std::os::unix::process::ExitStatusExt; +use std::path::Path; +use std::process::Stdio; use std::sync::{Arc, Weak}; use std::time::Duration; +use std::{ffi::OsString, path::PathBuf}; +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::{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,15 +37,16 @@ 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::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 cli; @@ -68,6 +82,8 @@ pub enum LoadDisposition { Undo, } +struct RootCommand(pub String); + pub struct ServiceRef(Arc); impl ServiceRef { pub fn weak(&self) -> Weak { @@ -183,7 +199,7 @@ impl ServiceRef { impl Deref for ServiceRef { type Target = Service; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } impl From for ServiceRef { @@ -354,7 +370,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:?}") @@ -578,3 +594,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 dd7b5766d..ff9d74009 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -4,9 +4,10 @@ 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 +37,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 +85,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 +107,7 @@ pub struct PersistentContainer { volumes: BTreeMap, assets: BTreeMap, pub(super) images: BTreeMap>, - pub(super) subcontainers: Arc>>>>, + pub(super) subcontainers: Arc>>, pub(super) state: Arc>, pub(super) net_service: Mutex, destroyed: bool, @@ -405,7 +414,7 @@ impl PersistentContainer { errs.handle(assets.unmount(true).await); } for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) { - errs.handle(overlay.unmount(true).await); + errs.handle(overlay.overlay.unmount(true).await); } for (_, images) in images { errs.handle(images.unmount().await); 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..e3059c862 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 {} @@ -1267,3 +1269,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/sdk/lib/StartSdk.ts b/sdk/lib/StartSdk.ts index 658597bc2..87c4e14ce 100644 --- a/sdk/lib/StartSdk.ts +++ b/sdk/lib/StartSdk.ts @@ -782,6 +782,7 @@ export async function runCommand( effects, image, options.mounts || [], + commands.join(" "), (subcontainer) => subcontainer.exec(commands), ) } diff --git a/sdk/lib/mainFn/CommandController.ts b/sdk/lib/mainFn/CommandController.ts index 8a0505f68..9c206803a 100644 --- a/sdk/lib/mainFn/CommandController.ts +++ b/sdk/lib/mainFn/CommandController.ts @@ -31,6 +31,7 @@ export class CommandController { | SubContainer, command: T.CommandType, options: { + subcontainerName?: string // Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms sigtermTimeout?: number mounts?: { path: string; options: MountOptions }[] @@ -51,7 +52,11 @@ export class CommandController { subcontainer instanceof SubContainer ? subcontainer : await (async () => { - const subc = await SubContainer.of(effects, subcontainer) + const subc = await SubContainer.of( + effects, + subcontainer, + options?.subcontainerName || commands.join(" "), + ) for (let mount of options.mounts || []) { await subc.mount(mount.options, mount.path) } @@ -119,6 +124,11 @@ export class CommandController { 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}`, @@ -126,11 +136,6 @@ export class CommandController { } } - if (signal !== "SIGKILL") { - setTimeout(() => { - this.process.kill("SIGKILL") - }, timeout) - } await this.runningAnswer } finally { await this.subcontainer.destroy?.() diff --git a/sdk/lib/mainFn/Daemon.ts b/sdk/lib/mainFn/Daemon.ts index 87a7d705d..6fa1d1085 100644 --- a/sdk/lib/mainFn/Daemon.ts +++ b/sdk/lib/mainFn/Daemon.ts @@ -28,6 +28,7 @@ export class Daemon { | SubContainer, command: T.CommandType, options: { + subcontainerName?: string mounts?: { path: string; options: MountOptions }[] env?: | { diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/lib/mainFn/Daemons.ts index 1ecec28d3..b17f3a1a1 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/lib/mainFn/Daemons.ts @@ -127,6 +127,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/osBindings/CreateSubcontainerFsParams.ts b/sdk/lib/osBindings/CreateSubcontainerFsParams.ts index 729ad4240..32d301e4a 100644 --- a/sdk/lib/osBindings/CreateSubcontainerFsParams.ts +++ b/sdk/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 CreateSubcontainerFsParams = { imageId: ImageId } +export type CreateSubcontainerFsParams = { + imageId: ImageId + name: string | null +} diff --git a/sdk/lib/osBindings/SessionList.ts b/sdk/lib/osBindings/SessionList.ts index a85a42dee..af36aaa8a 100644 --- a/sdk/lib/osBindings/SessionList.ts +++ b/sdk/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/types.ts b/sdk/lib/types.ts index 4820f419d..68bd7b588 100644 --- a/sdk/lib/types.ts +++ b/sdk/lib/types.ts @@ -366,7 +366,10 @@ export type Effects = { // subcontainer subcontainer: { /** A low level api used by SubContainer */ - createFs(options: { imageId: string }): Promise<[string, string]> + createFs(options: { + imageId: string + name: string | null + }): Promise<[string, string]> /** A low level api used by SubContainer */ destroyFs(options: { guid: string }): Promise } diff --git a/sdk/lib/util/SubContainer.ts b/sdk/lib/util/SubContainer.ts index bbc5c5f64..82c1ae1b3 100644 --- a/sdk/lib/util/SubContainer.ts +++ b/sdk/lib/util/SubContainer.ts @@ -86,10 +86,12 @@ export class SubContainer implements ExecSpawnable { static async of( effects: T.Effects, image: { id: T.ImageId; sharedRun?: boolean }, + name: string, ) { const { id, sharedRun } = image const [rootfs, guid] = await effects.subcontainer.createFs({ imageId: id as string, + name, }) const shared = ["dev", "sys"] @@ -115,9 +117,10 @@ export class SubContainer implements ExecSpawnable { effects: T.Effects, image: { id: T.ImageId; sharedRun?: boolean }, mounts: { options: MountOptions; path: string }[], + name: string, fn: (subContainer: SubContainer) => Promise, ): Promise { - const subContainer = await SubContainer.of(effects, image) + const subContainer = await SubContainer.of(effects, image, name) try { for (let mount of mounts) { await subContainer.mount(mount.options, mount.path) @@ -179,10 +182,12 @@ export class SubContainer implements ExecSpawnable { } return new Promise((resolve, reject) => { try { + let timeout = setTimeout(() => this.leader.kill("SIGKILL"), 30000) this.leader.on("exit", () => { + clearTimeout(timeout) resolve() }) - if (!this.leader.kill("SIGKILL")) { + if (!this.leader.kill("SIGTERM")) { reject(new Error("kill(2) failed")) } } catch (e) { From db0695126ffa69c13f07410119e2996a41a65f03 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:12:52 -0600 Subject: [PATCH 09/46] Refactor/actions (#2733) * store, properties, manifest * interfaces * init and backups * fix init and backups * file models * more versions * dependencies * config except dynamic types * clean up config * remove disabled from non-dynamic vaues * actions * standardize example code block formats * wip: actions refactor Co-authored-by: Jade * commit types * fix types * update types * update action request type * update apis * add description to actionrequest * clean up imports * revert package json * chore: Remove the recursive to the index * chore: Remove the other thing I was testing * flatten action requests * update container runtime with new config paradigm * new actions strategy * seems to be working * misc backend fixes * fix fe bugs * only show breakages if breakages * only show success modal if result * don't panic on failed removal * hide config from actions page * polyfill autoconfig * use metadata strategy for actions instead of prev * misc fixes * chore: split the sdk into 2 libs (#2736) * follow sideload progress (#2718) * follow sideload progress * small bugfix * shareReplay with no refcount false * don't wrap sideload progress in RPCResult * dont present toast --------- Co-authored-by: Aiden McClelland * chore: Add the initial of the creation of the two sdk * chore: Add in the baseDist * chore: Add in the baseDist * chore: Get the web and the runtime-container running * chore: Remove the empty file * chore: Fix it so the container-runtime works --------- Co-authored-by: Matt Hill Co-authored-by: Aiden McClelland * misc fixes * update todos * minor clean up * fix link script * update node version in CI test * fix node version syntax in ci build * wip: fixing callbacks * fix sdk makefile dependencies * add support for const outside of main * update apis * don't panic! * Chore: Capture weird case on rpc, and log that * fix procedure id issue * pass input value for dep auto config * handle disabled and warning for actions * chore: Fix for link not having node_modules * sdk fixes * fix build * fix build * fix build --------- Co-authored-by: Matt Hill Co-authored-by: Jade Co-authored-by: J H Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com> Co-authored-by: Matt Hill --- .github/workflows/startos-iso.yaml | 3 + .github/workflows/test.yaml | 2 +- DEVELOPMENT.md | 1 + Makefile | 29 +- container-runtime/package-lock.json | 46 +- container-runtime/readme.md | 86 - .../src/Adapters/EffectCreator.ts | 91 +- container-runtime/src/Adapters/RpcListener.ts | 405 +- .../DockerProcedureContainer.ts | 2 +- .../Systems/SystemForEmbassy/MainLoop.ts | 9 +- .../Systems/SystemForEmbassy/index.ts | 192 +- .../Systems/SystemForEmbassy/matchManifest.ts | 1 + .../SystemForEmbassy/polyfillEffects.ts | 22 +- .../SystemForEmbassy/transformConfigSpec.ts | 14 +- .../src/Adapters/Systems/SystemForStartOs.ts | 101 +- .../src/Interfaces/AllGetDependencies.ts | 5 +- .../src/Interfaces/MakeEffects.ts | 4 - container-runtime/src/Interfaces/System.ts | 56 +- .../src/Models/CallbackHolder.ts | 51 +- container-runtime/src/Models/JsonPath.ts | 17 +- container-runtime/src/index.ts | 3 - core/Cargo.lock | 33 + core/helpers/src/lib.rs | 3 +- core/{startos/src/util => models/src}/clap.rs | 5 +- core/models/src/id/mod.rs | 2 + core/models/src/id/replay.rs | 45 + core/models/src/id/service_interface.rs | 16 +- core/models/src/lib.rs | 2 + core/models/src/procedure_name.rs | 20 +- core/startos/Cargo.toml | 1 + core/startos/src/action.rs | 194 +- core/startos/src/backup/backup_bulk.rs | 2 +- core/startos/src/backup/target/mod.rs | 3 +- core/startos/src/config/action.rs | 22 - core/startos/src/config/mod.rs | 281 - core/startos/src/config/util.rs | 406 -- core/startos/src/context/rpc.rs | 86 +- core/startos/src/db/model/package.rs | 102 +- core/startos/src/dependencies.rs | 140 - core/startos/src/disk/mount/backup.rs | 4 +- .../src/disk/mount/filesystem/overlayfs.rs | 6 +- core/startos/src/disk/mount/guard.rs | 2 +- core/startos/src/install/mod.rs | 3 +- core/startos/src/lib.rs | 14 +- core/startos/src/logs.rs | 42 +- core/startos/src/lxc/mod.rs | 5 +- core/startos/src/net/host/binding.rs | 41 +- core/startos/src/net/net_controller.rs | 78 +- core/startos/src/net/tor.rs | 2 +- core/startos/src/notifications.rs | 3 +- core/startos/src/registry/signer/mod.rs | 2 +- core/startos/src/registry/signer/sign/mod.rs | 2 +- core/startos/src/rpc_continuations.rs | 2 +- core/startos/src/s9pk/v2/compat.rs | 1 - core/startos/src/s9pk/v2/manifest.rs | 2 - core/startos/src/service/action.rs | 163 +- core/startos/src/service/config.rs | 78 - core/startos/src/service/control.rs | 11 +- core/startos/src/service/dependencies.rs | 86 - core/startos/src/service/effects/action.rs | 262 +- core/startos/src/service/effects/callbacks.rs | 34 +- core/startos/src/service/effects/config.rs | 53 - core/startos/src/service/effects/control.rs | 2 +- .../startos/src/service/effects/dependency.rs | 76 +- core/startos/src/service/effects/health.rs | 1 - core/startos/src/service/effects/mod.rs | 25 +- core/startos/src/service/effects/net/bind.rs | 16 +- .../src/service/effects/net/interface.rs | 14 +- core/startos/src/service/effects/store.rs | 11 +- core/startos/src/service/mod.rs | 75 +- .../src/service/persistent_container.rs | 8 +- core/startos/src/service/rpc.rs | 21 +- core/startos/src/service/service_actor.rs | 4 +- core/startos/src/service/service_map.rs | 8 +- core/startos/src/service/transition/backup.rs | 7 +- .../startos/src/service/transition/restart.rs | 7 +- core/startos/src/ssh.rs | 2 +- core/startos/src/status/health_check.rs | 3 +- core/startos/src/status/mod.rs | 10 +- core/startos/src/util/io.rs | 31 +- core/startos/src/util/mod.rs | 3 +- core/startos/src/util/serde.rs | 24 +- sdk/.gitignore | 9 +- sdk/.prettierignore | 2 +- sdk/Makefile | 71 +- sdk/base/.gitignore | 5 + sdk/{ => base}/LICENSE | 0 sdk/base/README.md | 1 + sdk/{ => base}/jest.config.js | 0 sdk/base/lib/Effects.ts | 191 + sdk/base/lib/actions/index.ts | 65 + .../lib/actions/input}/builder/index.ts | 4 +- .../lib/actions/input/builder/inputSpec.ts} | 42 +- .../lib/actions/input}/builder/list.ts | 68 +- .../lib/actions/input}/builder/value.ts | 287 +- .../lib/actions/input}/builder/variants.ts | 25 +- sdk/base/lib/actions/input/index.ts | 3 + .../lib/actions/input/inputSpecConstants.ts} | 21 +- .../lib/actions/input/inputSpecTypes.ts} | 37 +- sdk/base/lib/actions/setupActions.ts | 152 + sdk/base/lib/backup/Backups.ts | 208 + sdk/base/lib/backup/setupBackups.ts | 39 + sdk/base/lib/dependencies/Dependency.ts | 21 + .../lib/dependencies/dependencies.ts | 33 +- sdk/{ => base}/lib/dependencies/index.ts | 3 - .../lib/dependencies/setupDependencies.ts | 56 + sdk/{ => base}/lib/exver/exver.pegjs | 0 sdk/{ => base}/lib/exver/exver.ts | 0 sdk/{ => base}/lib/exver/index.ts | 0 .../index.browser.ts => base/lib/index.ts} | 9 +- .../lib/interfaces/AddressReceipt.ts | 0 sdk/{ => base}/lib/interfaces/Host.ts | 28 +- sdk/{ => base}/lib/interfaces/Origin.ts | 6 +- .../lib/interfaces/ServiceInterfaceBuilder.ts | 4 +- .../lib/interfaces/interfaceReceipt.ts | 0 sdk/base/lib/interfaces/setupInterfaces.ts | 54 + .../lib/osBindings/AcceptSigners.ts | 0 sdk/{ => base}/lib/osBindings/ActionId.ts | 0 .../lib/osBindings/ActionInput.ts} | 6 +- .../lib/osBindings/ActionMetadata.ts | 5 +- sdk/base/lib/osBindings/ActionRequest.ts | 13 + .../lib/osBindings/ActionRequestCondition.ts} | 2 +- sdk/base/lib/osBindings/ActionRequestEntry.ts | 4 + sdk/base/lib/osBindings/ActionRequestInput.ts | 6 + .../lib/osBindings/ActionRequestTrigger.ts | 7 + sdk/base/lib/osBindings/ActionVisibility.ts | 6 + .../lib/osBindings/AddAdminParams.ts | 0 .../lib/osBindings/AddAssetParams.ts | 0 .../lib/osBindings/AddPackageParams.ts | 0 .../lib/osBindings/AddSslOptions.ts | 0 .../lib/osBindings/AddVersionParams.ts | 0 sdk/{ => base}/lib/osBindings/AddressInfo.ts | 0 sdk/{ => base}/lib/osBindings/Alerts.ts | 0 sdk/{ => base}/lib/osBindings/Algorithm.ts | 0 .../lib/osBindings/AllPackageData.ts | 0 sdk/base/lib/osBindings/AllowedStatuses.ts | 3 + sdk/{ => base}/lib/osBindings/AlpnInfo.ts | 0 sdk/{ => base}/lib/osBindings/AnySignature.ts | 0 .../lib/osBindings/AnySigningKey.ts | 0 .../lib/osBindings/AnyVerifyingKey.ts | 0 sdk/{ => base}/lib/osBindings/ApiState.ts | 0 sdk/{ => base}/lib/osBindings/AttachParams.ts | 0 .../lib/osBindings/BackupProgress.ts | 0 .../lib/osBindings/BackupTargetFS.ts | 0 sdk/{ => base}/lib/osBindings/Base64.ts | 0 sdk/base/lib/osBindings/BindId.ts | 4 + sdk/{ => base}/lib/osBindings/BindInfo.ts | 2 +- sdk/{ => base}/lib/osBindings/BindOptions.ts | 0 sdk/{ => base}/lib/osBindings/BindParams.ts | 0 .../lib/osBindings/Blake3Commitment.ts | 0 sdk/{ => base}/lib/osBindings/BlockDev.ts | 0 sdk/{ => base}/lib/osBindings/CallbackId.ts | 0 sdk/{ => base}/lib/osBindings/Category.ts | 0 .../lib/osBindings/CheckDependenciesParam.ts | 0 .../lib/osBindings/CheckDependenciesResult.ts | 9 +- sdk/{ => base}/lib/osBindings/Cifs.ts | 0 .../osBindings/ClearActionRequestsParams.ts | 5 + sdk/base/lib/osBindings/ClearActionsParams.ts | 4 + .../lib/osBindings/ClearBindingsParams.ts | 4 + .../lib/osBindings/ClearCallbacksParams.ts | 3 + .../ClearServiceInterfacesParams.ts | 4 + sdk/{ => base}/lib/osBindings/ContactInfo.ts | 0 .../osBindings/CreateSubcontainerFsParams.ts | 0 .../lib/osBindings/CurrentDependencies.ts | 0 .../lib/osBindings/CurrentDependencyInfo.ts | 1 - sdk/{ => base}/lib/osBindings/DataUrl.ts | 0 sdk/{ => base}/lib/osBindings/DepInfo.ts | 0 sdk/{ => base}/lib/osBindings/Dependencies.ts | 0 .../lib/osBindings/DependencyKind.ts | 0 .../lib/osBindings/DependencyMetadata.ts | 0 .../lib/osBindings/DependencyRequirement.ts | 0 sdk/{ => base}/lib/osBindings/Description.ts | 0 .../osBindings/DestroySubcontainerFsParams.ts | 0 sdk/{ => base}/lib/osBindings/Duration.ts | 0 sdk/{ => base}/lib/osBindings/EchoParams.ts | 0 .../lib/osBindings/EncryptedWire.ts | 0 .../lib/osBindings/ExportActionParams.ts | 7 +- .../ExportServiceInterfaceParams.ts | 0 .../osBindings/ExposeForDependentsParams.ts | 0 sdk/{ => base}/lib/osBindings/FullIndex.ts | 0 sdk/{ => base}/lib/osBindings/FullProgress.ts | 0 .../lib/osBindings/GetActionInputParams.ts} | 3 +- .../lib/osBindings/GetHostInfoParams.ts | 0 .../lib/osBindings/GetOsAssetParams.ts | 0 .../lib/osBindings/GetOsVersionParams.ts | 0 .../lib/osBindings/GetPackageParams.ts | 0 .../lib/osBindings/GetPackageResponse.ts | 0 .../lib/osBindings/GetPackageResponseFull.ts | 0 .../lib/osBindings/GetPrimaryUrlParams.ts | 0 .../osBindings/GetServiceInterfaceParams.ts | 0 .../osBindings/GetServicePortForwardParams.ts | 0 .../lib/osBindings/GetSslCertificateParams.ts | 0 .../lib/osBindings/GetSslKeyParams.ts | 0 .../lib/osBindings/GetStoreParams.ts | 0 .../lib/osBindings/GetSystemSmtpParams.ts | 0 sdk/{ => base}/lib/osBindings/Governor.ts | 0 sdk/{ => base}/lib/osBindings/Guid.ts | 0 .../lib/osBindings/HardwareRequirements.ts | 0 .../lib/osBindings/HealthCheckId.ts | 0 sdk/{ => base}/lib/osBindings/Host.ts | 0 sdk/{ => base}/lib/osBindings/HostAddress.ts | 0 sdk/{ => base}/lib/osBindings/HostId.ts | 0 sdk/{ => base}/lib/osBindings/HostKind.ts | 0 sdk/{ => base}/lib/osBindings/HostnameInfo.ts | 0 sdk/{ => base}/lib/osBindings/Hosts.ts | 0 sdk/{ => base}/lib/osBindings/ImageConfig.ts | 0 sdk/{ => base}/lib/osBindings/ImageId.ts | 0 .../lib/osBindings/ImageMetadata.ts | 0 sdk/{ => base}/lib/osBindings/ImageSource.ts | 0 .../lib/osBindings/InitProgressRes.ts | 0 .../lib/osBindings/InstallParams.ts | 0 .../lib/osBindings/InstalledState.ts | 0 .../lib/osBindings/InstalledVersionParams.ts | 0 .../lib/osBindings/InstallingInfo.ts | 0 .../lib/osBindings/InstallingState.ts | 0 sdk/{ => base}/lib/osBindings/IpHostname.ts | 0 sdk/{ => base}/lib/osBindings/IpInfo.ts | 0 sdk/{ => base}/lib/osBindings/LanInfo.ts | 0 .../osBindings/ListServiceInterfacesParams.ts | 0 .../osBindings/ListVersionSignersParams.ts | 0 sdk/{ => base}/lib/osBindings/LoginParams.ts | 0 sdk/{ => base}/lib/osBindings/MainStatus.ts | 14 +- sdk/{ => base}/lib/osBindings/Manifest.ts | 1 - .../lib/osBindings/MaybeUtf8String.ts | 0 .../lib/osBindings/MerkleArchiveCommitment.ts | 0 sdk/{ => base}/lib/osBindings/MountParams.ts | 0 sdk/{ => base}/lib/osBindings/MountTarget.ts | 0 .../lib/osBindings/NamedHealthCheckResult.ts | 0 .../lib/osBindings/NamedProgress.ts | 0 .../lib/osBindings/OnionHostname.ts | 0 sdk/{ => base}/lib/osBindings/OsIndex.ts | 0 .../lib/osBindings/OsVersionInfo.ts | 0 .../lib/osBindings/OsVersionInfoMap.ts | 0 .../lib/osBindings/PackageDataEntry.ts | 6 +- .../lib/osBindings/PackageDetailLevel.ts | 0 sdk/{ => base}/lib/osBindings/PackageId.ts | 0 sdk/{ => base}/lib/osBindings/PackageIndex.ts | 0 sdk/{ => base}/lib/osBindings/PackageInfo.ts | 0 .../lib/osBindings/PackageInfoShort.ts | 0 sdk/{ => base}/lib/osBindings/PackageState.ts | 0 .../lib/osBindings/PackageVersionInfo.ts | 0 sdk/{ => base}/lib/osBindings/PasswordType.ts | 0 sdk/{ => base}/lib/osBindings/PathOrUrl.ts | 0 sdk/{ => base}/lib/osBindings/ProcedureId.ts | 0 sdk/{ => base}/lib/osBindings/Progress.ts | 0 sdk/{ => base}/lib/osBindings/Public.ts | 0 .../lib/osBindings/RecoverySource.ts | 0 .../lib/osBindings/RegistryAsset.ts | 0 sdk/{ => base}/lib/osBindings/RegistryInfo.ts | 0 .../lib/osBindings/RemoveVersionParams.ts | 0 .../lib/osBindings/ReplayId.ts} | 2 +- .../lib/osBindings/RequestActionParams.ts | 15 + .../lib/osBindings/RequestCommitment.ts | 0 .../lib/osBindings/RunActionParams.ts} | 2 +- sdk/{ => base}/lib/osBindings/Security.ts | 0 sdk/{ => base}/lib/osBindings/ServerInfo.ts | 0 sdk/{ => base}/lib/osBindings/ServerSpecs.ts | 0 sdk/{ => base}/lib/osBindings/ServerStatus.ts | 0 .../lib/osBindings/ServiceInterface.ts | 0 .../lib/osBindings/ServiceInterfaceId.ts | 0 .../lib/osBindings/ServiceInterfaceType.ts | 0 sdk/{ => base}/lib/osBindings/Session.ts | 0 sdk/{ => base}/lib/osBindings/SessionList.ts | 0 sdk/{ => base}/lib/osBindings/Sessions.ts | 0 .../lib/osBindings/SetDataVersionParams.ts | 0 .../lib/osBindings/SetDependenciesParams.ts | 2 - sdk/{ => base}/lib/osBindings/SetHealth.ts | 0 .../lib/osBindings/SetMainStatus.ts | 0 .../lib/osBindings/SetMainStatusStatus.ts | 0 .../lib/osBindings/SetStoreParams.ts | 0 .../lib/osBindings/SetupExecuteParams.ts | 0 .../lib/osBindings/SetupProgress.ts | 0 sdk/{ => base}/lib/osBindings/SetupResult.ts | 0 .../lib/osBindings/SetupStatusRes.ts | 0 .../lib/osBindings/SignAssetParams.ts | 0 sdk/{ => base}/lib/osBindings/SignerInfo.ts | 0 sdk/{ => base}/lib/osBindings/SmtpValue.ts | 0 sdk/{ => base}/lib/osBindings/StartStop.ts | 0 .../lib/osBindings/UpdatingState.ts | 0 .../lib/osBindings/VerifyCifsParams.ts | 0 sdk/{ => base}/lib/osBindings/Version.ts | 0 .../lib/osBindings/VersionSignerParams.ts | 0 sdk/{ => base}/lib/osBindings/VolumeId.ts | 0 sdk/{ => base}/lib/osBindings/WifiInfo.ts | 0 sdk/{ => base}/lib/osBindings/index.ts | 21 +- sdk/{ => base}/lib/s9pk/index.ts | 0 .../s9pk/merkleArchive/directoryContents.ts | 0 .../lib/s9pk/merkleArchive/fileContents.ts | 0 .../lib/s9pk/merkleArchive/index.ts | 0 .../lib/s9pk/merkleArchive/varint.ts | 2 +- sdk/{ => base}/lib/test/exverList.test.ts | 0 sdk/{ => base}/lib/test/graph.test.ts | 2 +- .../lib/test/inputSpecTypes.test.ts} | 15 +- .../lib/test/startosTypeValidation.test.ts | 34 +- .../lib/test/util.deepMerge.test.ts | 12 +- .../lib/test/util.getNetworkInterface.test.ts | 0 sdk/base/lib/types.ts | 297 ++ sdk/base/lib/types/ManifestTypes.ts | 159 + sdk/{ => base}/lib/util/GetSystemSmtp.ts | 4 +- sdk/{ => base}/lib/util/Hostname.ts | 0 .../store => base/lib/util}/PathBuilder.ts | 0 sdk/base/lib/util/asError.ts | 9 + sdk/{ => base}/lib/util/deepEqual.ts | 0 sdk/base/lib/util/deepMerge.ts | 86 + sdk/{ => base}/lib/util/getDefaultString.ts | 2 +- sdk/{ => base}/lib/util/getRandomCharInSet.ts | 0 sdk/{ => base}/lib/util/getRandomString.ts | 2 +- .../lib/util/getServiceInterface.ts | 6 +- .../lib/util/getServiceInterfaces.ts | 4 +- sdk/{ => base}/lib/util/graph.ts | 0 sdk/{ => base}/lib/util/inMs.test.ts | 0 sdk/{ => base}/lib/util/inMs.ts | 2 - sdk/base/lib/util/index.ts | 22 + sdk/base/lib/util/nullIfEmpty.ts | 10 + sdk/{ => base}/lib/util/once.ts | 0 sdk/{ => base}/lib/util/patterns.ts | 2 +- sdk/{ => base}/lib/util/regexes.ts | 0 sdk/{ => base}/lib/util/splitCommand.ts | 0 .../lib/util/stringFromStdErrOut.ts | 0 sdk/{ => base}/lib/util/typeHelpers.ts | 0 sdk/base/package-lock.json | 4677 ++++++++++++++++ sdk/base/package.json | 51 + .../tsconfig.json} | 8 +- sdk/lib/Dependency.ts | 18 - sdk/lib/StartSdk.ts | 805 --- sdk/lib/actions/createAction.ts | 89 - sdk/lib/actions/index.ts | 3 - sdk/lib/actions/setupActions.ts | 29 - sdk/lib/backup/Backups.ts | 209 - sdk/lib/backup/setupBackups.ts | 45 - sdk/lib/config/configDependencies.ts | 28 - sdk/lib/config/index.ts | 3 - sdk/lib/config/setupConfig.ts | 87 - sdk/lib/dependencies/DependencyConfig.ts | 39 - sdk/lib/dependencies/setupDependencyConfig.ts | 22 - sdk/lib/health/HealthReceipt.ts | 4 - sdk/lib/health/index.ts | 3 - sdk/lib/interfaces/setupInterfaces.ts | 23 - sdk/lib/manifest/ManifestTypes.ts | 84 - sdk/lib/manifest/index.ts | 2 - sdk/lib/store/setupExposeStore.ts | 12 - sdk/lib/test/setupDependencyConfig.test.ts | 27 - sdk/lib/types.ts | 523 -- sdk/lib/util/asError.ts | 6 - sdk/lib/util/deepMerge.ts | 17 - sdk/lib/util/fileHelper.ts | 150 - sdk/lib/util/index.browser.ts | 10 - sdk/lib/util/index.ts | 16 - sdk/lib/util/nullIfEmpty.ts | 12 - sdk/package-lock.json | 4674 +--------------- sdk/package/.gitignore | 5 + sdk/package/.npmignore | 1 + sdk/package/.prettierignore | 1 + sdk/package/LICENSE | 21 + sdk/{ => package}/README.md | 0 sdk/package/jest.config.js | 8 + sdk/package/lib/StartSdk.ts | 1410 +++++ sdk/package/lib/backup/Backups.ts | 208 + sdk/{ => package}/lib/backup/index.ts | 1 - sdk/package/lib/backup/setupBackups.ts | 39 + sdk/{ => package}/lib/health/HealthCheck.ts | 8 +- .../lib/health/checkFns/HealthCheckResult.ts | 2 +- .../lib/health/checkFns/checkPortListening.ts | 4 +- .../lib/health/checkFns/checkWebUrl.ts | 4 +- .../lib/health/checkFns/index.ts | 0 .../lib/health/checkFns/runHealthScript.ts | 4 +- sdk/package/lib/health/index.ts | 1 + sdk/{ => package}/lib/index.ts | 42 +- sdk/{ => package}/lib/inits/index.ts | 0 sdk/{ => package}/lib/inits/setupInit.ts | 42 +- sdk/{ => package}/lib/inits/setupInstall.ts | 4 +- sdk/{ => package}/lib/inits/setupUninstall.ts | 4 +- .../lib/mainFn/CommandController.ts | 8 +- sdk/{ => package}/lib/mainFn/Daemon.ts | 4 +- sdk/{ => package}/lib/mainFn/Daemons.ts | 30 +- sdk/{ => package}/lib/mainFn/HealthDaemon.ts | 4 +- sdk/{ => package}/lib/mainFn/Mounts.ts | 2 +- sdk/{ => package}/lib/mainFn/index.ts | 12 +- .../lib/manifest/setupManifest.ts | 27 +- sdk/{ => package}/lib/store/getStore.ts | 6 +- sdk/package/lib/store/setupExposeStore.ts | 28 + .../lib/test/health.readyCheck.test.ts | 0 sdk/{ => package}/lib/test/host.test.ts | 6 +- .../lib/test/inputSpecBuilder.test.ts} | 52 +- sdk/{ => package}/lib/test/makeOutput.ts | 2 +- sdk/{ => package}/lib/test/output.sdk.ts | 0 sdk/{ => package}/lib/test/output.test.ts | 43 +- sdk/{ => package}/lib/test/store.test.ts | 65 +- sdk/{ => package}/lib/trigger/TriggerInput.ts | 2 +- .../lib/trigger/changeOnFirstSuccess.ts | 0 .../lib/trigger/cooldownTrigger.ts | 0 .../lib/trigger/defaultTrigger.ts | 0 sdk/{ => package}/lib/trigger/index.ts | 1 - sdk/{ => package}/lib/trigger/lastStatus.ts | 2 +- .../lib/trigger/successFailure.ts | 0 .../lib/util/GetSslCertificate.ts | 4 +- sdk/{ => package}/lib/util/SubContainer.ts | 8 +- sdk/package/lib/util/fileHelper.ts | 214 + sdk/package/lib/util/index.ts | 4 + sdk/{ => package}/lib/version/VersionGraph.ts | 13 +- sdk/{ => package}/lib/version/VersionInfo.ts | 25 +- sdk/{ => package}/lib/version/index.ts | 0 sdk/package/package-lock.json | 4706 +++++++++++++++++ sdk/{ => package}/package.json | 14 +- sdk/{ => package}/scripts/oldSpecToBuilder.ts | 30 +- sdk/package/tsconfig.json | 19 + sdk/tsconfig-cjs.json | 8 - sdk/tsconfig.json | 8 - web/README.md | 16 +- web/package-lock.json | 31 +- web/package.json | 2 +- .../src/app/pages/loading/loading.page.html | 2 +- .../backup-drives/backup-drives.component.ts | 12 +- .../ui/src/app/components/form.component.ts | 15 +- .../ui/src/app/components/form/control.ts | 4 +- .../app/components/form/filter-hidden.pipe.ts | 15 + .../form/form-array/form-array.component.ts | 4 +- .../form/form-color/form-color.component.ts | 4 +- .../form-control/form-control.component.html | 2 +- .../form-control/form-control.component.ts | 4 +- .../form-control/form-control.providers.ts | 4 +- .../form-datetime/form-datetime.component.ts | 4 +- .../form/form-group/form-group.component.html | 2 +- .../form/form-group/form-group.component.ts | 4 +- .../form-multiselect.component.ts | 4 +- .../form/form-number/form-number.component.ts | 4 +- .../form/form-object/form-object.component.ts | 4 +- .../form/form-select/form-select.component.ts | 4 +- .../form/form-text/form-text.component.ts | 4 +- .../form-textarea/form-textarea.component.ts | 4 +- .../form/form-toggle/form-toggle.component.ts | 7 +- .../form/form-union/form-union.component.ts | 6 +- .../ui/src/app/components/form/form.module.ts | 2 + .../ui/src/app/components/form/hint.pipe.ts | 4 +- ...p.component.ts => action-dep.component.ts} | 29 +- .../src/app/modals/action-input.component.ts | 208 + .../action-success/action-success.page.ts | 4 +- .../ui/src/app/modals/config.component.ts | 261 - .../marketplace-settings.page.ts | 4 +- .../modals/os-welcome/os-welcome.page.html | 5 +- .../app-actions-item.component.html | 7 +- .../app-actions/app-actions.page.html | 50 +- .../app-actions/app-actions.page.ts | 196 +- .../app-list-pkg/app-list-pkg.component.html | 4 +- .../app-list-pkg/app-list-pkg.component.ts | 10 +- .../app-properties/app-properties.page.ts | 2 +- .../apps-routes/app-show/app-show.page.html | 4 +- .../apps-routes/app-show/app-show.page.ts | 48 +- .../app-show-status.component.html | 8 +- .../app-show-status.component.ts | 29 +- .../app-show/pipes/to-buttons.pipe.ts | 35 +- .../app-show/pipes/to-health-checks.pipe.ts | 8 +- .../backing-up/backing-up.component.ts | 4 +- .../server-show/server-show.page.ts | 10 +- .../app/pages/server-routes/wifi/wifi.page.ts | 4 +- .../app/pipes/launchable/launchable.pipe.ts | 2 +- .../ui/src/app/services/action.service.ts | 159 + .../ui/src/app/services/api/api.fixures.ts | 215 +- .../ui/src/app/services/api/api.types.ts | 52 +- .../app/services/api/embassy-api.service.ts | 22 +- .../services/api/embassy-live-api.service.ts | 35 +- .../services/api/embassy-mock-api.service.ts | 50 +- .../ui/src/app/services/api/mock-patch.ts | 78 +- .../ui/src/app/services/config.service.ts | 2 +- .../ui/src/app/services/dep-error.service.ts | 13 +- .../ui/src/app/services/form.service.ts | 90 +- .../services/pkg-status-rendering.service.ts | 15 +- .../ui/src/app/util/configBuilderToSpec.ts | 6 +- .../ui/src/app/util/get-package-data.ts | 12 + 469 files changed, 16218 insertions(+), 10485 deletions(-) delete mode 100644 container-runtime/readme.md delete mode 100644 container-runtime/src/Interfaces/MakeEffects.ts rename core/{startos/src/util => models/src}/clap.rs (91%) create mode 100644 core/models/src/id/replay.rs delete mode 100644 core/startos/src/config/action.rs delete mode 100644 core/startos/src/config/mod.rs delete mode 100644 core/startos/src/config/util.rs delete mode 100644 core/startos/src/service/config.rs delete mode 100644 core/startos/src/service/dependencies.rs delete mode 100644 core/startos/src/service/effects/config.rs create mode 100644 sdk/base/.gitignore rename sdk/{ => base}/LICENSE (100%) create mode 100644 sdk/base/README.md rename sdk/{ => base}/jest.config.js (100%) create mode 100644 sdk/base/lib/Effects.ts create mode 100644 sdk/base/lib/actions/index.ts rename sdk/{lib/config => base/lib/actions/input}/builder/index.ts (51%) rename sdk/{lib/config/builder/config.ts => base/lib/actions/input/builder/inputSpec.ts} (73%) rename sdk/{lib/config => base/lib/actions/input}/builder/list.ts (74%) rename sdk/{lib/config => base/lib/actions/input}/builder/value.ts (72%) rename sdk/{lib/config => base/lib/actions/input}/builder/variants.ts (80%) create mode 100644 sdk/base/lib/actions/input/index.ts rename sdk/{lib/config/configConstants.ts => base/lib/actions/input/inputSpecConstants.ts} (72%) rename sdk/{lib/config/configTypes.ts => base/lib/actions/input/inputSpecTypes.ts} (74%) create mode 100644 sdk/base/lib/actions/setupActions.ts create mode 100644 sdk/base/lib/backup/Backups.ts create mode 100644 sdk/base/lib/backup/setupBackups.ts create mode 100644 sdk/base/lib/dependencies/Dependency.ts rename sdk/{ => base}/lib/dependencies/dependencies.ts (89%) rename sdk/{ => base}/lib/dependencies/index.ts (78%) create mode 100644 sdk/base/lib/dependencies/setupDependencies.ts rename sdk/{ => base}/lib/exver/exver.pegjs (100%) rename sdk/{ => base}/lib/exver/exver.ts (100%) rename sdk/{ => base}/lib/exver/index.ts (100%) rename sdk/{lib/index.browser.ts => base/lib/index.ts} (51%) rename sdk/{ => base}/lib/interfaces/AddressReceipt.ts (100%) rename sdk/{ => base}/lib/interfaces/Host.ts (82%) rename sdk/{ => base}/lib/interfaces/Origin.ts (89%) rename sdk/{ => base}/lib/interfaces/ServiceInterfaceBuilder.ts (90%) rename sdk/{ => base}/lib/interfaces/interfaceReceipt.ts (100%) create mode 100644 sdk/base/lib/interfaces/setupInterfaces.ts rename sdk/{ => base}/lib/osBindings/AcceptSigners.ts (100%) rename sdk/{ => base}/lib/osBindings/ActionId.ts (100%) rename sdk/{lib/osBindings/Status.ts => base/lib/osBindings/ActionInput.ts} (50%) rename sdk/{ => base}/lib/osBindings/ActionMetadata.ts (74%) create mode 100644 sdk/base/lib/osBindings/ActionRequest.ts rename sdk/{lib/osBindings/AllowedStatuses.ts => base/lib/osBindings/ActionRequestCondition.ts} (61%) create mode 100644 sdk/base/lib/osBindings/ActionRequestEntry.ts create mode 100644 sdk/base/lib/osBindings/ActionRequestInput.ts create mode 100644 sdk/base/lib/osBindings/ActionRequestTrigger.ts create mode 100644 sdk/base/lib/osBindings/ActionVisibility.ts rename sdk/{ => base}/lib/osBindings/AddAdminParams.ts (100%) rename sdk/{ => base}/lib/osBindings/AddAssetParams.ts (100%) rename sdk/{ => base}/lib/osBindings/AddPackageParams.ts (100%) rename sdk/{ => base}/lib/osBindings/AddSslOptions.ts (100%) rename sdk/{ => base}/lib/osBindings/AddVersionParams.ts (100%) rename sdk/{ => base}/lib/osBindings/AddressInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/Alerts.ts (100%) rename sdk/{ => base}/lib/osBindings/Algorithm.ts (100%) rename sdk/{ => base}/lib/osBindings/AllPackageData.ts (100%) create mode 100644 sdk/base/lib/osBindings/AllowedStatuses.ts rename sdk/{ => base}/lib/osBindings/AlpnInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/AnySignature.ts (100%) rename sdk/{ => base}/lib/osBindings/AnySigningKey.ts (100%) rename sdk/{ => base}/lib/osBindings/AnyVerifyingKey.ts (100%) rename sdk/{ => base}/lib/osBindings/ApiState.ts (100%) rename sdk/{ => base}/lib/osBindings/AttachParams.ts (100%) rename sdk/{ => base}/lib/osBindings/BackupProgress.ts (100%) rename sdk/{ => base}/lib/osBindings/BackupTargetFS.ts (100%) rename sdk/{ => base}/lib/osBindings/Base64.ts (100%) create mode 100644 sdk/base/lib/osBindings/BindId.ts rename sdk/{ => base}/lib/osBindings/BindInfo.ts (71%) rename sdk/{ => base}/lib/osBindings/BindOptions.ts (100%) rename sdk/{ => base}/lib/osBindings/BindParams.ts (100%) rename sdk/{ => base}/lib/osBindings/Blake3Commitment.ts (100%) rename sdk/{ => base}/lib/osBindings/BlockDev.ts (100%) rename sdk/{ => base}/lib/osBindings/CallbackId.ts (100%) rename sdk/{ => base}/lib/osBindings/Category.ts (100%) rename sdk/{ => base}/lib/osBindings/CheckDependenciesParam.ts (100%) rename sdk/{ => base}/lib/osBindings/CheckDependenciesResult.ts (62%) rename sdk/{ => base}/lib/osBindings/Cifs.ts (100%) create mode 100644 sdk/base/lib/osBindings/ClearActionRequestsParams.ts create mode 100644 sdk/base/lib/osBindings/ClearActionsParams.ts create mode 100644 sdk/base/lib/osBindings/ClearBindingsParams.ts create mode 100644 sdk/base/lib/osBindings/ClearCallbacksParams.ts create mode 100644 sdk/base/lib/osBindings/ClearServiceInterfacesParams.ts rename sdk/{ => base}/lib/osBindings/ContactInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/CreateSubcontainerFsParams.ts (100%) rename sdk/{ => base}/lib/osBindings/CurrentDependencies.ts (100%) rename sdk/{ => base}/lib/osBindings/CurrentDependencyInfo.ts (92%) rename sdk/{ => base}/lib/osBindings/DataUrl.ts (100%) rename sdk/{ => base}/lib/osBindings/DepInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/Dependencies.ts (100%) rename sdk/{ => base}/lib/osBindings/DependencyKind.ts (100%) rename sdk/{ => base}/lib/osBindings/DependencyMetadata.ts (100%) rename sdk/{ => base}/lib/osBindings/DependencyRequirement.ts (100%) rename sdk/{ => base}/lib/osBindings/Description.ts (100%) rename sdk/{ => base}/lib/osBindings/DestroySubcontainerFsParams.ts (100%) rename sdk/{ => base}/lib/osBindings/Duration.ts (100%) rename sdk/{ => base}/lib/osBindings/EchoParams.ts (100%) rename sdk/{ => base}/lib/osBindings/EncryptedWire.ts (100%) rename sdk/{ => base}/lib/osBindings/ExportActionParams.ts (58%) rename sdk/{ => base}/lib/osBindings/ExportServiceInterfaceParams.ts (100%) rename sdk/{ => base}/lib/osBindings/ExposeForDependentsParams.ts (100%) rename sdk/{ => base}/lib/osBindings/FullIndex.ts (100%) rename sdk/{ => base}/lib/osBindings/FullProgress.ts (100%) rename sdk/{lib/osBindings/GetConfiguredParams.ts => base/lib/osBindings/GetActionInputParams.ts} (55%) rename sdk/{ => base}/lib/osBindings/GetHostInfoParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetOsAssetParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetOsVersionParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetPackageParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetPackageResponse.ts (100%) rename sdk/{ => base}/lib/osBindings/GetPackageResponseFull.ts (100%) rename sdk/{ => base}/lib/osBindings/GetPrimaryUrlParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetServiceInterfaceParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetServicePortForwardParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetSslCertificateParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetSslKeyParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetStoreParams.ts (100%) rename sdk/{ => base}/lib/osBindings/GetSystemSmtpParams.ts (100%) rename sdk/{ => base}/lib/osBindings/Governor.ts (100%) rename sdk/{ => base}/lib/osBindings/Guid.ts (100%) rename sdk/{ => base}/lib/osBindings/HardwareRequirements.ts (100%) rename sdk/{ => base}/lib/osBindings/HealthCheckId.ts (100%) rename sdk/{ => base}/lib/osBindings/Host.ts (100%) rename sdk/{ => base}/lib/osBindings/HostAddress.ts (100%) rename sdk/{ => base}/lib/osBindings/HostId.ts (100%) rename sdk/{ => base}/lib/osBindings/HostKind.ts (100%) rename sdk/{ => base}/lib/osBindings/HostnameInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/Hosts.ts (100%) rename sdk/{ => base}/lib/osBindings/ImageConfig.ts (100%) rename sdk/{ => base}/lib/osBindings/ImageId.ts (100%) rename sdk/{ => base}/lib/osBindings/ImageMetadata.ts (100%) rename sdk/{ => base}/lib/osBindings/ImageSource.ts (100%) rename sdk/{ => base}/lib/osBindings/InitProgressRes.ts (100%) rename sdk/{ => base}/lib/osBindings/InstallParams.ts (100%) rename sdk/{ => base}/lib/osBindings/InstalledState.ts (100%) rename sdk/{ => base}/lib/osBindings/InstalledVersionParams.ts (100%) rename sdk/{ => base}/lib/osBindings/InstallingInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/InstallingState.ts (100%) rename sdk/{ => base}/lib/osBindings/IpHostname.ts (100%) rename sdk/{ => base}/lib/osBindings/IpInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/LanInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/ListServiceInterfacesParams.ts (100%) rename sdk/{ => base}/lib/osBindings/ListVersionSignersParams.ts (100%) rename sdk/{ => base}/lib/osBindings/LoginParams.ts (100%) rename sdk/{ => base}/lib/osBindings/MainStatus.ts (69%) rename sdk/{ => base}/lib/osBindings/Manifest.ts (98%) rename sdk/{ => base}/lib/osBindings/MaybeUtf8String.ts (100%) rename sdk/{ => base}/lib/osBindings/MerkleArchiveCommitment.ts (100%) rename sdk/{ => base}/lib/osBindings/MountParams.ts (100%) rename sdk/{ => base}/lib/osBindings/MountTarget.ts (100%) rename sdk/{ => base}/lib/osBindings/NamedHealthCheckResult.ts (100%) rename sdk/{ => base}/lib/osBindings/NamedProgress.ts (100%) rename sdk/{ => base}/lib/osBindings/OnionHostname.ts (100%) rename sdk/{ => base}/lib/osBindings/OsIndex.ts (100%) rename sdk/{ => base}/lib/osBindings/OsVersionInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/OsVersionInfoMap.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageDataEntry.ts (83%) rename sdk/{ => base}/lib/osBindings/PackageDetailLevel.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageId.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageIndex.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageInfoShort.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageState.ts (100%) rename sdk/{ => base}/lib/osBindings/PackageVersionInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/PasswordType.ts (100%) rename sdk/{ => base}/lib/osBindings/PathOrUrl.ts (100%) rename sdk/{ => base}/lib/osBindings/ProcedureId.ts (100%) rename sdk/{ => base}/lib/osBindings/Progress.ts (100%) rename sdk/{ => base}/lib/osBindings/Public.ts (100%) rename sdk/{ => base}/lib/osBindings/RecoverySource.ts (100%) rename sdk/{ => base}/lib/osBindings/RegistryAsset.ts (100%) rename sdk/{ => base}/lib/osBindings/RegistryInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/RemoveVersionParams.ts (100%) rename sdk/{lib/osBindings/SetConfigured.ts => base/lib/osBindings/ReplayId.ts} (67%) create mode 100644 sdk/base/lib/osBindings/RequestActionParams.ts rename sdk/{ => base}/lib/osBindings/RequestCommitment.ts (100%) rename sdk/{lib/osBindings/ExecuteAction.ts => base/lib/osBindings/RunActionParams.ts} (88%) rename sdk/{ => base}/lib/osBindings/Security.ts (100%) rename sdk/{ => base}/lib/osBindings/ServerInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/ServerSpecs.ts (100%) rename sdk/{ => base}/lib/osBindings/ServerStatus.ts (100%) rename sdk/{ => base}/lib/osBindings/ServiceInterface.ts (100%) rename sdk/{ => base}/lib/osBindings/ServiceInterfaceId.ts (100%) rename sdk/{ => base}/lib/osBindings/ServiceInterfaceType.ts (100%) rename sdk/{ => base}/lib/osBindings/Session.ts (100%) rename sdk/{ => base}/lib/osBindings/SessionList.ts (100%) rename sdk/{ => base}/lib/osBindings/Sessions.ts (100%) rename sdk/{ => base}/lib/osBindings/SetDataVersionParams.ts (100%) rename sdk/{ => base}/lib/osBindings/SetDependenciesParams.ts (82%) rename sdk/{ => base}/lib/osBindings/SetHealth.ts (100%) rename sdk/{ => base}/lib/osBindings/SetMainStatus.ts (100%) rename sdk/{ => base}/lib/osBindings/SetMainStatusStatus.ts (100%) rename sdk/{ => base}/lib/osBindings/SetStoreParams.ts (100%) rename sdk/{ => base}/lib/osBindings/SetupExecuteParams.ts (100%) rename sdk/{ => base}/lib/osBindings/SetupProgress.ts (100%) rename sdk/{ => base}/lib/osBindings/SetupResult.ts (100%) rename sdk/{ => base}/lib/osBindings/SetupStatusRes.ts (100%) rename sdk/{ => base}/lib/osBindings/SignAssetParams.ts (100%) rename sdk/{ => base}/lib/osBindings/SignerInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/SmtpValue.ts (100%) rename sdk/{ => base}/lib/osBindings/StartStop.ts (100%) rename sdk/{ => base}/lib/osBindings/UpdatingState.ts (100%) rename sdk/{ => base}/lib/osBindings/VerifyCifsParams.ts (100%) rename sdk/{ => base}/lib/osBindings/Version.ts (100%) rename sdk/{ => base}/lib/osBindings/VersionSignerParams.ts (100%) rename sdk/{ => base}/lib/osBindings/VolumeId.ts (100%) rename sdk/{ => base}/lib/osBindings/WifiInfo.ts (100%) rename sdk/{ => base}/lib/osBindings/index.ts (88%) rename sdk/{ => base}/lib/s9pk/index.ts (100%) rename sdk/{ => base}/lib/s9pk/merkleArchive/directoryContents.ts (100%) rename sdk/{ => base}/lib/s9pk/merkleArchive/fileContents.ts (100%) rename sdk/{ => base}/lib/s9pk/merkleArchive/index.ts (100%) rename sdk/{ => base}/lib/s9pk/merkleArchive/varint.ts (96%) rename sdk/{ => base}/lib/test/exverList.test.ts (100%) rename sdk/{ => base}/lib/test/graph.test.ts (99%) rename sdk/{lib/test/configTypes.test.ts => base/lib/test/inputSpecTypes.test.ts} (64%) rename sdk/{ => base}/lib/test/startosTypeValidation.test.ts (81%) rename sdk/{ => base}/lib/test/util.deepMerge.test.ts (70%) rename sdk/{ => base}/lib/test/util.getNetworkInterface.test.ts (100%) create mode 100644 sdk/base/lib/types.ts create mode 100644 sdk/base/lib/types/ManifestTypes.ts rename sdk/{ => base}/lib/util/GetSystemSmtp.ts (90%) rename sdk/{ => base}/lib/util/Hostname.ts (100%) rename sdk/{lib/store => base/lib/util}/PathBuilder.ts (100%) create mode 100644 sdk/base/lib/util/asError.ts rename sdk/{ => base}/lib/util/deepEqual.ts (100%) create mode 100644 sdk/base/lib/util/deepMerge.ts rename sdk/{ => base}/lib/util/getDefaultString.ts (79%) rename sdk/{ => base}/lib/util/getRandomCharInSet.ts (100%) rename sdk/{ => base}/lib/util/getRandomString.ts (79%) rename sdk/{ => base}/lib/util/getServiceInterface.ts (98%) rename sdk/{ => base}/lib/util/getServiceInterfaces.ts (96%) rename sdk/{ => base}/lib/util/graph.ts (100%) rename sdk/{ => base}/lib/util/inMs.test.ts (100%) rename sdk/{ => base}/lib/util/inMs.ts (95%) create mode 100644 sdk/base/lib/util/index.ts create mode 100644 sdk/base/lib/util/nullIfEmpty.ts rename sdk/{ => base}/lib/util/once.ts (100%) rename sdk/{ => base}/lib/util/patterns.ts (96%) rename sdk/{ => base}/lib/util/regexes.ts (100%) rename sdk/{ => base}/lib/util/splitCommand.ts (100%) rename sdk/{ => base}/lib/util/stringFromStdErrOut.ts (100%) rename sdk/{ => base}/lib/util/typeHelpers.ts (100%) create mode 100644 sdk/base/package-lock.json create mode 100644 sdk/base/package.json rename sdk/{tsconfig-base.json => base/tsconfig.json} (78%) delete mode 100644 sdk/lib/Dependency.ts delete mode 100644 sdk/lib/StartSdk.ts delete mode 100644 sdk/lib/actions/createAction.ts delete mode 100644 sdk/lib/actions/index.ts delete mode 100644 sdk/lib/actions/setupActions.ts delete mode 100644 sdk/lib/backup/Backups.ts delete mode 100644 sdk/lib/backup/setupBackups.ts delete mode 100644 sdk/lib/config/configDependencies.ts delete mode 100644 sdk/lib/config/index.ts delete mode 100644 sdk/lib/config/setupConfig.ts delete mode 100644 sdk/lib/dependencies/DependencyConfig.ts delete mode 100644 sdk/lib/dependencies/setupDependencyConfig.ts delete mode 100644 sdk/lib/health/HealthReceipt.ts delete mode 100644 sdk/lib/health/index.ts delete mode 100644 sdk/lib/interfaces/setupInterfaces.ts delete mode 100644 sdk/lib/manifest/ManifestTypes.ts delete mode 100644 sdk/lib/manifest/index.ts delete mode 100644 sdk/lib/store/setupExposeStore.ts delete mode 100644 sdk/lib/test/setupDependencyConfig.test.ts delete mode 100644 sdk/lib/types.ts delete mode 100644 sdk/lib/util/asError.ts delete mode 100644 sdk/lib/util/deepMerge.ts delete mode 100644 sdk/lib/util/fileHelper.ts delete mode 100644 sdk/lib/util/index.browser.ts delete mode 100644 sdk/lib/util/index.ts delete mode 100644 sdk/lib/util/nullIfEmpty.ts create mode 100644 sdk/package/.gitignore create mode 100644 sdk/package/.npmignore create mode 100644 sdk/package/.prettierignore create mode 100644 sdk/package/LICENSE rename sdk/{ => package}/README.md (100%) create mode 100644 sdk/package/jest.config.js create mode 100644 sdk/package/lib/StartSdk.ts create mode 100644 sdk/package/lib/backup/Backups.ts rename sdk/{ => package}/lib/backup/index.ts (97%) create mode 100644 sdk/package/lib/backup/setupBackups.ts rename sdk/{ => package}/lib/health/HealthCheck.ts (88%) rename sdk/{ => package}/lib/health/checkFns/HealthCheckResult.ts (63%) rename sdk/{ => package}/lib/health/checkFns/checkPortListening.ts (94%) rename sdk/{ => package}/lib/health/checkFns/checkWebUrl.ts (90%) rename sdk/{ => package}/lib/health/checkFns/index.ts (100%) rename sdk/{ => package}/lib/health/checkFns/runHealthScript.ts (91%) create mode 100644 sdk/package/lib/health/index.ts rename sdk/{ => package}/lib/index.ts (58%) rename sdk/{ => package}/lib/inits/index.ts (100%) rename sdk/{ => package}/lib/inits/setupInit.ts (59%) rename sdk/{ => package}/lib/inits/setupInstall.ts (81%) rename sdk/{ => package}/lib/inits/setupUninstall.ts (86%) rename sdk/{ => package}/lib/mainFn/CommandController.ts (94%) rename sdk/{ => package}/lib/mainFn/Daemon.ts (96%) rename sdk/{ => package}/lib/mainFn/Daemons.ts (87%) rename sdk/{ => package}/lib/mainFn/HealthDaemon.ts (97%) rename sdk/{ => package}/lib/mainFn/Mounts.ts (98%) rename sdk/{ => package}/lib/mainFn/index.ts (77%) rename sdk/{ => package}/lib/manifest/setupManifest.ts (75%) rename sdk/{ => package}/lib/store/getStore.ts (90%) create mode 100644 sdk/package/lib/store/setupExposeStore.ts rename sdk/{ => package}/lib/test/health.readyCheck.test.ts (100%) rename sdk/{ => package}/lib/test/host.test.ts (78%) rename sdk/{lib/test/configBuilder.test.ts => package/lib/test/inputSpecBuilder.test.ts} (94%) rename sdk/{ => package}/lib/test/makeOutput.ts (99%) rename sdk/{ => package}/lib/test/output.sdk.ts (100%) rename sdk/{ => package}/lib/test/output.test.ts (72%) rename sdk/{ => package}/lib/test/store.test.ts (52%) rename sdk/{ => package}/lib/trigger/TriggerInput.ts (52%) rename sdk/{ => package}/lib/trigger/changeOnFirstSuccess.ts (100%) rename sdk/{ => package}/lib/trigger/cooldownTrigger.ts (100%) rename sdk/{ => package}/lib/trigger/defaultTrigger.ts (100%) rename sdk/{ => package}/lib/trigger/index.ts (83%) rename sdk/{ => package}/lib/trigger/lastStatus.ts (93%) rename sdk/{ => package}/lib/trigger/successFailure.ts (100%) rename sdk/{ => package}/lib/util/GetSslCertificate.ts (91%) rename sdk/{ => package}/lib/util/SubContainer.ts (98%) create mode 100644 sdk/package/lib/util/fileHelper.ts create mode 100644 sdk/package/lib/util/index.ts rename sdk/{ => package}/lib/version/VersionGraph.ts (94%) rename sdk/{ => package}/lib/version/VersionInfo.ts (68%) rename sdk/{ => package}/lib/version/index.ts (100%) create mode 100644 sdk/package/package-lock.json rename sdk/{ => package}/package.json (80%) rename sdk/{ => package}/scripts/oldSpecToBuilder.ts (94%) create mode 100644 sdk/package/tsconfig.json delete mode 100644 sdk/tsconfig-cjs.json delete mode 100644 sdk/tsconfig.json create mode 100644 web/projects/ui/src/app/components/form/filter-hidden.pipe.ts rename web/projects/ui/src/app/modals/{config-dep.component.ts => action-dep.component.ts} (78%) create mode 100644 web/projects/ui/src/app/modals/action-input.component.ts delete mode 100644 web/projects/ui/src/app/modals/config.component.ts create mode 100644 web/projects/ui/src/app/services/action.service.ts diff --git a/.github/workflows/startos-iso.yaml b/.github/workflows/startos-iso.yaml index 3497624e7..3490936b4 100644 --- a/.github/workflows/startos-iso.yaml +++ b/.github/workflows/startos-iso.yaml @@ -187,11 +187,14 @@ jobs: run: | mkdir -p web/node_modules mkdir -p web/dist/raw + 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 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/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 d085117b2..d7ff9ec6c 100644 --- a/Makefile +++ b/Makefile @@ -17,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/index.js web/patchdb-ui-seed.json sdk/dist/package.json +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) @@ -48,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) @@ -95,7 +95,7 @@ test: | test-core test-sdk test-container-runtime test-core: $(CORE_SRC) $(ENVIRONMENT_FILE) ./core/run-tests.sh -test-sdk: $(shell git ls-files sdk) sdk/lib/osBindings/index.ts +test-sdk: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts cd sdk && make test 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 @@ -104,6 +104,9 @@ test-container-runtime: container-runtime/node_modules/.package-lock.json $(shel cli: cd core && ./install-cli.sh +registry: + cd core && ./build-registrybox.sh + deb: results/$(BASENAME).deb debian/control: build/lib/depends build/lib/conflicts @@ -223,20 +226,22 @@ container-runtime/node_modules/.package-lock.json: container-runtime/package.jso npm --prefix container-runtime ci touch container-runtime/node_modules/.package-lock.json -sdk/lib/osBindings/index.ts: core/startos/bindings/index.ts - rsync -ac --delete core/startos/bindings/ sdk/lib/osBindings/ - touch sdk/lib/osBindings/index.ts +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/index.ts: $(shell git ls-files core) $(ENVIRONMENT_FILE) rm -rf core/startos/bindings ./core/build-ts.sh - 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 + 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/package.json: $(shell git ls-files sdk) sdk/lib/osBindings/index.ts +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/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json @@ -272,11 +277,11 @@ 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/package.json +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/.updated: patch-db/client/dist/index.js sdk/dist/package.json 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 @@ -329,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/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 3338560c9..022d697ab 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,7 +35,8 @@ let hostSystemId = 0 export type EffectContext = { procedureId: string | null - callbacks: CallbackHolder | null + callbacks?: CallbackHolder + constRetry: () => void } const rpcRoundFor = @@ -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,9 +101,49 @@ 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, @@ -138,16 +179,6 @@ function makeEffects(context: EffectContext): Effects { > }, }, - executeAction(...[options]: Parameters) { - return rpcRound("execute-action", options) as ReturnType< - T.Effects["executeAction"] - > - }, - exportAction(...[options]: Parameters) { - return rpcRound("export-action", options) as ReturnType< - T.Effects["exportAction"] - > - }, exportServiceInterface: (( ...[options]: Parameters ) => { @@ -162,11 +193,6 @@ function makeEffects(context: EffectContext): Effects { T.Effects["exposeForDependents"] > }, - getConfigured(...[]: Parameters) { - return rpcRound("get-configured", {}) as ReturnType< - T.Effects["getConfigured"] - > - }, getContainerIp(...[]: Parameters) { return rpcRound("get-container-ip", {}) as ReturnType< T.Effects["getContainerIp"] @@ -230,19 +256,9 @@ function makeEffects(context: EffectContext): Effects { mount(...[options]: Parameters) { return rpcRound("mount", options) as ReturnType }, - clearActions(...[]: Parameters) { - return rpcRound("clear-actions", {}) as ReturnType< - T.Effects["clearActions"] - > - }, restart(...[]: Parameters) { return rpcRound("restart", {}) as ReturnType }, - setConfigured(...[configured]: Parameters) { - return rpcRound("set-configured", { configured }) as ReturnType< - T.Effects["setConfigured"] - > - }, setDependencies( dependencies: Parameters[0], ): ReturnType { @@ -299,18 +315,3 @@ function makeEffects(context: EffectContext): Effects { } 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 458b1af0f..de2bb7f1a 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, unNestPath } from "../Models/JsonPath" -import { RunningMain, System } from "../Interfaces/System" -import { - MakeMainEffects, - MakeProcedureEffects, -} from "../Interfaces/MakeEffects" +import { System } from "../Interfaces/System" +import { makeEffects } from "./EffectCreator" type MaybePromise = T | Promise export const matchRpcResult = anyOf( object({ result: any }), @@ -55,33 +52,39 @@ 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"], - ), -}) -const sandboxRunType = object({ - id: idType, - method: literal("sandbox"), - params: object( - { - id: string, - procedure: string, - input: any, - timeout: number, - }, - ["timeout"], - ), -}) +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({ @@ -89,29 +92,44 @@ const callbackType = object({ 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) @@ -144,8 +162,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)) { @@ -212,18 +229,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 { @@ -231,40 +263,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) - const input = params.input - const timeout = params.timeout - const result = getResult(procedure, system, effects, timeout, input) + 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) - const result = getResult( + const { input, timeout, id: procedureId } = params + const result = this.getResult( procedure, system, - effects, - params.input, - params.input, + procedureId, + timeout, + input, ) return handleRpc(id, result) }) .when(callbackType, async ({ params: { callback, args } }) => { - this.system.callCallback(callback, args) + this.callCallback(callback, 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 })), @@ -284,7 +325,20 @@ export class RpcListener { (async () => { if (!this._system) { const system = await this.getDependencies.system() - await system.containerInit() + 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 })), @@ -316,17 +370,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( @@ -345,98 +402,84 @@ export class RpcListener { } }) } -} -function getResult( - procedure: typeof jsonPath._TYPE, - system: System, - effects: T.Effects, - timeout: number | undefined, - input: any, -) { - const ensureResultTypeShape = ( - result: - | void - | T.ConfigRes - | T.PropertiesReturn - | T.ActionMetadata[] - | T.ActionResult, - ): { result: any } => { - if (isResult(result)) return result - return { result } - } - return (async () => { - switch (procedure) { - case "/backup/create": - return system.createBackup(effects, timeout || null) - case "/backup/restore": - return system.restoreBackup(effects, timeout || null) - case "/config/get": - return system.getConfig(effects, timeout || null) - case "/config/set": - return system.setConfig(effects, input, timeout || null) - case "/properties": - return system.properties(effects, timeout || null) - case "/actions/metadata": - return system.actionsMetadata(effects) - case "/init": - return system.packageInit( - effects, - string.optional().unsafeCast(input), - timeout || null, - ) - case "/uninit": - return system.packageUninit( - effects, - string.optional().unsafeCast(input), - timeout || null, - ) - default: - const procedures = unNestPath(procedure) - switch (true) { - case procedures[1] === "actions" && procedures[3] === "get": - return system.action(effects, procedures[2], input, timeout || null) - case procedures[1] === "actions" && procedures[3] === "run": - return system.action(effects, procedures[2], input, timeout || null) - case procedures[1] === "dependencies" && procedures[3] === "query": - return system.dependenciesAutoconfig( - effects, - procedures[2], - input, - timeout || null, - ) - - case procedures[1] === "dependencies" && procedures[3] === "update": - return system.dependenciesAutoconfig( - effects, - procedures[2], - input, - timeout || null, - ) - } + private getResult( + procedure: typeof jsonPath._TYPE, + system: System, + procedureId: string, + timeout: number | undefined, + input: any, + ) { + const ensureResultTypeShape = ( + result: void | T.ActionInput | T.PropertiesReturn | T.ActionResult | null, + ): { result: any } => { + if (isResult(result)) return result + return { result } } - })().then(ensureResultTypeShape, (error) => - matches(error) - .when( - object( - { - error: string, - code: number, - }, - ["code"], - { code: 0 }, - ), - (error) => ({ + 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 "/properties": + return system.properties(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: error.code, - message: error.error, + code: 0, + message: String(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 0c1f9d07a..26e184cef 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -8,7 +8,7 @@ import { CommandOptions, ExecOptions, ExecSpawnable, -} from "@start9labs/start-sdk/cjs/lib/util/SubContainer" +} from "@start9labs/start-sdk/package/lib/util/SubContainer" export const exec = promisify(cp.exec) export const execFile = promisify(cp.execFile) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts index 91b0177eb..b6fe39854 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/MainLoop.ts @@ -2,11 +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 { asError } from "@start9labs/start-sdk/cjs/lib/util" +import { CommandController } from "@start9labs/start-sdk/package/lib/mainFn/CommandController" const EMBASSY_HEALTH_INTERVAL = 15 * 1000 const EMBASSY_PROPERTIES_LOOP = 30 * 1000 @@ -137,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)) } @@ -155,7 +154,7 @@ export class MainLoop { result: "starting", message: null, }) - .catch((e) => console.error(asError(e))) + .catch((e) => console.error(utils.asError(e))) const interval = setInterval(async () => { const actionProcedure = value const timeChanged = Date.now() - start diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 5b0380a1b..bed30b11a 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,16 @@ 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, @@ -248,50 +239,21 @@ export class SystemForEmbassy implements System { readonly moduleCode: Partial, ) {} - async actionsMetadata(effects: T.Effects): Promise { - const actions = Object.entries(this.manifest.actions ?? {}) - return Promise.all( - actions.map(async ([actionId, action]): Promise => { - const name = action.name ?? actionId - const description = action.description - const warning = action.warning ?? null - const disabled = false - const input = (await convertToNewConfig(action["input-spec"] as any)) - .spec - const hasRunning = !!action["allowed-statuses"].find( - (x) => x === "running", - ) - const hasStopped = !!action["allowed-statuses"].find( - (x) => x === "stopped", - ) - // prettier-ignore - const allowedStatuses = - hasRunning && hasStopped ? "any": - hasRunning ? "onlyRunning" : - "onlyStopped" - - const group = null - return { - name, - description, - warning, - disabled, - allowedStatuses, - group, - input, - } - }), - ) + async containerInit(effects: Effects): Promise { + for (let depId in this.manifest.dependencies) { + if (this.manifest.dependencies[depId].config) { + await this.dependenciesAutoconfig(effects, depId, null) + } + } } - async containerInit(): Promise {} - 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) @@ -308,13 +270,18 @@ export class SystemForEmbassy implements System { } } - async packageInit( - effects: Effects, - previousVersion: Optional, - timeoutMs: number | null, - ): Promise { - if (previousVersion) - await this.migration(effects, previousVersion, timeoutMs) + 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(), + }) + } await effects.setMainStatus({ status: "stopped" }) await this.exportActions(effects) await this.exportNetwork(effects) @@ -400,10 +367,57 @@ 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 { + 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 { + 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: "Edit the configuration of this service", + "allowed-statuses": ["running", "stopped"], + "input-spec": {}, + implementation: { type: "script", args: [] }, + } + await effects.action.request({ + packageId: this.manifest.id, + actionId: "config", + replayId: "needs-config", + description: "This service must be configured before it can be run", + }) + } + for (const [actionId, action] of Object.entries(actions)) { const hasRunning = !!action["allowed-statuses"].find( (x) => x === "running", ) @@ -412,21 +426,22 @@ 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) }) } async packageUninit( effects: Effects, @@ -483,10 +498,7 @@ export class SystemForEmbassy implements System { await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest)) } } - 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( @@ -614,7 +626,7 @@ export class SystemForEmbassy implements System { 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 } @@ -828,24 +840,44 @@ export class SystemForEmbassy implements System { 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 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`, + description: `Configure this dependency for the needs of ${this.manifest.title}`, + input: { + kind: "partial", + value: diff.diff, + }, + when: { + condition: "input-not-matches", + once: false, + }, + }) + } } } @@ -1020,9 +1052,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 675cb2e9a..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(), @@ -154,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: [ { @@ -172,6 +180,7 @@ export const polyfillEffects = ( }, ], }, + commands.join(" "), ) .then((x: any) => ({ stderr: x.stderr.toString(), @@ -189,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: [ { @@ -207,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..4c074a1bd 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 = { @@ -124,7 +124,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec { spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(spec)), }, }), - {} as Record, + {} as Record, ), disabled: false, required: true, @@ -141,7 +141,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec { ...inputSpec, [key]: newVal, } - }, {} as CT.InputSpec) + }, {} as IST.InputSpec) } export function transformOldConfigToNew( @@ -233,10 +233,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 51d91abb5..43096f0f0 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -6,16 +6,13 @@ 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 { @@ -25,23 +22,24 @@ export class SystemForStartOs implements System { return new SystemForStartOs(require(STARTOS_JS_LOCATION)) } - constructor(readonly abi: T.ABI) {} - containerInit(): Promise { - throw new Error("Method not implemented.") + constructor(readonly abi: T.ABI) { + this + } + async containerInit(effects: Effects): Promise { + return void (await this.abi.containerInit({ effects })) } async packageInit( effects: Effects, - previousVersion: Optional = null, timeoutMs: number | null = null, ): Promise { - return void (await this.abi.init({ effects })) + return void (await this.abi.packageInit({ effects })) } async packageUninit( effects: Effects, nextVersion: Optional = null, timeoutMs: number | null = null, ): Promise { - return void (await this.abi.uninit({ effects, nextVersion })) + return void (await this.abi.packageUninit({ effects, nextVersion })) } async createBackup( effects: T.Effects, @@ -49,8 +47,6 @@ export class SystemForStartOs implements System { ): Promise { return void (await this.abi.createBackup({ effects, - pathMaker: ((options) => - new Volume(options.volume, options.path).path) as T.PathMaker, })) } async restoreBackup( @@ -59,80 +55,38 @@ export class SystemForStartOs implements System { ): Promise { return void (await this.abi.restoreBackup({ effects, - pathMaker: ((options) => - new Volume(options.volume, options.path).path) as T.PathMaker, })) } - getConfig( - effects: T.Effects, - timeoutMs: number | null, - ): Promise { - return this.abi.getConfig({ effects }) - } - async setConfig( - effects: Effects, - input: { effects: Effects; input: Record }, - timeoutMs: number | null, - ): Promise { - const _: unknown = await this.abi.setConfig({ effects, input }) - return - } - migration( - effects: Effects, - fromVersion: string, - timeoutMs: number | null, - ): Promise { - throw new Error("Method not implemented.") - } properties( effects: Effects, timeoutMs: number | null, ): Promise { throw new Error("Method not implemented.") } - async action( + getActionInput( effects: Effects, id: string, - formData: unknown, timeoutMs: number | null, - ): Promise { - const action = (await this.abi.actions({ effects }))[id] + ): Promise { + const action = this.abi.actions.get(id) if (!action) throw new Error(`Action ${id} not found`) - return action.run({ effects }) + return action.getInput({ effects }) } - dependenciesCheck( + runAction( effects: Effects, id: string, - oldConfig: unknown, + input: unknown, timeoutMs: number | null, - ): Promise { - const dependencyConfig = this.abi.dependencyConfig[id] - if (!dependencyConfig) throw new Error(`dependencyConfig ${id} not found`) - return dependencyConfig.query({ effects }) + ): Promise { + const action = this.abi.actions.get(id) + if (!action) throw new Error(`Action ${id} not found`) + return action.run({ effects, input }) } - async dependenciesAutoconfig( - effects: Effects, - id: string, - remoteConfig: unknown, - timeoutMs: number | null, - ): Promise { - const dependencyConfig = this.abi.dependencyConfig[id] - if (!dependencyConfig) throw new Error(`dependencyConfig ${id} not found`) - const queryResults = await this.getConfig(effects, timeoutMs) - return void (await dependencyConfig.update({ - queryResults, - remoteConfig, - })) // TODO - } - async actionsMetadata(effects: T.Effects): Promise { - return this.abi.actionsMetadata({ effects }) - } - - async init(): Promise {} 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) => { @@ -141,36 +95,21 @@ export class SystemForStartOs implements System { } 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 } } 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 1348b79e9..14ca002ca 100644 --- a/container-runtime/src/Interfaces/System.ts +++ b/container-runtime/src/Interfaces/System.ts @@ -1,39 +1,27 @@ 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 = { - containerInit(): Promise + containerInit(effects: T.Effects): Promise - start(effects: MainEffects): Promise - callCallback(callback: number, args: any[]): void + start(effects: T.Effects): Promise stop(): Promise - packageInit( - effects: Effects, - previousVersion: Optional, - timeoutMs: number | null, - ): Promise + packageInit(effects: Effects, timeoutMs: number | null): Promise packageUninit( effects: Effects, nextVersion: Optional, @@ -42,41 +30,21 @@ export type System = { createBackup(effects: T.Effects, timeoutMs: number | null): Promise restoreBackup(effects: T.Effects, timeoutMs: number | null): Promise - getConfig(effects: T.Effects, timeoutMs: number | null): Promise - setConfig( - effects: Effects, - input: { effects: Effects; input: Record }, - timeoutMs: number | null, - ): Promise - migration( - effects: Effects, - fromVersion: string, - timeoutMs: number | null, - ): Promise properties( effects: Effects, timeoutMs: number | null, ): Promise - action( + runAction( effects: Effects, actionId: string, - formData: unknown, + input: unknown, timeoutMs: number | null, - ): Promise - - dependenciesCheck( + ): Promise + getActionInput( effects: Effects, - id: string, - oldConfig: unknown, + actionId: string, timeoutMs: number | null, - ): Promise - dependenciesAutoconfig( - effects: Effects, - id: string, - oldConfig: unknown, - timeoutMs: number | null, - ): Promise - actionsMetadata(effects: T.Effects): Promise + ): Promise exit(): Promise } diff --git a/container-runtime/src/Models/CallbackHolder.ts b/container-runtime/src/Models/CallbackHolder.ts index b51af0bee..e1034b473 100644 --- a/container-runtime/src/Models/CallbackHolder.ts +++ b/container-runtime/src/Models/CallbackHolder.ts @@ -1,9 +1,22 @@ +import { T } from "@start9labs/start-sdk" + +const CallbackIdCell = { inc: 0 } + +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) { @@ -11,12 +24,38 @@ export class CallbackHolder { } const id = this.newId() 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..a231adba7 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,21 +13,14 @@ 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", 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 ebbcf6b5d..c7eccf706 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -667,12 +667,24 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" + [[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.7.1" @@ -2387,6 +2399,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", +] + [[package]] name = "imbl" version = "2.0.3" @@ -3813,6 +3836,15 @@ dependencies = [ "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" @@ -5090,6 +5122,7 @@ dependencies = [ "procfs", "proptest", "proptest-derive", + "qrcode", "rand 0.8.5", "regex", "reqwest", 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/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/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..9f8a6c0dd 100644 --- a/core/models/src/procedure_name.rs +++ b/core/models/src/procedure_name.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{ActionId, PackageId}; +use crate::ActionId; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ProcedureName { @@ -9,30 +9,24 @@ pub enum ProcedureName { 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/startos/Cargo.toml b/core/startos/Cargo.toml index f2b780b7b..19d0df244 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -156,6 +156,7 @@ 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"] } diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index 7c4492adc..f7e17f564 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -1,15 +1,76 @@ -use clap::Parser; +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_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_call_remote::(), + ) +} + +#[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)] #[serde(tag = "version")] @@ -17,6 +78,13 @@ pub enum ActionResult { #[serde(rename = "0")] V0(ActionResultV0), } +impl fmt::Display for ActionResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::V0(res) => res.fmt(f), + } + } +} #[derive(Debug, Serialize, Deserialize)] pub struct ActionResultV0 { @@ -25,63 +93,111 @@ pub struct ActionResultV0 { pub copyable: bool, pub qr: bool, } - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum DockerStatus { - Running, - Stopped, +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(()) + } } -pub fn display_action_result(params: WithIoFormat, result: ActionResult) { +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); } - match result { - ActionResult::V0(ar) => { - println!( - "{}: {}", - ar.message, - serde_json::to_string(&ar.value).unwrap() - ); - } - } + println!("{result}") } -#[derive(Deserialize, Serialize, Parser, TS)] +#[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/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index b4419e88e..5defebe5d 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() }); } } diff --git a/core/startos/src/backup/target/mod.rs b/core/startos/src/backup/target/mod.rs index 032f70848..fcb291005 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, }; 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 c78f118ea..6ada12301 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::future::Future; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::ops::Deref; @@ -9,8 +9,11 @@ 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}; @@ -23,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}; @@ -33,6 +35,7 @@ use crate::net::wifi::WpaCli; use crate::prelude::*; use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle}; 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; @@ -100,14 +103,14 @@ impl InitRpcContextPhases { 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)), } } } @@ -309,7 +312,7 @@ impl RpcContext { CleanupInitPhases { mut cleanup_sessions, init_services, - mut check_dependencies, + mut check_requested_actions, }: CleanupInitPhases, ) -> Result<(), Error> { cleanup_sessions.start(); @@ -366,35 +369,68 @@ impl RpcContext { 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(()) } diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 957e42c54..5fb6b9655 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 { reason: 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,66 @@ 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, + #[ts(optional)] + pub description: Option, + #[ts(optional)] + pub when: Option, + #[ts(optional)] + pub input: Option, +} + +#[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/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/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/install/mod.rs b/core/startos/src/install/mod.rs index 32b0f6ce4..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}; @@ -30,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; diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index b5629aa04..8d4f831bf 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; @@ -70,7 +69,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; @@ -240,15 +238,7 @@ pub fn server() -> ParentHandler { 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::(), - ) + .subcommand("action", action::action_api::()) .subcommand( "install", from_fn_async(install::install) @@ -281,7 +271,6 @@ pub fn package() -> ParentHandler { .with_display_serializable() .with_call_remote::(), ) - .subcommand("config", config::config::()) .subcommand( "start", from_fn_async(control::start) @@ -316,7 +305,6 @@ pub fn package() -> ParentHandler { }) .with_call_remote::(), ) - .subcommand("dependency", dependencies::dependency::()) .subcommand("backup", backup::package_backup::()) .subcommand("connect", from_fn_async(service::connect_rpc).no_cli()) .subcommand( diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index 9cf234f5f..7ff1d7d08 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; @@ -487,14 +486,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 +528,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 +536,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 +642,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 +693,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/mod.rs b/core/startos/src/lxc/mod.rs index 480f7a24c..ee08801df 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -7,7 +7,7 @@ use std::time::Duration; 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,7 +28,6 @@ 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}; @@ -365,7 +364,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/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/net_controller.rs b/core/startos/src/net/net_controller.rs index b7a8022b4..a94c7a7d0 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,31 +223,41 @@ impl NetService { self.update(id, host).await } - pub async fn clear_bindings(&mut self) -> Result<(), Error> { - let ctrl = self.net_controller()?; + 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()?); + } + Ok(res) + }) + .await?; 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?; - } - } - for (addr, (_, rcs)) in binds.tor { - drop(rcs); - errors.handle(ctrl.tor.gc(Some(addr), None).await); - } + 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() } @@ -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() @@ -395,7 +410,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 +439,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)), @@ -511,7 +529,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 +584,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/tor.rs b/core/startos/src/net/tor.rs index 24b8ddb02..16705776f 100644 --- a/core/startos/src/net/tor.rs +++ b/core/startos/src/net/tor.rs @@ -307,7 +307,7 @@ async fn torctl( let logs = journalctl( LogSource::Unit(SYSTEMD_UNIT), - 0, + Some(0), None, Some("0"), false, diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index c99ffb356..c8e1725f4 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))] diff --git a/core/startos/src/registry/signer/mod.rs b/core/startos/src/registry/signer/mod.rs index 99b23b88e..c203f0dad 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; 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/v2/compat.rs b/core/startos/src/s9pk/v2/compat.rs index 8e62c69d0..e3f54d3a2 100644 --- a/core/startos/src/s9pk/v2/compat.rs +++ b/core/startos/src/s9pk/v2/compat.rs @@ -249,7 +249,6 @@ impl TryFrom for Manifest { hardware_requirements: value.hardware_requirements, 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..f8102c736 100644 --- a/core/startos/src/s9pk/v2/manifest.rs +++ b/core/startos/src/s9pk/v2/manifest.rs @@ -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>( 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,42 +1,38 @@ +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, - 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(_: &GetActionInput) -> ConflictBuilder { + ConflictBuilder::nothing() } async fn handle( &mut self, id: Guid, - Action { - id: action_id, - input, - }: Action, + GetActionInput { id: action_id }: GetActionInput, _: &BackgroundJobQueue, ) -> Self::Response { let container = &self.0.persistent_container; container - .execute::( + .execute::>( id, - ProcedureName::RunAction(action_id), - input, + ProcedureName::GetActionInput(action_id), + Value::Null, Some(Duration::from_secs(30)), ) .await @@ -45,16 +41,139 @@ impl Handler for ServiceActor { } impl Service { - pub async fn action( + 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, Error>; + fn conflicts_with(_: &RunAction) -> ConflictBuilder { + ConflictBuilder::everything().except::() + } + async fn handle( + &mut self, + id: Guid, + RunAction { + id: action_id, + input, + }: RunAction, + _: &BackgroundJobQueue, + ) -> Self::Response { + let container = &self.0.persistent_container; + let result = container + .execute::>( + id, + ProcedureName::RunAction(action_id.clone()), + json!({ + "input": input, + }), + Some(Duration::from_secs(30)), + ) + .await + .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 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,34 +100,32 @@ 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 ExecuteAction { +pub struct GetActionInputParams { #[serde(default)] #[ts(skip)] + #[arg(skip)] procedure_id: Guid, #[ts(optional)] package_id: Option, action_id: ActionId, - #[ts(type = "any")] - input: Value, } -pub async fn execute_action( +async fn get_action_input( context: EffectContext, - ExecuteAction { + GetActionInputParams { procedure_id, package_id, action_id, - input, - }: ExecuteAction, -) -> Result { + }: GetActionInputParams, +) -> Result, Error> { let context = context.deref()?; if let Some(package_id) = package_id { @@ -93,9 +137,179 @@ pub async fn execute_action( .await .as_ref() .or_not_found(&package_id)? - .action(procedure_id, action_id, input) + .get_action_input(procedure_id, action_id) .await } else { - context.action(procedure_id, action_id, input).await + context.get_action_input(procedure_id, action_id).await } } + +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RunActionParams { + #[serde(default)] + #[ts(skip)] + #[arg(skip)] + procedure_id: Guid, + #[ts(optional)] + package_id: Option, + action_id: ActionId, + #[ts(type = "any")] + input: Value, +} +async fn run_action( + context: EffectContext, + RunActionParams { + procedure_id, + package_id, + action_id, + input, + }: RunActionParams, +) -> Result, Error> { + let context = context.deref()?; + + 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 + .services + .get(&package_id) + .await + .as_ref() + .or_not_found(&package_id)? + .run_action(procedure_id, action_id, input) + .await + } else { + 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..05f36cb4b 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; @@ -272,6 +275,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 +303,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..4eb897c37 100644 --- a/core/startos/src/service/effects/control.rs +++ b/core/startos/src/service/effects/control.rs @@ -1,9 +1,9 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; +use models::FromStrParser; use crate::service::effects::prelude::*; -use crate::util::clap::FromStrParser; pub async fn restart( context: EffectContext, 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 9bf756d60..c95dea946 100644 --- a/core/startos/src/service/effects/health.rs +++ b/core/startos/src/service/effects/health.rs @@ -29,7 +29,6 @@ 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 { MainStatus::Running { ref mut health, .. } diff --git a/core/startos/src/service/effects/mod.rs b/core/startos/src/service/effects/mod.rs index e85481e96..844f5646b 100644 --- a/core/startos/src/service/effects/mod.rs +++ b/core/startos/src/service/effects/mod.rs @@ -7,7 +7,6 @@ use crate::service::effects::context::EffectContext; mod action; pub mod callbacks; -mod config; pub mod context; mod control; mod dependency; @@ -26,34 +25,12 @@ pub fn handler() -> ParentHandler { from_fn(echo::).with_call_remote::(), ) // action - .subcommand( - "execute-action", - from_fn_async(action::execute_action).no_cli(), - ) - .subcommand( - "export-action", - from_fn_async(action::export_action).no_cli(), - ) - .subcommand( - "clear-actions", - from_fn_async(action::clear_actions).no_cli(), - ) + .subcommand("action", action::action_api::()) // callbacks .subcommand( "clear-callbacks", from_fn(callbacks::clear_callbacks).no_cli(), ) - // config - .subcommand( - "get-configured", - from_fn_async(config::get_configured).no_cli(), - ) - .subcommand( - "set-configured", - from_fn_async(config::set_configured) - .no_display() - .with_call_remote::(), - ) // control .subcommand( "restart", diff --git a/core/startos/src/service/effects/net/bind.rs b/core/startos/src/service/effects/net/bind.rs index ba273323a..40bc550cb 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,20 @@ 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 { + 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/mod.rs b/core/startos/src/service/mod.rs index 55f76fd14..1e61b1191 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -1,11 +1,12 @@ +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; +use std::path::{Path, PathBuf}; use std::process::Stdio; use std::sync::{Arc, Weak}; use std::time::Duration; -use std::{ffi::OsString, path::PathBuf}; use axum::extract::ws::WebSocket; use chrono::{DateTime, Utc}; @@ -15,7 +16,7 @@ use futures::stream::FusedStream; use futures::{SinkExt, StreamExt, TryStreamExt}; use imbl_value::{json, InternedString}; use itertools::Itertools; -use models::{ImageId, PackageId, ProcedureName}; +use models::{ActionId, ImageId, PackageId, ProcedureName}; use nix::sys::signal::Signal; use persistent_container::{PersistentContainer, Subcontainer}; use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor}; @@ -39,6 +40,7 @@ use crate::prelude::*; use crate::progress::{NamedProgress, Progress}; 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::util::actor::concurrent::ConcurrentActor; use crate::util::io::{create_file, AsyncReadStream}; @@ -48,11 +50,9 @@ 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; @@ -97,7 +97,7 @@ impl ServiceRef { .persistent_container .execute::( Guid::new(), - ProcedureName::Uninit, + ProcedureName::PackageUninit, to_value(&target_version)?, None, ) // TODO timeout @@ -257,7 +257,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 @@ -429,12 +429,13 @@ 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 @@ -445,16 +446,60 @@ impl Service { 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 }))?; diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index ff9d74009..3f85a95d5 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -379,7 +379,11 @@ impl PersistentContainer { )); } - self.rpc_client.request(rpc::Init, Empty {}).await?; + self.rpc_client + .request(rpc::Init, Empty {}) + .await + .map_err(Error::from) + .log_err(); self.state.send_modify(|s| s.rt_initialized = true); @@ -548,7 +552,7 @@ impl PersistentContainer { impl Drop for PersistentContainer { fn drop(&mut self) { if let Some(destroy) = self.destroy() { - tokio::spawn(async move { destroy.await.unwrap() }); + tokio::spawn(async move { destroy.await.log_err() }); } } } 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 0839afc0b..b343e513e 100644 --- a/core/startos/src/service/service_actor.rs +++ b/core/startos/src/service/service_actor.rs @@ -49,7 +49,7 @@ async fn service_actor_loop( .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 = i.as_status().de()?; let main_status = match &kinds { ServiceStateKinds { transition_state: Some(TransitionKind::Restarting), @@ -89,7 +89,7 @@ async fn service_actor_loop( .. } => MainStatus::Stopped, }; - i.as_status_mut().as_main_mut().ser(&main_status)?; + i.as_status_mut().ser(&main_status)?; } Ok(()) }) diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 0e6a959ae..ebe45fcc5 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -23,7 +23,7 @@ use crate::s9pk::manifest::PackageId; use crate::s9pk::merkle_archive::source::FileSource; use crate::s9pk::S9pk; 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>; @@ -174,16 +174,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(), 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/ssh.rs b/core/startos/src/ssh.rs index b97fba3e4..728fd6480 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}; 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 c10a7b89f..e45085826 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -11,17 +11,9 @@ use crate::service::start_stop::StartStop; use crate::status::health_check::NamedHealthCheckResult; 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 { diff --git a/core/startos/src/util/io.rs b/core/startos/src/util/io.rs index e3059c862..c3bf83d00 100644 --- a/core/startos/src/util/io.rs +++ b/core/startos/src/util/io.rs @@ -442,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) { @@ -512,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 { @@ -869,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(); }); } } diff --git a/core/startos/src/util/mod.rs b/core/startos/src/util/mod.rs index fab0b127d..f26bfa6b5 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; @@ -568,7 +567,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/serde.rs b/core/startos/src/util/serde.rs index 88d7bfc11..8f4bf4c15 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 @@ -1358,3 +1356,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/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..bc025709d 100644 --- a/sdk/Makefile +++ b/sdk/Makefile @@ -1,48 +1,75 @@ -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: dist baseDist | 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 - cd dist && npm publish --access=public +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 README.md LICENSE + cd dist + npm publish --access=public link: bundle cd dist && npm link 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..3dab037c0 --- /dev/null +++ b/sdk/base/lib/Effects.ts @@ -0,0 +1,191 @@ +import { + ActionId, + ActionInput, + ActionMetadata, + SetMainStatus, + DependencyRequirement, + CheckDependenciesResult, + SetHealth, + BindParams, + HostId, + LanInfo, + Host, + ExportServiceInterfaceParams, + ServiceInterface, + ActionRequest, + RequestActionParams, +} 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: ActionId[] } | { except: ActionId[] }, + ): 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 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..cebb03ee1 --- /dev/null +++ b/sdk/base/lib/actions/index.ts @@ -0,0 +1,65 @@ +import * as T from "../types" +import * as IST from "../actions/input/inputSpecTypes" + +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, + }) + } +} + +// prettier-ignore +export type ActionRequest> = + T extends { when: { condition: "input-not-matches" } } + ? (T extends { input: T.ActionRequestInput } ? T : "input is required for condition 'input-not-matches'") + : T + +export const requestAction = < + T extends Omit, +>(options: { + effects: T.Effects + request: ActionRequest & { replayId?: string; packageId: T.PackageId } +}) => { + const request = options.request + const req = { + ...request, + replayId: request.replayId || `${request.packageId}:${request.actionId}`, + } + 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 73% rename from sdk/lib/config/builder/config.ts rename to sdk/base/lib/actions/input/builder/inputSpec.ts index c30f37890..288ce8b3e 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 { ValueSpec } from "../inputSpecTypes" import { Value } from "./value" -import { _ } from "../../util" -import { Effects } from "../../types" +import { _ } from "../../../util" +import { Effects } from "../../../Effects" import { Parser, object } from "ts-matches" export type LazyBuildOptions = { @@ -12,20 +12,20 @@ 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 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 +35,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,11 +73,11 @@ 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 @@ -105,7 +105,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 +119,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 72% rename from sdk/lib/config/builder/value.ts rename to sdk/base/lib/actions/input/builder/value.ts index 01673a6df..ad30f1376 100644 --- a/sdk/lib/config/builder/value.ts +++ b/sdk/base/lib/actions/input/builder/value.ts @@ -1,4 +1,4 @@ -import { Config, LazyBuild, LazyBuildOptions } from "./config" +import { InputSpec, LazyBuild } from "./inputSpec" import { List } from "./list" import { Variants } from "./variants" import { @@ -7,13 +7,15 @@ import { 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,7 +26,6 @@ import { string, unknown, } from "ts-matches" -import { once } from "../../util/once" export type RequiredDefault = | false @@ -54,11 +55,6 @@ type AsRequired = MaybeRequiredType extends ? 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, ) @@ -73,28 +69,6 @@ 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 class Value { protected constructor( public build: LazyBuild, @@ -103,10 +77,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( @@ -148,22 +125,53 @@ export class Value { static text>(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: string | RandomString | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'World' } + * @example required: { default: { charset: 'abcdefg', len: 16 } } + */ 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 () => ({ @@ -193,19 +201,13 @@ export class Value { 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"] disabled?: string | false - /** Immutable means it can only be configured at the first config then never again - * Default is false - */ generate?: null | RandomString } >, @@ -233,13 +235,19 @@ export class Value { static textarea(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description Unlike other "required" fields, for textarea this is a simple boolean. + */ required: boolean 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 () => { @@ -290,17 +298,36 @@ export class Value { static number>(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: number | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 7 } + */ 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>( @@ -331,7 +358,6 @@ export class Value { required: RequiredDefault min?: number | null max?: number | null - /** Default = '1' */ step?: number | null integer: boolean units?: string | null @@ -361,10 +387,20 @@ export class Value { static color>(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'ffffff' } + */ 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>( @@ -410,14 +446,27 @@ export class Value { static datetime>(a: { name: string description?: string | null + /** Presents a warning prompt before permitting the value to change. */ warning?: string | null + /** + * @description Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: '1985-12-16 18:00:00.000' } + */ 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>( @@ -445,7 +494,6 @@ export class Value { description?: string | null warning?: string | null required: RequiredDefault - /** Default = 'datetime-local' */ inputmode?: ValueSpecDatetime["inputmode"] min?: string | null max?: string | null @@ -471,24 +519,39 @@ export class Value { } static select< Required extends RequiredDefault, - B extends Record, + Values extends Record, >(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 { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'radio1' } + */ + required: Required + /** + * @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, never>( () => ({ description: null, warning: null, @@ -500,7 +563,9 @@ export class Value { }), asRequiredParser( anyOf( - ...Object.keys(a.values).map((x: keyof B & string) => literal(x)), + ...Object.keys(a.values).map((x: keyof Values & string) => + literal(x), + ), ), a, ) as any, @@ -515,11 +580,6 @@ export class Value { warning?: string | null required: RequiredDefault 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[] } >, @@ -540,20 +600,31 @@ export class Value { static multiselect>(a: { 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: 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 +653,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 +675,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,12 +689,11 @@ export class Value { } }, spec.validator) } - static file, Store>(a: { + static file(a: { name: string description?: string | null - warning?: string | null extensions: string[] - required: Required + required: boolean }) { const buildValue = { type: "file" as const, @@ -637,11 +701,9 @@ export class Value { warning: null, ...a, } - return new Value, Store>( + return new Value( () => ({ ...buildValue, - - ...requiredLikeToAbove(a.required), }), asRequiredParser(object({ filePath: string }), a), ) @@ -672,17 +734,21 @@ export class Value { 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 Determines if the field is required. If so, optionally provide a default value from the list of variants. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'variant1' } */ - disabled?: false | string | string[] + required: Required + /** + * @description Once set, the value can never be changed. + * @default false + */ + immutable?: boolean }, aVariants: Variants, ) { @@ -736,11 +802,11 @@ export class Value { getA: LazyBuild< Store, { - disabled: string[] | false | string name: string description?: string | null warning?: string | null required: Required + disabled: string[] | false | string } >, aVariants: Variants | Variants, @@ -763,16 +829,25 @@ export class Value { 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 80% rename from sdk/lib/config/builder/variants.ts rename to sdk/base/lib/actions/input/builder/variants.ts index 352f16828..6c0f83905 100644 --- a/sdk/lib/config/builder/variants.ts +++ b/sdk/base/lib/actions/input/builder/variants.ts @@ -1,5 +1,5 @@ -import { InputSpec, ValueSpecUnion } from "../configTypes" -import { LazyBuild, Config } from "./config" +import { ValueSpec, ValueSpecUnion } from "../inputSpecTypes" +import { LazyBuild, InputSpec } from "./inputSpec" import { Parser, anyOf, literals, object } from "ts-matches" /** @@ -8,7 +8,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 +20,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 +32,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 }, @@ -61,7 +61,7 @@ export class Variants { VariantValues extends { [K in string]: { name: string - spec: Config | Config + spec: InputSpec | InputSpec } }, Store = never, @@ -81,14 +81,17 @@ export class Variants { selection: K // prettier-ignore value: - VariantValues[K]["spec"] extends (Config | Config) ? B : + VariantValues[K]["spec"] extends (InputSpec | InputSpec) ? B : never } }[keyof VariantValues], Store >(async (options) => { const variants = {} as { - [K in keyof VariantValues]: { name: string; spec: InputSpec } + [K in keyof VariantValues]: { + name: string + spec: Record + } } for (const key in a) { const value = a[key] @@ -102,14 +105,14 @@ 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(), }) ``` 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 72% rename from sdk/lib/config/configConstants.ts rename to sdk/base/lib/actions/input/inputSpecConstants.ts index aa0e024c9..3beaefd51 100644 --- a/sdk/lib/config/configConstants.ts +++ b/sdk/base/lib/actions/input/inputSpecConstants.ts @@ -1,14 +1,13 @@ -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: { @@ -29,7 +28,7 @@ export const customSmtp = Config.of, never>({ }, placeholder: "test@example.com", inputmode: "email", - patterns: [email], + patterns: [Patterns.email], }), login: Value.text({ name: "Login", @@ -45,9 +44,9 @@ export const customSmtp = Config.of, never>({ }) /** - * 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"] @@ -58,10 +57,10 @@ export const smtpConfig = Value.filteredUnion( required: { 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: @@ -69,7 +68,7 @@ export const smtpConfig = Value.filteredUnion( required: false, 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..ee9189ae3 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 = { @@ -120,13 +117,7 @@ export type ValueSpecSelect = { 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 +130,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 +142,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 +157,9 @@ 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 +177,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 +221,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..f78da1f0a --- /dev/null +++ b/sdk/base/lib/actions/setupActions.ts @@ -0,0 +1,152 @@ +import { InputSpec } from "./input/builder" +import { ExtractInputSpecType } from "./input/builder/inputSpec" +import * as T from "../types" + +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 & 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, + Type extends + ExtractInputSpecType = ExtractInputSpecType, +> { + 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, + Type extends + ExtractInputSpecType = ExtractInputSpecType, + >( + 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: true })), + {}, + 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: Type + }): Promise { + return this.runFn(options) + } +} + +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 }) + } + update(options: { effects: T.Effects }): Promise { + const updater = async (options: { effects: T.Effects }) => { + for (let action of Object.values(this.actions)) { + await action.exportMetadata(options) + } + await options.effects.action.clear({ except: Object.keys(this.actions) }) + } + const updaterCtx = { options } + updaterCtx.options = { + effects: { + ...options.effects, + constRetry: () => updater(updaterCtx.options), + }, + } + return updater(updaterCtx.options) + } + 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/base/lib/dependencies/Dependency.ts b/sdk/base/lib/dependencies/Dependency.ts new file mode 100644 index 000000000..8a1a862c7 --- /dev/null +++ b/sdk/base/lib/dependencies/Dependency.ts @@ -0,0 +1,21 @@ +import { VersionRange } from "../exver" + +export class Dependency { + constructor( + readonly data: + | { + /** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */ + type: "running" + /** The acceptable version range of the dependency. */ + versionRange: VersionRange + /** A list of the dependency's health check IDs that must be passing for the service to be satisfied. */ + healthChecks: string[] + } + | { + /** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */ + type: "exists" + /** The acceptable version range of the dependency. */ + versionRange: VersionRange + }, + ) {} +} diff --git a/sdk/lib/dependencies/dependencies.ts b/sdk/base/lib/dependencies/dependencies.ts similarity index 89% rename from sdk/lib/dependencies/dependencies.ts rename to sdk/base/lib/dependencies/dependencies.ts index 287f63b06..fd3c6bb69 100644 --- a/sdk/lib/dependencies/dependencies.ts +++ b/sdk/base/lib/dependencies/dependencies.ts @@ -1,18 +1,12 @@ 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, @@ -22,7 +16,7 @@ export type CheckDependencies = { throwIfInstalledNotSatisfied: (packageId: DependencyId) => void throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => void throwIfRunningNotSatisfied: (packageId: DependencyId) => void - throwIfConfigNotSatisfied: (packageId: DependencyId) => void + throwIfActionsNotSatisfied: (packageId: DependencyId) => void throwIfHealthNotSatisfied: ( packageId: DependencyId, healthCheckId?: HealthCheckId, @@ -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 @@ -130,11 +124,12 @@ export async function checkDependencies< throw new Error(`${dep.result.title || packageId} is not running`) } } - 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(", ")}`, ) } } @@ -168,7 +163,7 @@ export async function checkDependencies< throwIfInstalledNotSatisfied(packageId) throwIfInstalledVersionNotSatisfied(packageId) throwIfRunningNotSatisfied(packageId) - throwIfConfigNotSatisfied(packageId) + throwIfActionsNotSatisfied(packageId) throwIfHealthNotSatisfied(packageId) } const throwIfNotSatisfied = (packageId?: DependencyId) => @@ -193,13 +188,13 @@ export async function checkDependencies< 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..0f9bd6da8 --- /dev/null +++ b/sdk/base/lib/dependencies/setupDependencies.ts @@ -0,0 +1,56 @@ +import * as T from "../types" +import { Dependency } from "./Dependency" + +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 +} + +export function setupDependencies( + fn: (options: { effects: T.Effects }) => Promise>, +): (options: { effects: T.Effects }) => Promise { + return (options: { effects: T.Effects }) => { + const updater = async (options: { effects: T.Effects }) => { + 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(), + }), + ), + }) + } + const updaterCtx = { options } + updaterCtx.options = { + effects: { + ...options.effects, + constRetry: () => updater(updaterCtx.options), + }, + } + return updater(updaterCtx.options) + } +} 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..474841aa3 --- /dev/null +++ b/sdk/base/lib/interfaces/setupInterfaces.ts @@ -0,0 +1,54 @@ +import * as T from "../types" +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, +) => + ((options: { effects: T.Effects }) => { + const updater = async (options: { effects: T.Effects }) => { + 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 + } + const updaterCtx = { options } + updaterCtx.options = { + effects: { + ...options.effects, + constRetry: () => updater(updaterCtx.options), + }, + } + return updater(updaterCtx.options) + }) as UpdateServiceInterfaces 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/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/lib/osBindings/Status.ts b/sdk/base/lib/osBindings/ActionInput.ts similarity index 50% rename from sdk/lib/osBindings/Status.ts rename to sdk/base/lib/osBindings/ActionInput.ts index b784f4d6a..a19a5f1a4 100644 --- a/sdk/lib/osBindings/Status.ts +++ b/sdk/base/lib/osBindings/ActionInput.ts @@ -1,4 +1,6 @@ // 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 } +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..63b5607d8 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionRequest.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 { ActionId } from "./ActionId" +import type { ActionRequestInput } from "./ActionRequestInput" +import type { ActionRequestTrigger } from "./ActionRequestTrigger" +import type { PackageId } from "./PackageId" + +export type ActionRequest = { + packageId: PackageId + actionId: ActionId + description?: 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/ActionVisibility.ts b/sdk/base/lib/osBindings/ActionVisibility.ts new file mode 100644 index 000000000..f7d5a2a2a --- /dev/null +++ b/sdk/base/lib/osBindings/ActionVisibility.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 ActionVisibility = + | "hidden" + | { disabled: { reason: 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/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/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/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/CreateSubcontainerFsParams.ts b/sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts similarity index 100% rename from sdk/lib/osBindings/CreateSubcontainerFsParams.ts rename to sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts 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/DestroySubcontainerFsParams.ts b/sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts similarity index 100% rename from sdk/lib/osBindings/DestroySubcontainerFsParams.ts rename to sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts 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/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/lib/osBindings/GetConfiguredParams.ts b/sdk/base/lib/osBindings/GetActionInputParams.ts similarity index 55% rename from sdk/lib/osBindings/GetConfiguredParams.ts rename to sdk/base/lib/osBindings/GetActionInputParams.ts index 66fb6e320..568ceb907 100644 --- a/sdk/lib/osBindings/GetConfiguredParams.ts +++ b/sdk/base/lib/osBindings/GetActionInputParams.ts @@ -1,4 +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 GetConfiguredParams = { packageId?: 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/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 100% rename from sdk/lib/osBindings/HardwareRequirements.ts rename to sdk/base/lib/osBindings/HardwareRequirements.ts 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/lib/osBindings/ImageSource.ts b/sdk/base/lib/osBindings/ImageSource.ts similarity index 100% rename from sdk/lib/osBindings/ImageSource.ts rename to sdk/base/lib/osBindings/ImageSource.ts 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/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/lib/osBindings/MainStatus.ts b/sdk/base/lib/osBindings/MainStatus.ts similarity index 69% rename from sdk/lib/osBindings/MainStatus.ts rename to sdk/base/lib/osBindings/MainStatus.ts index 1f6b3babe..dbd9a8fcc 100644 --- a/sdk/lib/osBindings/MainStatus.ts +++ b/sdk/base/lib/osBindings/MainStatus.ts @@ -4,17 +4,17 @@ import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" import type { StartStop } from "./StartStop" export type MainStatus = - | { status: "stopped" } - | { status: "restarting" } - | { status: "restoring" } - | { status: "stopping" } + | { main: "stopped" } + | { main: "restarting" } + | { main: "restoring" } + | { main: "stopping" } | { - status: "starting" + main: "starting" health: { [key: HealthCheckId]: NamedHealthCheckResult } } | { - status: "running" + main: "running" started: string health: { [key: HealthCheckId]: NamedHealthCheckResult } } - | { status: "backingUp"; onComplete: StartStop } + | { 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/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/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/lib/osBindings/SetConfigured.ts b/sdk/base/lib/osBindings/ReplayId.ts similarity index 67% rename from sdk/lib/osBindings/SetConfigured.ts rename to sdk/base/lib/osBindings/ReplayId.ts index e1478422a..048ef183a 100644 --- a/sdk/lib/osBindings/SetConfigured.ts +++ b/sdk/base/lib/osBindings/ReplayId.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 ReplayId = string diff --git a/sdk/base/lib/osBindings/RequestActionParams.ts b/sdk/base/lib/osBindings/RequestActionParams.ts new file mode 100644 index 000000000..db83be595 --- /dev/null +++ b/sdk/base/lib/osBindings/RequestActionParams.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 { PackageId } from "./PackageId" +import type { ReplayId } from "./ReplayId" + +export type RequestActionParams = { + replayId: ReplayId + packageId: PackageId + actionId: ActionId + description?: 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 100% rename from sdk/lib/osBindings/ServerInfo.ts rename to sdk/base/lib/osBindings/ServerInfo.ts 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 100% rename from sdk/lib/osBindings/SessionList.ts rename to sdk/base/lib/osBindings/SessionList.ts 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/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/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 100% rename from sdk/lib/osBindings/SetupResult.ts rename to sdk/base/lib/osBindings/SetupResult.ts 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/lib/osBindings/StartStop.ts b/sdk/base/lib/osBindings/StartStop.ts similarity index 100% rename from sdk/lib/osBindings/StartStop.ts rename to sdk/base/lib/osBindings/StartStop.ts 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 88% rename from sdk/lib/osBindings/index.ts rename to sdk/base/lib/osBindings/index.ts index 9492fe796..4b6af0ae1 100644 --- a/sdk/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -1,6 +1,13 @@ export { AcceptSigners } from "./AcceptSigners" 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 { ActionVisibility } from "./ActionVisibility" export { AddAdminParams } from "./AddAdminParams" export { AddAssetParams } from "./AddAssetParams" export { AddPackageParams } from "./AddPackageParams" @@ -20,6 +27,7 @@ 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" @@ -30,6 +38,11 @@ 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 { ContactInfo } from "./ContactInfo" export { CreateSubcontainerFsParams } from "./CreateSubcontainerFsParams" export { CurrentDependencies } from "./CurrentDependencies" @@ -45,13 +58,12 @@ export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" export { Duration } from "./Duration" export { EchoParams } from "./EchoParams" 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" @@ -120,7 +132,10 @@ export { RecoverySource } from "./RecoverySource" export { RegistryAsset } from "./RegistryAsset" export { RegistryInfo } from "./RegistryInfo" 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,7 +146,6 @@ 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" @@ -146,7 +160,6 @@ export { SignAssetParams } from "./SignAssetParams" export { SignerInfo } from "./SignerInfo" export { SmtpValue } from "./SmtpValue" export { StartStop } from "./StartStop" -export { Status } from "./Status" 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 100% rename from sdk/lib/s9pk/index.ts rename to sdk/base/lib/s9pk/index.ts 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 81% rename from sdk/lib/test/startosTypeValidation.test.ts rename to sdk/base/lib/test/startosTypeValidation.test.ts index bcbc4b6ec..7686328bd 100644 --- a/sdk/lib/test/startosTypeValidation.test.ts +++ b/sdk/base/lib/test/startosTypeValidation.test.ts @@ -1,9 +1,15 @@ import { Effects } from "../types" import { CheckDependenciesParam, - ExecuteAction, - GetConfiguredParams, + ClearActionRequestsParams, + ClearActionsParams, + ClearBindingsParams, + ClearCallbacksParams, + ClearServiceInterfacesParams, + GetActionInputParams, GetStoreParams, + RequestActionParams, + RunActionParams, SetDataVersionParams, SetMainStatus, SetStoreParams, @@ -12,7 +18,6 @@ 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" @@ -41,21 +46,27 @@ type EffectsTypeChecker = { describe("startosTypeValidation ", () => { test(`checking the params match`, () => { - const testInput: any = {} typeEquality({ - executeAction: {} as ExecuteAction, + 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: undefined, + 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, @@ -71,19 +82,14 @@ describe("startosTypeValidation ", () => { 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, 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..56e310efe --- /dev/null +++ b/sdk/base/lib/types.ts @@ -0,0 +1,297 @@ +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 ExportedAction = (options: { + effects: Effects + input?: Record +}) => Promise +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 properties = (options: { + effects: Effects + }) => 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 + properties: ExpectedExports.properties + 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 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 = + | { + /** 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 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 ActionResult = { + version: "0" + message: string + value: string | null + 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/base/lib/types/ManifestTypes.ts b/sdk/base/lib/types/ManifestTypes.ts new file mode 100644 index 000000000..c416588b3 --- /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?: { display?: RegExp; processor?: RegExp } + 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 100% rename from sdk/lib/util/graph.ts rename to sdk/base/lib/util/graph.ts 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/lib/util/typeHelpers.ts b/sdk/base/lib/util/typeHelpers.ts similarity index 100% rename from sdk/lib/util/typeHelpers.ts rename to sdk/base/lib/util/typeHelpers.ts diff --git a/sdk/base/package-lock.json b/sdk/base/package-lock.json new file mode 100644 index 000000000..3a8e40c0c --- /dev/null +++ b/sdk/base/package-lock.json @@ -0,0 +1,4677 @@ +{ + "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": "^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, + "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": { + "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, + "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": "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" + } + } + } +} diff --git a/sdk/base/package.json b/sdk/base/package.json new file mode 100644 index 000000000..a290d2a75 --- /dev/null +++ b/sdk/base/package.json @@ -0,0 +1,51 @@ +{ + "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": { + "isomorphic-fetch": "^3.0.0", + "lodash.merge": "^4.6.2", + "mime": "^4.0.3", + "ts-matches": "^5.5.1", + "yaml": "^2.2.2", + "@iarna/toml": "^2.2.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + }, + "prettier": { + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": false + }, + "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" + } +} 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 87c4e14ce..000000000 --- a/sdk/lib/StartSdk.ts +++ /dev/null @@ -1,805 +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 { 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 { 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, SubContainer } from "./util/SubContainer" -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" -import { VersionGraph } from "./version" - -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 - } - - type NestedEffects = "subcontainer" | "store" - type InterfaceEffects = - | "getServiceInterface" - | "listServiceInterfaces" - | "exportServiceInterface" - | "clearServiceInterfaces" - | "bind" - | "getHostInfo" - | "getPrimaryUrl" - type MainUsedEffects = "setMainStatus" | "setHealth" - type AlreadyExposed = "getSslCertificate" | "getSystemSmtp" - - // prettier-ignore - type StartSdkEffectWrapper = { - [K in keyof Omit]: (effects: Effects, ...args: Parameters) => ReturnType - } - const startSdkEffectWrapper: StartSdkEffectWrapper = { - executeAction: (effects, ...args) => effects.executeAction(...args), - exportAction: (effects, ...args) => effects.exportAction(...args), - clearActions: (effects, ...args) => effects.clearActions(...args), - getConfigured: (effects, ...args) => effects.getConfigured(...args), - setConfigured: (effects, ...args) => effects.setConfigured(...args), - 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), - } - - return { - ...startSdkEffectWrapper, - - 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), - 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), - }, - 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 SubContainer.with( - effects, - image, - options.mounts || [], - commands.join(" "), - (subcontainer) => subcontainer.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/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/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/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/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 68bd7b588..000000000 --- a/sdk/lib/types.ts +++ /dev/null @@ -1,523 +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" -import { StringObject, ToKebab } from "./util" -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 -} - -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] - -/** 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 - - // 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 setupConfig */ - 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 = { - version: "0" - message: string - value: string | null - 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 9246cf791..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 "./SubContainer" -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/package-lock.json b/sdk/package-lock.json index 59f7ab718..9699e0851 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -1,4676 +1,6 @@ { - "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha8", + "name": "sdk", "lockfileVersion": 3, "requires": true, - "packages": { - "": { - "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha8", - "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..05cc003cb --- /dev/null +++ b/sdk/package/lib/StartSdk.ts @@ -0,0 +1,1410 @@ +import { + RequiredDefault, + 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 { Dependency } from "../../base/lib/dependencies/Dependency" +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), + } + + return { + ...startSdkEffectWrapper, + action: { + run: actions.runAction, + request: actions.requestAction, + requestOwn: >( + effects: T.Effects, + request: actions.ActionRequest & { + replayId?: string + }, + ) => + actions.requestAction({ + effects, + request: { ...request, packageId: this.manifest.id }, + }), + }, + 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(o: HealthCheckParams) { + return healthCheck(o) + }, + }, + Dependency: { + /** + * @description Use this function to create a dependency for the service. + * @property {DependencyType} type + * @property {VersionRange} versionRange + * @property {string[]} healthChecks + */ + of(data: Dependency["data"]) { + return new Dependency({ ...data }) + }, + }, + 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), + /** + * @description Use this function to determine which information to expose to the UI in the "Properties" section. + * + * Values can be obtained from anywhere: the Store, the upstream service, or another service. + * @example + * In this example, we retrieve the admin password from the Store and expose it, masked and copyable, to + * the UI as "Admin Password". + * + * ``` + export const properties = sdk.setupProperties(async ({ effects }) => { + const store = await sdk.store.getOwn(effects, sdk.StorePath).once() + + return { + 'Admin Password': { + type: 'string', + value: store.adminPassword, + description: 'Used for logging into the admin UI', + copyable: true, + masked: true, + qr: false, + }, + } + }) + * ``` + */ + setupProperties: + ( + fn: (options: { effects: Effects }) => Promise, + ): T.ExpectedExports.properties => + (options) => + fn(options).then(nullifyProperties), + /** + * 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(inputSpec: { + effects: Effects + started: (onTerm: () => PromiseLike) => PromiseLike + healthReceipts: HealthReceipt[] + }) { + return Daemons.of(inputSpec) + }, + }, + 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, + 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 Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: string | RandomString | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'World' } + * @example required: { default: { charset: 'abcdefg', len: 16 } } + */ + required: RequiredDefault + /** + * @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 + /** + * @description Unlike other "required" fields, for textarea this is a simple boolean. + */ + 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 + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: number | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 7 } + */ + required: RequiredDefault + 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 Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'ffffff' } + */ + required: RequiredDefault + 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 Determines if the field is required. If so, optionally provide a default value. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: '1985-12-16 18:00:00.000' } + */ + required: RequiredDefault + /** + * @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 Determines if the field is required. If so, optionally provide a default value from the list of values. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'radio1' } + */ + required: RequiredDefault + /** + * @description A mapping of unique radio options to their human readable display format. + * @example + * ``` + { + radio1: "Radio 1" + radio2: "Radio 2" + radio3: "Radio 3" + } + * ``` + */ + values: Record + /** + * @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: < + 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, + { + name: string + description?: string | null + /** Presents a warning prompt before permitting the value to change. */ + warning?: string | null + /** + * @description Determines if the field is required. If so, optionally provide a default value from the list of variants. + * @type { false | { default: string | null } } + * @example required: false + * @example required: { default: null } + * @example required: { default: 'variant1' } + */ + required: Required + /** + * @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), + ) +} +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, + masked: 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/package/lib/backup/Backups.ts b/sdk/package/lib/backup/Backups.ts new file mode 100644 index 000000000..8d8f03295 --- /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..b9654824b --- /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 e007c4ea2..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 { SubContainer } from "../util/SubContainer" +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 91% rename from sdk/lib/health/checkFns/runHealthScript.ts rename to sdk/package/lib/health/checkFns/runHealthScript.ts index 4bac211a9..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 { SubContainer } from "../../util/SubContainer" -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 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/lib/index.ts b/sdk/package/lib/index.ts similarity index 58% rename from sdk/lib/index.ts rename to sdk/package/lib/index.ts index a4caf9c01..a83e35745 100644 --- a/sdk/lib/index.ts +++ b/sdk/package/lib/index.ts @@ -1,32 +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 } 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 { pathBuilder } from "../../base/lib/util/PathBuilder" -export * as actions from "./actions" +export * as actions from "../../base/lib/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" 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 59% rename from sdk/lib/inits/setupInit.ts rename to sdk/package/lib/inits/setupInit.ts index 5fd1c481c..c9ab4f171 100644 --- a/sdk/lib/inits/setupInit.ts +++ b/sdk/package/lib/inits/setupInit.ts @@ -1,9 +1,8 @@ -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 { 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" @@ -12,18 +11,17 @@ export function setupInit( versions: VersionGraph, install: Install, uninstall: Uninstall, - setInterfaces: SetInterfaces, - setDependencies: (options: { - effects: T.Effects - input: any - }) => Promise, + setServiceInterfaces: UpdateServiceInterfaces, + setDependencies: (options: { effects: T.Effects }) => 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 +35,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 +50,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/lib/inits/setupInstall.ts b/sdk/package/lib/inits/setupInstall.ts similarity index 81% rename from sdk/lib/inits/setupInstall.ts rename to sdk/package/lib/inits/setupInstall.ts index ab21380a0..d9f694021 100644 --- a/sdk/lib/inits/setupInstall.ts +++ b/sdk/package/lib/inits/setupInstall.ts @@ -1,4 +1,4 @@ -import * as T from "../types" +import * as T from "../../../base/lib/types" export type InstallFn = (opts: { effects: T.Effects @@ -11,7 +11,7 @@ export class Install { return new Install(fn) } - async install({ effects }: Parameters[0]) { + async install({ effects }: Parameters[0]) { await this.fn({ effects, }) diff --git a/sdk/lib/inits/setupUninstall.ts b/sdk/package/lib/inits/setupUninstall.ts similarity index 86% rename from sdk/lib/inits/setupUninstall.ts rename to sdk/package/lib/inits/setupUninstall.ts index 918f417e5..c863e1ad7 100644 --- a/sdk/lib/inits/setupUninstall.ts +++ b/sdk/package/lib/inits/setupUninstall.ts @@ -1,4 +1,4 @@ -import * as T from "../types" +import * as T from "../../../base/lib/types" export type UninstallFn = (opts: { effects: T.Effects @@ -14,7 +14,7 @@ export class Uninstall { async uninstall({ effects, nextVersion, - }: Parameters[0]) { + }: Parameters[0]) { if (!nextVersion) await this.fn({ effects, diff --git a/sdk/lib/mainFn/CommandController.ts b/sdk/package/lib/mainFn/CommandController.ts similarity index 94% rename from sdk/lib/mainFn/CommandController.ts rename to sdk/package/lib/mainFn/CommandController.ts index 9c206803a..78d888b95 100644 --- a/sdk/lib/mainFn/CommandController.ts +++ b/sdk/package/lib/mainFn/CommandController.ts @@ -1,15 +1,15 @@ import { DEFAULT_SIGTERM_TIMEOUT } from "." -import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk" +import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../../../base/lib/types" -import * as T from "../types" -import { asError } from "../util/asError" +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/splitCommand" +import { splitCommand } from "../util" import * as cp from "child_process" export class CommandController { diff --git a/sdk/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts similarity index 96% rename from sdk/lib/mainFn/Daemon.ts rename to sdk/package/lib/mainFn/Daemon.ts index 6fa1d1085..45f252dac 100644 --- a/sdk/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -1,5 +1,5 @@ -import * as T from "../types" -import { asError } from "../util/asError" +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" diff --git a/sdk/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts similarity index 87% rename from sdk/lib/mainFn/Daemons.ts rename to sdk/package/lib/mainFn/Daemons.ts index b17f3a1a1..0e05214f7 100644 --- a/sdk/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -1,19 +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, - ExecSpawnable, - MountOptions, - SubContainer, -} from "../util/SubContainer" -import { splitCommand } from "../util/splitCommand" +import { ExecSpawnable, MountOptions } from "../util/SubContainer" import { promisify } from "node:util" import * as CP from "node:child_process" @@ -77,7 +69,9 @@ Daemons.of({ }) ``` */ -export class Daemons { +export class Daemons + implements T.DaemonBuildable +{ private constructor( readonly effects: T.Effects, readonly started: (onTerm: () => PromiseLike) => PromiseLike, @@ -86,23 +80,23 @@ export class Daemons { 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 inputSpec * @returns */ - static of(config: { + static of(inputSpec: { effects: T.Effects started: (onTerm: () => PromiseLike) => PromiseLike healthReceipts: HealthReceipt[] }) { return new Daemons( - config.effects, - config.started, + inputSpec.effects, + inputSpec.started, [], [], [], diff --git a/sdk/lib/mainFn/HealthDaemon.ts b/sdk/package/lib/mainFn/HealthDaemon.ts similarity index 97% rename from sdk/lib/mainFn/HealthDaemon.ts rename to sdk/package/lib/mainFn/HealthDaemon.ts index 7ace3ed7b..71ba83b3f 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 diff --git a/sdk/lib/mainFn/Mounts.ts b/sdk/package/lib/mainFn/Mounts.ts similarity index 98% rename from sdk/lib/mainFn/Mounts.ts rename to sdk/package/lib/mainFn/Mounts.ts index bd947b759..42d6d66ab 100644 --- a/sdk/lib/mainFn/Mounts.ts +++ b/sdk/package/lib/mainFn/Mounts.ts @@ -1,4 +1,4 @@ -import * as T from "../types" +import * as T from "../../../base/lib/types" import { MountOptions } from "../util/SubContainer" type MountArray = { path: string; options: MountOptions }[] diff --git a/sdk/lib/mainFn/index.ts b/sdk/package/lib/mainFn/index.ts similarity index 77% rename from sdk/lib/mainFn/index.ts rename to sdk/package/lib/mainFn/index.ts index 7a094a31a..ee0481630 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 /** @@ -20,7 +16,7 @@ export const DEFAULT_SIGTERM_TIMEOUT = 30_000 */ export const setupMain = ( fn: (o: { - effects: MainEffects + effects: T.Effects started(onTerm: () => PromiseLike): PromiseLike }) => Promise>, ): T.ExpectedExports.main => { diff --git a/sdk/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts similarity index 75% rename from sdk/lib/manifest/setupManifest.ts rename to sdk/package/lib/manifest/setupManifest.ts index 10aaa03db..088575c1a 100644 --- a/sdk/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -1,13 +1,17 @@ -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 "../version/VersionGraph" /** - * 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, @@ -20,7 +24,7 @@ export function setupManifest< dependencies: Dependencies id: Id assets: AssetTypes[] - images: Record + images: Record volumes: VolumesTypes[] }, Satisfies extends string[] = [], @@ -56,7 +60,6 @@ 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( @@ -67,14 +70,14 @@ export function setupManifest< 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 94% rename from sdk/lib/test/configBuilder.test.ts rename to sdk/package/lib/test/inputSpecBuilder.test.ts index 5c65271a8..bb5a8e25d 100644 --- a/sdk/lib/test/configBuilder.test.ts +++ b/sdk/package/lib/test/inputSpecBuilder.test.ts @@ -1,9 +1,9 @@ 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 "../version/VersionGraph" @@ -13,7 +13,7 @@ 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", @@ -232,9 +232,8 @@ describe("values", () => { { name: "Testing", description: null, - warning: null, }, - Config.of({ + InputSpec.of({ a: Value.toggle({ name: "test", description: null, @@ -258,7 +257,7 @@ describe("values", () => { Variants.of({ a: { name: "a", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -277,7 +276,7 @@ describe("values", () => { describe("dynamic", () => { const fakeOptions = { - config: "config", + inputSpec: "inputSpec", effects: "effects", utils: "utils", } as any @@ -546,7 +545,7 @@ describe("values", () => { Variants.of({ a: { name: "a", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -557,7 +556,7 @@ describe("values", () => { }, b: { name: "b", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -613,7 +612,7 @@ describe("values", () => { Variants.of({ a: { name: "a", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -624,7 +623,7 @@ describe("values", () => { }, b: { name: "b", - spec: Config.of({ + spec: InputSpec.of({ b: Value.toggle({ name: "b", description: null, @@ -679,7 +678,7 @@ describe("Builder List", () => { name: "test", }, { - spec: Config.of({ + spec: InputSpec.of({ test: Value.toggle({ name: "test", description: null, @@ -732,11 +731,11 @@ 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, }), }) @@ -747,11 +746,11 @@ 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, warning: null, placeholder: null, @@ -769,11 +768,11 @@ 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, warning: null, }), @@ -785,11 +784,11 @@ 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", + "If no name is provided, the name from inputSpec will be used", required: false, warning: null, values: { @@ -799,7 +798,8 @@ 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", + description: + "If no name is provided, the name from inputSpec will be used", required: false, warning: null, values: { @@ -814,11 +814,11 @@ describe("Nested nullable values", () => { 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/lib/test/output.sdk.ts b/sdk/package/lib/test/output.sdk.ts similarity index 100% rename from sdk/lib/test/output.sdk.ts rename to sdk/package/lib/test/output.sdk.ts diff --git a/sdk/lib/test/output.test.ts b/sdk/package/lib/test/output.test.ts similarity index 72% rename from sdk/lib/test/output.test.ts rename to sdk/package/lib/test/output.test.ts index 1df84f3af..37636e52f 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: [ { @@ -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 83% rename from sdk/lib/trigger/index.ts rename to sdk/package/lib/trigger/index.ts index eb058437f..6da034262 100644 --- a/sdk/lib/trigger/index.ts +++ b/sdk/package/lib/trigger/index.ts @@ -1,4 +1,3 @@ -import { ExecSpawnable } from "../util/SubContainer" import { TriggerInput } from "./TriggerInput" export { changeOnFirstSuccess } from "./changeOnFirstSuccess" export { cooldownTrigger } from "./cooldownTrigger" 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/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts similarity index 98% rename from sdk/lib/util/SubContainer.ts rename to sdk/package/lib/util/SubContainer.ts index 82c1ae1b3..b6a7492a7 100644 --- a/sdk/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -1,9 +1,9 @@ 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 "./once" +import { once } from "../../../base/lib/util/once" export const execFile = promisify(cp.execFile) const WORKDIR = (imageId: string) => `/media/startos/images/${imageId}/` const False = () => false @@ -266,8 +266,8 @@ export class SubContainer implements ExecSpawnable { appendTo.data += chunk } else if (typeof chunk === "string" || chunk instanceof Buffer) { appendTo.data = Buffer.concat([ - Buffer.from(appendTo.data), - Buffer.from(chunk), + new Uint8Array(Buffer.from(appendTo.data).buffer), + new Uint8Array(Buffer.from(chunk).buffer), ]) } else { console.error("received unexpected chunk", chunk) diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts new file mode 100644 index 000000000..bb7190fe1 --- /dev/null +++ b/sdk/package/lib/util/fileHelper.ts @@ -0,0 +1,214 @@ +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) => A, + ) {} + + /** + * Accepts structured data and overwrites the existing file on disk. + */ + async write(data: A) { + const parent = previousPath.exec(this.path) + if (parent) { + await fs.mkdir(parent[1], { recursive: true }) + } + + await fs.writeFile(this.path, this.writeData(data)) + } + + /** + * Reads the file from disk and converts it to structured data. + */ + async read() { + if (!(await exists(this.path))) { + return null + } + return this.readData( + await fs.readFile(this.path).then((data) => data.toString("utf-8")), + ) + } + + async const(effects: T.Effects) { + const watch = this.watch() + const res = await watch.next() + watch.next().then(effects.constRetry) + return res.value + } + + async *watch() { + 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.read() + const listen = Promise.resolve() + .then(async () => { + for await (const _ of watch) { + ctrl.abort("finished") + return + } + }) + .catch((e) => console.error(asError(e))) + yield res + await listen + } else { + yield null + await onCreated(this.path).catch((e) => console.error(asError(e))) + } + } + } + + /** + * Accepts structured data and performs a merge with the existing file on disk. + */ + async merge(data: A) { + const fileData = (await this.read().catch(() => ({}))) || {} + const mergeData = merge({}, fileData, data) + return await this.write(mergeData) + } + /** + * 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) => 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/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/version/VersionGraph.ts b/sdk/package/lib/version/VersionGraph.ts similarity index 94% rename from sdk/lib/version/VersionGraph.ts rename to sdk/package/lib/version/VersionGraph.ts index 1b6b49bb9..91c7a0cc0 100644 --- a/sdk/lib/version/VersionGraph.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>, diff --git a/sdk/lib/version/VersionInfo.ts b/sdk/package/lib/version/VersionInfo.ts similarity index 68% rename from sdk/lib/version/VersionInfo.ts rename to sdk/package/lib/version/VersionInfo.ts index beea16019..952ae5352 100644 --- a/sdk/lib/version/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/lib/version/index.ts b/sdk/package/lib/version/index.ts similarity index 100% rename from sdk/lib/version/index.ts rename to sdk/package/lib/version/index.ts diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json new file mode 100644 index 000000000..b5c0fba5d --- /dev/null +++ b/sdk/package/package-lock.json @@ -0,0 +1,4706 @@ +{ + "name": "@start9labs/start-sdk", + "version": "0.3.6-alpha8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@start9labs/start-sdk", + "version": "0.3.6-alpha8", + "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": { + "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": { + "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" + } + } + } +} diff --git a/sdk/package.json b/sdk/package/package.json similarity index 80% rename from sdk/package.json rename to sdk/package/package.json index f3af7ff1b..530a22053 100644 --- a/sdk/package.json +++ b/sdk/package/package.json @@ -2,22 +2,22 @@ "name": "@start9labs/start-sdk", "version": "0.3.6-alpha8", "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", diff --git a/sdk/scripts/oldSpecToBuilder.ts b/sdk/package/scripts/oldSpecToBuilder.ts similarity index 94% rename from sdk/scripts/oldSpecToBuilder.ts rename to sdk/package/scripts/oldSpecToBuilder.ts index 6dd726e1b..04128f2bd 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) { @@ -195,7 +195,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": { @@ -319,7 +318,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)}, @@ -352,10 +350,10 @@ const {Config, List, Value, Variants} = sdk }, ${variants}) `, ) - const listConfig = maybeNewConst( - value.name + "_list_config", + const listInputSpec = maybeNewConst( + value.name + "_list_inputSpec", ` - Config.of({ + InputSpec.of({ "union": ${unionValueName} }) `, @@ -368,7 +366,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 +405,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..7665a26a6 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,6 +41,7 @@ git submodule update --init --recursive ``` #### Move to web directory and install dependencies + ```sh cd web npm i @@ -44,6 +49,7 @@ npm run build:deps ``` #### Copy `config-sample.json` to a new file `config.json`. + ```sh cp config-sample.json config.json ``` @@ -59,10 +65,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 +77,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 +85,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/package-lock.json b/web/package-lock.json index 7ceeb4399..2f05ddab3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -28,7 +28,7 @@ "@noble/hashes": "^1.4.0", "@start9labs/argon2": "^0.2.2", "@start9labs/emver": "^0.1.5", - "@start9labs/start-sdk": "file:../sdk/dist", + "@start9labs/start-sdk": "file:../sdk/baseDist", "@taiga-ui/addon-charts": "3.86.0", "@taiga-ui/cdk": "3.86.0", "@taiga-ui/core": "3.86.0", @@ -114,9 +114,36 @@ "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": "^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-alpha8", + "extraneous": true, "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -4116,7 +4143,7 @@ "integrity": "sha512-1dhiG03VkfEwSLx/JPKVms6srAbYFQgwfSGhwpUKMDliMXuAHGVaueStmqzVxn3JpH/HEVz0QW8w/PXHqjdiIg==" }, "node_modules/@start9labs/start-sdk": { - "resolved": "../sdk/dist", + "resolved": "../sdk/baseDist", "link": true }, "node_modules/@stencil/core": { diff --git a/web/package.json b/web/package.json index 945dde7cf..c73d93859 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,7 @@ "@noble/hashes": "^1.4.0", "@start9labs/argon2": "^0.2.2", "@start9labs/emver": "^0.1.5", - "@start9labs/start-sdk": "file:../sdk/dist", + "@start9labs/start-sdk": "file:../sdk/baseDist", "@taiga-ui/addon-charts": "3.86.0", "@taiga-ui/cdk": "3.86.0", "@taiga-ui/core": "3.86.0", 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/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..743d3546b 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,8 +261,8 @@ 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.', @@ -271,19 +271,19 @@ const cifsSpec = CB.Config.of({ required: { 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 }, }), - 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 }, 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, diff --git a/web/projects/ui/src/app/components/form.component.ts b/web/projects/ui/src/app/components/form.component.ts index 830e8de1e..386bd5f8e 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,8 +148,8 @@ export class FormComponent> implements OnInit { this.context?.$implicit.complete() } - private process(patch: Operation[]) { - patch.forEach(({ op, path }) => { + private process(operations: Operation[]) { + operations.forEach(({ op, path }) => { const control = this.form.get(path.substring(1).split('/')) if (!control || !control.parent) return 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.ts b/web/projects/ui/src/app/components/form/form-select/form-select.component.ts index ccbbccae8..ac478a4d1 100644 --- a/web/projects/ui/src/app/components/form/form-select/form-select.component.ts +++ b/web/projects/ui/src/app/components/form/form-select/form-select.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { invert } from '@start9labs/shared' import { Control } from '../control' @@ -7,7 +7,7 @@ import { Control } from '../control' selector: 'form-select', templateUrl: './form-select.component.html', }) -export class FormSelectComponent extends Control { +export class FormSelectComponent extends Control { private readonly inverted = invert(this.spec.values) readonly items = Object.values(this.spec.values) diff --git a/web/projects/ui/src/app/components/form/form-text/form-text.component.ts b/web/projects/ui/src/app/components/form/form-text/form-text.component.ts index 8a5ac86f0..703570dbc 100644 --- a/web/projects/ui/src/app/components/form/form-text/form-text.component.ts +++ b/web/projects/ui/src/app/components/form/form-text/form-text.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core' -import { CT, utils } from '@start9labs/start-sdk' +import { IST, utils } from '@start9labs/start-sdk' import { Control } from '../control' @Component({ @@ -7,7 +7,7 @@ import { Control } from '../control' templateUrl: './form-text.component.html', styleUrls: ['./form-text.component.scss'], }) -export class FormTextComponent extends Control { +export class FormTextComponent extends Control { masked = true generate() { diff --git a/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts b/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts index b32685c21..0c2bd054d 100644 --- a/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts +++ b/web/projects/ui/src/app/components/form/form-textarea/form-textarea.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' @Component({ @@ -7,6 +7,6 @@ import { Control } from '../control' templateUrl: './form-textarea.component.html', }) export class FormTextareaComponent extends Control< - CT.ValueSpecTextarea, + IST.ValueSpecTextarea, string > {} diff --git a/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts b/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts index 6a3c0196f..fd4f18218 100644 --- a/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts +++ b/web/projects/ui/src/app/components/form/form-toggle/form-toggle.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' @Component({ @@ -7,4 +7,7 @@ import { Control } from '../control' templateUrl: './form-toggle.component.html', host: { class: 'g-toggle' }, }) -export class FormToggleComponent extends Control {} +export class FormToggleComponent extends Control< + IST.ValueSpecToggle, + boolean +> {} diff --git a/web/projects/ui/src/app/components/form/form-union/form-union.component.ts b/web/projects/ui/src/app/components/form/form-union/form-union.component.ts index 2c164be48..83c69bd6d 100644 --- a/web/projects/ui/src/app/components/form/form-union/form-union.component.ts +++ b/web/projects/ui/src/app/components/form/form-union/form-union.component.ts @@ -6,7 +6,7 @@ import { OnChanges, } from '@angular/core' import { ControlContainer, FormGroupName } from '@angular/forms' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' import { FormService } from 'src/app/services/form.service' import { tuiPure } from '@taiga-ui/cdk' @@ -24,9 +24,9 @@ import { tuiPure } from '@taiga-ui/cdk' }) export class FormUnionComponent implements OnChanges { @Input() - spec!: CT.ValueSpecUnion + spec!: IST.ValueSpecUnion - selectSpec!: CT.ValueSpecSelect + selectSpec!: IST.ValueSpecSelect private readonly form = inject(FormGroupName) private readonly formService = inject(FormService) diff --git a/web/projects/ui/src/app/components/form/form.module.ts b/web/projects/ui/src/app/components/form/form.module.ts index 5417309a9..f57f6ef46 100644 --- a/web/projects/ui/src/app/components/form/form.module.ts +++ b/web/projects/ui/src/app/components/form/form.module.ts @@ -50,6 +50,7 @@ import { ControlDirective } from './control.directive' import { FormColorComponent } from './form-color/form-color.component' import { FormDatetimeComponent } from './form-datetime/form-datetime.component' import { HintPipe } from './hint.pipe' +import { FilterHiddenPipe } from './filter-hidden.pipe' @NgModule({ imports: [ @@ -101,6 +102,7 @@ import { HintPipe } from './hint.pipe' MustachePipe, HintPipe, ControlDirective, + FilterHiddenPipe, ], exports: [FormGroupComponent], }) diff --git a/web/projects/ui/src/app/components/form/hint.pipe.ts b/web/projects/ui/src/app/components/form/hint.pipe.ts index b5a730661..f03d9577f 100644 --- a/web/projects/ui/src/app/components/form/hint.pipe.ts +++ b/web/projects/ui/src/app/components/form/hint.pipe.ts @@ -1,11 +1,11 @@ import { Pipe, PipeTransform } from '@angular/core' -import { CT } from '@start9labs/start-sdk' +import { IST } from '@start9labs/start-sdk' @Pipe({ name: 'hint', }) export class HintPipe implements PipeTransform { - transform(spec: CT.ValueSpec): string { + transform(spec: Exclude): string { const hint = [] if (spec.description) { diff --git a/web/projects/ui/src/app/modals/config-dep.component.ts b/web/projects/ui/src/app/modals/action-dep.component.ts similarity index 78% rename from web/projects/ui/src/app/modals/config-dep.component.ts rename to web/projects/ui/src/app/modals/action-dep.component.ts index 14becf2e8..8eeb9faa8 100644 --- a/web/projects/ui/src/app/modals/config-dep.component.ts +++ b/web/projects/ui/src/app/modals/action-dep.component.ts @@ -2,50 +2,49 @@ import { ChangeDetectionStrategy, Component, Input, - OnChanges, + OnInit, } from '@angular/core' -import { compare, getValueByPointer, Operation } from 'fast-json-patch' +import { getValueByPointer, Operation } from 'fast-json-patch' import { isObject } from '@start9labs/shared' import { tuiIsNumber } from '@taiga-ui/cdk' import { CommonModule } from '@angular/common' import { TuiNotificationModule } from '@taiga-ui/core' @Component({ - selector: 'config-dep', + selector: 'action-dep', template: `

- {{ package }} + {{ pkgTitle }}

- The following modifications have been made to {{ package }} to satisfy - {{ dep }}: + The following modifications have been made to {{ pkgTitle }} to satisfy + {{ depTitle }}:
- To accept these modifications, click "Save".
`, standalone: true, imports: [CommonModule, TuiNotificationModule], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ConfigDepComponent implements OnChanges { +export class ActionDepComponent implements OnInit { @Input() - package = '' + pkgTitle = '' @Input() - dep = '' + depTitle = '' @Input() - original: object = {} + originalValue: object = {} @Input() - value: object = {} + operations: Operation[] = [] diff: string[] = [] - ngOnChanges() { - this.diff = compare(this.original, this.value).map( + ngOnInit() { + this.diff = this.operations.map( op => `${this.getPath(op)}: ${this.getMessage(op)}`, ) } @@ -82,7 +81,7 @@ export class ConfigDepComponent implements OnChanges { } private getOldValue(path: any): string { - const val = getValueByPointer(this.original, path) + const val = getValueByPointer(this.originalValue, path) if (['string', 'number', 'boolean'].includes(typeof val)) { return val } else if (isObject(val)) { diff --git a/web/projects/ui/src/app/modals/action-input.component.ts b/web/projects/ui/src/app/modals/action-input.component.ts new file mode 100644 index 000000000..494dfcdda --- /dev/null +++ b/web/projects/ui/src/app/modals/action-input.component.ts @@ -0,0 +1,208 @@ +import { CommonModule } from '@angular/common' +import { Component, Inject } from '@angular/core' +import { getErrorMessage } from '@start9labs/shared' +import { T, utils } 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 } from 'fast-json-patch' +import { PatchDB } from 'patch-db-client' +import { catchError, defer, EMPTY, endWith, firstValueFrom, map } from 'rxjs' +import { InvalidService } from 'src/app/components/form/invalid.service' +import { ActionDepComponent } from 'src/app/modals/action-dep.component' +import { UiPipeModule } from 'src/app/pipes/ui/ui.module' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { getAllPackages, getManifest } from 'src/app/util/get-package-data' +import * as json from 'fast-json-patch' +import { ActionService } from '../services/action.service' +import { ActionButton, FormComponent } from '../components/form.component' + +export interface PackageActionData { + readonly pkgInfo: { + id: string + title: string + } + readonly actionInfo: { + id: string + warning: string | null + } + readonly dependentInfo?: { + title: string + request: T.ActionRequest + } +} + +@Component({ + template: ` + + +
+
+ + + +
+
+ + + + + + +
+
+ + + + + `, + styles: [ + ` + tui-notification { + font-size: 1rem; + margin-bottom: 1rem; + } + `, + ], + standalone: true, + imports: [ + CommonModule, + TuiLoaderModule, + TuiNotificationModule, + TuiButtonModule, + TuiModeModule, + ActionDepComponent, + UiPipeModule, + FormComponent, + ], + providers: [InvalidService], +}) +export class ActionInputModal { + readonly actionId = this.context.data.actionInfo.id + readonly warning = this.context.data.actionInfo.warning + readonly pkgInfo = this.context.data.pkgInfo + readonly dependentInfo = this.context.data.dependentInfo + + buttons: ActionButton[] = [ + { + text: 'Submit', + handler: value => this.execute(value), + }, + ] + + error = '' + + res$ = defer(() => + this.api.getActionInput({ + packageId: this.pkgInfo.id, + actionId: this.actionId, + }), + ).pipe( + map(res => { + const originalValue = res.value || {} + + return { + spec: res.spec, + originalValue, + operations: this.dependentInfo?.request.input + ? compare( + originalValue, + utils.deepMerge( + originalValue, + this.dependentInfo.request.input.value, + ) as object, + ) + : null, + } + }), + catchError(e => { + this.error = String(getErrorMessage(e)) + return EMPTY + }), + ) + + constructor( + @Inject(POLYMORPHEUS_CONTEXT) + private readonly context: TuiDialogContext, + private readonly dialogs: TuiDialogService, + private readonly api: ApiService, + private readonly patch: PatchDB, + private readonly actionService: ActionService, + ) {} + + async execute(input: object) { + if (await this.checkConflicts(input)) { + const res = await firstValueFrom(this.res$) + + return this.actionService.execute(this.pkgInfo.id, this.actionId, { + prev: { + spec: res.spec, + value: res.originalValue, + }, + curr: input, + }) + } + } + + private async checkConflicts(input: object): Promise { + const packages = await getAllPackages(this.patch) + + const breakages = Object.keys(packages) + .filter( + id => + id !== this.pkgInfo.id && + Object.values(packages[id].requestedActions).some( + ({ request, active }) => + !active && + request.packageId === this.pkgInfo.id && + request.actionId === this.actionId && + request.when?.condition === 'input-not-matches' && + request.input && + json + .compare(input, request.input) + .some(op => op.op === 'add' || op.op === 'replace'), + ), + ) + .map(id => id) + + if (!breakages.length) return true + + 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/action-success/action-success.page.ts b/web/projects/ui/src/app/modals/action-success/action-success.page.ts index 30c5c02cd..8912ed6ba 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,7 +1,7 @@ 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 { T } from '@start9labs/start-sdk' @Component({ selector: 'action-success', @@ -10,7 +10,7 @@ import { copyToClipboard } from '@start9labs/shared' }) export class ActionSuccessPage { @Input() - actionRes!: ActionResponse + actionRes!: T.ActionResult constructor( private readonly modalCtrl: ModalController, 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 ee938240a..a0f4f46db 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 @@ -14,7 +14,10 @@

0.3.6-alpha.5

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.page.html b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html index ec425f8bb..6b72c38a8 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,28 @@ - - - - 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..f52cd604d 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,15 @@ 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' +import { AlertController, NavController } from '@ionic/angular' +import { ErrorService, getPkgId, LoadingService } 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 { ActionService } from 'src/app/services/action.service' +import { DataModel } 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 { filter, map } from 'rxjs' @Component({ selector: 'app-actions', @@ -42,101 +19,49 @@ 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, + manifest: getManifest(pkg), + actions: Object.keys(pkg.actions) + .filter(id => id !== 'config') + .map(id => ({ + id, + ...pkg.actions[id], + })), + })), + ) constructor( private readonly route: ActivatedRoute, - private readonly embassyApi: ApiService, - private readonly modalCtrl: ModalController, + private readonly api: ApiService, 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, ) {} async handleAction( - status: T.Status, - action: { key: string; value: T.ActionMetadata }, + mainStatus: T.MainStatus['main'], + 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() - } + this.actionService.present( + { id: manifest.id, title: manifest.title, mainStatus }, + { id: action.id, metadata: action }, + ) } - async tryUninstall(pkg: PackageDataEntry): Promise { - const { title, alerts } = getManifest(pkg) - + async tryUninstall(manifest: T.Manifest): Promise { let message = - alerts.uninstall || - `Uninstalling ${title} will permanently delete its data` + manifest.alerts.uninstall || + `Uninstalling ${manifest.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` + message = `${message}. Services that depend on ${manifest.title} will no longer work properly and may crash` } const alert = await this.alertCtrl.create({ @@ -165,8 +90,8 @@ export class AppActionsPage { const loader = this.loader.open(`Beginning uninstall...`).subscribe() try { - await this.embassyApi.uninstallPackage({ id: this.pkgId }) - this.embassyApi + await this.api.uninstallPackage({ id: this.pkgId }) + this.api .setDbValue(['ackInstructions', this.pkgId], false) .catch(e => console.error('Failed to mark instructions as unseen', e)) this.navCtrl.navigateRoot('/services') @@ -176,46 +101,6 @@ export class AppActionsPage { loader.unsubscribe() } } - - 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 - } -} - -interface LocalAction { - name: string - description: string - icon: string } @Component({ @@ -225,5 +110,18 @@ interface LocalAction { changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppActionsItemComponent { - @Input() action!: LocalAction + @Input() action!: { + name: string + description: string + visibility: T.ActionVisibility + } + + @Input() icon!: string + + get disabledText() { + return ( + typeof this.action.visibility === 'object' && + this.action.visibility.disabled.reason + ) + } } 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 @@ 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-properties/app-properties.page.ts b/web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts index af659c32b..521542c10 100644 --- 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 @@ -34,7 +34,7 @@ export class AppPropertiesPage { unmasked: { [key: string]: boolean } = {} stopped$ = this.patch - .watch$('packageData', this.pkgId, 'status', 'main', 'status') + .watch$('packageData', this.pkgId, 'status', 'main') .pipe(map(status => status === 'stopped')) @ViewChild(IonBackButtonDelegate, { static: false }) 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..7ea0b86fd 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 @@ -22,8 +22,8 @@ > , private readonly depErrorService: DepErrorService, - private readonly formDialog: FormDialogService, + private readonly actionService: ActionService, ) {} showProgress( @@ -89,9 +89,9 @@ export class AppShowPage { ): DependencyInfo[] { const manifest = getManifest(pkg) - return Object.keys(pkg.currentDependencies) - .filter(id => !!manifest.dependencies[id]) - .map(id => this.getDepValues(pkg, allPkgs, manifest, id, depErrors)) + return Object.keys(pkg.currentDependencies).map(id => + this.getDepValues(pkg, allPkgs, manifest, id, depErrors), + ) } private getDepDetails( @@ -113,8 +113,8 @@ export class AppShowPage { } } else { return { - title: title ? title : depId, - icon: icon ? icon : 'assets/img/service-icons/fallback.png', + title: title || depId, + icon: icon || 'assets/img/service-icons/fallback.png', versionRange, } } @@ -200,20 +200,34 @@ export class AppShowPage { pkg: PackageDataEntry, pkgManifest: T.Manifest, action: 'install' | 'update' | 'configure', - id: string, - ): Promise { + depId: string, + ) { switch (action) { case 'install': case 'update': - return this.installDep(pkg, pkgManifest, id) + return this.installDep(pkg, pkgManifest, depId) case 'configure': - return this.formDialog.open(ConfigModal, { - label: `${pkgManifest.title} config`, - data: { - pkgId: id, - dependentInfo: pkgManifest, + const depPkg = await getPackage(this.patch, depId) + if (!depPkg) return + + const depManifest = getManifest(depPkg) + return this.actionService.present( + { + id: depId, + title: depManifest.title, + mainStatus: depPkg.status.main, }, - }) + { id: 'config', metadata: pkg.actions['config'] }, + { + title: pkgManifest.title, + request: Object.values(pkg.requestedActions).find( + r => + r.active && + r.request.packageId === depId && + r.request.actionId === 'config', + )!.request, + }, + ) } } 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..5dc5faf67 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 @@ -36,7 +36,7 @@ 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..c355bd008 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,9 @@ 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 { ActionService } from 'src/app/services/action.service' 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, @@ -20,6 +19,7 @@ import { getAllPackages, getManifest, isInstalled, + needsConfig, } from 'src/app/util/get-package-data' import { hasCurrentDeps } from 'src/app/util/has-deps' @@ -39,6 +39,7 @@ export class AppShowStatusComponent { PR = PrimaryRendering isInstalled = isInstalled + needsConfig = needsConfig constructor( private readonly alertCtrl: AlertController, @@ -46,9 +47,9 @@ 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, + private readonly actionService: ActionService, ) {} get interfaces(): PackageDataEntry['serviceInterfaces'] { @@ -59,7 +60,7 @@ export class AppShowStatusComponent { return this.pkg.hosts } - get pkgStatus(): T.Status { + get pkgStatus(): T.MainStatus { return this.pkg.status } @@ -75,12 +76,15 @@ export class AppShowStatusComponent { return ['running', 'starting', 'restarting'].includes(this.status.primary) } - get isStopped(): boolean { - return this.status.primary === 'stopped' + get canStart(): boolean { + return ( + this.status.primary === 'stopped' && + !Object.keys(this.pkg.requestedActions).length + ) } get sigtermTimeout(): string | null { - return this.pkgStatus?.main.status === 'stopping' ? '30s' : null // @dr-bonez TODO + return this.pkgStatus?.main === 'stopping' ? '30s' : null // @dr-bonez TODO } launchUi( @@ -91,9 +95,14 @@ export class AppShowStatusComponent { } async presentModalConfig(): Promise { - return this.formDialog.open(ConfigModal, { - data: { pkgId: this.manifest.id }, - }) + return this.actionService.present( + { + id: this.manifest.id, + title: this.manifest.title, + mainStatus: this.pkg.status.main, + }, + { id: 'config', metadata: this.pkg.actions['config'] }, + ) } async tryStart(): Promise { 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..c9bb73bfe 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 @@ -1,6 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core' import { ActivatedRoute } from '@angular/router' -import { ModalController, NavController } from '@ionic/angular' +import { AlertController, ModalController, NavController } from '@ionic/angular' import { MarkdownComponent } from '@start9labs/shared' import { DataModel, @@ -8,10 +8,10 @@ import { PackageDataEntry, } from 'src/app/services/patch-db/data-model' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { from, map, Observable } from 'rxjs' +import { from, map, Observable, of } 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' +import { ActionService } from 'src/app/services/action.service' +import { needsConfig } from 'src/app/util/get-package-data' export interface Button { title: string @@ -33,7 +33,8 @@ export class ToButtonsPipe implements PipeTransform { private readonly apiService: ApiService, private readonly api: ApiService, private readonly patch: PatchDB, - private readonly formDialog: FormDialogService, + private readonly actionService: ActionService, + private readonly alertCtrl: AlertController, ) {} transform(pkg: PackageDataEntry): Button[] { @@ -53,13 +54,29 @@ export class ToButtonsPipe implements PipeTransform { // config { action: async () => - this.formDialog.open(ConfigModal, { - label: `${manifest.title} configuration`, - data: { pkgId: manifest.id }, - }), + pkg.actions['config'] + ? this.actionService.present( + { + id: manifest.id, + title: manifest.title, + mainStatus: pkg.status.main, + }, + { + id: 'config', + metadata: pkg.actions['config'], + }, + ) + : this.alertCtrl + .create({ + header: 'No Config', + message: `No config options for ${manifest.title} v${manifest.version}`, + buttons: ['OK'], + }) + .then(a => a.present()), title: 'Config', description: `Customize ${manifest.title}`, icon: 'options-outline', + highlighted$: of(needsConfig(manifest.id, pkg.requestedActions)), }, // properties { 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/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..12ecd4e61 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,22 +694,22 @@ 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, }, masked: true, }), - newPassword1: CB.Value.text({ + newPassword1: ISB.Value.text({ name: 'New Password', required: { default: null, }, masked: true, }), - newPassword2: CB.Value.text({ + newPassword2: ISB.Value.text({ name: 'Retype New Password', required: { default: null, 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..74df5ffc6 --- /dev/null +++ b/web/projects/ui/src/app/services/action.service.ts @@ -0,0 +1,159 @@ +import { Injectable } from '@angular/core' +import { AlertController, ModalController } from '@ionic/angular' +import { ErrorService, LoadingService } from '@start9labs/shared' +import { T } from '@start9labs/start-sdk' +import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' +import { RR } from 'src/app/services/api/api.types' +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 modalCtrl: ModalController, + private readonly alertCtrl: AlertController, + private readonly errorService: ErrorService, + private readonly loader: LoadingService, + private readonly formDialog: FormDialogService, + ) {} + + async present( + pkgInfo: { + id: string + title: string + mainStatus: T.MainStatus['main'] + }, + actionInfo: { + id: string + metadata: T.ActionMetadata + }, + dependentInfo?: { + title: string + request: T.ActionRequest + }, + ) { + if ( + allowedStatuses[actionInfo.metadata.allowedStatuses].has( + pkgInfo.mainStatus, + ) + ) { + if (actionInfo.metadata.hasInput) { + this.formDialog.open(ActionInputModal, { + label: actionInfo.metadata.name, + data: { + pkgInfo, + actionInfo: { + id: actionInfo.id, + warning: actionInfo.metadata.warning, + }, + dependentInfo, + }, + }) + } else { + const alert = await this.alertCtrl.create({ + header: 'Confirm', + message: `Are you sure you want to execute action "${ + actionInfo.metadata.name + }"? ${actionInfo.metadata.warning || ''}`, + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Execute', + handler: () => { + this.execute(pkgInfo.id, actionInfo.id) + }, + cssClass: 'enter-click', + }, + ], + }) + await alert.present() + } + } 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, + inputs?: { + prev: RR.GetActionInputRes + curr: object + }, + ): Promise { + const loader = this.loader.open('Executing action...').subscribe() + + try { + const res = await this.api.runAction({ + packageId, + actionId, + prev: inputs?.prev || null, + input: inputs?.curr || null, + }) + + if (res) { + 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() + } + } +} 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..e3aa63ee3 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -6,7 +6,7 @@ import { Metric, 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 = { @@ -112,7 +112,6 @@ export module Mock { }, osVersion: '0.2.12', dependencies: {}, - hasConfig: true, images: { main: { source: 'packed', @@ -170,7 +169,6 @@ export module Mock { s9pk: '', }, }, - hasConfig: true, images: { main: { source: 'packed', @@ -221,7 +219,6 @@ export module Mock { s9pk: '', }, }, - hasConfig: false, images: { main: { source: 'packed', @@ -949,7 +946,8 @@ export module Mock { }, } - export const ActionResponse: RR.ExecutePackageActionRes = { + export const ActionResponse: T.ActionResult = { + version: '0', message: 'Password changed successfully. If you lose your new password, you will be lost forever.', value: 'NewPassword1234!', @@ -1137,31 +1135,29 @@ export module Mock { }, } - 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' }, }, - 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, @@ -1169,7 +1165,7 @@ export module Mock { 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', @@ -1186,24 +1182,23 @@ export module Mock { ), }), ), - color: CB.Value.color({ + color: ISB.Value.color({ name: 'Color', required: false, }), - datetime: CB.Value.datetime({ + datetime: ISB.Value.datetime({ name: 'Datetime', required: false, }), - file: CB.Value.file({ + file: ISB.Value.file({ name: 'File', required: false, extensions: ['png', 'pdf'], }), - users: CB.Value.multiselect({ + users: ISB.Value.multiselect({ name: 'Users', default: [], maxLength: 2, - disabled: ['matt'], values: { matt: 'Matt Hill', alex: 'Alex Inkin', @@ -1211,21 +1206,19 @@ 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', @@ -1238,7 +1231,7 @@ export module Mock { }, ], }), - rpcuser: CB.Value.text({ + rpcuser: ISB.Value.text({ name: 'RPC Username', required: { default: 'defaultrpcusername', @@ -1251,7 +1244,7 @@ export module Mock { }, ], }), - rpcpass: CB.Value.text({ + rpcpass: ISB.Value.text({ name: 'RPC User Password', required: { default: { @@ -1261,7 +1254,7 @@ export module Mock { }, description: 'rpc password', }), - rpcpass2: CB.Value.text({ + rpcpass2: ISB.Value.text({ name: 'RPC User Password', required: { default: { @@ -1275,15 +1268,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,13 +1288,13 @@ 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', }), - 'last-name': CB.Value.text({ + 'last-name': ISB.Value.text({ name: 'Last Name', required: { default: { @@ -1317,7 +1310,7 @@ 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.', @@ -1331,8 +1324,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,27 +1335,27 @@ 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' }, }, - 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', }, 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: { @@ -1379,8 +1372,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,7 +1387,7 @@ 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!', @@ -1407,47 +1400,47 @@ export module Mock { 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: { - default: 7, + /* 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: { + 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', }), - law2: CB.Value.text({ + law2: ISB.Value.text({ name: 'Second Law', required: false, description: 'the second law', }), }), ), - rulemakers: CB.Value.list( - CB.List.obj( + rulemakers: ISB.Value.list( + ISB.List.obj( { name: 'Rule Makers', minLength: 0, @@ -1455,8 +1448,8 @@ 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: { @@ -1466,7 +1459,7 @@ export module Mock { }, description: 'the name of the rule maker', }), - rulemakerip: CB.Value.text({ + rulemakerip: ISB.Value.text({ name: 'Rulemaker IP', required: { default: '192.168.1.0', @@ -1484,7 +1477,7 @@ export module Mock { }, ), ), - rpcuser: CB.Value.text({ + rpcuser: ISB.Value.text({ name: 'RPC Username', required: { default: 'defaultrpcusername', @@ -1497,7 +1490,7 @@ export module Mock { }, ], }), - rpcpass: CB.Value.text({ + rpcpass: ISB.Value.text({ name: 'RPC User Password', required: { default: { @@ -1510,33 +1503,32 @@ export module Mock { }), }), ), - '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'], }, - 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, @@ -1548,7 +1540,7 @@ export module Mock { }, ], }), - email: CB.Value.text({ + email: ISB.Value.text({ name: 'Email', inputmode: 'email', required: { @@ -1557,7 +1549,7 @@ export module Mock { }), }), ), - 'public-domain': CB.Value.text({ + 'public-domain': ISB.Value.text({ name: 'Public Domain', required: { default: 'bitcoinnode.com', @@ -1570,7 +1562,7 @@ export module Mock { }, ], }), - 'private-domain': CB.Value.text({ + 'private-domain': ISB.Value.text({ name: 'Private Domain', required: { default: null, @@ -1583,7 +1575,7 @@ 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', @@ -1595,7 +1587,7 @@ export module Mock { 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', @@ -1606,8 +1598,8 @@ export module Mock { '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 +1621,8 @@ export module Mock { }, ), ), - rpcauth: CB.Value.list( - CB.List.text( + rpcauth: ISB.Value.list( + ISB.List.text( { name: 'RPC Auth', description: @@ -1694,14 +1686,21 @@ 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: 'Bitcoin Config', + description: 'edit bitcoin.conf', + warning: null, + visibility: 'enabled', + allowedStatuses: 'any', + hasInput: true, + group: null, }, }, - actions: {}, serviceInterfaces: { ui: { id: 'ui', @@ -1860,6 +1859,7 @@ export module Mock { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: {}, } export const bitcoinProxy: PackageDataEntry = { @@ -1871,10 +1871,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 +1899,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 +1917,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 +1980,19 @@ 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: {}, } 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..e428456f6 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -2,7 +2,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 +72,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 @@ -226,14 +225,19 @@ export module RR { 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 GetActionInputReq = { packageId: string; actionId: string } // package.action.get-input + export type GetActionInputRes = { + spec: IST.InputSpec + value: object | null + } - export type DrySetPackageConfigReq = { id: string; config: object } // package.config.set.dry - export type DrySetPackageConfigRes = T.PackageId[] - - export type SetPackageConfigReq = DrySetPackageConfigReq // package.config.set - export type SetPackageConfigRes = null + export type RunActionReq = { + packageId: string + actionId: string + prev: GetActionInputRes | null + input: object | null + } // package.action.run + export type RunActionRes = T.ActionResult | null export type RestorePackagesReq = { // package.backup.restore @@ -244,13 +248,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 @@ -263,16 +260,6 @@ export module RR { 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 +293,6 @@ export interface TaggedDependencyError { error: DependencyError } -export interface ActionResponse { - message: string - value: string | null - copyable: boolean - qr: boolean -} - interface MetricData { value: string unit: string 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 31e3ac86e..1a5c1c57c 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 @@ -231,26 +231,16 @@ export abstract class ApiService { params: RR.InstallPackageReq, ): Promise - abstract getPackageConfig( - params: RR.GetPackageConfigReq, - ): Promise + abstract getActionInput( + params: RR.GetActionInputReq, + ): Promise - abstract drySetPackageConfig( - params: RR.DrySetPackageConfigReq, - ): Promise - - abstract setPackageConfig( - params: RR.SetPackageConfigReq, - ): Promise + abstract runAction(params: RR.RunActionReq): Promise abstract restorePackages( params: RR.RestorePackagesReq, ): Promise - abstract executePackageAction( - params: RR.ExecutePackageActionReq, - ): Promise - abstract startPackage(params: RR.StartPackageReq): Promise abstract restartPackage( @@ -263,9 +253,5 @@ export abstract class ApiService { 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 48cf6edd3..742076ec8 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 @@ -468,22 +468,14 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.install', params }) } - async getPackageConfig( - params: RR.GetPackageConfigReq, - ): Promise { - return this.rpcRequest({ method: 'package.config.get', params }) + async getActionInput( + params: RR.GetActionInputReq, + ): Promise { + return this.rpcRequest({ method: 'package.action.get-input', params }) } - async drySetPackageConfig( - params: RR.DrySetPackageConfigReq, - ): Promise { - return this.rpcRequest({ method: 'package.config.set.dry', params }) - } - - async setPackageConfig( - params: RR.SetPackageConfigReq, - ): Promise { - return this.rpcRequest({ method: 'package.config.set', params }) + async runAction(params: RR.RunActionReq): Promise { + return this.rpcRequest({ method: 'package.action.run', params }) } async restorePackages( @@ -492,12 +484,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 }) } @@ -518,15 +504,6 @@ export class LiveApiService extends ApiService { 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 a40f29a0a..6c55bb9db 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 @@ -780,37 +780,19 @@ 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.RunActionReq): 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 + return Mock.ActionResponse } async restorePackages( @@ -843,13 +825,6 @@ 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` @@ -1069,17 +1044,6 @@ 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 { 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 34804fbc6..cdc8f7173 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -96,40 +96,47 @@ 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: '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, }, }, }, - actions: {}, + actions: { + config: { + name: 'Bitcoin Config', + description: 'edit bitcoin.conf', + warning: null, + visibility: 'enabled', + allowedStatuses: 'any', + hasInput: true, + group: null, + }, + }, serviceInterfaces: { ui: { id: 'ui', @@ -288,6 +295,7 @@ export const mockPatchData: DataModel = { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: {}, }, lnd: { stateInfo: { @@ -301,10 +309,7 @@ export const mockPatchData: DataModel = { icon: '/assets/img/service-icons/lnd.png', lastBackup: null, status: { - configured: true, - main: { - status: 'stopped', - }, + main: 'stopped', }, actions: {}, serviceInterfaces: { @@ -367,7 +372,6 @@ export const mockPatchData: DataModel = { kind: 'running', versionRange: '>=26.0.0', healthChecks: [], - configSatisfied: true, }, 'btc-rpc-proxy': { title: 'Bitcoin Proxy', @@ -375,13 +379,13 @@ export const mockPatchData: DataModel = { kind: 'running', versionRange: '>2.0.0', healthChecks: [], - configSatisfied: false, }, }, hosts: {}, storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', + requestedActions: {}, }, }, } 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..c4de94609 100644 --- a/web/projects/ui/src/app/services/dep-error.service.ts +++ b/web/projects/ui/src/app/services/dep-error.service.ts @@ -102,13 +102,20 @@ export class DepErrorService { } // invalid config - if (!currentDep.configSatisfied) { + if ( + Object.values(pkg.requestedActions).some( + a => + a.active && + a.request.packageId === depId && + a.request.actionId === 'config', + ) + ) { return { type: 'configUnsatisfied', } } - 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/form.service.ts b/web/projects/ui/src/app/services/form.service.ts index 779fb1f53..33fa29016 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 @@ -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,7 +231,7 @@ function numberValidators(spec: CT.ValueSpecNumber): ValidatorFn[] { return validators } -function selectValidators(spec: CT.ValueSpecSelect): ValidatorFn[] { +function selectValidators(spec: IST.ValueSpecSelect): ValidatorFn[] { const validators: ValidatorFn[] = [] if (spec.required) { @@ -241,13 +241,13 @@ function selectValidators(spec: CT.ValueSpecSelect): ValidatorFn[] { 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 +352,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 +389,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 +406,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 +418,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 +446,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 +474,8 @@ function listObjEquals( } function objEquals( - uniqueBy: CT.UniqueBy, - spec: CT.ValueSpecObject, + uniqueBy: IST.UniqueBy, + spec: IST.ValueSpecObject, val1: any, val2: any, ): boolean { @@ -499,8 +503,8 @@ function objEquals( } function unionEquals( - uniqueBy: CT.UniqueBy, - spec: CT.ValueSpecUnion, + uniqueBy: IST.UniqueBy, + spec: IST.ValueSpecUnion, val1: any, val2: any, ): boolean { @@ -532,8 +536,8 @@ function unionEquals( } function uniqueByMessageWrapper( - uniqueBy: CT.UniqueBy, - spec: CT.ListValueSpecObject, + uniqueBy: IST.UniqueBy, + spec: IST.ListValueSpecObject, ) { let configSpec = spec.spec @@ -544,8 +548,8 @@ function uniqueByMessageWrapper( } function uniqueByMessage( - uniqueBy: CT.UniqueBy, - configSpec: CT.InputSpec, + uniqueBy: IST.UniqueBy, + configSpec: IST.InputSpec, outermost = true, ): string { let joinFunc @@ -554,7 +558,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 +578,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 +615,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/pkg-status-rendering.service.ts b/web/projects/ui/src/app/services/pkg-status-rendering.service.ts index d73a9afe1..f987f6b2c 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 @@ -1,6 +1,7 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PkgDependencyErrors } from './dep-error.service' import { T } from '@start9labs/start-sdk' +import { getManifest, needsConfig } from '../util/get-package-data' export interface PackageStatus { primary: PrimaryStatus @@ -17,7 +18,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 +28,11 @@ export function renderPkgStatus( return { primary, dependency, health } } -function getInstalledPrimaryStatus(status: T.Status): PrimaryStatus { - if (!status.configured) { +function getInstalledPrimaryStatus(pkg: T.PackageDataEntry): PrimaryStatus { + if (needsConfig(getManifest(pkg).id, pkg.requestedActions)) { return 'needsConfig' } else { - return status.main.status as any as PrimaryStatus + return pkg.status.main } } @@ -39,12 +40,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' 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/get-package-data.ts b/web/projects/ui/src/app/util/get-package-data.ts index c4d0cc046..7ba7377fc 100644 --- a/web/projects/ui/src/app/util/get-package-data.ts +++ b/web/projects/ui/src/app/util/get-package-data.ts @@ -28,6 +28,18 @@ export function getManifest(pkg: PackageDataEntry): T.Manifest { return (pkg.stateInfo as InstallingState).installingInfo.newManifest } +export function needsConfig( + pkgId: string, + requestedActions: PackageDataEntry['requestedActions'], +): boolean { + return Object.values(requestedActions).some( + r => + r.active && + r.request.packageId === pkgId && + r.request.actionId === 'config', + ) +} + export function isInstalled( pkg: PackageDataEntry, ): pkg is PackageDataEntry { From e7fa94c3d32a151361d97049861b904bc24786fd Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:19:06 -0600 Subject: [PATCH 10/46] add error status (#2746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add error status * update types * ṗ̶̰̙̓͒̈́ͅü̵̢̙̫̣ŗ̷̪̺̺͛g̴̲͉͎̬̒̇e̵̪̎̅͌ ̶̡̜̘͐͛t̶͎͍̣̿̍̐h̴͕̩͗̈́̎̑e̵͚͒̂͝ ̸̛͙̦͈͝v̶̱͙̬̽̔ọ̶̧̡̒̓i̸̬̲͍̋̈́d̴͉̀ * fix some extra voids * add `package.rebuild` * introduce error status and pkg rebuild and fix mocks * minor fixes * fix build --------- Co-authored-by: Matt Hill --- .../src/Adapters/EffectCreator.ts | 4 +- .../src/Adapters/Systems/SystemForStartOs.ts | 1 + core/models/src/errors.rs | 8 + core/startos/src/disk/mount/util.rs | 18 +- core/startos/src/lib.rs | 7 + core/startos/src/lxc/mod.rs | 3 +- core/startos/src/service/mod.rs | 9 + core/startos/src/service/service_map.rs | 28 +- core/startos/src/status/mod.rs | 16 +- sdk/base/lib/Effects.ts | 36 +-- sdk/base/lib/actions/setupActions.ts | 25 +- sdk/base/lib/dependencies/dependencies.ts | 19 +- .../lib/dependencies/setupDependencies.ts | 67 +++-- sdk/base/lib/interfaces/setupInterfaces.ts | 55 ++-- sdk/base/lib/osBindings/MainStatus.ts | 6 + sdk/base/lib/s9pk/index.ts | 7 +- sdk/base/lib/types.ts | 6 +- sdk/base/lib/util/graph.ts | 33 ++- sdk/base/package-lock.json | 37 ++- sdk/base/package.json | 13 +- sdk/package/lib/StartSdk.ts | 8 +- sdk/package/lib/inits/setupInit.ts | 2 +- sdk/package/lib/inits/setupInstall.ts | 2 +- sdk/package/lib/inits/setupUninstall.ts | 2 +- sdk/package/lib/mainFn/CommandController.ts | 4 +- sdk/package/lib/mainFn/Daemon.ts | 4 +- sdk/package/lib/mainFn/Daemons.ts | 12 +- sdk/package/lib/mainFn/HealthDaemon.ts | 3 +- sdk/package/lib/mainFn/index.ts | 2 +- sdk/package/lib/util/SubContainer.ts | 17 +- web/package-lock.json | 6 +- web/package.json | 1 + .../app-actions/app-actions.page.html | 11 +- .../app-actions/app-actions.page.ts | 61 +--- .../apps-routes/app-show/app-show.module.ts | 2 + .../apps-routes/app-show/app-show.page.html | 9 +- .../apps-routes/app-show/app-show.page.ts | 9 +- .../app-show-error.component.html | 31 ++ .../app-show-error.component.ts | 45 +++ .../app-show-status.component.html | 9 +- .../ui/src/app/services/api/api.types.ts | 3 + .../app/services/api/embassy-api.service.ts | 4 + .../services/api/embassy-live-api.service.ts | 6 + .../services/api/embassy-mock-api.service.ts | 268 ++++++++---------- .../ui/src/app/services/api/mock-patch.ts | 36 +-- .../services/pkg-status-rendering.service.ts | 6 + .../app/services/standard-actions.service.ts | 85 ++++++ web/projects/ui/src/styles.scss | 6 + web/tsconfig.json | 3 +- 49 files changed, 642 insertions(+), 413 deletions(-) create mode 100644 web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.html create mode 100644 web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-error/app-show-error.component.ts create mode 100644 web/projects/ui/src/app/services/standard-actions.service.ts diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index 022d697ab..db176536f 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -173,7 +173,7 @@ export function makeEffects(context: EffectContext): Effects { T.Effects["subcontainer"]["createFs"] > }, - destroyFs(options: { guid: string }): Promise { + destroyFs(options: { guid: string }): Promise { return rpcRound("subcontainer.destroy-fs", options) as ReturnType< T.Effects["subcontainer"]["destroyFs"] > @@ -284,7 +284,7 @@ export function makeEffects(context: EffectContext): Effects { > }, - setMainStatus(o: { status: "running" | "stopped" }): Promise { + setMainStatus(o: { status: "running" | "stopped" }): Promise { return rpcRound("set-main-status", o) as ReturnType< T.Effects["setHealth"] > diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index 43096f0f0..c000b391b 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -92,6 +92,7 @@ export class SystemForStartOs implements System { const started = async (onTerm: () => Promise) => { await effects.setMainStatus({ status: "running" }) mainOnTerm = onTerm + return null } const daemons = await ( await this.abi.main({ diff --git a/core/models/src/errors.rs b/core/models/src/errors.rs index ee6b0ae12..708b57861 100644 --- a/core/models/src/errors.rs +++ b/core/models/src/errors.rs @@ -351,6 +351,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/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/lib.rs b/core/startos/src/lib.rs index 8d4f831bf..997a7199f 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -292,6 +292,13 @@ pub fn package() -> ParentHandler { .no_display() .with_call_remote::(), ) + .subcommand( + "rebuild", + from_fn_async(service::rebuild) + .with_metadata("sync_db", Value::Bool(true)) + .no_display() + .with_call_remote::(), + ) .subcommand("logs", logs::package_logs()) .subcommand( "logs", diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index ee08801df..3979263f0 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -126,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?, ) diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 1e61b1191..37579c03b 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -589,6 +589,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, diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index ebe45fcc5..68a5f2a5f 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,6 +23,7 @@ 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; use crate::util::serde::Pem; @@ -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(()) } diff --git a/core/startos/src/status/mod.rs b/core/startos/src/status/mod.rs index e45085826..14442bce6 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -17,6 +17,11 @@ pub mod health_check; #[serde(rename_all = "camelCase")] #[serde(rename_all_fields = "camelCase")] pub enum MainStatus { + Error { + on_rebuild: StartStop, + message: String, + debug: Option, + }, Stopped, Restarting, Restoring, @@ -43,12 +48,20 @@ impl MainStatus { | MainStatus::Restarting | MainStatus::BackingUp { on_complete: StartStop::Start, + } + | MainStatus::Error { + on_rebuild: StartStop::Start, + .. } => true, MainStatus::Stopped | MainStatus::Restoring | MainStatus::Stopping { .. } | MainStatus::BackingUp { on_complete: StartStop::Stop, + } + | MainStatus::Error { + on_rebuild: StartStop::Stop, + .. } => false, } } @@ -70,7 +83,8 @@ impl MainStatus { | MainStatus::Stopped | MainStatus::Restoring | MainStatus::Stopping { .. } - | MainStatus::Restarting => None, + | MainStatus::Restarting + | MainStatus::Error { .. } => None, } } } diff --git a/sdk/base/lib/Effects.ts b/sdk/base/lib/Effects.ts index 3dab037c0..16d82464d 100644 --- a/sdk/base/lib/Effects.ts +++ b/sdk/base/lib/Effects.ts @@ -31,14 +31,14 @@ export type Effects = { constRetry: () => void clearCallbacks: ( options: { only: number[] } | { except: number[] }, - ) => Promise + ) => Promise // action action: { /** Define an action that can be invoked by a user or service */ - export(options: { id: ActionId; metadata: ActionMetadata }): Promise + export(options: { id: ActionId; metadata: ActionMetadata }): Promise /** Remove all exported actions */ - clear(options: { except: ActionId[] }): Promise + clear(options: { except: ActionId[] }): Promise getInput(options: { packageId?: PackageId actionId: ActionId @@ -50,23 +50,23 @@ export type Effects = { }): Promise request>( options: RequestActionParams, - ): Promise + ): Promise clearRequests( options: { only: ActionId[] } | { except: ActionId[] }, - ): Promise + ): Promise } // control /** restart this service's main function */ - restart(): Promise + restart(): Promise /** stop this service's main function */ - shutdown(): Promise + shutdown(): Promise /** indicate to the host os what runstate the service is in */ - setMainStatus(options: SetMainStatus): Promise + 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 + 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 */ @@ -86,11 +86,11 @@ export type Effects = { /** 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 + exposeForDependents(options: { paths: string[] }): Promise // health /** sets the result of a health check */ - setHealth(o: SetHealth): Promise + setHealth(o: SetHealth): Promise // subcontainer subcontainer: { @@ -100,13 +100,13 @@ export type Effects = { name: string | null }): Promise<[string, string]> /** A low level api used by SubContainer */ - destroyFs(options: { guid: string }): Promise + destroyFs(options: { guid: string }): Promise } // net // bind /** Creates a host connected to the specified port with the provided options */ - bind(options: BindParams): Promise + bind(options: BindParams): Promise /** Get the port address for a service */ getServicePortForward(options: { packageId?: PackageId @@ -116,7 +116,7 @@ export type Effects = { /** Removes all network bindings, called in the setupInputSpec */ clearBindings(options: { except: { id: HostId; internalPort: number }[] - }): Promise + }): Promise // host /** Returns information about the specified host, if it exists */ getHostInfo(options: { @@ -134,7 +134,7 @@ export type Effects = { getContainerIp(): Promise // interface /** Creates an interface bound to a specific host and port to show to the user */ - exportServiceInterface(options: ExportServiceInterfaceParams): Promise + exportServiceInterface(options: ExportServiceInterfaceParams): Promise /** Returns an exported service interface */ getServiceInterface(options: { packageId?: PackageId @@ -149,7 +149,7 @@ export type Effects = { /** Removes all service interfaces */ clearServiceInterfaces(options: { except: ServiceInterfaceId[] - }): Promise + }): Promise // ssl /** Returns a PEM encoded fullchain for the hostnames specified */ getSslCertificate: (options: { @@ -178,10 +178,10 @@ export type Effects = { /** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */ path: StorePath value: ExtractStore - }): Promise + }): Promise } /** sets the version that this service's data has been migrated to */ - setDataVersion(options: { version: string }): Promise + setDataVersion(options: { version: string }): Promise /** returns the version that this service's data has been migrated to */ getDataVersion(): Promise diff --git a/sdk/base/lib/actions/setupActions.ts b/sdk/base/lib/actions/setupActions.ts index f78da1f0a..8c6b7734a 100644 --- a/sdk/base/lib/actions/setupActions.ts +++ b/sdk/base/lib/actions/setupActions.ts @@ -1,6 +1,7 @@ import { InputSpec } from "./input/builder" import { ExtractInputSpecType } from "./input/builder/inputSpec" import * as T from "../types" +import { once } from "../util" export type Run< A extends @@ -130,21 +131,19 @@ export class Actions< ): Actions { return new Actions({ ...this.actions, [action.id]: action }) } - update(options: { effects: T.Effects }): Promise { - const updater = async (options: { effects: T.Effects }) => { - for (let action of Object.values(this.actions)) { - await action.exportMetadata(options) - } - await options.effects.action.clear({ except: Object.keys(this.actions) }) + 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 + }), } - const updaterCtx = { options } - updaterCtx.options = { - effects: { - ...options.effects, - constRetry: () => updater(updaterCtx.options), - }, + for (let action of Object.values(this.actions)) { + await action.exportMetadata(options) } - return updater(updaterCtx.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/dependencies/dependencies.ts b/sdk/base/lib/dependencies/dependencies.ts index fd3c6bb69..20049b5e8 100644 --- a/sdk/base/lib/dependencies/dependencies.ts +++ b/sdk/base/lib/dependencies/dependencies.ts @@ -13,15 +13,15 @@ export type CheckDependencies = { ) => boolean satisfied: () => boolean - throwIfInstalledNotSatisfied: (packageId: DependencyId) => void - throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => void - throwIfRunningNotSatisfied: (packageId: DependencyId) => void - throwIfActionsNotSatisfied: (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, @@ -100,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) @@ -117,12 +118,14 @@ 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 throwIfActionsNotSatisfied = (packageId: DependencyId) => { const dep = find(packageId) @@ -132,6 +135,7 @@ export async function checkDependencies< `The following action requests have not been fulfilled: ${reqs.join(", ")}`, ) } + return null } const throwIfHealthNotSatisfied = ( packageId: DependencyId, @@ -158,6 +162,7 @@ export async function checkDependencies< .join("; "), ) } + return null } const throwIfPkgNotSatisfied = (packageId: DependencyId) => { throwIfInstalledNotSatisfied(packageId) @@ -165,6 +170,7 @@ export async function checkDependencies< throwIfRunningNotSatisfied(packageId) throwIfActionsNotSatisfied(packageId) throwIfHealthNotSatisfied(packageId) + return null } const throwIfNotSatisfied = (packageId?: DependencyId) => packageId @@ -182,6 +188,7 @@ export async function checkDependencies< if (err.length) { throw new Error(err.join("; ")) } + return null })() return { diff --git a/sdk/base/lib/dependencies/setupDependencies.ts b/sdk/base/lib/dependencies/setupDependencies.ts index 0f9bd6da8..9d613ff74 100644 --- a/sdk/base/lib/dependencies/setupDependencies.ts +++ b/sdk/base/lib/dependencies/setupDependencies.ts @@ -1,4 +1,5 @@ import * as T from "../types" +import { once } from "../util" import { Dependency } from "./Dependency" type DependencyType = { @@ -17,40 +18,38 @@ type DependencyType = { export function setupDependencies( fn: (options: { effects: T.Effects }) => Promise>, -): (options: { effects: T.Effects }) => Promise { - return (options: { effects: T.Effects }) => { - const updater = async (options: { effects: T.Effects }) => { - 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(), - }), - ), - }) +): (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 updaterCtx = { options } - updaterCtx.options = { - effects: { - ...options.effects, - constRetry: () => updater(updaterCtx.options), - }, - } - return updater(updaterCtx.options) + 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(), + }), + ), + }) } + return cell.updater } diff --git a/sdk/base/lib/interfaces/setupInterfaces.ts b/sdk/base/lib/interfaces/setupInterfaces.ts index 474841aa3..ba284bcb3 100644 --- a/sdk/base/lib/interfaces/setupInterfaces.ts +++ b/sdk/base/lib/interfaces/setupInterfaces.ts @@ -1,4 +1,5 @@ import * as T from "../types" +import { once } from "../util" import { AddressReceipt } from "./AddressReceipt" declare const UpdateServiceInterfacesProof: unique symbol @@ -21,34 +22,36 @@ export const setupServiceInterfaces: SetupServiceInterfaces = < Output extends ServiceInterfacesReceipt, >( fn: SetServiceInterfaces, -) => - ((options: { effects: T.Effects }) => { - const updater = async (options: { effects: T.Effects }) => { - 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 +) => { + 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 updaterCtx = { options } - updaterCtx.options = { + const bindings: T.BindId[] = [] + const interfaces: T.ServiceInterfaceId[] = [] + const res = await fn({ effects: { ...options.effects, - constRetry: () => updater(updaterCtx.options), + 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) + }, }, - } - return updater(updaterCtx.options) + }) + await options.effects.clearBindings({ except: bindings }) + await options.effects.clearServiceInterfaces({ except: interfaces }) + return res }) as UpdateServiceInterfaces + return cell.updater +} diff --git a/sdk/base/lib/osBindings/MainStatus.ts b/sdk/base/lib/osBindings/MainStatus.ts index dbd9a8fcc..64e081ab9 100644 --- a/sdk/base/lib/osBindings/MainStatus.ts +++ b/sdk/base/lib/osBindings/MainStatus.ts @@ -4,6 +4,12 @@ import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" import type { StartStop } from "./StartStop" export type MainStatus = + | { + main: "error" + onRebuild: StartStop + message: string + debug: string | null + } | { main: "stopped" } | { main: "restarting" } | { main: "restoring" } diff --git a/sdk/base/lib/s9pk/index.ts b/sdk/base/lib/s9pk/index.ts index eba8ed3f8..84a1ec644 100644 --- a/sdk/base/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/base/lib/types.ts b/sdk/base/lib/types.ts index 56e310efe..071647961 100644 --- a/sdk/base/lib/types.ts +++ b/sdk/base/lib/types.ts @@ -54,7 +54,7 @@ export namespace ExpectedExports { */ export type main = (options: { effects: Effects - started(onTerm: () => PromiseLike): PromiseLike + started(onTerm: () => PromiseLike): PromiseLike }) => Promise /** @@ -118,7 +118,7 @@ export type DaemonReceipt = { } export type Daemon = { wait(): Promise - term(): Promise + term(): Promise [DaemonProof]: never } @@ -135,7 +135,7 @@ export type CommandType = string | [string, ...string[]] export type DaemonReturned = { wait(): Promise - term(options?: { signal?: Signals; timeout?: number }): Promise + term(options?: { signal?: Signals; timeout?: number }): Promise } export declare const hostName: unique symbol diff --git a/sdk/base/lib/util/graph.ts b/sdk/base/lib/util/graph.ts index 5ad71a04d..682ccf63e 100644 --- a/sdk/base/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/base/package-lock.json b/sdk/base/package-lock.json index 3a8e40c0c..91c470676 100644 --- a/sdk/base/package-lock.json +++ b/sdk/base/package-lock.json @@ -13,13 +13,14 @@ "@noble/hashes": "^1.4.0", "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", - "mime": "^4.0.3", + "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", @@ -1249,6 +1250,13 @@ "@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", @@ -3106,18 +3114,25 @@ "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" + "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": ">=16" + "node": ">= 0.6" } }, "node_modules/mimic-fn": { diff --git a/sdk/base/package.json b/sdk/base/package.json index a290d2a75..3d1d4b62e 100644 --- a/sdk/base/package.json +++ b/sdk/base/package.json @@ -21,14 +21,14 @@ }, "homepage": "https://github.com/Start9Labs/start-sdk#readme", "dependencies": { - "isomorphic-fetch": "^3.0.0", - "lodash.merge": "^4.6.2", - "mime": "^4.0.3", - "ts-matches": "^5.5.1", - "yaml": "^2.2.2", "@iarna/toml": "^2.2.5", "@noble/curves": "^1.4.0", - "@noble/hashes": "^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" }, "prettier": { "trailingComma": "all", @@ -39,6 +39,7 @@ "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", diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 05cc003cb..412dafe52 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -563,7 +563,7 @@ export class StartSdk { setupMain: ( fn: (o: { effects: Effects - started(onTerm: () => PromiseLike): PromiseLike + started(onTerm: () => PromiseLike): PromiseLike }) => Promise>, ) => setupMain(fn), /** @@ -657,12 +657,12 @@ export class StartSdk { ) => InputSpec.of(spec), }, Daemons: { - of(inputSpec: { + of(options: { effects: Effects - started: (onTerm: () => PromiseLike) => PromiseLike + started: (onTerm: () => PromiseLike) => PromiseLike healthReceipts: HealthReceipt[] }) { - return Daemons.of(inputSpec) + return Daemons.of(options) }, }, List: { diff --git a/sdk/package/lib/inits/setupInit.ts b/sdk/package/lib/inits/setupInit.ts index c9ab4f171..af2503dad 100644 --- a/sdk/package/lib/inits/setupInit.ts +++ b/sdk/package/lib/inits/setupInit.ts @@ -12,7 +12,7 @@ export function setupInit( install: Install, uninstall: Uninstall, setServiceInterfaces: UpdateServiceInterfaces, - setDependencies: (options: { effects: T.Effects }) => Promise, + setDependencies: (options: { effects: T.Effects }) => Promise, actions: Actions, exposedStore: ExposedStorePaths, ): { diff --git a/sdk/package/lib/inits/setupInstall.ts b/sdk/package/lib/inits/setupInstall.ts index d9f694021..9b5e92afe 100644 --- a/sdk/package/lib/inits/setupInstall.ts +++ b/sdk/package/lib/inits/setupInstall.ts @@ -2,7 +2,7 @@ import * as T from "../../../base/lib/types" export type InstallFn = (opts: { effects: T.Effects -}) => Promise +}) => Promise export class Install { private constructor(readonly fn: InstallFn) {} static of( diff --git a/sdk/package/lib/inits/setupUninstall.ts b/sdk/package/lib/inits/setupUninstall.ts index c863e1ad7..c169457ec 100644 --- a/sdk/package/lib/inits/setupUninstall.ts +++ b/sdk/package/lib/inits/setupUninstall.ts @@ -2,7 +2,7 @@ import * as T from "../../../base/lib/types" export type UninstallFn = (opts: { effects: T.Effects -}) => Promise +}) => Promise export class Uninstall { private constructor(readonly fn: UninstallFn) {} static of( diff --git a/sdk/package/lib/mainFn/CommandController.ts b/sdk/package/lib/mainFn/CommandController.ts index 78d888b95..601b759e6 100644 --- a/sdk/package/lib/mainFn/CommandController.ts +++ b/sdk/package/lib/mainFn/CommandController.ts @@ -43,8 +43,8 @@ export class CommandController { | undefined cwd?: string | undefined user?: string | undefined - onStdout?: (x: Buffer) => void - onStderr?: (x: Buffer) => void + onStdout?: (x: Buffer) => null + onStderr?: (x: Buffer) => null }, ) => { const commands = splitCommand(command) diff --git a/sdk/package/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts index 45f252dac..bde4654ee 100644 --- a/sdk/package/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -37,8 +37,8 @@ export class Daemon { | undefined cwd?: string | undefined user?: string | undefined - onStdout?: (x: Buffer) => void - onStderr?: (x: Buffer) => void + onStdout?: (x: Buffer) => null + onStderr?: (x: Buffer) => null sigtermTimeout?: number }, ) => { diff --git a/sdk/package/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts index 0e05214f7..4c2506d1d 100644 --- a/sdk/package/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -74,7 +74,7 @@ export class Daemons { 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[], @@ -86,17 +86,17 @@ export class Daemons * * Daemons run in the order they are defined, with latter daemons being capable of * depending on prior daemons - * @param inputSpec + * @param options * @returns */ - static of(inputSpec: { + static of(options: { effects: T.Effects - started: (onTerm: () => PromiseLike) => PromiseLike + started: (onTerm: () => PromiseLike) => PromiseLike healthReceipts: HealthReceipt[] }) { return new Daemons( - inputSpec.effects, - inputSpec.started, + options.effects, + options.started, [], [], [], diff --git a/sdk/package/lib/mainFn/HealthDaemon.ts b/sdk/package/lib/mainFn/HealthDaemon.ts index 71ba83b3f..b66e3e406 100644 --- a/sdk/package/lib/mainFn/HealthDaemon.ts +++ b/sdk/package/lib/mainFn/HealthDaemon.ts @@ -81,7 +81,7 @@ export class HealthDaemon { } } - private healthCheckCleanup: (() => void) | null = null + private healthCheckCleanup: (() => null) | null = null private turnOffHealthCheck() { this.healthCheckCleanup?.() } @@ -125,6 +125,7 @@ export class HealthDaemon { this.healthCheckCleanup = () => { setStatus({ done: true }) this.healthCheckCleanup = null + return null } } diff --git a/sdk/package/lib/mainFn/index.ts b/sdk/package/lib/mainFn/index.ts index ee0481630..f1373c44e 100644 --- a/sdk/package/lib/mainFn/index.ts +++ b/sdk/package/lib/mainFn/index.ts @@ -17,7 +17,7 @@ export const DEFAULT_SIGTERM_TIMEOUT = 30_000 export const setupMain = ( fn: (o: { effects: T.Effects - started(onTerm: () => PromiseLike): PromiseLike + started(onTerm: () => PromiseLike): PromiseLike }) => Promise>, ): T.ExpectedExports.main => { return async (options) => { diff --git a/sdk/package/lib/util/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts index b6a7492a7..16476f564 100644 --- a/sdk/package/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -27,7 +27,7 @@ const TIMES_TO_WAIT_FOR_PROC = 100 * 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 & ExecOptions, @@ -47,7 +47,7 @@ export interface ExecSpawnable { export class SubContainer implements ExecSpawnable { private leader: cp.ChildProcess private leaderExited: boolean = false - private waitProc: () => Promise + private waitProc: () => Promise private constructor( readonly effects: T.Effects, readonly imageId: T.ImageId, @@ -79,7 +79,7 @@ export class SubContainer implements ExecSpawnable { } await wait(1) } - resolve() + resolve(null) }), ) } @@ -180,12 +180,12 @@ export class SubContainer implements ExecSpawnable { if (this.leaderExited) { return } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { try { let timeout = setTimeout(() => this.leader.kill("SIGKILL"), 30000) this.leader.on("exit", () => { clearTimeout(timeout) - resolve() + resolve(null) }) if (!this.leader.kill("SIGTERM")) { reject(new Error("kill(2) failed")) @@ -201,6 +201,7 @@ export class SubContainer implements ExecSpawnable { const guid = this.guid await this.killLeader() await this.effects.subcontainer.destroyFs({ guid }) + return null } } @@ -245,16 +246,16 @@ export class SubContainer implements ExecSpawnable { options || {}, ) if (options?.input) { - await new Promise((resolve, reject) => + await new Promise((resolve, reject) => child.stdin.write(options.input, (e) => { if (e) { reject(e) } else { - resolve() + resolve(null) } }), ) - await new Promise((resolve) => child.stdin.end(resolve)) + await new Promise((resolve) => child.stdin.end(resolve)) } const pid = child.pid const stdout = { data: "" as string | Buffer } diff --git a/web/package-lock.json b/web/package-lock.json index 2f05ddab3..1cc096b38 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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", @@ -123,13 +124,14 @@ "@noble/hashes": "^1.4.0", "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", - "mime": "^4.0.3", + "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", @@ -11731,7 +11733,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", diff --git a/web/package.json b/web/package.json index c73d93859..5d241c2f3 100644 --- a/web/package.json +++ b/web/package.json @@ -80,6 +80,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/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 6b72c38a8..c9ecc8ed5 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 @@ -11,10 +11,19 @@ Standard Actions + , private readonly actionService: ActionService, + private readonly standardActionsService: StandardActionsService, ) {} async handleAction( @@ -55,51 +49,12 @@ export class AppActionsPage { ) } - async tryUninstall(manifest: T.Manifest): Promise { - let message = - manifest.alerts.uninstall || - `Uninstalling ${manifest.title} will permanently delete its data` - - if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patch))) { - message = `${message}. Services that depend on ${manifest.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', - }) - - await alert.present() + async rebuild(id: string) { + return this.standardActionsService.rebuild(id) } - private async uninstall() { - const loader = this.loader.open(`Beginning uninstall...`).subscribe() - - try { - await this.api.uninstallPackage({ id: this.pkgId }) - this.api - .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 tryUninstall(manifest: T.Manifest) { + return this.standardActionsService.tryUninstall(manifest) } } 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 a3c6cc584..7d51a6fc8 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,7 @@ 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 { HealthColorPipe } from './pipes/health-color.pipe' import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe' import { ToButtonsPipe } from './pipes/to-buttons.pipe' @@ -43,6 +44,7 @@ const routes: Routes = [ AppShowMenuComponent, AppShowHealthChecksComponent, AppShowAdditionalComponent, + AppShowErrorComponent, ], imports: [ CommonModule, 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 7ea0b86fd..ec59b1042 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,9 +16,9 @@ - + + 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 1743a86ec..c1574e476 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 @@ -45,7 +45,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 +58,11 @@ export class AppShowPage { }), map(([allPkgs, depErrors]) => { const pkg = allPkgs[this.pkgId] + const manifest = getManifest(pkg) return { pkg, - dependencies: this.getDepInfo(pkg, allPkgs, depErrors), + manifest, + dependencies: this.getDepInfo(pkg, manifest, allPkgs, depErrors), status: renderPkgStatus(pkg, depErrors), } }), @@ -84,11 +86,10 @@ export class AppShowPage { private getDepInfo( pkg: PackageDataEntry, + manifest: T.Manifest, allPkgs: AllPackageData, depErrors: PkgDependencyErrors, ): DependencyInfo[] { - const manifest = getManifest(pkg) - return Object.keys(pkg.currentDependencies).map(id => this.getDepValues(pkg, allPkgs, manifest, id, depErrors), ) 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 5dc5faf67..e31d4cd24 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 @@ - + 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 e428456f6..65997c49d 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -257,6 +257,9 @@ 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 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 1a5c1c57c..49571d97f 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 @@ -249,6 +249,10 @@ export abstract class ApiService { abstract stopPackage(params: RR.StopPackageReq): Promise + abstract rebuildPackage( + params: RR.RebuildPackageReq, + ): Promise + abstract uninstallPackage( params: RR.UninstallPackageReq, ): 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 742076ec8..73f3e67c6 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 @@ -498,6 +498,12 @@ 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 { 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 6c55bb9db..c4834c99e 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 { @@ -636,14 +638,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, @@ -660,40 +662,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) @@ -750,7 +755,7 @@ export class MockApiService extends ApiService { this.installProgress(params.id) }, 1000) - const patch: Operation< + const patch: AddOperation< PackageDataEntry >[] = [ { @@ -799,7 +804,7 @@ export class MockApiService extends ApiService { 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) @@ -826,76 +831,61 @@ export class MockApiService extends ApiService { } 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 = [ + const patch2: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: 'running', - }, - { - op: PatchOp.REPLACE, - path: path + '/started', - value: new Date().toISOString(), + path, + value: { + 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(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 = [ - { - op: PatchOp.REPLACE, - path: path + '/health', - 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.', - }, - }, - }, - ] - this.mockRevision(patch4) }, 2000) - const originalPatch = [ + const originalPatch: ReplaceOperation< + T.MainStatus & { main: 'starting' } + >[] = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: 'starting', + path, + value: { + main: 'starting', + health: {}, + }, }, ] @@ -907,74 +897,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[] = [ + const patch2: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: 'starting', - }, - { - op: PatchOp.ADD, - path: path + '/restarting', - value: true, + path, + value: { + 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(patch2) - - await pauseFor(2000) - - const patch3: Operation[] = [ - { - op: PatchOp.REPLACE, - path: path + '/status', - value: 'running', - }, - { - op: PatchOp.REMOVE, - path: path + '/restarting', - }, - { - op: PatchOp.REPLACE, - path: path + '/health', - 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.', - }, - }, - } as any, - ] - this.mockRevision(patch3) }, this.revertTime) - const patch = [ + const patch: ReplaceOperation[] = [ { op: PatchOp.REPLACE, - path: path + '/status', - value: 'restarting', - }, - { - op: PatchOp.REPLACE, - path: path + '/health', - value: {}, + path, + value: { + main: 'restarting', + }, }, ] @@ -985,29 +958,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' }, }, ] @@ -1016,6 +984,12 @@ export class MockApiService extends ApiService { return null } + async rebuildPackage( + params: RR.RebuildPackageReq, + ): Promise { + return this.restartPackage(params) + } + async uninstallPackage( params: RR.UninstallPackageReq, ): Promise { @@ -1031,7 +1005,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`, 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 cdc8f7173..d89b5e87f 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -96,36 +96,14 @@ export const mockPatchData: DataModel = { icon: '/assets/img/service-icons/bitcoind.svg', lastBackup: null, status: { - main: '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: 'Bitcoin Config', 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 f987f6b2c..3f2a7b917 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 @@ -80,6 +80,7 @@ export type PrimaryStatus = | 'stopped' | 'backingUp' | 'needsConfig' + | 'error' export type DependencyStatus = 'warning' | 'satisfied' @@ -139,6 +140,11 @@ export const PrimaryRendering: Record = { 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/styles.scss b/web/projects/ui/src/styles.scss index fa4a6598e..34c76848b 100644 --- a/web/projects/ui/src/styles.scss +++ b/web/projects/ui/src/styles.scss @@ -110,6 +110,12 @@ $subheader-height: 48px; } } +.code-block { + background-color: rgb(69, 69, 69); + padding: 12px; + margin-bottom: 32px; +} + .center { display: block; margin: auto; diff --git a/web/tsconfig.json b/web/tsconfig.json index 89f8e9548..6663ac431 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -22,7 +22,8 @@ "paths": { /* These paths are relative to each app base folder */ "@start9labs/marketplace": ["../marketplace/src/public-api"], - "@start9labs/shared": ["../shared/src/public-api"] + "@start9labs/shared": ["../shared/src/public-api"], + "path": ["../../node_modules/path-browserify"] }, "typeRoots": ["node_modules/@types"], "types": ["node"] From 51466891588af89e8ecc4c0c40f87e7adbfec711 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:38:28 -0600 Subject: [PATCH 11/46] v0.3.6-alpha.6 (#2748) --- core/Cargo.lock | 2 +- core/startos/Cargo.toml | 2 +- core/startos/src/version/mod.rs | 2 +- web/package-lock.json | 4 ++-- web/package.json | 2 +- .../ui/src/app/modals/os-welcome/os-welcome.page.html | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index c7eccf706..e1c237917 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5041,7 +5041,7 @@ dependencies = [ [[package]] name = "start-os" -version = "0.3.6-alpha.5" +version = "0.3.6-alpha.6" dependencies = [ "aes", "async-compression", diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 19d0df244..99384345d 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.5" +version = "0.3.6-alpha.6" license = "MIT" [lib] diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index 18ff0e5b2..1dc190d53 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -22,7 +22,7 @@ mod v0_3_6_alpha_5; mod v0_3_6_alpha_6; mod v0_3_6_alpha_7; -pub type Current = v0_3_6_alpha_5::Version; // VERSION_BUMP +pub type Current = v0_3_6_alpha_6::Version; // VERSION_BUMP #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(untagged)] diff --git a/web/package-lock.json b/web/package-lock.json index 1cc096b38..55925e77f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.5", + "version": "0.3.6-alpha.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.3.6-alpha.5", + "version": "0.3.6-alpha.6", "license": "MIT", "dependencies": { "@angular/animations": "^14.1.0", diff --git a/web/package.json b/web/package.json index 5d241c2f3..0de550c62 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.5", + "version": "0.3.6-alpha.6", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", 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 a0f4f46db..2a43292ad 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,7 +12,7 @@

This Release

-

0.3.6-alpha.5

+

0.3.6-alpha.6

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 From 0c04802560f16c3b11b545f7f538c28e413870f1 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:44:24 -0600 Subject: [PATCH 12/46] fix cors (#2749) --- core/startos/src/net/static_server.rs | 2 +- core/startos/src/registry/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/registry/mod.rs b/core/startos/src/registry/mod.rs index d34ebb841..529cb5082 100644 --- a/core/startos/src/registry/mod.rs +++ b/core/startos/src/registry/mod.rs @@ -98,11 +98,11 @@ pub fn registry_api() -> ParentHandler { 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()) From 9fc082d1e62e875f9e1e92d9dd647d13a8c41e1d Mon Sep 17 00:00:00 2001 From: Dominion5254 Date: Wed, 16 Oct 2024 09:11:32 -0600 Subject: [PATCH 13/46] add with_about for CLI commands (#2741) * add with_about for echo, server, and auth * update for feedback * finish (most) remaining command documentation * update comments after additional clarification * add expanded_api descriptions * add comments for action_api * add comments for remaining apis * add comment for package-rebuild * fix build errors * missed one with_about * add context to git-info subcommands * remove context from git-info subcommands * Make git-info from_fns generic over context * make version::git_info generic over the context * try removing generics from subcommand and version::git_info * try adding a closure with context * Updates for reviewer feedback --- core/Cargo.lock | 448 +++++++++--------- core/startos/src/action.rs | 6 +- core/startos/src/auth.rs | 20 +- core/startos/src/backup/mod.rs | 7 +- core/startos/src/backup/target/cifs.rs | 3 + core/startos/src/backup/target/mod.rs | 12 +- core/startos/src/db/mod.rs | 20 +- core/startos/src/diagnostic.rs | 32 +- core/startos/src/disk/mod.rs | 5 +- core/startos/src/init.rs | 25 +- core/startos/src/lib.rs | 254 +++++++--- core/startos/src/lxc/dev.rs | 13 +- core/startos/src/net/dhcp.rs | 1 + core/startos/src/net/mod.rs | 12 +- core/startos/src/net/tor.rs | 8 +- core/startos/src/net/wifi.rs | 16 +- core/startos/src/notifications.rs | 4 + core/startos/src/os_install/mod.rs | 5 +- core/startos/src/registry/admin.rs | 19 +- core/startos/src/registry/db.rs | 14 +- core/startos/src/registry/mod.rs | 22 +- core/startos/src/registry/os/asset/get.rs | 16 +- core/startos/src/registry/os/asset/mod.rs | 19 +- core/startos/src/registry/os/mod.rs | 12 +- core/startos/src/registry/os/version/mod.rs | 8 +- .../startos/src/registry/os/version/signer.rs | 3 + core/startos/src/registry/package/mod.rs | 9 +- core/startos/src/s9pk/rpc.rs | 49 +- core/startos/src/ssh.rs | 3 + core/startos/src/system.rs | 2 + core/startos/src/util/rpc.rs | 7 +- 31 files changed, 738 insertions(+), 336 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index e1c237917..5f59c8bb4 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -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" @@ -156,15 +162,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[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" @@ -231,18 +237,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -268,9 +274,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" +checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" dependencies = [ "aws-lc-sys", "mirai-annotations", @@ -280,9 +286,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.20.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" +checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" dependencies = [ "bindgen", "cc", @@ -301,7 +307,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core 0.3.4", - "bitflags 1.3.2", + "bitflags 1.2.1", "bytes", "futures-util", "http 0.2.12", @@ -441,7 +447,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -532,7 +538,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.74", + "syn 2.0.77", "which", ] @@ -553,9 +559,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitflags" @@ -603,18 +609,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", @@ -687,9 +693,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cache-padded" @@ -699,9 +705,9 @@ checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] name = "cc" -version = "1.1.12" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68064e60dbf1f17005c2fde4d07c16d8baa506fd7ffed8ccab702d93617975c7" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -812,9 +818,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -822,9 +828,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -841,7 +847,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -989,9 +995,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" @@ -1054,9 +1060,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -1249,7 +1255,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1273,7 +1279,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1284,7 +1290,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1315,7 +1321,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1338,7 +1344,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1361,7 +1367,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1576,14 +1582,14 @@ dependencies = [ [[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.74", + "syn 2.0.77", ] [[package]] @@ -1670,9 +1676,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fd-lock-rs" @@ -1701,9 +1707,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -1719,12 +1725,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -1862,7 +1868,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1975,7 +1981,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.4.0", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -1984,9 +1990,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -1994,7 +2000,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.4.0", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -2254,7 +2260,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -2268,15 +2274,15 @@ 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-util", - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pki-types", "tokio", "tokio-rustls", @@ -2313,9 +2319,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", @@ -2333,9 +2339,9 @@ dependencies = [ [[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", @@ -2483,9 +2489,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -2536,9 +2542,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" dependencies = [ "serde", ] @@ -2814,7 +2820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec 0.5.2", - "bitflags 1.3.2", + "bitflags 1.2.1", "cfg-if", "ryu", "static_assertions", @@ -2822,9 +2828,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" @@ -2850,7 +2856,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.4", ] [[package]] @@ -2949,9 +2955,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", ] @@ -2995,6 +3001,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.2" @@ -3084,7 +3099,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "cfg-if", "libc", "memoffset 0.6.5", @@ -3096,7 +3111,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "cfg-if", "libc", "memoffset 0.7.1", @@ -3282,7 +3297,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -3348,7 +3363,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -3359,9 +3374,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.3.2+3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" dependencies = [ "cc", ] @@ -3447,7 +3462,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.4", "smallvec", "windows-targets 0.52.6", ] @@ -3526,9 +3541,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -3537,9 +3552,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -3547,22 +3562,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -3576,7 +3591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.5.0", ] [[package]] @@ -3611,7 +3626,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -3655,9 +3670,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" [[package]] name = "powerfmt" @@ -3682,12 +3697,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -3715,11 +3730,11 @@ 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.21", ] [[package]] @@ -3785,7 +3800,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -3808,7 +3823,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -3853,9 +3868,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", ] @@ -3983,32 +3998,23 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.4" 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" 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", @@ -4061,9 +4067,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", @@ -4072,7 +4078,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "http-body-util", @@ -4104,7 +4110,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -4158,7 +4164,7 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/no-dyn-ctx#60a974a29c5e6380f7bbfbc1b4716f6d2b20b189" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/no-dyn-ctx#39a872a1294c7d864faca63f072092ce300ffbe5" dependencies = [ "async-stream", "async-trait", @@ -4239,18 +4245,18 @@ 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 = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -4272,15 +4278,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "aws-lc-rs", "log", "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -4322,9 +4328,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", @@ -4383,11 +4389,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4454,9 +4460,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -4480,22 +4486,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "itoa", "memchr", "ryu", @@ -4543,7 +4549,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.4.0", + "indexmap 2.5.0", "serde", "serde_derive", "serde_json", @@ -4560,7 +4566,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -4569,7 +4575,7 @@ version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "itoa", "libyml", "log", @@ -4761,9 +4767,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", @@ -4804,7 +4810,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.4.0", + "indexmap 2.5.0", "log", "memchr", "once_cell", @@ -4993,7 +4999,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.74", + "syn 2.0.77", "unicode-width", ] @@ -5086,7 +5092,7 @@ dependencies = [ "imbl", "imbl-value", "include_dir", - "indexmap 2.4.0", + "indexmap 2.5.0", "indicatif", "integer-encoding", "ipnet", @@ -5235,9 +5241,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -5255,23 +5261,26 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[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", @@ -5365,7 +5374,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -5446,9 +5455,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -5481,7 +5490,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -5500,7 +5509,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -5519,9 +5528,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", @@ -5571,9 +5580,9 @@ dependencies = [ [[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", @@ -5603,7 +5612,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.21", ] [[package]] @@ -5621,7 +5630,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", @@ -5630,22 +5639,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ - "indexmap 2.4.0", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap 2.4.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", @@ -5750,7 +5748,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -5902,7 +5900,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", "termcolor", ] @@ -5973,7 +5971,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -6011,9 +6009,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[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" @@ -6023,24 +6021,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.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[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" @@ -6050,9 +6048,9 @@ checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[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" @@ -6201,7 +6199,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -6235,7 +6233,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6289,11 +6287,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.4", "wasite", ] @@ -6337,6 +6335,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" @@ -6503,16 +6531,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wyz" version = "0.2.0" @@ -6599,7 +6617,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -6619,7 +6637,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index f7e17f564..a18173add 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -22,7 +22,8 @@ pub fn action_api() -> ParentHandler { "get-input", from_fn_async(get_action_input) .with_display_serializable() - .with_call_remote::(), + .with_about("Get action input spec") + .with_call_remote::() ) .subcommand( "run", @@ -34,7 +35,8 @@ pub fn action_api() -> ParentHandler { } Ok(()) }) - .with_call_remote::(), + .with_about("Run service action") + .with_call_remote::() ) } diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index 9a881d0e6..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::(), ) } @@ -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::(), ) } 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 fcb291005..eb3fc29bc 100644 --- a/core/startos/src/backup/target/mod.rs +++ b/core/startos/src/backup/target/mod.rs @@ -141,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( @@ -155,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/db/mod.rs b/core/startos/src/db/mod.rs index ff42d6a5c..1ed7e9ab9 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()) } @@ -299,6 +312,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/diagnostic.rs b/core/startos/src/diagnostic.rs index 5e99580e9..3eab3b16b 100644 --- a/core/startos/src/diagnostic.rs +++ b/core/startos/src/diagnostic.rs @@ -13,31 +13,48 @@ 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", - from_fn_async(crate::logs::cli_logs::).no_display(), + crate::system::logs::().with_about("Display 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::(), + 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,7 +89,8 @@ pub fn disk() -> ParentHandler { CallRemoteHandler::::new( from_fn_async(forget_disk::).no_display(), ) - .no_display(), + .no_display() + .with_about("Remove disk from filesystem"), ) } 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/init.rs b/core/startos/src/init.rs index e6b7be598..7ca4576b1 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -549,18 +549,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/lib.rs b/core/startos/src/lib.rs index 997a7199f..754362bc9 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -114,29 +114,71 @@ 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(|_: RpcContext| 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( @@ -144,10 +186,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 } @@ -160,42 +212,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("experimental", system::experimental::()) - .subcommand("logs", system::logs::()) .subcommand( "logs", - from_fn_async(logs::cli_logs::).no_display(), + system::logs::().with_about("Display OS logs"), + ) + .subcommand( + "logs", + 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", @@ -205,7 +272,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", @@ -220,25 +287,31 @@ 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::() ) } pub fn package() -> ParentHandler { ParentHandler::new() - .subcommand("action", action::action_api::()) + .subcommand( + "action", + action::action_api::().with_about("Commands to get action input or run an action"), + ) .subcommand( "install", from_fn_async(install::install) @@ -251,24 +324,32 @@ 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( @@ -276,6 +357,7 @@ pub fn package() -> ParentHandler { from_fn_async(control::start) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Start a package container") .with_call_remote::(), ) .subcommand( @@ -283,6 +365,7 @@ pub fn package() -> ParentHandler { from_fn_async(control::stop) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Stop a package container") .with_call_remote::(), ) .subcommand( @@ -290,6 +373,7 @@ pub fn package() -> ParentHandler { from_fn_async(control::restart) .with_metadata("sync_db", Value::Bool(true)) .no_display() + .with_about("Restart a package container") .with_call_remote::(), ) .subcommand( @@ -297,12 +381,19 @@ pub fn package() -> ParentHandler { 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( + "logs", + from_fn_async(logs::cli_logs::) + .no_display() + .with_about("Display package logs"), ) .subcommand( "properties", @@ -310,13 +401,20 @@ pub fn package() -> ParentHandler { .with_custom_display_fn(|_handle, result| { Ok(properties::display_properties(result)) }) + .with_about("Display package Properties") .with_call_remote::(), ) - .subcommand("backup", backup::package_backup::()) + .subcommand( + "backup", + backup::package_backup::() + .with_about("Commands for restoring package(s) from 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", @@ -329,74 +427,124 @@ pub fn package() -> ParentHandler { 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/lxc/dev.rs b/core/startos/src/lxc/dev.rs index 61dd8e598..506cb2e9b 100644 --- a/core/startos/src/lxc/dev.rs +++ b/core/startos/src/lxc/dev.rs @@ -17,7 +17,9 @@ 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 +33,23 @@ pub fn lxc() -> ParentHandler { table.printstd(); Ok(()) }) + .with_about("List lxc containers") .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 { 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/mod.rs b/core/startos/src/net/mod.rs index e55da4206..2735454e7 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -1,4 +1,4 @@ -use rpc_toolkit::{Context, ParentHandler}; +use rpc_toolkit::{Context, HandlerExt, ParentHandler}; pub mod dhcp; pub mod dns; @@ -20,6 +20,12 @@ 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"), + ) } diff --git a/core/startos/src/net/tor.rs b/core/startos/src/net/tor.rs index 16705776f..d93ce9302 100644 --- a/core/startos/src/net/tor.rs +++ b/core/startos/src/net/tor.rs @@ -91,17 +91,21 @@ 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::(), ) } 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 c8e1725f4..aff69ab23 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -26,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::(), ) } diff --git a/core/startos/src/os_install/mod.rs b/core/startos/src/os_install/mod.rs index 3d80f6cbd..46ed4ae8c 100644 --- a/core/startos/src/os_install/mod.rs +++ b/core/startos/src/os_install/mod.rs @@ -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::(), ) } diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs index 8125580a4..a327a9587 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,10 @@ 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"), + ) } impl Model> { 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/mod.rs b/core/startos/src/registry/mod.rs index 529cb5082..6a90ab640 100644 --- a/core/startos/src/registry/mod.rs +++ b/core/startos/src/registry/mod.rs @@ -82,18 +82,32 @@ pub fn registry_api() -> ParentHandler { "index", from_fn_async(get_full_index) .with_display_serializable() + .with_about("List info including registry name and packages") .with_call_remote::(), ) .subcommand( "info", from_fn_async(get_info) .with_display_serializable() + .with_about("Display registry name, icon, and package categories") .with_call_remote::(), ) - .subcommand("os", os::os_api::()) - .subcommand("package", package::package_api::()) - .subcommand("admin", admin::admin_api::()) - .subcommand("db", db::db_api::()) + .subcommand( + "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"), + ) } pub fn registry_router(ctx: RegistryContext) -> Router { diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs index ad0010dca..6c0479964 100644 --- a/core/startos/src/registry/os/asset/get.rs +++ b/core/startos/src/registry/os/asset/get.rs @@ -26,11 +26,21 @@ 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)] 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/mod.rs b/core/startos/src/registry/package/mod.rs index cb2d317f9..ef312dc28 100644 --- a/core/startos/src/registry/package/mod.rs +++ b/core/startos/src/registry/package/mod.rs @@ -14,6 +14,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 +23,12 @@ 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( "get", from_fn_async(get::get_package) @@ -31,6 +37,7 @@ 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::(), ) } 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/ssh.rs b/core/startos/src/ssh.rs index 728fd6480..99c2ae66c 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -86,12 +86,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 +103,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/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/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)] From fb074c8c321bee7708b8ccfc2aa1984ce7779f40 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:09:30 -0600 Subject: [PATCH 14/46] 036 migration (#2750) * chore: convert to use a value, cause why not * wip: Add the up for this going up * wip: trait changes * wip: Add in some more of the private transformations * chore(wip): Adding the ssh_keys todo * wip: Add cifs * fix migration structure * chore: Fix the trait for the version * wip(feat): Notifications are in the system * fix marker trait hell * handle key todos * wip: Testing the migration in a system. * fix pubkey parser * fix: migration works * wip: Trying to get the migration stuff? * fix: Can now install the packages that we wanted, yay!" * Merge branch 'next/minor' of github.com:Start9Labs/start-os into feat/migration --------- Co-authored-by: Aiden McClelland --- core/startos/src/backup/backup_bulk.rs | 4 +- core/startos/src/bins/container_cli.rs | 2 +- core/startos/src/bins/start_cli.rs | 2 +- core/startos/src/context/rpc.rs | 2 + core/startos/src/db/model/private.rs | 2 +- core/startos/src/db/model/public.rs | 11 +- core/startos/src/init.rs | 6 +- core/startos/src/notifications.rs | 14 +- core/startos/src/os_install/mod.rs | 17 - core/startos/src/registry/device_info.rs | 4 +- core/startos/src/s9pk/v2/manifest.rs | 2 +- core/startos/src/service/mod.rs | 3 + core/startos/src/service/service_map.rs | 1 - core/startos/src/ssh.rs | 8 +- core/startos/src/version/mod.rs | 488 ++++++++++----- core/startos/src/version/v0_3_5.rs | 29 +- core/startos/src/version/v0_3_5_1.rs | 27 +- core/startos/src/version/v0_3_5_2.rs | 27 +- core/startos/src/version/v0_3_6_alpha_0.rs | 569 +++++++++++++++++- core/startos/src/version/v0_3_6_alpha_1.rs | 27 +- core/startos/src/version/v0_3_6_alpha_2.rs | 27 +- core/startos/src/version/v0_3_6_alpha_3.rs | 27 +- core/startos/src/version/v0_3_6_alpha_4.rs | 27 +- core/startos/src/version/v0_3_6_alpha_5.rs | 27 +- core/startos/src/version/v0_3_6_alpha_6.rs | 27 +- core/startos/src/version/v0_3_6_alpha_7.rs | 35 -- sdk/base/lib/osBindings/ServerInfo.ts | 3 +- web/patchdb-ui-seed.json | 2 +- .../server-routes/sideload/sideload.page.ts | 3 +- .../ui/src/app/services/api/mock-patch.ts | 3 +- 30 files changed, 1056 insertions(+), 370 deletions(-) delete mode 100644 core/startos/src/version/v0_3_6_alpha_7.rs diff --git a/core/startos/src/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index 5defebe5d..136595b67 100644 --- a/core/startos/src/backup/backup_bulk.rs +++ b/core/startos/src/backup/backup_bulk.rs @@ -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/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/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/context/rpc.rs b/core/startos/src/context/rpc.rs index 6ada12301..cb5ce83c5 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -88,6 +88,7 @@ pub struct InitRpcContextPhases { init_net_ctrl: PhaseProgressTrackerHandle, read_device_info: PhaseProgressTrackerHandle, cleanup_init: CleanupInitPhases, + // TODO: migrations } impl InitRpcContextPhases { pub fn new(handle: &FullProgressTracker) -> Self { @@ -286,6 +287,7 @@ 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) } 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..7e5e96cde 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -43,10 +43,11 @@ 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(), + 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()) @@ -112,11 +113,13 @@ pub struct ServerInfo { pub hostname: InternedString, #[ts(type = "string")] pub version: Version, + #[ts(type = "string")] + pub 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, diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 7ca4576b1..206f1c8e0 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -323,7 +323,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"); @@ -528,8 +530,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()?; diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index aff69ab23..b310220b5 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -256,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 46ed4ae8c..a87a71209 100644 --- a/core/startos/src/os_install/mod.rs +++ b/core/startos/src/os_install/mod.rs @@ -150,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 { diff --git a/core/startos/src/registry/device_info.rs b/core/startos/src/registry/device_info.rs index 172348a10..04a38a223 100644 --- a/core/startos/src/registry/device_info.rs +++ b/core/startos/src/registry/device_info.rs @@ -108,8 +108,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), } } diff --git a/core/startos/src/s9pk/v2/manifest.rs b/core/startos/src/s9pk/v2/manifest.rs index f8102c736..9b9455999 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)] diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 37579c03b..d7b91df7b 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -408,6 +408,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 @@ -429,6 +430,7 @@ impl Service { .clone(), ); } + let procedure_id = Guid::new(); service .seed @@ -441,6 +443,7 @@ impl Service { ) // TODO timeout .await .with_kind(ErrorKind::MigrationFailed)?; // TODO: handle cancellation + if let Some(mut progress) = progress { progress.finalization_progress.complete(); progress.progress.complete(); diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 68a5f2a5f..0faab8dd5 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -307,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/ssh.rs b/core/startos/src/ssh.rs index 99c2ae66c..afc5a5bf1 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -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; diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index 1dc190d53..76cc7a574 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; @@ -20,10 +25,68 @@ mod v0_3_6_alpha_3; mod v0_3_6_alpha_4; mod v0_3_6_alpha_5; mod v0_3_6_alpha_6; -mod v0_3_6_alpha_7; pub type Current = v0_3_6_alpha_6::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)] #[allow(non_camel_case_types)] @@ -39,7 +102,6 @@ enum Version { V0_3_6_alpha_4(Wrapper), V0_3_6_alpha_5(Wrapper), V0_3_6_alpha_6(Wrapper), - V0_3_6_alpha_7(Wrapper), Other(exver::Version), } @@ -52,6 +114,32 @@ 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::Other(v) => { + return Err(Error::new( + eyre!("unknown version {v}"), + ErrorKind::MigrationFailed, + )) + } + }) + } #[cfg(test)] fn as_exver(&self) -> exver::Version { match self { @@ -66,116 +154,246 @@ impl Version { Version::V0_3_6_alpha_4(Wrapper(x)) => x.semver(), 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::Other(x) => x.clone(), } } } +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 version_compat_accessor(db: &mut Value) -> Option<&mut Value> { + if db.get("public").is_some() { + db.get_mut("public")? + .get_mut("serverInfo")? + .get_mut("versionCompat") + } else { + db.get_mut("server-info")?.get_mut("eos-version-compat") + } +} + +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 = 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 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)? + } + _ 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: Sized + Send + Sync, + Self: Default + Copy + Sized + RefUnwindSafe + Send + Sync + 'static, { type Previous: VersionT; - fn new() -> Self; + 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(self, ctx: &RpcContext) -> impl Future> + Send + 'static { + 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 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 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 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 semver(&self) -> exver::Version { + VersionT::semver(*self) } - 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); - 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(()) - } - .boxed() + fn compat(&self) -> &'static exver::VersionRange { + VersionT::compat(*self) } - 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(()) - } - .boxed() + 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 +413,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 +438,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 +447,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 +477,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 - } - fn semver(&self) -> exver::Version { - V0_3_5.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_5.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_5_1.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_5_1.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_5_2.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_5_2.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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..2f4b141b4 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::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::OnionStore; +use crate::notifications::{Notification, Notifications}; use crate::prelude::*; +use crate::ssh::{SshKeys, SshPubKey}; +use crate::util::crypto::ed25519_expand_key; +use crate::util::serde::{Pem, PemEncoding}; +use crate::util::Invoke; +use crate::{account::AccountInfo, net::tor}; +use crate::{auth::Sessions, context::RpcContext}; +use crate::{ + backup::target::cifs::CifsTargets, + s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile, +}; lazy_static::lazy_static! { static ref V0_3_6_alpha_0: exver::Version = exver::Version::new( @@ -12,24 +47,540 @@ 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 + fn post_up(self, ctx: &RpcContext) -> impl Future> + Send + 'static { + let ctx = ctx.clone(); + async move { + 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 - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_1.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_6_alpha_1.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_2.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_6_alpha_2.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_3.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_6_alpha_3.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_4.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_6_alpha_4.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_5.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_6_alpha_5.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_6.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { + type PreUpRes = (); + + async fn pre_up(self) -> Result { Ok(()) } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { + fn semver(self) -> exver::Version { + V0_3_6_alpha_6.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + Ok(()) + } + 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 deleted file mode 100644 index 7aa63fb2e..000000000 --- a/core/startos/src/version/v0_3_6_alpha_7.rs +++ /dev/null @@ -1,35 +0,0 @@ -use exver::{PreReleaseSegment, VersionRange}; - -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::*; - -lazy_static::lazy_static! { - static ref V0_3_6_alpha_7: exver::Version = exver::Version::new( - [0, 3, 6], - [PreReleaseSegment::String("alpha".into()), 7.into()] - ); -} - -#[derive(Clone, Debug)] -pub struct Version; - -impl VersionT for Version { - type Previous = v0_3_6_alpha_6::Version; - fn new() -> Self { - Version - } - fn semver(&self) -> exver::Version { - V0_3_6_alpha_7.clone() - } - fn compat(&self) -> &'static VersionRange { - &V0_3_0_COMPAT - } - async fn up(&self, _db: &TypedPatchDb) -> Result<(), Error> { - Ok(()) - } - async fn down(&self, _db: &TypedPatchDb) -> Result<(), Error> { - Ok(()) - } -} diff --git a/sdk/base/lib/osBindings/ServerInfo.ts b/sdk/base/lib/osBindings/ServerInfo.ts index 76840cfc4..ff04d42b0 100644 --- a/sdk/base/lib/osBindings/ServerInfo.ts +++ b/sdk/base/lib/osBindings/ServerInfo.ts @@ -11,8 +11,9 @@ export type ServerInfo = { id: string hostname: string version: string + versionCompat: string + postInitMigrationTodos: string[] lastBackup: string | null - eosVersionCompat: string lanAddress: string onionAddress: string /** diff --git a/web/patchdb-ui-seed.json b/web/patchdb-ui-seed.json index 221b80a3b..5862c6f83 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.5" + "ack-welcome": "0.3.6-alpha.6" } 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 b5f684103..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,12 +1,13 @@ import { Component } from '@angular/core' import { isPlatform } from '@ionic/angular' import { ErrorService, LoadingService } from '@start9labs/shared' -import { S9pk } from '@start9labs/start-sdk' +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] 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 d89b5e87f..55ea59a77 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -60,7 +60,7 @@ export const mockPatchData: DataModel = { // 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', + versionCompat: '>=0.3.0 <=0.3.6', statusInfo: { backupProgress: null, updated: false, @@ -82,6 +82,7 @@ export const mockPatchData: DataModel = { selected: null, lastRegion: null, }, + postInitMigrationTodos: [], }, packageData: { bitcoind: { From 2ba56b8c59a546fae783bc28e3adb174e0def4be Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 17 Oct 2024 13:31:56 -0600 Subject: [PATCH 15/46] Convert properties to an action (#2751) * update actions response types and partially implement in UI * further remove diagnostic ui * convert action response nested to array * prepare action res modal for Alex * ad dproperties action for Bitcoin * feat: add action success dialog (#2753) * feat: add action success dialog * mocks for string action res and hide properties from actions page --------- Co-authored-by: Matt Hill * return null * remove properties from backend * misc fixes * make severity separate argument * rename ActionRequest to ActionRequestOptions * add clearRequests * fix s9pk build * remove config and properties, introduce action requests * better ux, better moocks, include icons * fix dependency types * add variant for versionCompat * fix dep icon display and patch operation display * misc fixes * misc fixes * alpha 12 * honor provided input to set values in action * fix: show full descriptions of action success items (#2758) * fix type * fix: fix build:deps command on Windows (#2752) * fix: fix build:deps command on Windows * fix: add escaped quotes --------- Co-authored-by: Aiden McClelland * misc db compatibility fixes --------- Co-authored-by: Alex Inkin Co-authored-by: Aiden McClelland Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> --- .../src/Adapters/EffectCreator.ts | 8 +- container-runtime/src/Adapters/RpcListener.ts | 11 +- .../Systems/SystemForEmbassy/index.ts | 107 ++++++++-- .../src/Adapters/Systems/SystemForStartOs.ts | 6 - container-runtime/src/Interfaces/System.ts | 5 - .../src/Models/CallbackHolder.ts | 3 +- container-runtime/src/Models/JsonPath.ts | 2 - core/models/src/procedure_name.rs | 2 - core/startos/src/action.rs | 99 +++++++++- core/startos/src/db/mod.rs | 24 ++- core/startos/src/db/model/package.rs | 19 +- core/startos/src/lib.rs | 10 - core/startos/src/logs.rs | 2 +- core/startos/src/properties.rs | 35 ---- core/startos/src/service/effects/net/bind.rs | 1 + core/startos/src/service/mod.rs | 2 +- .../src/service/persistent_container.rs | 34 +++- core/startos/src/service/properties.rs | 23 --- core/startos/src/util/logger.rs | 8 +- core/startos/src/version/mod.rs | 14 +- sdk/Makefile | 7 +- sdk/base/lib/Effects.ts | 2 +- sdk/base/lib/actions/index.ts | 58 +++++- sdk/base/lib/actions/setupActions.ts | 10 +- sdk/base/lib/dependencies/Dependency.ts | 21 -- .../lib/dependencies/setupDependencies.ts | 41 +--- sdk/base/lib/osBindings/ActionRequest.ts | 4 +- sdk/base/lib/osBindings/ActionResult.ts | 7 + sdk/base/lib/osBindings/ActionResultV0.ts | 8 + sdk/base/lib/osBindings/ActionResultV1.ts | 18 ++ sdk/base/lib/osBindings/ActionSeverity.ts | 3 + sdk/base/lib/osBindings/ActionVisibility.ts | 5 +- .../lib/osBindings/RequestActionParams.ts | 4 +- sdk/base/lib/osBindings/index.ts | 4 + sdk/base/lib/types.ts | 68 ------- sdk/base/package.json | 4 +- sdk/package/lib/StartSdk.ts | 107 +++------- sdk/package/lib/backup/Backups.ts | 8 +- sdk/package/lib/backup/setupBackups.ts | 4 +- sdk/package/lib/index.ts | 2 +- sdk/package/lib/inits/setupInit.ts | 8 +- sdk/package/lib/inits/setupInstall.ts | 10 +- sdk/package/lib/inits/setupUninstall.ts | 10 +- sdk/package/lib/mainFn/CommandController.ts | 2 +- sdk/package/lib/mainFn/Daemon.ts | 2 +- sdk/package/lib/mainFn/Daemons.ts | 8 +- sdk/package/lib/mainFn/Mounts.ts | 6 +- sdk/package/lib/mainFn/index.ts | 2 +- sdk/package/lib/manifest/setupManifest.ts | 18 +- sdk/package/lib/test/inputSpecBuilder.test.ts | 71 +++---- sdk/package/lib/test/output.sdk.ts | 75 +++---- sdk/package/lib/util/fileHelper.ts | 27 ++- sdk/package/package-lock.json | 33 ++-- sdk/package/package.json | 6 +- web/README.md | 2 + web/ionic.config.json | 6 - web/lint-staged.config.js | 1 - web/package.json | 5 +- .../src/app/pages/success/success.page.ts | 2 +- .../shared/src/types/workspace-config.ts | 2 +- .../app/app/preloader/preloader.component.ts | 1 - .../ui/src/app/components/form.component.ts | 7 +- .../src/app/modals/action-input.component.ts | 67 ++++--- ...t.ts => action-request-input.component.ts} | 33 ++-- .../action-success-group.component.ts | 54 +++++ .../action-success-item.component.ts | 184 ++++++++++++++++++ .../action-success/action-success.module.ts | 12 -- .../action-success/action-success.page.html | 35 ---- .../action-success/action-success.page.scss | 0 .../action-success/action-success.page.ts | 63 +++--- .../app-actions/app-actions.module.ts | 2 - .../app-actions/app-actions.page.html | 4 +- .../app-actions/app-actions.page.ts | 22 +-- .../app-metrics/app-metrics.module.ts | 26 --- .../app-metrics/app-metrics.page.html | 25 --- .../app-metrics/app-metrics.page.scss | 3 - .../app-metrics/app-metrics.page.ts | 59 ------ .../app-properties/app-properties.module.ts | 32 --- .../app-properties/app-properties.page.html | 119 ----------- .../app-properties/app-properties.page.scss | 3 - .../app-properties/app-properties.page.ts | 147 -------------- .../apps-routes/app-show/app-show.module.ts | 2 + .../apps-routes/app-show/app-show.page.html | 6 + .../apps-routes/app-show/app-show.page.ts | 100 ++-------- .../app-show-action-requests.component.html | 45 +++++ .../app-show-action-requests.component.scss | 16 ++ .../app-show-action-requests.component.ts | 94 +++++++++ .../app-show-dependencies.component.html | 6 +- .../app-show-status.component.html | 10 - .../app-show-status.component.ts | 23 +-- .../app-show/pipes/to-buttons.pipe.ts | 48 +---- .../pages/apps-routes/apps-routing.module.ts | 14 -- .../ui/src/app/services/action.service.ts | 100 ++++------ .../ui/src/app/services/api/api.fixures.ts | 144 +++++++++----- .../ui/src/app/services/api/api.types.ts | 19 +- .../app/services/api/embassy-api.service.ts | 10 +- .../services/api/embassy-live-api.service.ts | 17 +- .../services/api/embassy-mock-api.service.ts | 33 ++-- .../ui/src/app/services/api/mock-patch.ts | 67 ++++++- .../ui/src/app/services/dep-error.service.ts | 6 +- .../services/pkg-status-rendering.service.ts | 15 +- web/projects/ui/src/app/util/dep-info.ts | 30 +++ .../ui/src/app/util/get-package-data.ts | 12 -- .../ui/src/app/util/get-package-info.ts | 2 +- .../ui/src/app/util/properties.util.ts | 150 -------------- 105 files changed, 1385 insertions(+), 1578 deletions(-) delete mode 100644 core/startos/src/properties.rs delete mode 100644 core/startos/src/service/properties.rs delete mode 100644 sdk/base/lib/dependencies/Dependency.ts create mode 100644 sdk/base/lib/osBindings/ActionResult.ts create mode 100644 sdk/base/lib/osBindings/ActionResultV0.ts create mode 100644 sdk/base/lib/osBindings/ActionResultV1.ts create mode 100644 sdk/base/lib/osBindings/ActionSeverity.ts rename web/projects/ui/src/app/modals/{action-dep.component.ts => action-request-input.component.ts} (77%) create mode 100644 web/projects/ui/src/app/modals/action-success/action-success-group.component.ts create mode 100644 web/projects/ui/src/app/modals/action-success/action-success-item.component.ts delete mode 100644 web/projects/ui/src/app/modals/action-success/action-success.module.ts delete mode 100644 web/projects/ui/src/app/modals/action-success/action-success.page.html delete mode 100644 web/projects/ui/src/app/modals/action-success/action-success.page.scss delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.module.ts delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.scss delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.module.ts delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.scss delete mode 100644 web/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.ts create mode 100644 web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.html create mode 100644 web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.scss create mode 100644 web/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-action-requests/app-show-action-requests.component.ts create mode 100644 web/projects/ui/src/app/util/dep-info.ts delete mode 100644 web/projects/ui/src/app/util/properties.util.ts diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index db176536f..4ec3a5b7d 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -150,15 +150,15 @@ export function makeEffects(context: EffectContext): Effects { stack: new Error().stack, }) as ReturnType }, - clearBindings(...[]: Parameters) { - return rpcRound("clear-bindings", {}) as ReturnType< + clearBindings(...[options]: Parameters) { + return rpcRound("clear-bindings", { ...options }) as ReturnType< T.Effects["clearBindings"] > }, clearServiceInterfaces( - ...[]: Parameters + ...[options]: Parameters ) { - return rpcRound("clear-service-interfaces", {}) as ReturnType< + return rpcRound("clear-service-interfaces", { ...options }) as ReturnType< T.Effects["clearServiceInterfaces"] > }, diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index de2bb7f1a..f84f7b282 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -42,6 +42,7 @@ export const matchRpcResult = anyOf( ), }), ) + export type RpcResult = typeof matchRpcResult._TYPE type SocketResponse = ({ jsonrpc: "2.0"; id: IdType } & RpcResult) | null @@ -88,7 +89,7 @@ const sandboxRunType = object( const callbackType = object({ method: literal("callback"), params: object({ - callback: number, + id: number, args: array, }), }) @@ -288,8 +289,8 @@ export class RpcListener { return handleRpc(id, result) }) - .when(callbackType, async ({ params: { callback, args } }) => { - this.callCallback(callback, args) + .when(callbackType, async ({ params: { id, args } }) => { + this.callCallback(id, args) return null }) .when(startType, async ({ id }) => { @@ -410,7 +411,7 @@ export class RpcListener { input: any, ) { const ensureResultTypeShape = ( - result: void | T.ActionInput | T.PropertiesReturn | T.ActionResult | null, + result: void | T.ActionInput | T.ActionResult | null, ): { result: any } => { if (isResult(result)) return result return { result } @@ -428,8 +429,6 @@ export class RpcListener { return system.createBackup(effects, timeout || null) case "/backup/restore": return system.restoreBackup(effects, timeout || null) - case "/properties": - return system.properties(effects, timeout || null) case "/packageInit": return system.packageInit(effects, timeout || null) case "/packageUninit": diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index bed30b11a..ae55eb690 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -135,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 } @@ -157,7 +185,7 @@ export type PackagePropertyObject = { const asProperty_ = ( x: PackagePropertyString | PackagePropertyObject, -): T.PropertiesValue => { +): PropertiesValue => { if (x.type === "object") { return { ...x, @@ -177,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)]), ) @@ -214,6 +242,31 @@ const matchProperties = object({ data: matchPackageProperties, }) +function convertProperties( + name: string, + value: PropertiesValue, +): T.ActionResultV1 { + if (value.type === "string") { + return { + type: "string", + name, + description: value.description, + copyable: value.copyable || false, + masked: value.masked || false, + qr: value.qr || false, + value: value.value, + } + } + return { + type: "object", + name, + description: value.description || undefined, + 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 @@ -245,6 +298,9 @@ export class SystemForEmbassy implements System { await this.dependenciesAutoconfig(effects, depId, null) } } + await effects.setMainStatus({ status: "stopped" }) + await this.exportActions(effects) + await this.exportNetwork(effects) } async exit(): Promise { @@ -281,10 +337,15 @@ export class SystemForEmbassy implements System { await effects.setDataVersion({ version: ExtendedVersion.parseEmver(this.manifest.version).toString(), }) + } else { + 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", + }) } - await effects.setMainStatus({ status: "stopped" }) - await this.exportActions(effects) - await this.exportNetwork(effects) } async exportNetwork(effects: Effects) { for (const [id, interfaceValue] of Object.entries( @@ -375,6 +436,8 @@ export class SystemForEmbassy implements System { 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 @@ -393,6 +456,17 @@ export class SystemForEmbassy implements System { if (actionId === "config") { await this.setConfig(effects, input, timeoutMs) return null + } else if (actionId === "properties") { + return { + version: "1", + type: "object", + name: "Properties", + description: + "Runtime information, credentials, and other values of interest", + value: Object.entries(await this.properties(effects, timeoutMs)).map( + ([name, value]) => convertProperties(name, value), + ), + } } else { return this.action(effects, actionId, input, timeoutMs) } @@ -405,17 +479,21 @@ export class SystemForEmbassy implements System { if (manifest.config) { actions.config = { name: "Configure", - description: "Edit the configuration of this service", + description: `Customize ${manifest.title}`, "allowed-statuses": ["running", "stopped"], "input-spec": {}, implementation: { type: "script", args: [] }, } - await effects.action.request({ - packageId: this.manifest.id, - actionId: "config", - replayId: "needs-config", - description: "This service must be configured before it can be run", - }) + } + 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( @@ -694,7 +772,7 @@ export class SystemForEmbassy implements System { 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") @@ -867,7 +945,8 @@ export class SystemForEmbassy implements System { actionId: "config", packageId: id, replayId: `${id}/config`, - description: `Configure this dependency for the needs of ${this.manifest.title}`, + severity: "important", + reason: `Configure this dependency for the needs of ${this.manifest.title}`, input: { kind: "partial", value: diff.diff, diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index c000b391b..51b7c24b8 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -57,12 +57,6 @@ export class SystemForStartOs implements System { effects, })) } - properties( - effects: Effects, - timeoutMs: number | null, - ): Promise { - throw new Error("Method not implemented.") - } getActionInput( effects: Effects, id: string, diff --git a/container-runtime/src/Interfaces/System.ts b/container-runtime/src/Interfaces/System.ts index 14ca002ca..63781cfbd 100644 --- a/container-runtime/src/Interfaces/System.ts +++ b/container-runtime/src/Interfaces/System.ts @@ -8,7 +8,6 @@ export type Procedure = | "/packageUninit" | "/backup/create" | "/backup/restore" - | "/properties" | `/actions/${string}/getInput` | `/actions/${string}/run` @@ -30,10 +29,6 @@ export type System = { createBackup(effects: T.Effects, timeoutMs: number | null): Promise restoreBackup(effects: T.Effects, timeoutMs: number | null): Promise - properties( - effects: Effects, - timeoutMs: number | null, - ): Promise runAction( effects: Effects, actionId: string, diff --git a/container-runtime/src/Models/CallbackHolder.ts b/container-runtime/src/Models/CallbackHolder.ts index e1034b473..ce474268a 100644 --- a/container-runtime/src/Models/CallbackHolder.ts +++ b/container-runtime/src/Models/CallbackHolder.ts @@ -1,6 +1,6 @@ import { T } from "@start9labs/start-sdk" -const CallbackIdCell = { inc: 0 } +const CallbackIdCell = { inc: 1 } const callbackRegistry = new FinalizationRegistry( async (options: { cbs: Map; effects: T.Effects }) => { @@ -23,6 +23,7 @@ export class CallbackHolder { return } const id = this.newId() + console.error("adding callback", id) this.callbacks.set(id, callback) if (this.effects) callbackRegistry.register(this, { diff --git a/container-runtime/src/Models/JsonPath.ts b/container-runtime/src/Models/JsonPath.ts index a231adba7..d101836da 100644 --- a/container-runtime/src/Models/JsonPath.ts +++ b/container-runtime/src/Models/JsonPath.ts @@ -23,8 +23,6 @@ export const jsonPath = some( "/packageUninit", "/backup/create", "/backup/restore", - "/actions/metadata", - "/properties", ), string.refine(isNestedPath, "isNestedPath"), ) diff --git a/core/models/src/procedure_name.rs b/core/models/src/procedure_name.rs index 9f8a6c0dd..4a4a682e6 100644 --- a/core/models/src/procedure_name.rs +++ b/core/models/src/procedure_name.rs @@ -7,7 +7,6 @@ pub enum ProcedureName { GetConfig, SetConfig, CreateBackup, - Properties, RestoreBackup, GetActionInput(ActionId), RunAction(ActionId), @@ -23,7 +22,6 @@ impl ProcedureName { 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::RunAction(id) => format!("/actions/{}/run", id), ProcedureName::GetActionInput(id) => format!("/actions/{}/getInput", id), diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index a18173add..3f1912c34 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -6,6 +6,7 @@ use models::PackageId; use qrcode::QrCode; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use tracing::instrument; use ts_rs::TS; @@ -74,21 +75,25 @@ pub async fn get_action_input( .await } -#[derive(Debug, Serialize, Deserialize)] +#[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, @@ -116,6 +121,96 @@ impl fmt::Display for ActionResultV0 { } } +#[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[serde(rename_all_fields = "camelCase")] +#[serde(tag = "type")] +pub enum ActionResultV1 { + String { + name: String, + value: String, + description: Option, + copyable: bool, + qr: bool, + masked: bool, + }, + Object { + name: String, + value: Vec, + #[ts(optional)] + description: Option, + }, +} +impl ActionResultV1 { + fn fmt_rec(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + match self { + Self::String { + name, + value, + description, + qr, + .. + } => { + for i in 0..indent { + write!(f, " ")?; + } + write!(f, "{name}")?; + if let Some(description) = description { + write!(f, ": {description}")?; + } + if !value.is_empty() { + write!(f, ":\n")?; + for i in 0..indent { + write!(f, " ")?; + } + write!(f, "{value}")?; + if *qr { + use qrcode::render::unicode; + write!(f, "\n")?; + for i in 0..indent { + write!(f, " ")?; + } + write!( + f, + "{}", + QrCode::new(value.as_bytes()) + .unwrap() + .render::() + .build() + )?; + } + } + } + Self::Object { + name, + value, + description, + } => { + for i in 0..indent { + write!(f, " ")?; + } + write!(f, "{name}")?; + if let Some(description) = description { + write!(f, ": {description}")?; + } + for value in value { + write!(f, ":\n")?; + for i in 0..indent { + write!(f, " ")?; + } + value.fmt_rec(f, indent + 1)?; + } + } + } + Ok(()) + } +} +impl fmt::Display for ActionResultV1 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_rec(f, 0) + } +} + pub fn display_action_result(params: WithIoFormat, result: Option) { let Some(result) = result else { return; diff --git a/core/startos/src/db/mod.rs b/core/startos/src/db/mod.rs index 1ed7e9ab9..c7e38e81c 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -228,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, } @@ -238,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> { @@ -253,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( |_| { @@ -263,9 +277,9 @@ async fn cli_apply( ) }, )?, - )?, - (), - )) + )? + }; + Ok::<_, Error>((value, ())) }) .await?; } else { diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 5fb6b9655..df5773cc3 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -338,7 +338,7 @@ pub struct ActionMetadata { #[serde(rename_all_fields = "camelCase")] pub enum ActionVisibility { Hidden, - Disabled { reason: String }, + Disabled(String), Enabled, } impl Default for ActionVisibility { @@ -444,14 +444,29 @@ pub struct ActionRequestEntry { pub struct ActionRequest { pub package_id: PackageId, pub action_id: ActionId, + #[serde(default)] + pub severity: ActionSeverity, #[ts(optional)] - pub description: Option, + 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)] diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index 754362bc9..d9d626754 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -49,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; @@ -395,15 +394,6 @@ pub fn package() -> ParentHandler { .no_display() .with_about("Display package logs"), ) - .subcommand( - "properties", - from_fn_async(properties::properties) - .with_custom_display_fn(|_handle, result| { - Ok(properties::display_properties(result)) - }) - .with_about("Display package Properties") - .with_call_remote::(), - ) .subcommand( "backup", backup::package_backup::() diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index 7ff1d7d08..2db7e9952 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -113,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, } 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/service/effects/net/bind.rs b/core/startos/src/service/effects/net/bind.rs index 40bc550cb..5619375eb 100644 --- a/core/startos/src/service/effects/net/bind.rs +++ b/core/startos/src/service/effects/net/bind.rs @@ -32,6 +32,7 @@ pub async fn bind( #[ts(export)] #[serde(rename_all = "camelCase")] pub struct ClearBindingsParams { + #[serde(default)] pub except: Vec, } diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index d7b91df7b..cc31efe11 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -55,7 +55,6 @@ pub mod cli; mod control; pub mod effects; pub mod persistent_container; -mod properties; mod rpc; mod service_actor; pub mod service_map; @@ -132,6 +131,7 @@ impl ServiceRef { ); Ok(()) })?; + d.as_private_mut().as_package_stores_mut().remove(&id)?; Ok(Some(pde)) } else { Ok(None) diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index 3f85a95d5..13cb7688c 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::ops::Deref; use std::path::Path; use std::sync::{Arc, Weak}; use std::time::Duration; @@ -379,11 +380,7 @@ impl PersistentContainer { )); } - self.rpc_client - .request(rpc::Init, Empty {}) - .await - .map_err(Error::from) - .log_err(); + self.rpc_client.request(rpc::Init, Empty {}).await?; self.state.send_modify(|s| s.rt_initialized = true); @@ -391,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; } @@ -406,6 +406,24 @@ impl PersistentContainer { 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(); @@ -433,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); @@ -551,7 +569,7 @@ impl PersistentContainer { impl Drop for PersistentContainer { fn drop(&mut self) { - if let Some(destroy) = self.destroy() { + 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/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/version/mod.rs b/core/startos/src/version/mod.rs index 76cc7a574..ae203f3e5 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -171,9 +171,17 @@ fn version_accessor(db: &mut Value) -> Option<&mut Value> { fn version_compat_accessor(db: &mut Value) -> Option<&mut Value> { if db.get("public").is_some() { - db.get_mut("public")? - .get_mut("serverInfo")? - .get_mut("versionCompat") + let server_info = db.get_mut("public")?.get_mut("serverInfo")?; + if server_info.get("versionCompat").is_some() { + server_info.get_mut("versionCompat") + } else { + if let Some(prev) = server_info.get("eosVersionCompat").cloned() { + server_info + .as_object_mut()? + .insert("versionCompat".into(), prev); + } + server_info.get_mut("versionCompat") + } } else { db.get_mut("server-info")?.get_mut("eos-version-compat") } diff --git a/sdk/Makefile b/sdk/Makefile index bc025709d..3f8ae533a 100644 --- a/sdk/Makefile +++ b/sdk/Makefile @@ -24,7 +24,7 @@ clean: package/lib/test/output.ts: package/node_modules package/lib/test/makeOutput.ts package/scripts/oldSpecToBuilder.ts cd package && npm run buildOutput -bundle: dist baseDist | test fmt +bundle: baseDist dist | test fmt touch dist base/lib/exver/exver.ts: base/node_modules base/lib/exver/exver.pegjs @@ -67,9 +67,8 @@ base/node_modules: base/package.json node_modules: package/node_modules base/node_modules -publish: bundle package/package.json README.md LICENSE - cd dist - npm publish --access=public +publish: bundle package/package.json package/README.md package/LICENSE + cd dist && npm publish --access=public link: bundle cd dist && npm link diff --git a/sdk/base/lib/Effects.ts b/sdk/base/lib/Effects.ts index 16d82464d..f985db77b 100644 --- a/sdk/base/lib/Effects.ts +++ b/sdk/base/lib/Effects.ts @@ -52,7 +52,7 @@ export type Effects = { options: RequestActionParams, ): Promise clearRequests( - options: { only: ActionId[] } | { except: ActionId[] }, + options: { only: string[] } | { except: string[] }, ): Promise } diff --git a/sdk/base/lib/actions/index.ts b/sdk/base/lib/actions/index.ts index cebb03ee1..2d052e072 100644 --- a/sdk/base/lib/actions/index.ts +++ b/sdk/base/lib/actions/index.ts @@ -1,5 +1,6 @@ import * as T from "../types" import * as IST from "../actions/input/inputSpecTypes" +import { Action } from "./setupActions" export type RunActionInput = | Input @@ -43,23 +44,62 @@ export const runAction = async < }) } } +type GetActionInputType< + A extends Action>, +> = A extends Action ? I : never -// prettier-ignore -export type ActionRequest> = - T extends { when: { condition: "input-not-matches" } } - ? (T extends { input: T.ActionRequestInput } ? T : "input is required for condition 'input-not-matches'") - : T +type ActionRequestBase = { + reason?: string + replayId?: string +} +type ActionRequestInput< + T extends Action>, +> = { + kind: "partial" + value: Partial> +} +export type ActionRequestOptions< + T extends Action>, +> = 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 = < - T extends Omit, + T extends Action>, >(options: { effects: T.Effects - request: ActionRequest & { replayId?: string; packageId: T.PackageId } + packageId: T.PackageId + action: T + severity: T.ActionSeverity + options?: ActionRequestOptions }) => { - const request = options.request + const request = options.options || {} + const actionId = options.action.id const req = { ...request, - replayId: request.replayId || `${request.packageId}:${request.actionId}`, + 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/base/lib/actions/setupActions.ts b/sdk/base/lib/actions/setupActions.ts index 8c6b7734a..62d6cedd9 100644 --- a/sdk/base/lib/actions/setupActions.ts +++ b/sdk/base/lib/actions/setupActions.ts @@ -11,7 +11,7 @@ export type Run< > = (options: { effects: T.Effects input: ExtractInputSpecType & Record -}) => Promise +}) => Promise export type GetInput< A extends | Record @@ -19,7 +19,9 @@ export type GetInput< | InputSpec, never>, > = (options: { effects: T.Effects -}) => Promise & Record)> +}) => Promise< + null | void | undefined | (ExtractInputSpecType & Record) +> export type MaybeFn = T | ((options: { effects: T.Effects }) => Promise) function callMaybeFn( @@ -91,7 +93,7 @@ export class Action< ): Action { return new Action( id, - mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })), + mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })), {}, async () => null, run, @@ -114,7 +116,7 @@ export class Action< effects: T.Effects input: Type }): Promise { - return this.runFn(options) + return (await this.runFn(options)) || null } } diff --git a/sdk/base/lib/dependencies/Dependency.ts b/sdk/base/lib/dependencies/Dependency.ts deleted file mode 100644 index 8a1a862c7..000000000 --- a/sdk/base/lib/dependencies/Dependency.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { VersionRange } from "../exver" - -export class Dependency { - constructor( - readonly data: - | { - /** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */ - type: "running" - /** The acceptable version range of the dependency. */ - versionRange: VersionRange - /** A list of the dependency's health check IDs that must be passing for the service to be satisfied. */ - healthChecks: string[] - } - | { - /** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */ - type: "exists" - /** The acceptable version range of the dependency. */ - versionRange: VersionRange - }, - ) {} -} diff --git a/sdk/base/lib/dependencies/setupDependencies.ts b/sdk/base/lib/dependencies/setupDependencies.ts index 9d613ff74..f694c042c 100644 --- a/sdk/base/lib/dependencies/setupDependencies.ts +++ b/sdk/base/lib/dependencies/setupDependencies.ts @@ -1,22 +1,11 @@ import * as T from "../types" import { once } from "../util" -import { Dependency } from "./Dependency" -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 +type DependencyType = { + [K in keyof Manifest["dependencies"]]: Omit } -export function setupDependencies( +export function setupDependencies( fn: (options: { effects: T.Effects }) => Promise>, ): (options: { effects: T.Effects }) => Promise { const cell = { updater: async (_: { effects: T.Effects }) => null } @@ -30,24 +19,12 @@ export function setupDependencies( 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(), - }), + ([id, { versionRange, ...x }, ,]) => + ({ + id, + ...x, + versionRange: versionRange.toString(), + }) as T.DependencyRequirement, ), }) } diff --git a/sdk/base/lib/osBindings/ActionRequest.ts b/sdk/base/lib/osBindings/ActionRequest.ts index 63b5607d8..552f37bc6 100644 --- a/sdk/base/lib/osBindings/ActionRequest.ts +++ b/sdk/base/lib/osBindings/ActionRequest.ts @@ -2,12 +2,14 @@ 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 - description?: string + severity: ActionSeverity + reason?: string when?: ActionRequestTrigger input?: ActionRequestInput } 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/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..cda0e5df9 --- /dev/null +++ b/sdk/base/lib/osBindings/ActionResultV1.ts @@ -0,0 +1,18 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ActionResultV1 = + | { + type: "string" + name: string + value: string + description: string | null + copyable: boolean + qr: boolean + masked: boolean + } + | { + type: "object" + name: string + value: Array + description?: string + } 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 index f7d5a2a2a..ab1e6e1b9 100644 --- a/sdk/base/lib/osBindings/ActionVisibility.ts +++ b/sdk/base/lib/osBindings/ActionVisibility.ts @@ -1,6 +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: { reason: string } } - | "enabled" +export type ActionVisibility = "hidden" | { disabled: string } | "enabled" diff --git a/sdk/base/lib/osBindings/RequestActionParams.ts b/sdk/base/lib/osBindings/RequestActionParams.ts index db83be595..ccc8d0e61 100644 --- a/sdk/base/lib/osBindings/RequestActionParams.ts +++ b/sdk/base/lib/osBindings/RequestActionParams.ts @@ -2,6 +2,7 @@ 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" @@ -9,7 +10,8 @@ export type RequestActionParams = { replayId: ReplayId packageId: PackageId actionId: ActionId - description?: string + severity: ActionSeverity + reason?: string when?: ActionRequestTrigger input?: ActionRequestInput } diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index 4b6af0ae1..5d5574acf 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -7,6 +7,10 @@ export { ActionRequestEntry } from "./ActionRequestEntry" export { ActionRequestInput } from "./ActionRequestInput" export { ActionRequestTrigger } from "./ActionRequestTrigger" export { ActionRequest } from "./ActionRequest" +export { ActionResult } from "./ActionResult" +export { ActionResultV0 } from "./ActionResultV0" +export { ActionResultV1 } from "./ActionResultV1" +export { ActionSeverity } from "./ActionSeverity" export { ActionVisibility } from "./ActionVisibility" export { AddAdminParams } from "./AddAdminParams" export { AddAssetParams } from "./AddAssetParams" diff --git a/sdk/base/lib/types.ts b/sdk/base/lib/types.ts index 071647961..d7ab1e51b 100644 --- a/sdk/base/lib/types.ts +++ b/sdk/base/lib/types.ts @@ -33,10 +33,6 @@ export const SIGKILL: Signals = "SIGKILL" export const NO_TIMEOUT = -1 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 @@ -86,10 +82,6 @@ export namespace ExpectedExports { nextVersion: null | string }) => Promise - export type properties = (options: { - effects: Effects - }) => Promise - export type manifest = Manifest export type actions = Actions< @@ -105,7 +97,6 @@ export type ABI = { containerInit: ExpectedExports.containerInit packageInit: ExpectedExports.packageInit packageUninit: ExpectedExports.packageUninit - properties: ExpectedExports.properties manifest: ExpectedExports.manifest actions: ExpectedExports.actions } @@ -177,58 +168,6 @@ export type ExposeServicePaths = { 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 = - | { - /** 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 EffectMethod = { [K in keyof T]-?: K extends string ? T[K] extends Function @@ -264,13 +203,6 @@ export type Metadata = { mode: number } -export type ActionResult = { - version: "0" - message: string - value: string | null - copyable: boolean - qr: boolean -} export type SetResult = { dependsOn: DependsOn signal: Signals diff --git a/sdk/base/package.json b/sdk/base/package.json index 3d1d4b62e..e38cc5ac6 100644 --- a/sdk/base/package.json +++ b/sdk/base/package.json @@ -4,9 +4,9 @@ "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", + "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'", + "buildOutput": "npx prettier --write \"**/*.ts\"", "check": "tsc --noEmit", "tsc": "tsc" }, diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 412dafe52..12e92faf2 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -55,7 +55,6 @@ import { getStore } from "./store/getStore" import { CommandOptions, MountOptions, SubContainer } from "./util/SubContainer" import { splitCommand } from "./util" import { Mounts } from "./mainFn/Mounts" -import { Dependency } from "../../base/lib/dependencies/Dependency" import { setupDependencies } from "../../base/lib/dependencies/setupDependencies" import * as T from "../../base/lib/types" import { testTypeVersion } from "../../base/lib/exver" @@ -86,12 +85,12 @@ type AnyNeverCond = T extends [any, ...infer U] ? AnyNeverCond : never -export class StartSdk { +export class StartSdk { private constructor(readonly manifest: Manifest) {} static of() { return new StartSdk(null as never) } - withManifest(manifest: Manifest) { + withManifest(manifest: Manifest) { return new StartSdk(manifest) } withStore>() { @@ -141,17 +140,39 @@ export class StartSdk { ...startSdkEffectWrapper, action: { run: actions.runAction, - request: actions.requestAction, - requestOwn: >( + request: < + T extends Action>, + >( effects: T.Effects, - request: actions.ActionRequest & { - replayId?: string - }, + packageId: T.PackageId, + action: T, + severity: T.ActionSeverity, + options?: actions.ActionRequestOptions, ) => actions.requestAction({ effects, - request: { ...request, packageId: this.manifest.id }, + packageId, + action, + severity, + options: options, }), + requestOwn: < + T extends Action>, + >( + 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"] & @@ -370,17 +391,6 @@ export class StartSdk { return healthCheck(o) }, }, - Dependency: { - /** - * @description Use this function to create a dependency for the service. - * @property {DependencyType} type - * @property {VersionRange} versionRange - * @property {string[]} healthChecks - */ - of(data: Dependency["data"]) { - return new Dependency({ ...data }) - }, - }, healthCheck: { checkPortListening, checkWebUrl, @@ -566,37 +576,6 @@ export class StartSdk { started(onTerm: () => PromiseLike): PromiseLike }) => Promise>, ) => setupMain(fn), - /** - * @description Use this function to determine which information to expose to the UI in the "Properties" section. - * - * Values can be obtained from anywhere: the Store, the upstream service, or another service. - * @example - * In this example, we retrieve the admin password from the Store and expose it, masked and copyable, to - * the UI as "Admin Password". - * - * ``` - export const properties = sdk.setupProperties(async ({ effects }) => { - const store = await sdk.store.getOwn(effects, sdk.StorePath).once() - - return { - 'Admin Password': { - type: 'string', - value: store.adminPassword, - description: 'Used for logging into the admin UI', - copyable: true, - masked: true, - qr: false, - }, - } - }) - * ``` - */ - setupProperties: - ( - fn: (options: { effects: Effects }) => Promise, - ): T.ExpectedExports.properties => - (options) => - fn(options).then(nullifyProperties), /** * Use this function to execute arbitrary logic *once*, on uninstall only. Most services will not use this. */ @@ -1057,6 +1036,7 @@ export class StartSdk { * ``` */ list: Value.list, + hidden: Value.hidden, dynamicToggle: ( a: LazyBuild< Store, @@ -1367,7 +1347,7 @@ export class StartSdk { } } -export async function runCommand( +export async function runCommand( effects: Effects, image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean }, command: string | [string, ...string[]], @@ -1385,26 +1365,3 @@ export async function runCommand( (subcontainer) => subcontainer.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, - masked: 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/package/lib/backup/Backups.ts b/sdk/package/lib/backup/Backups.ts index 8d8f03295..c27f2be72 100644 --- a/sdk/package/lib/backup/Backups.ts +++ b/sdk/package/lib/backup/Backups.ts @@ -35,7 +35,7 @@ export type BackupSync = { * ).build()q * ``` */ -export class Backups { +export class Backups { private constructor( private options = DEFAULT_OPTIONS, private restoreOptions: Partial = {}, @@ -43,7 +43,7 @@ export class Backups { private backupSet = [] as BackupSync[], ) {} - static withVolumes( + static withVolumes( ...volumeNames: Array ): Backups { return Backups.withSyncs( @@ -54,13 +54,13 @@ export class Backups { ) } - static withSyncs( + static withSyncs( ...syncs: BackupSync[] ) { return syncs.reduce((acc, x) => acc.addSync(x), new Backups()) } - static withOptions( + static withOptions( options?: Partial, ) { return new Backups({ ...DEFAULT_OPTIONS, ...options }) diff --git a/sdk/package/lib/backup/setupBackups.ts b/sdk/package/lib/backup/setupBackups.ts index b9654824b..722d245ff 100644 --- a/sdk/package/lib/backup/setupBackups.ts +++ b/sdk/package/lib/backup/setupBackups.ts @@ -2,7 +2,7 @@ import { Backups } from "./Backups" import * as T from "../../../base/lib/types" import { _ } from "../util" -export type SetupBackupsParams = +export type SetupBackupsParams = | M["volumes"][number][] | ((_: { effects: T.Effects }) => Promise>) @@ -11,7 +11,7 @@ type SetupBackupsRes = { restoreBackup: T.ExpectedExports.restoreBackup } -export function setupBackups( +export function setupBackups( options: SetupBackupsParams, ) { let backupsFactory: (_: { effects: T.Effects }) => Promise> diff --git a/sdk/package/lib/index.ts b/sdk/package/lib/index.ts index a83e35745..3619765e1 100644 --- a/sdk/package/lib/index.ts +++ b/sdk/package/lib/index.ts @@ -28,7 +28,7 @@ export { export { Daemons } from "./mainFn/Daemons" export { SubContainer } from "./util/SubContainer" export { StartSdk } from "./StartSdk" -export { setupManifest } from "./manifest/setupManifest" +export { setupManifest, buildManifest } from "./manifest/setupManifest" export { FileHelper } from "./util/fileHelper" export { setupExposeStore } from "./store/setupExposeStore" export { pathBuilder } from "../../base/lib/util/PathBuilder" diff --git a/sdk/package/lib/inits/setupInit.ts b/sdk/package/lib/inits/setupInit.ts index af2503dad..8d30f5b1f 100644 --- a/sdk/package/lib/inits/setupInit.ts +++ b/sdk/package/lib/inits/setupInit.ts @@ -7,12 +7,14 @@ 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, setServiceInterfaces: UpdateServiceInterfaces, - setDependencies: (options: { effects: T.Effects }) => Promise, + setDependencies: (options: { + effects: T.Effects + }) => Promise, actions: Actions, exposedStore: ExposedStorePaths, ): { diff --git a/sdk/package/lib/inits/setupInstall.ts b/sdk/package/lib/inits/setupInstall.ts index 9b5e92afe..38a96a00b 100644 --- a/sdk/package/lib/inits/setupInstall.ts +++ b/sdk/package/lib/inits/setupInstall.ts @@ -1,11 +1,11 @@ import * as T from "../../../base/lib/types" -export type InstallFn = (opts: { +export type InstallFn = (opts: { effects: T.Effects -}) => Promise -export class Install { +}) => Promise +export class Install { private constructor(readonly fn: InstallFn) {} - static of( + static of( fn: InstallFn, ) { return new Install(fn) @@ -18,7 +18,7 @@ export class Install { } } -export function setupInstall( +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 index c169457ec..fc4a71b8e 100644 --- a/sdk/package/lib/inits/setupUninstall.ts +++ b/sdk/package/lib/inits/setupUninstall.ts @@ -1,11 +1,11 @@ import * as T from "../../../base/lib/types" -export type UninstallFn = (opts: { +export type UninstallFn = (opts: { effects: T.Effects -}) => Promise -export class Uninstall { +}) => Promise +export class Uninstall { private constructor(readonly fn: UninstallFn) {} - static of( + static of( fn: UninstallFn, ) { return new Uninstall(fn) @@ -22,7 +22,7 @@ export class Uninstall { } } -export function setupUninstall( +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 index 601b759e6..27089ea86 100644 --- a/sdk/package/lib/mainFn/CommandController.ts +++ b/sdk/package/lib/mainFn/CommandController.ts @@ -20,7 +20,7 @@ export class CommandController { private process: cp.ChildProcessWithoutNullStreams, readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, ) {} - static of() { + static of() { return async ( effects: T.Effects, subcontainer: diff --git a/sdk/package/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts index bde4654ee..7877dce08 100644 --- a/sdk/package/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -17,7 +17,7 @@ export class Daemon { get subContainerHandle(): undefined | ExecSpawnable { return this.commandController?.subContainerHandle } - static of() { + static of() { return async ( effects: T.Effects, subcontainer: diff --git a/sdk/package/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts index 4c2506d1d..4ad573b0b 100644 --- a/sdk/package/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -27,7 +27,7 @@ export type Ready = { } type DaemonsParams< - Manifest extends T.Manifest, + Manifest extends T.SDKManifest, Ids extends string, Command extends string, Id extends string, @@ -43,7 +43,7 @@ type DaemonsParams< type ErrorDuplicateId = `The id '${Id}' is already used` -export const runCommand = () => +export const runCommand = () => CommandController.of() /** @@ -69,7 +69,7 @@ Daemons.of({ }) ``` */ -export class Daemons +export class Daemons implements T.DaemonBuildable { private constructor( @@ -89,7 +89,7 @@ export class Daemons * @param options * @returns */ - static of(options: { + static of(options: { effects: T.Effects started: (onTerm: () => PromiseLike) => PromiseLike healthReceipts: HealthReceipt[] diff --git a/sdk/package/lib/mainFn/Mounts.ts b/sdk/package/lib/mainFn/Mounts.ts index 42d6d66ab..38b3ce2a7 100644 --- a/sdk/package/lib/mainFn/Mounts.ts +++ b/sdk/package/lib/mainFn/Mounts.ts @@ -3,7 +3,7 @@ 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/package/lib/mainFn/index.ts b/sdk/package/lib/mainFn/index.ts index f1373c44e..be30c652d 100644 --- a/sdk/package/lib/mainFn/index.ts +++ b/sdk/package/lib/mainFn/index.ts @@ -14,7 +14,7 @@ export const DEFAULT_SIGTERM_TIMEOUT = 30_000 * @param fn * @returns */ -export const setupMain = ( +export const setupMain = ( fn: (o: { effects: T.Effects started(onTerm: () => PromiseLike): PromiseLike diff --git a/sdk/package/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts index 088575c1a..eb83741c8 100644 --- a/sdk/package/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -14,6 +14,23 @@ import { VersionGraph } from "../version/VersionGraph" * @param manifest Static properties of the package */ export function setupManifest< + Id extends string, + Dependencies extends Record, + VolumesTypes extends VolumeId, + AssetTypes extends VolumeId, + ImagesTypes extends ImageId, + Manifest extends { + dependencies: Dependencies + id: Id + assets: AssetTypes[] + images: Record + volumes: VolumesTypes[] + }, +>(manifest: SDKManifest & Manifest): SDKManifest & Manifest { + return manifest +} + +export function buildManifest< Id extends string, Version extends string, Dependencies extends Record, @@ -27,7 +44,6 @@ export function setupManifest< images: Record volumes: VolumesTypes[] }, - Satisfies extends string[] = [], >( versions: VersionGraph, manifest: SDKManifest & Manifest, diff --git a/sdk/package/lib/test/inputSpecBuilder.test.ts b/sdk/package/lib/test/inputSpecBuilder.test.ts index bb5a8e25d..f9d4321a6 100644 --- a/sdk/package/lib/test/inputSpecBuilder.test.ts +++ b/sdk/package/lib/test/inputSpecBuilder.test.ts @@ -367,48 +367,39 @@ describe("values", () => { test("datetime", async () => { const sdk = StartSdk.of() .withManifest( - setupManifest( - VersionGraph.of( - VersionInfo.of({ - version: "1.0.0:0", - releaseNotes: "", - migrations: {}, - }), - ), - { - 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", }, }, - ), + }), ) .withStore<{ test: "a" }>() .build(true) diff --git a/sdk/package/lib/test/output.sdk.ts b/sdk/package/lib/test/output.sdk.ts index 4cdf85111..d87446585 100644 --- a/sdk/package/lib/test/output.sdk.ts +++ b/sdk/package/lib/test/output.sdk.ts @@ -6,51 +6,40 @@ import { VersionGraph } from "../version/VersionGraph" export type Manifest = any export const sdk = StartSdk.of() .withManifest( - setupManifest( - VersionGraph.of( - VersionInfo.of({ - version: "1.0.0:0", - releaseNotes: "", - migrations: {}, - }) - .satisfies("#other:1.0.0:0") - .satisfies("#other:2.0.0:0"), - ), - { - 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", - }, + 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/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index bb7190fe1..1a3be3e4f 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -86,19 +86,21 @@ export class FileHelper { /** * Accepts structured data and overwrites the existing file on disk. */ - async write(data: A) { + async write(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 } /** * Reads the file from disk and converts it to structured data. */ - async read() { + private async readOnce(): Promise { if (!(await exists(this.path))) { return null } @@ -107,14 +109,14 @@ export class FileHelper { ) } - async const(effects: T.Effects) { - const watch = this.watch() + private async readConst(effects: T.Effects): Promise { + const watch = this.readWatch() const res = await watch.next() watch.next().then(effects.constRetry) return res.value } - async *watch() { + private async *readWatch() { let res while (true) { if (await exists(this.path)) { @@ -123,12 +125,12 @@ export class FileHelper { persistent: false, signal: ctrl.signal, }) - res = await this.read() + res = await this.readOnce() const listen = Promise.resolve() .then(async () => { for await (const _ of watch) { ctrl.abort("finished") - return + return null } }) .catch((e) => console.error(asError(e))) @@ -139,13 +141,22 @@ export class FileHelper { 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 structured data and performs a merge with the existing file on disk. */ async merge(data: A) { - const fileData = (await this.read().catch(() => ({}))) || {} + const fileData = (await this.readOnce().catch(() => ({}))) || {} const mergeData = merge({}, fileData, data) return await this.write(mergeData) } diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index b5c0fba5d..2294d800b 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,12 +1,12 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha8", + "version": "0.3.6-alpha9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha8", + "version": "0.3.6-alpha9", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -14,7 +14,7 @@ "@noble/hashes": "^1.4.0", "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", - "mime": "^4.0.3", + "mime-types": "^2.1.35", "ts-matches": "^5.5.1", "yaml": "^2.2.2" }, @@ -3136,18 +3136,25 @@ "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" + "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": ">=16" + "node": ">= 0.6" } }, "node_modules/mimic-fn": { diff --git a/sdk/package/package.json b/sdk/package/package.json index 530a22053..acc523594 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha8", + "version": "0.3.6-alpha.12", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", @@ -15,7 +15,7 @@ }, "scripts": { "test": "jest -c ./jest.config.js --coverage", - "buildOutput": "ts-node ./lib/test/makeOutput.ts && npx prettier --write '**/*.ts'", + "buildOutput": "ts-node ./lib/test/makeOutput.ts && npx prettier --write \"**/*.ts\"", "check": "tsc --noEmit", "tsc": "tsc" }, @@ -32,7 +32,7 @@ "dependencies": { "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", - "mime": "^4.0.3", + "mime-types": "^2.1.35", "ts-matches": "^5.5.1", "yaml": "^2.2.2", "@iarna/toml": "^2.2.5", diff --git a/web/README.md b/web/README.md index 7665a26a6..feddf713e 100644 --- a/web/README.md +++ b/web/README.md @@ -48,6 +48,8 @@ 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 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.json b/web/package.json index 0de550c62..5fdf2c859 100644 --- a/web/package.json +++ b/web/package.json @@ -6,14 +6,13 @@ "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", 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/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/form.component.ts b/web/projects/ui/src/app/components/form.component.ts index 386bd5f8e..93f65115a 100644 --- a/web/projects/ui/src/app/components/form.component.ts +++ b/web/projects/ui/src/app/components/form.component.ts @@ -149,14 +149,15 @@ export class FormComponent> implements OnInit { } private process(operations: Operation[]) { - operations.forEach(({ op, path }) => { - const control = this.form.get(path.substring(1).split('/')) + 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/modals/action-input.component.ts b/web/projects/ui/src/app/modals/action-input.component.ts index 494dfcdda..4543e29fd 100644 --- a/web/projects/ui/src/app/modals/action-input.component.ts +++ b/web/projects/ui/src/app/modals/action-input.component.ts @@ -16,7 +16,7 @@ import { compare } from 'fast-json-patch' import { PatchDB } from 'patch-db-client' import { catchError, defer, EMPTY, endWith, firstValueFrom, map } from 'rxjs' import { InvalidService } from 'src/app/components/form/invalid.service' -import { ActionDepComponent } from 'src/app/modals/action-dep.component' +import { ActionRequestInfoComponent } from 'src/app/modals/action-request-input.component' import { UiPipeModule } from 'src/app/pipes/ui/ui.module' import { ApiService } from 'src/app/services/api/embassy-api.service' import { DataModel } from 'src/app/services/patch-db/data-model' @@ -25,23 +25,29 @@ import * as json from 'fast-json-patch' import { ActionService } from '../services/action.service' import { ActionButton, FormComponent } from '../components/form.component' -export interface PackageActionData { - readonly pkgInfo: { +export type PackageActionData = { + pkgInfo: { id: string title: string + icon: string + mainStatus: T.MainStatus['main'] } - readonly actionInfo: { + actionInfo: { id: string - warning: string | null + metadata: T.ActionMetadata } - readonly dependentInfo?: { - title: string + requestInfo?: { + dependentId?: string request: T.ActionRequest } } @Component({ template: ` +
+ +

{{ pkgInfo.title }}

+
@@ -52,13 +58,11 @@ export interface PackageActionData {
- + > [] = [ { @@ -131,12 +147,12 @@ export class ActionInputModal { return { spec: res.spec, originalValue, - operations: this.dependentInfo?.request.input + operations: this.requestInfo?.request.input ? compare( - originalValue, + JSON.parse(JSON.stringify(originalValue)), utils.deepMerge( - originalValue, - this.dependentInfo.request.input.value, + JSON.parse(JSON.stringify(originalValue)), + this.requestInfo.request.input.value, ) as object, ) : null, @@ -159,15 +175,7 @@ export class ActionInputModal { async execute(input: object) { if (await this.checkConflicts(input)) { - const res = await firstValueFrom(this.res$) - - return this.actionService.execute(this.pkgInfo.id, this.actionId, { - prev: { - spec: res.spec, - value: res.originalValue, - }, - curr: input, - }) + return this.actionService.execute(this.pkgInfo.id, this.actionId, input) } } @@ -181,6 +189,7 @@ export class ActionInputModal { Object.values(packages[id].requestedActions).some( ({ request, active }) => !active && + request.severity === 'critical' && request.packageId === this.pkgInfo.id && request.actionId === this.actionId && request.when?.condition === 'input-not-matches' && diff --git a/web/projects/ui/src/app/modals/action-dep.component.ts b/web/projects/ui/src/app/modals/action-request-input.component.ts similarity index 77% rename from web/projects/ui/src/app/modals/action-dep.component.ts rename to web/projects/ui/src/app/modals/action-request-input.component.ts index 8eeb9faa8..2451de876 100644 --- a/web/projects/ui/src/app/modals/action-dep.component.ts +++ b/web/projects/ui/src/app/modals/action-request-input.component.ts @@ -11,14 +11,10 @@ import { CommonModule } from '@angular/common' import { TuiNotificationModule } from '@taiga-ui/core' @Component({ - selector: 'action-dep', + selector: 'action-request-info', template: ` - -

- {{ pkgTitle }} -

- The following modifications have been made to {{ pkgTitle }} to satisfy - {{ depTitle }}: + + The following modifications were made:
@@ -27,14 +23,15 @@ import { TuiNotificationModule } from '@taiga-ui/core' standalone: true, imports: [CommonModule, TuiNotificationModule], changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + tui-notification { + margin-bottom: 1.5rem; + } + `, + ], }) -export class ActionDepComponent implements OnInit { - @Input() - pkgTitle = '' - - @Input() - depTitle = '' - +export class ActionRequestInfoComponent implements OnInit { @Input() originalValue: object = {} @@ -68,15 +65,15 @@ export class ActionDepComponent implements OnInit { private getMessage(operation: Operation): string { switch (operation.op) { case 'add': - return `Added ${this.getNewValue(operation.value)}` + return `added ${this.getNewValue(operation.value)}` case 'remove': - return `Removed ${this.getOldValue(operation.path)}` + return `removed ${this.getOldValue(operation.path)}` case 'replace': - return `Changed from ${this.getOldValue( + return `changed from ${this.getOldValue( operation.path, )} to ${this.getNewValue(operation.value)}` default: - return `Unknown operation` + return `Unknown operation` // unreachable } } diff --git a/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts b/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts new file mode 100644 index 000000000..c02b51ee9 --- /dev/null +++ b/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts @@ -0,0 +1,54 @@ +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { T } from '@start9labs/start-sdk' +import { TuiFadeModule, TuiTitleModule } from '@taiga-ui/experimental' +import { TuiAccordionModule } from '@taiga-ui/kit' +import { ActionSuccessItemComponent } from './action-success-item.component' + +@Component({ + standalone: true, + selector: 'app-action-success-group', + template: ` +

+ + +

{{ item.name }}
+ + + + +

+ `, + styles: [ + ` + p:first-child { + margin-top: 0; + } + + p:last-child { + margin-bottom: 0; + } + `, + ], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + TuiTitleModule, + ActionSuccessItemComponent, + TuiAccordionModule, + TuiFadeModule, + ], +}) +export class ActionSuccessGroupComponent { + @Input() + value?: T.ActionResultV1 & { type: 'object' } + + isSingle( + value: T.ActionResultV1, + ): value is T.ActionResultV1 & { type: 'string' } { + return value.type === 'string' + } +} diff --git a/web/projects/ui/src/app/modals/action-success/action-success-item.component.ts b/web/projects/ui/src/app/modals/action-success/action-success-item.component.ts new file mode 100644 index 000000000..25054f2f6 --- /dev/null +++ b/web/projects/ui/src/app/modals/action-success/action-success-item.component.ts @@ -0,0 +1,184 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + ElementRef, + inject, + Input, + TemplateRef, + ViewChild, +} from '@angular/core' +import { FormsModule } from '@angular/forms' +import { T } from '@start9labs/start-sdk' +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 { ActionSuccessGroupComponent } from './action-success-group.component' + +@Component({ + standalone: true, + selector: 'app-action-success-item', + 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 ActionSuccessItemComponent { + @ViewChild(TuiTextfieldComponent, { read: ElementRef }) + private readonly input!: ElementRef + private readonly dialogs = inject(TuiDialogService) + + readonly parent = inject(ActionSuccessGroupComponent, { + optional: true, + }) + + @Input() + value!: T.ActionResultV1 & { type: 'string' } + + masked = true + + get border(): number { + let border = 0 + + if (this.value.masked) { + border += 2 + } + + if (this.value.copyable) { + border += 2 + } + + if (this.value.qr && this.parent) { + border += 2 + } + + return border + } + + show(template: TemplateRef) { + const masked = this.masked + + this.masked = this.value.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.value.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 8912ed6ba..afcab5793 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 { copyToClipboard } from '@start9labs/shared' -import { T } from '@start9labs/start-sdk' +import { CommonModule } from '@angular/common' +import { Component, inject } from '@angular/core' +import { TuiDialogContext, TuiTextfieldControllerModule } from '@taiga-ui/core' +import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' +import { RR } from 'src/app/services/api/api.types' +import { ActionSuccessGroupComponent } from './action-success-group.component' +import { ActionSuccessItemComponent } from './action-success-item.component' @Component({ - selector: 'action-success', - templateUrl: './action-success.page.html', - styleUrls: ['./action-success.page.scss'], + standalone: true, + template: ` + + + + + `, + imports: [ + CommonModule, + ActionSuccessGroupComponent, + ActionSuccessItemComponent, + TuiTextfieldControllerModule, + ], }) export class ActionSuccessPage { - @Input() - actionRes!: T.ActionResult + 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 item = this.data?.type === 'string' ? this.data : null + readonly group = this.data?.type === 'object' ? this.data : null } 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 c9ecc8ed5..bacbed729 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 @@ -37,8 +37,8 @@ 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 95be585ba..feea649d1 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 @@ -21,13 +21,12 @@ export class AppActionsPage { filter(pkg => pkg.stateInfo.state === 'installed'), map(pkg => ({ mainStatus: pkg.status.main, + icon: pkg.icon, manifest: getManifest(pkg), - actions: Object.keys(pkg.actions) - .filter(id => id !== 'config') - .map(id => ({ - id, - ...pkg.actions[id], - })), + actions: Object.keys(pkg.actions).map(id => ({ + id, + ...pkg.actions[id], + })), })), ) @@ -40,13 +39,14 @@ export class AppActionsPage { async handleAction( mainStatus: T.MainStatus['main'], + icon: string, manifest: T.Manifest, action: T.ActionMetadata & { id: string }, ) { - this.actionService.present( - { id: manifest.id, title: manifest.title, mainStatus }, - { id: action.id, metadata: action }, - ) + this.actionService.present({ + pkgInfo: { id: manifest.id, title: manifest.title, icon, mainStatus }, + actionInfo: { id: action.id, metadata: action }, + }) } async rebuild(id: string) { @@ -76,7 +76,7 @@ export class AppActionsItemComponent { get disabledText() { return ( typeof this.action.visibility === 'object' && - this.action.visibility.disabled.reason + this.action.visibility.disabled ) } } 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 521542c10..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') - .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 7d51a6fc8..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 @@ -19,6 +19,7 @@ import { AppShowMenuComponent } from './components/app-show-menu/app-show-menu.c 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' @@ -45,6 +46,7 @@ const routes: Routes = [ AppShowHealthChecksComponent, AppShowAdditionalComponent, AppShowErrorComponent, + AppShowActionRequestsComponent, ], imports: [ CommonModule, 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 ec59b1042..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 @@ -20,6 +20,12 @@ + + any } @@ -60,6 +59,7 @@ export class AppShowPage { const pkg = allPkgs[this.pkgId] const manifest = getManifest(pkg) return { + allPkgs, pkg, manifest, dependencies: this.getDepInfo(pkg, manifest, allPkgs, depErrors), @@ -75,7 +75,6 @@ export class AppShowPage { private readonly navCtrl: NavController, private readonly patch: PatchDB, private readonly depErrorService: DepErrorService, - private readonly actionService: ActionService, ) {} showProgress( @@ -95,32 +94,6 @@ export class AppShowPage { ) } - 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 || depId, - icon: icon || 'assets/img/service-icons/fallback.png', - versionRange, - } - } - } - private getDepValues( pkg: PackageDataEntry, allPkgs: AllPackageData, @@ -135,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, } } @@ -165,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}`) } } @@ -197,41 +166,6 @@ export class AppShowPage { } } - private async fixDep( - pkg: PackageDataEntry, - pkgManifest: T.Manifest, - action: 'install' | 'update' | 'configure', - depId: string, - ) { - switch (action) { - case 'install': - case 'update': - return this.installDep(pkg, pkgManifest, depId) - case 'configure': - const depPkg = await getPackage(this.patch, depId) - if (!depPkg) return - - const depManifest = getManifest(depPkg) - return this.actionService.present( - { - id: depId, - title: depManifest.title, - mainStatus: depPkg.status.main, - }, - { id: 'config', metadata: pkg.actions['config'] }, - { - title: pkgManifest.title, - request: Object.values(pkg.requestedActions).find( - r => - r.active && - r.request.packageId === depId && - r.request.actionId === 'config', - )!.request, - }, - ) - } - } - 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-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 e31d4cd24..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 @@ -52,16 +52,6 @@ Start - - - Configure - - , - private readonly actionService: ActionService, ) {} get interfaces(): PackageDataEntry['serviceInterfaces'] { @@ -77,14 +73,11 @@ export class AppShowStatusComponent { } get canStart(): boolean { - return ( - this.status.primary === 'stopped' && - !Object.keys(this.pkg.requestedActions).length - ) + return this.status.primary === 'stopped' } get sigtermTimeout(): string | null { - return this.pkgStatus?.main === 'stopping' ? '30s' : null // @dr-bonez TODO + return this.pkgStatus?.main === 'stopping' ? '30s' : null // @TODO Aiden } launchUi( @@ -94,17 +87,6 @@ export class AppShowStatusComponent { this.launcherService.launch(interfaces, hosts) } - async presentModalConfig(): Promise { - return this.actionService.present( - { - id: this.manifest.id, - title: this.manifest.title, - mainStatus: this.pkg.status.main, - }, - { id: 'config', metadata: this.pkg.actions['config'] }, - ) - } - async tryStart(): Promise { if (this.status.dependency === 'warning') { const depErrMsg = `${this.manifest.title} has unmet dependencies. It will not work as expected.` @@ -221,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 c9bb73bfe..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 @@ -1,6 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core' import { ActivatedRoute } from '@angular/router' -import { AlertController, ModalController, NavController } from '@ionic/angular' +import { ModalController, NavController } from '@ionic/angular' import { MarkdownComponent } from '@start9labs/shared' import { DataModel, @@ -8,10 +8,8 @@ import { PackageDataEntry, } from 'src/app/services/patch-db/data-model' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { from, map, Observable, of } from 'rxjs' +import { from, map, Observable } from 'rxjs' import { PatchDB } from 'patch-db-client' -import { ActionService } from 'src/app/services/action.service' -import { needsConfig } from 'src/app/util/get-package-data' export interface Button { title: string @@ -33,8 +31,6 @@ export class ToButtonsPipe implements PipeTransform { private readonly apiService: ApiService, private readonly api: ApiService, private readonly patch: PatchDB, - private readonly actionService: ActionService, - private readonly alertCtrl: AlertController, ) {} transform(pkg: PackageDataEntry): Button[] { @@ -51,50 +47,12 @@ export class ToButtonsPipe implements PipeTransform { .watch$('ui', 'ackInstructions', manifest.id) .pipe(map(seen => !seen)), }, - // config - { - action: async () => - pkg.actions['config'] - ? this.actionService.present( - { - id: manifest.id, - title: manifest.title, - mainStatus: pkg.status.main, - }, - { - id: 'config', - metadata: pkg.actions['config'], - }, - ) - : this.alertCtrl - .create({ - header: 'No Config', - message: `No config options for ${manifest.title} v${manifest.version}`, - buttons: ['OK'], - }) - .then(a => a.present()), - title: 'Config', - description: `Customize ${manifest.title}`, - icon: 'options-outline', - highlighted$: of(needsConfig(manifest.id, pkg.requestedActions)), - }, - // 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/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/services/action.service.ts b/web/projects/ui/src/app/services/action.service.ts index 74df5ffc6..df09daa32 100644 --- a/web/projects/ui/src/app/services/action.service.ts +++ b/web/projects/ui/src/app/services/action.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core' -import { AlertController, ModalController } from '@ionic/angular' +import { AlertController } from '@ionic/angular' import { ErrorService, LoadingService } from '@start9labs/shared' -import { T } from '@start9labs/start-sdk' +import { TuiDialogService } from '@taiga-ui/core' +import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus' import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' -import { RR } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' import { FormDialogService } from 'src/app/services/form-dialog.service' import { @@ -31,28 +31,16 @@ const allowedStatuses = { export class ActionService { constructor( private readonly api: ApiService, - private readonly modalCtrl: ModalController, + private readonly dialogs: TuiDialogService, private readonly alertCtrl: AlertController, private readonly errorService: ErrorService, private readonly loader: LoadingService, private readonly formDialog: FormDialogService, ) {} - async present( - pkgInfo: { - id: string - title: string - mainStatus: T.MainStatus['main'] - }, - actionInfo: { - id: string - metadata: T.ActionMetadata - }, - dependentInfo?: { - title: string - request: T.ActionRequest - }, - ) { + async present(data: PackageActionData) { + const { pkgInfo, actionInfo } = data + if ( allowedStatuses[actionInfo.metadata.allowedStatuses].has( pkgInfo.mainStatus, @@ -61,36 +49,32 @@ export class ActionService { if (actionInfo.metadata.hasInput) { this.formDialog.open(ActionInputModal, { label: actionInfo.metadata.name, - data: { - pkgInfo, - actionInfo: { - id: actionInfo.id, - warning: actionInfo.metadata.warning, - }, - dependentInfo, - }, + data, }) } else { - const alert = await this.alertCtrl.create({ - header: 'Confirm', - message: `Are you sure you want to execute action "${ - actionInfo.metadata.name - }"? ${actionInfo.metadata.warning || ''}`, - buttons: [ - { - text: 'Cancel', - role: 'cancel', - }, - { - text: 'Execute', - handler: () => { - this.execute(pkgInfo.id, actionInfo.id) + if (actionInfo.metadata.warning) { + const alert = await this.alertCtrl.create({ + header: 'Warning', + message: actionInfo.metadata.warning, + buttons: [ + { + text: 'Cancel', + role: 'cancel', }, - cssClass: 'enter-click', - }, - ], - }) - await alert.present() + { + 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]] @@ -123,30 +107,24 @@ export class ActionService { async execute( packageId: string, actionId: string, - inputs?: { - prev: RR.GetActionInputRes - curr: object - }, + input?: object, ): Promise { - const loader = this.loader.open('Executing action...').subscribe() + const loader = this.loader.open('Loading...').subscribe() try { const res = await this.api.runAction({ packageId, actionId, - prev: inputs?.prev || null, - input: inputs?.curr || null, + input: input || null, }) if (res) { - const successModal = await this.modalCtrl.create({ - component: ActionSuccessPage, - componentProps: { - actionRes: res, - }, - }) - - setTimeout(() => successModal.present(), 500) + this.dialogs + .open(new PolymorpheusComponent(ActionSuccessPage), { + label: res.name, + data: res, + }) + .subscribe() } return true // needed to dismiss original modal/alert } catch (e: any) { 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 e3aa63ee3..d6c27e353 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, 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, @@ -880,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', @@ -946,15 +922,6 @@ export module Mock { }, } - export const ActionResponse: T.ActionResult = { - version: '0', - 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(), @@ -1082,11 +1049,26 @@ export module Mock { }, } - export const PackageProperties: RR.GetPackagePropertiesRes<2> = { - version: 2, - data: { - lndconnect: { + export const ActionRes: RR.ActionRes = { + version: '1', + type: 'string', + name: 'New Password', + description: + 'Action was run successfully Action was run successfully Action was run successfully Action was run successfully Action was run successfully', + copyable: true, + qr: true, + masked: true, + value: 'iwejdoiewdhbew', + } + + export const ActionProperties: RR.ActionRes = { + version: '1', + type: 'object', + name: 'Properties', + value: [ + { type: 'string', + name: 'LND Connect', description: 'This is some information about the thing.', copyable: true, qr: true, @@ -1094,45 +1076,50 @@ export module Mock { value: 'lndconnect://udlyfq2mxa4355pt7cqlrdipnvk2tsl4jtsdw7zaeekenufwcev2wlad.onion:10009?cert=MIICJTCCAcugAwIBAgIRAOyq85fqAiA3U3xOnwhH678wCgYIKoZIzj0EAwIwODEfMB0GAkUEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMB4XDTIwMTAyNjA3MzEyN1oXDTIxMTIyMTA3MzEyN1owODEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKqfhAMMZdY-eFnU5P4bGrQTSx0lo7m8u4V0yYkzUM6jlql_u31_mU2ovLTj56wnZApkEjoPl6fL2yasZA2wiy6OBtTCBsjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH_BAUwAwEB_zAdBgNVHQ4EFgQUYQ9uIO6spltnVCx4rLFL5BvBF9IwWwYDVR0RBFQwUoIMNTc0OTkwMzIyYzZlgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwSAAswCgYIKoZIzj0EAwIDSAAwRQIgVZH2Z2KlyAVY2Q2aIQl0nsvN-OEN49wreFwiBqlxNj4CIQD5_JbpuBFJuf81I5J0FQPtXY-4RppWOPZBb-y6-rkIUQ&macaroon=AgEDbG5kAusBAwoQuA8OUMeQ8Fr2h-f65OdXdRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFAoIbWFjYXJvb24SCGdlbmVyYXRlGhYKB21lc3NhZ2USBHJlYWQSBXdyaXRlGhcKCG9mZmNoYWluEgRyZWFkEgV3cml0ZRoWCgdvbmNoYWluEgRyZWFkEgV3cml0ZRoUCgVwZWVycxIEcmVhZBIFd3JpdGUaGAoGc2lnbmVyEghnZW5lcmF0ZRIEcmVhZAAABiCYsRUoUWuAHAiCSLbBR7b_qULDSl64R8LIU2aqNIyQfA', }, - Nested: { + { type: 'object', + name: 'Nested Stuff', description: 'This is a nested thing metric', - value: { - 'Last Name': { + value: [ + { type: 'string', + name: 'Last Name', description: 'The last name of the user', copyable: true, qr: true, masked: false, value: 'Hill', }, - Age: { + { type: 'string', + name: 'Age', description: 'The age of the user', copyable: false, qr: false, masked: false, value: '35', }, - Password: { + { type: 'string', + name: 'Password', description: 'A secret password', copyable: true, qr: false, masked: true, value: 'password123', }, - }, + ], }, - 'Another Value': { + { type: 'string', + name: 'Another Value', description: 'Some more information about the service.', copyable: false, qr: true, masked: false, value: 'https://guessagain.com', }, - }, + ], } export const getActionInputSpec = async (): Promise => @@ -1692,7 +1679,7 @@ export module Mock { }, actions: { config: { - name: 'Bitcoin Config', + name: 'Set Config', description: 'edit bitcoin.conf', warning: null, visibility: 'enabled', @@ -1700,6 +1687,25 @@ export module Mock { 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: 'enabled', + allowedStatuses: 'only-running', + hasInput: false, + group: null, + }, }, serviceInterfaces: { ui: { @@ -1859,7 +1865,27 @@ export module Mock { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', - requestedActions: {}, + 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 = { @@ -1992,7 +2018,27 @@ export module Mock { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', - requestedActions: {}, + 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 65997c49d..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,5 +1,4 @@ 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 { IST, T } from '@start9labs/start-sdk' @@ -209,19 +208,12 @@ 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 @@ -231,13 +223,12 @@ export module RR { value: object | null } - export type RunActionReq = { + export type ActionReq = { packageId: string actionId: string - prev: GetActionInputRes | null input: object | null } // package.action.run - export type RunActionRes = T.ActionResult | null + export type ActionRes = (T.ActionResult & { version: '1' }) | null export type RestorePackagesReq = { // package.backup.restore @@ -494,7 +485,7 @@ export type DependencyError = | DependencyErrorNotInstalled | DependencyErrorNotRunning | DependencyErrorIncorrectVersion - | DependencyErrorConfigUnsatisfied + | DependencyErrorActionRequired | DependencyErrorHealthChecksFailed | DependencyErrorTransitive @@ -512,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 49571d97f..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 @@ -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 @@ -235,7 +227,7 @@ export abstract class ApiService { params: RR.GetActionInputReq, ): Promise - abstract runAction(params: RR.RunActionReq): Promise + abstract runAction(params: RR.ActionReq): Promise abstract restorePackages( params: RR.RestorePackagesReq, 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 73f3e67c6..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' @@ -436,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 { @@ -456,12 +447,6 @@ 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 { @@ -474,7 +459,7 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'package.action.get-input', params }) } - async runAction(params: RR.RunActionReq): Promise { + async runAction(params: RR.ActionReq): Promise { return this.rpcRequest({ method: 'package.action.run', params }) } 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 c4834c99e..e62094057 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 @@ -17,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 { @@ -368,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 = { @@ -707,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 { @@ -795,9 +780,23 @@ export class MockApiService extends ApiService { } } - async runAction(params: RR.RunActionReq): Promise { + async runAction(params: RR.ActionReq): Promise { await pauseFor(2000) - return Mock.ActionResponse + + if (params.actionId === 'properties') { + return Mock.ActionProperties + } 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.ActionRes + } } async restorePackages( 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 55ea59a77..3a8560336 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -61,6 +61,7 @@ export const mockPatchData: DataModel = { passwordHash: '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ', versionCompat: '>=0.3.0 <=0.3.6', + postInitMigrationTodos: [], statusInfo: { backupProgress: null, updated: false, @@ -82,7 +83,6 @@ export const mockPatchData: DataModel = { selected: null, lastRegion: null, }, - postInitMigrationTodos: [], }, packageData: { bitcoind: { @@ -107,7 +107,7 @@ export const mockPatchData: DataModel = { // }, actions: { config: { - name: 'Bitcoin Config', + name: 'Set Config', description: 'edit bitcoin.conf', warning: null, visibility: 'enabled', @@ -115,6 +115,25 @@ export const mockPatchData: DataModel = { 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: 'enabled', + allowedStatuses: 'only-running', + hasInput: false, + group: null, + }, }, serviceInterfaces: { ui: { @@ -274,7 +293,27 @@ export const mockPatchData: DataModel = { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', - requestedActions: {}, + 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: { @@ -364,7 +403,27 @@ export const mockPatchData: DataModel = { storeExposedDependents: [], registry: 'https://registry.start9.com/', developerKey: 'developer-key', - requestedActions: {}, + 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/dep-error.service.ts b/web/projects/ui/src/app/services/dep-error.service.ts index c4de94609..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,17 +101,17 @@ export class DepErrorService { } } - // invalid config + // action required if ( Object.values(pkg.requestedActions).some( a => a.active && a.request.packageId === depId && - a.request.actionId === 'config', + a.request.severity === 'critical', ) ) { return { - type: 'configUnsatisfied', + type: 'actionRequired', } } 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 3f2a7b917..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 @@ -1,7 +1,6 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PkgDependencyErrors } from './dep-error.service' import { T } from '@start9labs/start-sdk' -import { getManifest, needsConfig } from '../util/get-package-data' export interface PackageStatus { primary: PrimaryStatus @@ -29,8 +28,12 @@ export function renderPkgStatus( } function getInstalledPrimaryStatus(pkg: T.PackageDataEntry): PrimaryStatus { - if (needsConfig(getManifest(pkg).id, pkg.requestedActions)) { - return 'needsConfig' + if ( + Object.values(pkg.requestedActions).some( + r => r.active && r.request.severity === 'critical', + ) + ) { + return 'actionRequired' } else { return pkg.status.main } @@ -79,7 +82,7 @@ export type PrimaryStatus = | 'restarting' | 'stopped' | 'backingUp' - | 'needsConfig' + | 'actionRequired' | 'error' export type DependencyStatus = 'warning' | 'satisfied' @@ -135,8 +138,8 @@ export const PrimaryRendering: Record = { color: 'success', showDots: false, }, - needsConfig: { - display: 'Needs Config', + actionRequired: { + display: 'Action Required', color: 'warning', showDots: false, }, 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-data.ts b/web/projects/ui/src/app/util/get-package-data.ts index 7ba7377fc..c4d0cc046 100644 --- a/web/projects/ui/src/app/util/get-package-data.ts +++ b/web/projects/ui/src/app/util/get-package-data.ts @@ -28,18 +28,6 @@ export function getManifest(pkg: PackageDataEntry): T.Manifest { return (pkg.stateInfo as InstallingState).installingInfo.newManifest } -export function needsConfig( - pkgId: string, - requestedActions: PackageDataEntry['requestedActions'], -): boolean { - return Object.values(requestedActions).some( - r => - r.active && - r.request.packageId === pkgId && - r.request.actionId === 'config', - ) -} - export function isInstalled( pkg: PackageDataEntry, ): pkg is PackageDataEntry { 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 -} From c9f3e1bdabea8292a970a82aadcfe2ebf11ff320 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Thu, 17 Oct 2024 21:20:46 -0600 Subject: [PATCH 16/46] fix bug allowing click on disabled actions --- .../app-actions/app-actions-item.component.html | 2 +- .../apps-routes/app-actions/app-actions.page.html | 6 +++--- .../pages/apps-routes/app-actions/app-actions.page.ts | 11 +++++++++-- web/projects/ui/src/app/services/api/api.fixures.ts | 2 +- web/projects/ui/src/app/services/api/mock-patch.ts | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html index 79d7af03f..11faa8235 100644 --- a/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html +++ b/web/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html @@ -1,4 +1,4 @@ - +

{{ action.name }}

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 bacbed729..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 @@ -18,7 +18,7 @@ visibility: 'enabled' }" icon="construct-outline" - (click)="rebuild(pkg.manifest.id)" + (onClick)="rebuild(pkg.manifest.id)" > @@ -38,7 +38,7 @@ *ngFor="let action of pkg.actions" [action]="action" icon="play-outline" - (click)="handleAction(pkg.mainStatus, pkg.icon, pkg.manifest, action)" + (onClick)="handleAction(pkg.mainStatus, pkg.icon, pkg.manifest, action)" > 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 feea649d1..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,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core' import { ActivatedRoute } from '@angular/router' import { getPkgId } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' @@ -70,9 +76,10 @@ export class AppActionsItemComponent { description: string visibility: T.ActionVisibility } - @Input() icon!: string + @Output() onClick: EventEmitter = new EventEmitter() + get disabledText() { return ( typeof this.action.visibility === 'object' && 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 d6c27e353..828b36642 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -1701,7 +1701,7 @@ export module Mock { description: 'An example of an action that shows a warning and takes no input', warning: 'careful running this action', - visibility: 'enabled', + visibility: { disabled: 'This is temporarily disabled' }, allowedStatuses: 'only-running', hasInput: false, group: null, 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 3a8560336..d5989affa 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -129,7 +129,7 @@ export const mockPatchData: DataModel = { description: 'An example of an action that shows a warning and takes no input', warning: 'careful running this action', - visibility: 'enabled', + visibility: { disabled: 'This is temporarily disabled' }, allowedStatuses: 'only-running', hasInput: false, group: null, From 2fa0a57d2b1c3e9e08b454e94318eb73379aae19 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:17:56 -0600 Subject: [PATCH 17/46] fixing raspi image (#2712) * wip: fixing raspi image * fix pi build --- image-recipe/build.sh | 25 ++++++++++++++----- image-recipe/prepare.sh | 3 ++- .../usr/lib/startos/scripts/init_resize.sh | 11 +++----- .../etc/{embassy => startos}/config.yaml | 0 4 files changed, 25 insertions(+), 14 deletions(-) rename image-recipe/raspberrypi/squashfs/etc/{embassy => startos}/config.yaml (100%) diff --git a/image-recipe/build.sh b/image-recipe/build.sh index 5635e94f3..c183218ff 100755 --- a/image-recipe/build.sh +++ b/image-recipe/build.sh @@ -317,18 +317,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/usr/lib/startos/scripts/init_resize.sh b/image-recipe/raspberrypi/img/usr/lib/startos/scripts/init_resize.sh index cf6489f5f..9e357cd57 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/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 From 28e39c57bd479034bc12de0026d68e77512a2be4 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:47:09 -0600 Subject: [PATCH 18/46] Fix: Error Messages in HealthCheck (#2759) * Fix: Error Messages in HealthCheck * Update sdk/package/lib/util/SubContainer.ts Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> * fix ts error --------- Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Co-authored-by: Aiden McClelland --- sdk/package/lib/util/SubContainer.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sdk/package/lib/util/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts index 16476f564..be1491050 100644 --- a/sdk/package/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -258,18 +258,12 @@ export class SubContainer implements ExecSpawnable { 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([ - new Uint8Array(Buffer.from(appendTo.data).buffer), - new Uint8Array(Buffer.from(chunk).buffer), - ]) + (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) } From 7694b68e06fffd971879ab70dc92c77aad9b8737 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:49:01 -0600 Subject: [PATCH 19/46] Feat/stats (#2761) * Feat: Add the memory for the stats. * Chore: Add % --- core/startos/src/context/rpc.rs | 4 +-- core/startos/src/lxc/dev.rs | 57 ++++++++++++++++++++++++++++++++- core/startos/src/lxc/mod.rs | 28 ++++++++++++++-- core/startos/src/service/mod.rs | 50 +++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 5 deletions(-) diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index cb5ce83c5..0e0bbb986 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -70,7 +70,7 @@ pub struct RpcContextSeed { pub hardware: Hardware, pub start_time: Instant, pub crons: SyncMutex>>, - #[cfg(feature = "dev")] + // #[cfg(feature = "dev")] pub dev: Dev, } @@ -278,7 +278,7 @@ impl RpcContext { hardware: Hardware { devices, ram }, start_time: Instant::now(), crons, - #[cfg(feature = "dev")] + // #[cfg(feature = "dev")] dev: Dev { lxc: Mutex::new(BTreeMap::new()), }, diff --git a/core/startos/src/lxc/dev.rs b/core/startos/src/lxc/dev.rs index 506cb2e9b..248546d88 100644 --- a/core/startos/src/lxc/dev.rs +++ b/core/startos/src/lxc/dev.rs @@ -8,10 +8,13 @@ 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() @@ -36,6 +39,42 @@ pub fn lxc() -> ParentHandler { .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) @@ -63,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 3979263f0..c0fb6eaba 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -1,8 +1,8 @@ -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}; @@ -32,7 +32,7 @@ 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"; @@ -287,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") diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index cc31efe11..078631252 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -83,6 +83,32 @@ pub enum LoadDisposition { 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 { @@ -553,6 +579,30 @@ 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), + }) + } } #[derive(Debug, Clone)] From 42cfd69463acd1bad3648700c6d4a9eb41197c38 Mon Sep 17 00:00:00 2001 From: Remco Ros Date: Fri, 25 Oct 2024 00:29:12 +0200 Subject: [PATCH 20/46] sdk: fix piping stdio of Daemons, support onStdOut/onStderr (#2762) --- .../DockerProcedureContainer.ts | 2 +- sdk/package/lib/mainFn/CommandController.ts | 13 ++++++++---- sdk/package/lib/mainFn/Daemon.ts | 4 ++-- sdk/package/lib/mainFn/Daemons.ts | 2 ++ sdk/package/lib/util/SubContainer.ts | 20 +++++++++++-------- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 26e184cef..26e4dd8bf 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -151,7 +151,7 @@ export class DockerProcedureContainer { } } - async spawn(commands: string[]): Promise { + async spawn(commands: string[]): Promise { return await this.subcontainer.spawn(commands) } } diff --git a/sdk/package/lib/mainFn/CommandController.ts b/sdk/package/lib/mainFn/CommandController.ts index 27089ea86..1aefc854f 100644 --- a/sdk/package/lib/mainFn/CommandController.ts +++ b/sdk/package/lib/mainFn/CommandController.ts @@ -17,7 +17,7 @@ export class CommandController { readonly runningAnswer: Promise, private state: { exited: boolean }, private readonly subcontainer: SubContainer, - private process: cp.ChildProcessWithoutNullStreams, + private process: cp.ChildProcess, readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, ) {} static of() { @@ -43,8 +43,8 @@ export class CommandController { | undefined cwd?: string | undefined user?: string | undefined - onStdout?: (x: Buffer) => null - onStderr?: (x: Buffer) => null + onStdout?: (chunk: Buffer | string | any) => void + onStderr?: (chunk: Buffer | string | any) => void }, ) => { const commands = splitCommand(command) @@ -62,7 +62,7 @@ export class CommandController { } return subc })() - let childProcess: cp.ChildProcessWithoutNullStreams + let childProcess: cp.ChildProcess if (options.runAsInit) { childProcess = await subc.launch(commands, { env: options.env, @@ -70,8 +70,13 @@ export class CommandController { } 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) => { diff --git a/sdk/package/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts index 7877dce08..d9db89b6a 100644 --- a/sdk/package/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -37,8 +37,8 @@ export class Daemon { | undefined cwd?: string | undefined user?: string | undefined - onStdout?: (x: Buffer) => null - onStderr?: (x: Buffer) => null + onStdout?: (chunk: Buffer | string | any) => void + onStderr?: (chunk: Buffer | string | any) => void sigtermTimeout?: number }, ) => { diff --git a/sdk/package/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts index 4ad573b0b..cefa06698 100644 --- a/sdk/package/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -39,6 +39,8 @@ 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` diff --git a/sdk/package/lib/util/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts index be1491050..785c6fea0 100644 --- a/sdk/package/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -35,8 +35,8 @@ export interface ExecSpawnable { ): 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. @@ -332,8 +332,8 @@ export class SubContainer implements ExecSpawnable { async spawn( command: string[], - options?: CommandOptions, - ): Promise { + options: CommandOptions & StdioOptions = { stdio: "inherit" }, + ): Promise { await this.waitProc() const imageMeta: any = await fs .readFile(`/media/startos/images/${this.imageId}.json`, { @@ -342,12 +342,12 @@ export class SubContainer implements ExecSpawnable { .catch(() => "{}") .then(JSON.parse) let extra: string[] = [] - if (options?.user) { + if (options.user) { extra.push(`--user=${options.user}`) delete options.user } let workdir = imageMeta.workdir || "/" - if (options?.cwd) { + if (options.cwd) { workdir = options.cwd delete options.cwd } @@ -387,8 +387,8 @@ export class SubContainerHandle implements ExecSpawnable { } spawn( command: string[], - options?: CommandOptions, - ): Promise { + options: CommandOptions & StdioOptions = { stdio: "inherit" }, + ): Promise { return this.subContainer.spawn(command, options) } } @@ -399,6 +399,10 @@ export type CommandOptions = { user?: string } +export type StdioOptions = { + stdio?: cp.IOType +} + export type MountOptions = | MountOptionsVolume | MountOptionsAssets From 26ae0bf20710f756226f36565a7136a0bcfcb4ad Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:12:36 -0600 Subject: [PATCH 21/46] sdk tweaks (#2760) * sdk tweaks * update action result types * accommodate new action response types * fix: show action value labels * Feature/get status effect (#2765) * wip: get status * feat: Add the get_status for effects * feat: Do a callback --------- Co-authored-by: J H --------- Co-authored-by: Matt Hill Co-authored-by: waterplea Co-authored-by: J H --- .../src/Adapters/EffectCreator.ts | 3 + .../Systems/SystemForEmbassy/index.ts | 27 +- core/startos/src/action.rs | 122 ++--- core/startos/src/service/effects/callbacks.rs | 19 + core/startos/src/service/effects/control.rs | 44 +- core/startos/src/service/effects/mod.rs | 6 + core/startos/src/service/service_actor.rs | 18 +- core/startos/src/status/mod.rs | 14 + sdk/base/lib/Effects.ts | 6 + sdk/base/lib/osBindings/ActionResultMember.ts | 15 + sdk/base/lib/osBindings/ActionResultV1.ts | 22 +- sdk/base/lib/osBindings/ActionResultValue.ts | 12 + sdk/base/lib/osBindings/GetStatusParams.ts | 5 + sdk/base/lib/osBindings/index.ts | 3 + .../lib/test/startosTypeValidation.test.ts | 2 + sdk/package/lib/StartSdk.ts | 17 +- sdk/package/lib/manifest/setupManifest.ts | 8 +- sdk/package/lib/util/fileHelper.ts | 24 +- web/package-lock.json | 484 +++++++++++------- web/package.json | 18 +- .../action-success-group.component.ts | 30 +- ....ts => action-success-member.component.ts} | 77 ++- .../action-success-single.component.ts | 145 ++++++ .../action-success/action-success.page.ts | 36 +- .../ui/src/app/modals/action-success/types.ts | 7 + .../ui/src/app/services/action.service.ts | 8 +- .../ui/src/app/services/api/api.fixures.ts | 148 +++--- .../services/api/embassy-mock-api.service.ts | 7 +- 28 files changed, 871 insertions(+), 456 deletions(-) create mode 100644 sdk/base/lib/osBindings/ActionResultMember.ts create mode 100644 sdk/base/lib/osBindings/ActionResultValue.ts create mode 100644 sdk/base/lib/osBindings/GetStatusParams.ts rename web/projects/ui/src/app/modals/action-success/{action-success-item.component.ts => action-success-member.component.ts} (67%) create mode 100644 web/projects/ui/src/app/modals/action-success/action-success-single.component.ts create mode 100644 web/projects/ui/src/app/modals/action-success/types.ts diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index 4ec3a5b7d..0123b0cbc 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -284,6 +284,9 @@ export function makeEffects(context: EffectContext): Effects { > }, + 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"] diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index ae55eb690..b33e83c0f 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -245,10 +245,10 @@ const matchProperties = object({ function convertProperties( name: string, value: PropertiesValue, -): T.ActionResultV1 { +): T.ActionResultMember { if (value.type === "string") { return { - type: "string", + type: "single", name, description: value.description, copyable: value.copyable || false, @@ -258,9 +258,9 @@ function convertProperties( } } return { - type: "object", + type: "group", name, - description: value.description || undefined, + description: value.description, value: Object.entries(value.value).map(([name, value]) => convertProperties(name, value), ), @@ -459,13 +459,14 @@ export class SystemForEmbassy implements System { } else if (actionId === "properties") { return { version: "1", - type: "object", - name: "Properties", - description: - "Runtime information, credentials, and other values of interest", - value: Object.entries(await this.properties(effects, timeoutMs)).map( - ([name, value]) => convertProperties(name, value), - ), + 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) @@ -814,13 +815,13 @@ export class SystemForEmbassy implements System { const actionProcedure = this.manifest.actions?.[actionId]?.implementation const toActionResult = ({ message, - value = "", + value, copyable, qr, }: U.ActionResult): T.ActionResult => ({ version: "0", message, - value, + value: value ?? null, copyable, qr, }) diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index 3f1912c34..d0748265b 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::fmt; use clap::{CommandFactory, FromArgMatches, Parser}; @@ -6,7 +7,6 @@ use models::PackageId; use qrcode::QrCode; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; use tracing::instrument; use ts_rs::TS; @@ -24,7 +24,7 @@ pub fn action_api() -> ParentHandler { from_fn_async(get_action_input) .with_display_serializable() .with_about("Get action input spec") - .with_call_remote::() + .with_call_remote::(), ) .subcommand( "run", @@ -37,7 +37,7 @@ pub fn action_api() -> ParentHandler { Ok(()) }) .with_about("Run service action") - .with_call_remote::() + .with_call_remote::(), ) } @@ -121,83 +121,78 @@ impl fmt::Display for ActionResultV0 { } } +#[derive(Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +pub struct ActionResultV1 { + pub title: String, + pub message: Option, + pub result: Option, +} + +#[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 ActionResultV1 { - String { - name: String, +pub enum ActionResultValue { + Single { value: String, - description: Option, copyable: bool, qr: bool, masked: bool, }, - Object { - name: String, - value: Vec, - #[ts(optional)] - description: Option, + Group { + value: Vec, }, } -impl ActionResultV1 { +impl ActionResultValue { fn fmt_rec(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { match self { - Self::String { - name, - value, - description, - qr, - .. - } => { - for i in 0..indent { + Self::Single { value, qr, .. } => { + for _ in 0..indent { write!(f, " ")?; } - write!(f, "{name}")?; - if let Some(description) = description { - write!(f, ": {description}")?; - } - if !value.is_empty() { - write!(f, ":\n")?; - for i in 0..indent { + write!(f, "{value}")?; + if *qr { + use qrcode::render::unicode; + writeln!(f)?; + for _ in 0..indent { write!(f, " ")?; } - write!(f, "{value}")?; - if *qr { - use qrcode::render::unicode; - write!(f, "\n")?; - for i in 0..indent { - write!(f, " ")?; - } - write!( - f, - "{}", - QrCode::new(value.as_bytes()) - .unwrap() - .render::() - .build() - )?; - } + write!( + f, + "{}", + QrCode::new(value.as_bytes()) + .unwrap() + .render::() + .build() + )?; } } - Self::Object { - name, - value, - description, - } => { - for i in 0..indent { - write!(f, " ")?; - } - write!(f, "{name}")?; - if let Some(description) = description { - write!(f, ": {description}")?; - } - for value in value { - write!(f, ":\n")?; - for i in 0..indent { + 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)?; } } @@ -207,7 +202,14 @@ impl ActionResultV1 { } impl fmt::Display for ActionResultV1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_rec(f, 0) + 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(()) } } diff --git a/core/startos/src/service/effects/callbacks.rs b/core/startos/src/service/effects/callbacks.rs index 05f36cb4b..65eb707d8 100644 --- a/core/startos/src/service/effects/callbacks.rs +++ b/core/startos/src/service/effects/callbacks.rs @@ -36,6 +36,7 @@ struct ServiceCallbackMap { (NonDetachingJoinHandle<()>, Vec), >, get_store: BTreeMap>>, + get_status: BTreeMap>, } impl ServiceCallbacks { @@ -71,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() + }); }) } @@ -220,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, diff --git a/core/startos/src/service/effects/control.rs b/core/startos/src/service/effects/control.rs index 4eb897c37..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; +use models::{FromStrParser, PackageId}; use crate::service::effects::prelude::*; +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/mod.rs b/core/startos/src/service/effects/mod.rs index 844f5646b..f68985268 100644 --- a/core/startos/src/service/effects/mod.rs +++ b/core/startos/src/service/effects/mod.rs @@ -50,6 +50,12 @@ pub fn handler() -> ParentHandler { .no_display() .with_call_remote::(), ) + .subcommand( + "get-status", + from_fn_async(control::get_status) + .no_display() + .with_call_remote::(), + ) // dependency .subcommand( "set-dependencies", diff --git a/core/startos/src/service/service_actor.rs b/core/startos/src/service/service_actor.rs index b343e513e..712baaf1e 100644 --- a/core/startos/src/service/service_actor.rs +++ b/core/startos/src/service/service_actor.rs @@ -1,6 +1,8 @@ use std::sync::Arc; use std::time::Duration; +use imbl::vector; + use super::start_stop::StartStop; use super::ServiceActorSeed; use crate::prelude::*; @@ -45,7 +47,8 @@ async fn service_actor_loop( let id = &seed.id; let kinds = current.borrow().kinds(); if let Err(e) = async { - 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) { @@ -89,11 +92,22 @@ async fn service_actor_loop( .. } => MainStatus::Stopped, }; + 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 { diff --git a/core/startos/src/status/mod.rs b/core/startos/src/status/mod.rs index 14442bce6..398797a5a 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -66,6 +66,20 @@ impl MainStatus { } } + 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 { MainStatus::BackingUp { on_complete: if self.running() { diff --git a/sdk/base/lib/Effects.ts b/sdk/base/lib/Effects.ts index f985db77b..00d56cfba 100644 --- a/sdk/base/lib/Effects.ts +++ b/sdk/base/lib/Effects.ts @@ -14,6 +14,7 @@ import { ServiceInterface, ActionRequest, RequestActionParams, + MainStatus, } from "./osBindings" import { StorePath } from "./util/PathBuilder" import { @@ -61,6 +62,11 @@ export type Effects = { 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 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/ActionResultV1.ts b/sdk/base/lib/osBindings/ActionResultV1.ts index cda0e5df9..eece18477 100644 --- a/sdk/base/lib/osBindings/ActionResultV1.ts +++ b/sdk/base/lib/osBindings/ActionResultV1.ts @@ -1,18 +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 = - | { - type: "string" - name: string - value: string - description: string | null - copyable: boolean - qr: boolean - masked: boolean - } - | { - type: "object" - name: string - value: Array - description?: string - } +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/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/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index 5d5574acf..6c18fa197 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -7,9 +7,11 @@ 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" @@ -79,6 +81,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" diff --git a/sdk/base/lib/test/startosTypeValidation.test.ts b/sdk/base/lib/test/startosTypeValidation.test.ts index 7686328bd..596da3b49 100644 --- a/sdk/base/lib/test/startosTypeValidation.test.ts +++ b/sdk/base/lib/test/startosTypeValidation.test.ts @@ -7,6 +7,7 @@ import { ClearCallbacksParams, ClearServiceInterfacesParams, GetActionInputParams, + GetStatusParams, GetStoreParams, RequestActionParams, RunActionParams, @@ -89,6 +90,7 @@ describe("startosTypeValidation ", () => { mount: {} as MountParams, checkDependencies: {} as CheckDependenciesParam, getDependencies: undefined, + getStatus: {} as WithCallback, setMainStatus: {} as SetMainStatus, }) }) diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 12e92faf2..f41961d16 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -134,6 +134,7 @@ export class StartSdk { 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 { @@ -387,8 +388,8 @@ export class StartSdk { algorithm?: T.Algorithm, ) => new GetSslCertificate(effects, hostnames, algorithm), HealthCheck: { - of(o: HealthCheckParams) { - return healthCheck(o) + of(effects: T.Effects, o: Omit) { + return healthCheck({ effects, ...o }) }, }, healthCheck: { @@ -636,12 +637,12 @@ export class StartSdk { ) => InputSpec.of(spec), }, Daemons: { - of(options: { - effects: Effects - started: (onTerm: () => PromiseLike) => PromiseLike - healthReceipts: HealthReceipt[] - }) { - return Daemons.of(options) + of( + effects: Effects, + started: (onTerm: () => PromiseLike) => PromiseLike, + healthReceipts: HealthReceipt[], + ) { + return Daemons.of({ effects, started, healthReceipts }) }, }, List: { diff --git a/sdk/package/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts index eb83741c8..919412f7d 100644 --- a/sdk/package/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -15,18 +15,14 @@ import { VersionGraph } from "../version/VersionGraph" */ export function setupManifest< Id extends string, - Dependencies extends Record, VolumesTypes extends VolumeId, AssetTypes extends VolumeId, - ImagesTypes extends ImageId, Manifest extends { - dependencies: Dependencies id: Id assets: AssetTypes[] - images: Record volumes: VolumesTypes[] - }, ->(manifest: SDKManifest & Manifest): SDKManifest & Manifest { + } & SDKManifest, +>(manifest: Manifest): Manifest { return manifest } diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index 1a3be3e4f..0420a4cbf 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -86,7 +86,7 @@ export class FileHelper { /** * Accepts structured data and overwrites the existing file on disk. */ - async write(data: A): Promise { + private async writeFile(data: A): Promise { const parent = previousPath.exec(this.path) if (parent) { await fs.mkdir(parent[1], { recursive: true }) @@ -153,13 +153,27 @@ export class FileHelper { } /** - * Accepts structured data and performs a merge with the existing file on disk. + * Accepts full structured data and performs a merge with the existing file on disk if it exists. */ - async merge(data: A) { - const fileData = (await this.readOnce().catch(() => ({}))) || {} + async write(data: A) { + const fileData = (await this.readOnce()) || {} const mergeData = merge({}, fileData, data) - return await this.write(mergeData) + return await this.writeFile(mergeData) } + + /** + * Accepts partial structured data and performs a merge with the existing file on disk. + */ + async merge(data: Partial) { + const fileData = + (await this.readOnce()) || + (() => { + throw new Error(`${this.path}: does not exist`) + })() + const mergeData = merge({}, fileData, data) + return await this.writeFile(mergeData) + } + /** * Create a File Helper for an arbitrary file type. * diff --git a/web/package-lock.json b/web/package-lock.json index 55925e77f..826feb631 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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/baseDist", - "@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", + "@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", @@ -3761,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", @@ -3786,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" }, @@ -3800,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" } @@ -3828,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" }, @@ -3841,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" }, @@ -3853,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" }, @@ -3865,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" }, @@ -4161,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==", + "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.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" + "@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", @@ -4224,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", @@ -4268,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" } }, @@ -4400,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" } @@ -4415,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" @@ -4430,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" @@ -4613,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": { @@ -5237,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" @@ -5252,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" @@ -5261,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" @@ -6088,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": { @@ -7868,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", @@ -10651,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", @@ -10670,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", @@ -10680,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" @@ -10781,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", @@ -14794,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", @@ -14880,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": { @@ -14933,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 5fdf2c859..e900277c4 100644 --- a/web/package.json +++ b/web/package.json @@ -43,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/baseDist", - "@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", + "@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", diff --git a/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts b/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts index c02b51ee9..480146668 100644 --- a/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts +++ b/web/projects/ui/src/app/modals/action-success/action-success-group.component.ts @@ -1,23 +1,23 @@ import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { T } from '@start9labs/start-sdk' import { TuiFadeModule, TuiTitleModule } from '@taiga-ui/experimental' import { TuiAccordionModule } from '@taiga-ui/kit' -import { ActionSuccessItemComponent } from './action-success-item.component' +import { ActionSuccessMemberComponent } from './action-success-member.component' +import { GroupResult } from './types' @Component({ standalone: true, selector: 'app-action-success-group', template: ` -

- - -

{{ item.name }}
+

+ + +

{{ member.name }}
- +

@@ -37,18 +37,12 @@ import { ActionSuccessItemComponent } from './action-success-item.component' imports: [ CommonModule, TuiTitleModule, - ActionSuccessItemComponent, + ActionSuccessMemberComponent, TuiAccordionModule, TuiFadeModule, ], }) export class ActionSuccessGroupComponent { @Input() - value?: T.ActionResultV1 & { type: 'object' } - - isSingle( - value: T.ActionResultV1, - ): value is T.ActionResultV1 & { type: 'string' } { - return value.type === 'string' - } + group!: GroupResult } diff --git a/web/projects/ui/src/app/modals/action-success/action-success-item.component.ts b/web/projects/ui/src/app/modals/action-success/action-success-member.component.ts similarity index 67% rename from web/projects/ui/src/app/modals/action-success/action-success-item.component.ts rename to web/projects/ui/src/app/modals/action-success/action-success-member.component.ts index 25054f2f6..55bffc7bc 100644 --- a/web/projects/ui/src/app/modals/action-success/action-success-item.component.ts +++ b/web/projects/ui/src/app/modals/action-success/action-success-member.component.ts @@ -9,41 +9,38 @@ import { ViewChild, } from '@angular/core' import { FormsModule } from '@angular/forms' -import { T } from '@start9labs/start-sdk' import { TuiDialogService, - TuiLabelModule, TuiTextfieldComponent, TuiTextfieldControllerModule, } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' +import { TuiButtonModule, TuiTitleModule } from '@taiga-ui/experimental' import { TuiInputModule } from '@taiga-ui/kit' import { QrCodeModule } from 'ng-qrcode' -import { ActionSuccessGroupComponent } from './action-success-group.component' +import { T } from '@start9labs/start-sdk' @Component({ standalone: true, - selector: 'app-action-success-item', + selector: 'app-action-success-member', template: ` -

- -

-
{ return await this.writeFile(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) + } + /** * Create a File Helper for an arbitrary file type. * From 1be9cdae6773b892479fe8c22406269d8802fb2a Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 29 Oct 2024 13:48:03 -0600 Subject: [PATCH 24/46] use hardware requirements to display conflicts and prevent install (#2700) * use hardware requirements to display conflicts and prevent install * better messaging and also consider OS compatibility * wip: backend hw requirements * update backend components * migration --------- Co-authored-by: Aiden McClelland --- core/Cargo.lock | 676 +++++++++--------- core/startos/src/context/rpc.rs | 13 +- core/startos/src/db/model/public.rs | 10 +- core/startos/src/init.rs | 4 + core/startos/src/registry/context.rs | 2 +- core/startos/src/registry/device_info.rs | 73 +- core/startos/src/registry/package/index.rs | 7 +- core/startos/src/s9pk/v1/manifest.rs | 16 +- core/startos/src/s9pk/v2/compat.rs | 20 +- core/startos/src/s9pk/v2/manifest.rs | 14 +- core/startos/src/util/lshw.rs | 1 + core/startos/src/version/mod.rs | 18 +- core/startos/src/version/v0_3_6_alpha_7.rs | 50 ++ sdk/base/lib/osBindings/DeviceFilter.ts | 7 + .../lib/osBindings/HardwareRequirements.ts | 3 +- sdk/base/lib/osBindings/LshwDevice.ts | 7 + sdk/base/lib/osBindings/LshwDisplay.ts | 3 + sdk/base/lib/osBindings/LshwProcessor.ts | 3 + sdk/base/lib/osBindings/ServerInfo.ts | 5 +- sdk/base/lib/osBindings/index.ts | 4 + sdk/base/lib/types/ManifestTypes.ts | 2 +- sdk/package/lib/manifest/setupManifest.ts | 6 +- .../pages/show/package/package.component.scss | 4 +- .../unit-conversion/unit-conversion.pipe.ts | 16 +- .../shared/src/services/exver.service.ts | 8 +- .../backup-drives/backup.service.ts | 14 +- .../refresh-alert/refresh-alert.service.ts | 6 +- .../app-recover-select/to-options.pipe.ts | 13 +- .../marketplace-show-controls.component.html | 72 +- .../marketplace-show-controls.component.ts | 3 + .../marketplace-show.page.html | 10 +- .../marketplace-show/marketplace-show.page.ts | 97 ++- .../ui/src/app/services/api/api.fixures.ts | 28 +- .../ui/src/app/services/api/mock-patch.ts | 4 +- .../ui/src/app/services/eos.service.ts | 6 +- 35 files changed, 725 insertions(+), 500 deletions(-) create mode 100644 core/startos/src/version/v0_3_6_alpha_7.rs create mode 100644 sdk/base/lib/osBindings/DeviceFilter.ts create mode 100644 sdk/base/lib/osBindings/LshwDevice.ts create mode 100644 sdk/base/lib/osBindings/LshwDisplay.ts create mode 100644 sdk/base/lib/osBindings/LshwProcessor.ts diff --git a/core/Cargo.lock b/core/Cargo.lock index 5f59c8bb4..cfa00f33f 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" @@ -113,9 +113,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", @@ -128,43 +128,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[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.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "arrayref" @@ -206,9 +206,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", @@ -220,9 +220,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", @@ -231,24 +231,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.77", + "syn 2.0.85", ] [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -268,15 +268,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.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" +checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" dependencies = [ "aws-lc-sys", "mirai-annotations", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.21.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" +checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" dependencies = [ "bindgen", "cc", @@ -307,12 +307,12 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core 0.3.4", - "bitflags 1.2.1", + "bitflags 1.3.2", "bytes", "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "itoa", "matchit", "memchr", @@ -322,26 +322,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.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" 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.0", "hyper-util", "itoa", "matchit", @@ -357,8 +357,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", @@ -383,9 +383,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", @@ -396,7 +396,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", "tracing", @@ -413,11 +413,11 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "pin-project-lite", "tokio", - "tower", + "tower 0.4.13", "tower-service", ] @@ -521,9 +521,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", @@ -538,7 +538,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.77", + "syn 2.0.85", "which", ] @@ -559,9 +559,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" @@ -648,9 +648,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", @@ -675,9 +675,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" [[package]] name = "byteorder" @@ -693,9 +693,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cache-padded" @@ -705,9 +705,9 @@ checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" [[package]] name = "cc" -version = "1.1.21" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", @@ -818,9 +818,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -828,9 +828,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -840,14 +840,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -894,9 +894,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" @@ -1255,7 +1255,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1279,7 +1279,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1290,7 +1290,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1321,7 +1321,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1344,7 +1344,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1367,7 +1367,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1573,9 +1573,9 @@ 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", ] @@ -1589,7 +1589,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1725,9 +1725,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -1735,9 +1735,9 @@ dependencies = [ [[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", @@ -1803,9 +1803,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", @@ -1818,9 +1818,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", @@ -1828,15 +1828,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", @@ -1856,38 +1856,38 @@ 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.77", + "syn 2.0.85", ] [[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", @@ -1981,7 +1981,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -2000,7 +2000,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -2048,6 +2048,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hashlink" version = "0.8.4" @@ -2211,9 +2217,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" @@ -2229,9 +2235,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", @@ -2253,9 +2259,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -2280,9 +2286,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.16", "rustls-pki-types", "tokio", "tokio-rustls", @@ -2295,7 +2301,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", @@ -2309,7 +2315,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "native-tls", "tokio", @@ -2319,20 +2325,19 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.0", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -2407,9 +2412,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" dependencies = [ "bytemuck", "byteorder-lite", @@ -2442,7 +2447,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", @@ -2489,12 +2494,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -2542,9 +2547,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" dependencies = [ "serde", ] @@ -2704,9 +2709,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2765,7 +2770,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", @@ -2779,7 +2784,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.8", ] [[package]] @@ -2820,7 +2825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec 0.5.2", - "bitflags 1.2.1", + "bitflags 1.3.2", "cfg-if", "ryu", "static_assertions", @@ -2828,9 +2833,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.158" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libloading" @@ -2844,9 +2849,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "a00419de735aac21d53b0de5ce2c03bd3627277cf471300f27ebc89f7d828047" [[package]] name = "libredox" @@ -2856,7 +2861,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", ] [[package]] @@ -3033,7 +3038,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" name = "models" version = "0.1.0" dependencies = [ - "axum 0.7.5", + "axum 0.7.7", "base64 0.21.7", "color-eyre", "ed25519-dalek 2.1.1", @@ -3099,7 +3104,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags 1.2.1", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", @@ -3111,7 +3116,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "bitflags 1.2.1", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", @@ -3297,7 +3302,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3317,9 +3322,9 @@ dependencies = [ [[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" @@ -3342,9 +3347,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", @@ -3363,7 +3368,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3374,18 +3379,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.4.0+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" 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", @@ -3462,7 +3467,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -3541,9 +3546,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -3552,9 +3557,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -3562,22 +3567,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "pest_meta" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -3591,7 +3596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap 2.6.0", ] [[package]] @@ -3611,29 +3616,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.77", + "syn 2.0.85", ] [[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" @@ -3664,15 +3669,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.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "powerfmt" @@ -3697,12 +3702,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3734,14 +3739,14 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.21", + "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", ] @@ -3786,7 +3791,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", @@ -3800,7 +3805,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3823,7 +3828,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3998,14 +4003,14 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.2.1", + "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -4023,14 +4028,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -4044,13 +4049,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -4061,15 +4066,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.7" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -4082,7 +4087,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls", "hyper-tls", "hyper-util", @@ -4094,7 +4099,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", @@ -4164,11 +4169,11 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/no-dyn-ctx#39a872a1294c7d864faca63f072092ce300ffbe5" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor%2Fno-dyn-ctx#39a872a1294c7d864faca63f072092ce300ffbe5" dependencies = [ "async-stream", "async-trait", - "axum 0.7.5", + "axum 0.7.7", "clap", "futures", "http 1.1.0", @@ -4254,9 +4259,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -4278,9 +4283,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "aws-lc-rs", "log", @@ -4302,19 +4307,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -4340,9 +4344,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" @@ -4389,9 +4393,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -4441,9 +4445,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -4460,9 +4464,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -4486,22 +4490,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -4520,9 +4524,9 @@ dependencies = [ [[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", ] @@ -4541,15 +4545,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.5.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -4559,14 +4563,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.77", + "syn 2.0.85", ] [[package]] @@ -4575,7 +4579,7 @@ version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "itoa", "libyml", "log", @@ -4810,7 +4814,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.5.0", + "indexmap 2.6.0", "log", "memchr", "once_cell", @@ -4999,7 +5003,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.77", + "syn 2.0.85", "unicode-width", ] @@ -5026,9 +5030,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", @@ -5053,7 +5057,7 @@ dependencies = [ "async-compression", "async-stream", "async-trait", - "axum 0.7.5", + "axum 0.7.7", "axum-server", "backhand", "barrage", @@ -5092,7 +5096,7 @@ dependencies = [ "imbl", "imbl-value", "include_dir", - "indexmap 2.5.0", + "indexmap 2.6.0", "indicatif", "integer-encoding", "ipnet", @@ -5241,9 +5245,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -5294,9 +5298,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", @@ -5305,9 +5309,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -5359,22 +5363,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -5455,9 +5459,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -5490,7 +5494,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -5509,7 +5513,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.16", "rustls-pki-types", "tokio", ] @@ -5552,18 +5556,6 @@ dependencies = [ "xattr 0.2.3", ] -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.21.0", -] - [[package]] name = "tokio-tungstenite" version = "0.23.1" @@ -5578,6 +5570,18 @@ dependencies = [ "tungstenite 0.23.0", ] +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.24.0", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -5612,7 +5616,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.21", + "toml_edit 0.22.22", ] [[package]] @@ -5630,7 +5634,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -5639,15 +5643,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.21" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -5664,14 +5668,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", @@ -5680,7 +5684,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", @@ -5716,6 +5720,22 @@ 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.3" @@ -5748,7 +5768,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -5886,7 +5906,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", @@ -5895,12 +5915,12 @@ 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.77", + "syn 2.0.85", "termcolor", ] @@ -5915,25 +5935,6 @@ dependencies = [ "signal-hook", ] -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.1.0", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror", - "url", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.23.0" @@ -5954,6 +5955,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typed-builder" version = "0.18.2" @@ -5971,7 +5990,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -5982,9 +6001,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" @@ -5994,18 +6013,15 @@ 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" @@ -6030,9 +6046,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" @@ -6106,9 +6122,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", ] @@ -6179,9 +6195,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -6190,24 +6206,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -6217,9 +6233,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6227,28 +6243,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +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", @@ -6259,9 +6275,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -6291,7 +6307,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.5.4", + "redox_syscall 0.5.7", "wasite", ] @@ -6524,9 +6540,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -6617,7 +6633,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -6637,7 +6653,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index 0e0bbb986..73d103adc 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -39,8 +39,7 @@ 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 { @@ -67,7 +66,6 @@ pub struct RpcContextSeed { pub wifi_manager: Option>>, pub current_secret: Arc, pub client: Client, - pub hardware: Hardware, pub start_time: Instant, pub crons: SyncMutex>>, // #[cfg(feature = "dev")] @@ -86,7 +84,6 @@ pub struct Hardware { pub struct InitRpcContextPhases { load_db: PhaseProgressTrackerHandle, init_net_ctrl: PhaseProgressTrackerHandle, - read_device_info: PhaseProgressTrackerHandle, cleanup_init: CleanupInitPhases, // TODO: migrations } @@ -95,7 +92,6 @@ impl InitRpcContextPhases { 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), } } @@ -127,7 +123,6 @@ impl RpcContext { InitRpcContextPhases { mut load_db, mut init_net_ctrl, - mut read_device_info, cleanup_init, }: InitRpcContextPhases, ) -> Result { @@ -179,11 +174,6 @@ 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 @@ -275,7 +265,6 @@ impl RpcContext { })) .build() .with_kind(crate::ErrorKind::ParseUrl)?, - hardware: Hardware { devices, ram }, start_time: Instant::now(), crons, // #[cfg(feature = "dev")] diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index 7e5e96cde..353c1f89b 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}; @@ -46,7 +47,7 @@ impl Public { version: Current::default().semver(), hostname: account.hostname.no_dot_host_name(), last_backup: None, - version_compat: Current::default().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(), @@ -78,6 +79,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!( @@ -114,7 +117,7 @@ pub struct ServerInfo { #[ts(type = "string")] pub version: Version, #[ts(type = "string")] - pub version_compat: VersionRange, + pub package_version_compat: VersionRange, #[ts(type = "string[]")] pub post_init_migration_todos: BTreeSet, #[ts(type = "string | null")] @@ -141,6 +144,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)] diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 206f1c8e0..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; @@ -508,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, 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/device_info.rs b/core/startos/src/registry/device_info.rs index 04a38a223..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(), }, }) } @@ -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/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/s9pk/v1/manifest.rs b/core/startos/src/s9pk/v1/manifest.rs index 9b3eb9895..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")] @@ -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 e3f54d3a2..db8cbc414 100644 --- a/core/startos/src/s9pk/v2/compat.rs +++ b/core/startos/src/s9pk/v2/compat.rs @@ -10,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}; @@ -246,7 +246,23 @@ impl TryFrom 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, }) diff --git a/core/startos/src/s9pk/v2/manifest.rs b/core/startos/src/s9pk/v2/manifest.rs index 9b9455999..85f3cd796 100644 --- a/core/startos/src/s9pk/v2/manifest.rs +++ b/core/startos/src/s9pk/v2/manifest.rs @@ -161,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/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/version/mod.rs b/core/startos/src/version/mod.rs index ae203f3e5..bc8deea17 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -25,8 +25,9 @@ mod v0_3_6_alpha_3; mod v0_3_6_alpha_4; mod v0_3_6_alpha_5; mod v0_3_6_alpha_6; +mod v0_3_6_alpha_7; -pub type Current = v0_3_6_alpha_6::Version; // VERSION_BUMP +pub type Current = v0_3_6_alpha_7::Version; // VERSION_BUMP impl Current { #[instrument(skip(self, db))] @@ -102,6 +103,7 @@ enum Version { V0_3_6_alpha_4(Wrapper), V0_3_6_alpha_5(Wrapper), V0_3_6_alpha_6(Wrapper), + V0_3_6_alpha_7(Wrapper), Other(exver::Version), } @@ -132,6 +134,7 @@ impl Version { 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::Other(v) => { return Err(Error::new( eyre!("unknown version {v}"), @@ -154,6 +157,7 @@ impl Version { Version::V0_3_6_alpha_4(Wrapper(x)) => x.semver(), 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::Other(x) => x.clone(), } } @@ -172,15 +176,19 @@ fn version_accessor(db: &mut Value) -> Option<&mut Value> { 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("versionCompat").is_some() { - server_info.get_mut("versionCompat") + 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("versionCompat".into(), prev); + .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("versionCompat") + server_info.get_mut("packageVersionCompat") } } else { db.get_mut("server-info")?.get_mut("eos-version-compat") diff --git a/core/startos/src/version/v0_3_6_alpha_7.rs b/core/startos/src/version/v0_3_6_alpha_7.rs new file mode 100644 index 000000000..75c0af72f --- /dev/null +++ b/core/startos/src/version/v0_3_6_alpha_7.rs @@ -0,0 +1,50 @@ +use exver::{PreReleaseSegment, VersionRange}; +use imbl_value::{json, InOMap}; + +use super::v0_3_5::V0_3_0_COMPAT; +use super::{v0_3_6_alpha_6, VersionT}; +use crate::prelude::*; + +lazy_static::lazy_static! { + static ref V0_3_6_alpha_7: exver::Version = exver::Version::new( + [0, 3, 6], + [PreReleaseSegment::String("alpha".into()), 7.into()] + ); +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Version; + +impl VersionT for Version { + type Previous = v0_3_6_alpha_6::Version; + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) + } + fn semver(self) -> exver::Version { + V0_3_6_alpha_7.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + 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(()) + } + fn down(self, _db: &mut Value) -> Result<(), Error> { + Ok(()) + } +} 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/base/lib/osBindings/HardwareRequirements.ts b/sdk/base/lib/osBindings/HardwareRequirements.ts index e17568eec..d420f846b 100644 --- a/sdk/base/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/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/base/lib/osBindings/ServerInfo.ts b/sdk/base/lib/osBindings/ServerInfo.ts index ff04d42b0..435bf874a 100644 --- a/sdk/base/lib/osBindings/ServerInfo.ts +++ b/sdk/base/lib/osBindings/ServerInfo.ts @@ -1,6 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. 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,7 +12,7 @@ export type ServerInfo = { id: string hostname: string version: string - versionCompat: string + packageVersionCompat: string postInitMigrationTodos: string[] lastBackup: string | null lanAddress: string @@ -31,4 +32,6 @@ export type ServerInfo = { zram: boolean governor: Governor | null smtp: SmtpValue | null + ram: number + devices: Array } diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index a4f734778..10c2098bf 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -62,6 +62,7 @@ export { DependencyRequirement } from "./DependencyRequirement" export { DepInfo } from "./DepInfo" export { Description } from "./Description" export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" +export { DeviceFilter } from "./DeviceFilter" export { Duration } from "./Duration" export { EchoParams } from "./EchoParams" export { EncryptedWire } from "./EncryptedWire" @@ -111,6 +112,9 @@ export { LanInfo } from "./LanInfo" 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" diff --git a/sdk/base/lib/types/ManifestTypes.ts b/sdk/base/lib/types/ManifestTypes.ts index c416588b3..86ecf9140 100644 --- a/sdk/base/lib/types/ManifestTypes.ts +++ b/sdk/base/lib/types/ManifestTypes.ts @@ -144,7 +144,7 @@ export type SDKManifest = { * ``` */ readonly hardwareRequirements?: { - readonly device?: { display?: RegExp; processor?: RegExp } + readonly device?: T.DeviceFilter[] readonly ram?: number | null readonly arch?: string[] | null } diff --git a/sdk/package/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts index 919412f7d..2ae999e62 100644 --- a/sdk/package/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -73,11 +73,7 @@ export function buildManifest< stop: manifest.alerts?.stop || null, }, 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 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/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,15 +6,19 @@ import { Pipe, PipeTransform } from '@angular/core' }) export class ConvertBytesPipe implements PipeTransform { transform(bytes: number): string { - if (bytes === 0) return '0 Bytes' - - const k = 1024 - const i = Math.floor(Math.log(bytes) / Math.log(k)) - - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i] + return convertBytes(bytes) } } +export function convertBytes(bytes: number): string { + if (bytes === 0) return '0 Bytes' + + 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({ name: 'durationToSeconds', }) 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/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/toast-container/refresh-alert/refresh-alert.service.ts b/web/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts index 8cf5ce76d..d567e5956 100644 --- a/web/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts +++ b/web/projects/ui/src/app/components/toast-container/refresh-alert/refresh-alert.service.ts @@ -1,24 +1,24 @@ import { Injectable } from '@angular/core' import { endWith, Observable } from 'rxjs' import { map } from 'rxjs/operators' -import { Exver } from '@start9labs/shared' import { PatchDB } from 'patch-db-client' import { ConfigService } from '../../../services/config.service' import { DataModel } from 'src/app/services/patch-db/data-model' +import { Version } from '@start9labs/start-sdk' @Injectable({ providedIn: 'root' }) export class RefreshAlertService extends Observable { private readonly stream$ = this.patch.watch$('serverInfo', 'version').pipe( map( version => - this.exver.compareOsVersion(this.config.version, version) !== 'equal', + Version.parse(this.config.version).compare(Version.parse(version)) !== + 'equal', ), endWith(false), ) constructor( private readonly patch: PatchDB, - private readonly exver: Exver, private readonly config: ConfigService, ) { super(subscriber => this.stream$.subscribe(subscriber)) 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/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 @@ - + + + + +

+
+
+
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 d2af27efc..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,6 +59,83 @@ 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( 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 7e0aa9116..a2e672b20 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -117,7 +117,7 @@ export module Mock { assets: [], volumes: ['main'], hardwareRequirements: { - device: {}, + device: [], arch: null, ram: null, }, @@ -174,7 +174,7 @@ export module Mock { assets: [], volumes: ['main'], hardwareRequirements: { - device: {}, + device: [], arch: null, ram: null, }, @@ -224,7 +224,7 @@ export module Mock { assets: [], volumes: ['main'], hardwareRequirements: { - device: {}, + device: [], arch: null, ram: null, }, @@ -253,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', @@ -286,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', @@ -329,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', @@ -362,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', @@ -407,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', @@ -463,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', @@ -521,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', @@ -565,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', @@ -598,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', @@ -641,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', @@ -697,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', 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 d5989affa..797fde614 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -60,7 +60,7 @@ export const mockPatchData: DataModel = { // password is asdfasdf passwordHash: '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ', - versionCompat: '>=0.3.0 <=0.3.6', + packageVersionCompat: '>=0.3.0 <=0.3.6', postInitMigrationTodos: [], statusInfo: { backupProgress: null, @@ -83,6 +83,8 @@ export const mockPatchData: DataModel = { selected: null, lastRegion: null, }, + ram: 8 * 1024 * 1024 * 1024, + devices: [], }, packageData: { bitcoind: { 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) } } From 8e0db2705fd9cea06e1cf03405dfd00b99ac2674 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:38:24 -0600 Subject: [PATCH 25/46] Fix/mac start cli packing (#2767) * wip * wip: Adding more of the docker for the mac build * fix: Running a build * chore: Make the code a little cleaner * optimize: reduce docker image size for mac-tar2sqfs * feat: Update sdk-utils container usage and Dockerfile * feat: Publish SDK Utils Container image * clean up ... * feat: Add manual input to control tagging Docker image as 'latest' * fix: Update workflow input handling * switch to different repo and clean --------- Co-authored-by: Mariusz Kogen Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> --- core/startos/src/s9pk/v2/pack.rs | 38 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/core/startos/src/s9pk/v2/pack.rs b/core/startos/src/s9pk/v2/pack.rs index 27cad6e92..1911152c4 100644 --- a/core/startos/src/s9pk/v2/pack.rs +++ b/core/startos/src/s9pk/v2/pack.rs @@ -62,9 +62,7 @@ impl SqfsDir { let guid = Guid::new(); let path = self.tmpdir.join(guid.as_ref()).with_extension("squashfs"); if self.path.extension().and_then(|s| s.to_str()) == Some("tar") { - Command::new("tar2sqfs") - .arg("-q") - .arg(&path) + tar2sqfs(&self.path)? .input(Some(&mut open_file(&self.path).await?)) .invoke(ErrorKind::Filesystem) .await?; @@ -553,7 +551,7 @@ impl ImageSource { Command::new(CONTAINER_TOOL) .arg("export") .arg(container.trim()) - .pipe(Command::new("tar2sqfs").arg("-q").arg(&dest)) + .pipe(&mut tar2sqfs(&dest)?) .capture(false) .invoke(ErrorKind::Docker) .await?; @@ -575,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(&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)] From 480f5c1a9af662ed27096c7273c375289a710613 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Wed, 30 Oct 2024 09:15:24 -0600 Subject: [PATCH 26/46] pi 5 support (#2640) * prioritize raspi repo * change kernel * use newer kernel * Update build.sh * fix ssh keygen * switch to .com * use raspi-update to download firmware * Update build.sh * Update build.sh * Update build.sh * Update build.sh * switch to boot/firmware * fix fstab * update-initramfs * skip check partition * switch back to boot * fix initramfs * use rpi-update kernels * simplify kernel selection --- core/startos/src/util/mod.rs | 8 ++---- debian/postinst | 1 + image-recipe/build.sh | 27 +++++++------------ image-recipe/raspberrypi/img/etc/fstab | 4 +-- .../usr/lib/startos/scripts/init_resize.sh | 2 +- .../raspberrypi/squashfs/boot/config.txt | 3 ++- 6 files changed, 17 insertions(+), 28 deletions(-) diff --git a/core/startos/src/util/mod.rs b/core/startos/src/util/mod.rs index f26bfa6b5..0f3563018 100644 --- a/core/startos/src/util/mod.rs +++ b/core/startos/src/util/mod.rs @@ -236,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 { @@ -267,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 diff --git a/debian/postinst b/debian/postinst index bbf61f344..6e2e1383a 100755 --- a/debian/postinst +++ b/debian/postinst @@ -80,6 +80,7 @@ sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.co 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 diff --git a/image-recipe/build.sh b/image-recipe/build.sh index c183218ff..ea88824d0 100755 --- a/image-recipe/build.sh +++ b/image-recipe/build.sh @@ -61,14 +61,7 @@ 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="$PLATFORM_CONFIG_EXTRAS --linux-flavours rpi-v8" elif [ "${IB_TARGET_PLATFORM}" = "rockchip64" ]; then PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --linux-flavours rockchip64" fi @@ -148,13 +141,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 +173,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 +198,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 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 9e357cd57..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 @@ -94,7 +94,7 @@ main () { return 1 fi - if ! mkdir -p /media/startos/config/overlay/etc/ssh && ssh-keygen -A -f /media/startos/config/overlay/; 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 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 From 2091abeea23dc8009b0e565cbb0920e553290d97 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:55:36 -0600 Subject: [PATCH 27/46] persist hostname in config overlay (#2769) * persist hostname * add migration * fix version mismatch * remove dmesg logging from build --- Makefile | 2 +- core/Cargo.lock | 2 +- core/startos/Cargo.toml | 2 +- core/startos/src/context/setup.rs | 4 + core/startos/src/diagnostic.rs | 7 +- core/startos/src/os_install/mod.rs | 17 +--- core/startos/src/setup.rs | 7 ++ core/startos/src/util/io.rs | 14 ++++ core/startos/src/version/mod.rs | 5 +- core/startos/src/version/v0_3_6_alpha_0.rs | 83 +++++++++---------- core/startos/src/version/v0_3_6_alpha_7.rs | 13 +++ image-recipe/build.sh | 13 +-- sdk/base/lib/osBindings/SetupResult.ts | 1 + web/package.json | 2 +- web/patchdb-ui-seed.json | 2 +- .../src/app/services/api/mock-api.service.ts | 2 + .../modals/os-welcome/os-welcome.page.html | 2 +- 17 files changed, 105 insertions(+), 73 deletions(-) diff --git a/Makefile b/Makefile index d7ff9ec6c..c13beef94 100644 --- a/Makefile +++ b/Makefile @@ -213,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 diff --git a/core/Cargo.lock b/core/Cargo.lock index cfa00f33f..acd2ceb32 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5051,7 +5051,7 @@ dependencies = [ [[package]] name = "start-os" -version = "0.3.6-alpha.6" +version = "0.3.6-alpha.7" dependencies = [ "aes", "async-compression", diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 99384345d..77a710df7 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.6" +version = "0.3.6-alpha.7" license = "MIT" [lib] 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/diagnostic.rs b/core/startos/src/diagnostic.rs index 3eab3b16b..f0c142706 100644 --- a/core/startos/src/diagnostic.rs +++ b/core/startos/src/diagnostic.rs @@ -9,6 +9,7 @@ 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 { @@ -95,9 +96,7 @@ pub fn disk() -> ParentHandler { } 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/os_install/mod.rs b/core/startos/src/os_install/mod.rs index a87a71209..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; @@ -180,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/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/util/io.rs b/core/startos/src/util/io.rs index c3bf83d00..0e7aada54 100644 --- a/core/startos/src/util/io.rs +++ b/core/startos/src/util/io.rs @@ -923,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(); diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index bc8deea17..10a02d63b 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -306,7 +306,10 @@ where Ok(()) } /// MUST be idempotent, and is run after *all* db migrations - fn post_up(self, ctx: &RpcContext) -> impl Future> + Send + 'static { + fn post_up<'a>( + self, + ctx: &'a RpcContext, + ) -> impl Future> + Send + 'a { async { Ok(()) } } fn down(self, db: &mut Value) -> Result<(), Error> { 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 2f4b141b4..7a6045a3a 100644 --- a/core/startos/src/version/v0_3_6_alpha_0.rs +++ b/core/startos/src/version/v0_3_6_alpha_0.rs @@ -19,6 +19,10 @@ 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; @@ -26,19 +30,15 @@ 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; -use crate::{account::AccountInfo, net::tor}; -use crate::{auth::Sessions, context::RpcContext}; -use crate::{ - backup::target::cifs::CifsTargets, - s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile, -}; lazy_static::lazy_static! { static ref V0_3_6_alpha_0: exver::Version = exver::Version::new( @@ -328,61 +328,58 @@ impl VersionT for Version { #[instrument(skip(self, ctx))] /// MUST be idempotent, and is run after *all* db migrations - fn post_up(self, ctx: &RpcContext) -> impl Future> + Send + 'static { - let ctx = ctx.clone(); - async move { - let path = Path::new("/embassy-data/package-data/archive/"); + 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() { - return Err(Error::new( - eyre!( - "expected path ({}) to be a directory", - path.to_string_lossy() - ), - ErrorKind::Filesystem, - )); + continue; } - // Should be the name of the package + // 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 the version of the package + + // 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() { + 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 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?; - } + 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(()) } + 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 75c0af72f..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::prelude::*; +use crate::util::Invoke; lazy_static::lazy_static! { static ref V0_3_6_alpha_7: exver::Version = exver::Version::new( @@ -44,6 +46,17 @@ impl VersionT for Version { } 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(()) + } fn down(self, _db: &mut Value) -> Result<(), Error> { Ok(()) } diff --git a/image-recipe/build.sh b/image-recipe/build.sh index ea88824d0..ae1831704 100755 --- a/image-recipe/build.sh +++ b/image-recipe/build.sh @@ -57,13 +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" - PLATFORM_CONFIG_EXTRAS="$PLATFORM_CONFIG_EXTRAS --linux-flavours rpi-v8" + 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 @@ -87,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 diff --git a/sdk/base/lib/osBindings/SetupResult.ts b/sdk/base/lib/osBindings/SetupResult.ts index 464aeb4b7..d81c7f039 100644 --- a/sdk/base/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/web/package.json b/web/package.json index e900277c4..f1146118d 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.6", + "version": "0.3.6-alpha.7", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", diff --git a/web/patchdb-ui-seed.json b/web/patchdb-ui-seed.json index 5862c6f83..3256bb87c 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.6" + "ack-welcome": "0.3.6-alpha.7" } 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/ui/src/app/modals/os-welcome/os-welcome.page.html b/web/projects/ui/src/app/modals/os-welcome/os-welcome.page.html index 2a43292ad..3e4fe52f3 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,7 +12,7 @@

This Release

-

0.3.6-alpha.6

+

0.3.6-alpha.7

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 From 88320488a7cbea8385f3513eeb19d72d9cbd715b Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:41:06 -0600 Subject: [PATCH 28/46] fix: Actions like the action don't give the results (#2770) --- container-runtime/src/Adapters/RpcListener.ts | 13 +++++++------ .../src/Adapters/Systems/SystemForStartOs.ts | 8 +------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index f84f7b282..3e86e60d1 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -136,11 +136,13 @@ 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) || @@ -413,7 +415,6 @@ export class RpcListener { const ensureResultTypeShape = ( result: void | T.ActionInput | T.ActionResult | null, ): { result: any } => { - if (isResult(result)) return result return { result } } const callbacks = this.callbackHolderFor(procedure) diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index 51b7c24b8..334764a87 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -1,12 +1,6 @@ -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 { CallbackHolder } from "../../Models/CallbackHolder" import { Optional } from "ts-matches/lib/parsers/interfaces" export const STARTOS_JS_LOCATION = "/usr/lib/startos/package/index.js" From 5ab2efa0c0e768534d88a772370971b499249c00 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:49:38 -0700 Subject: [PATCH 29/46] wip(fix): Working on fixing the migration. (#2771) * wip(fix): Working on fixing the migration. * get s9pk compat key * wip: Change to just using the archive to not use the manifest parsing. * fix: Fix the rebuild --------- Co-authored-by: Aiden McClelland --- code | 690 +++++++++++++++++++++ core/startos/src/s9pk/v2/mod.rs | 28 +- core/startos/src/version/mod.rs | 6 +- core/startos/src/version/v0_3_6_alpha_8.rs | 125 ++++ 4 files changed, 843 insertions(+), 6 deletions(-) create mode 100644 code create mode 100644 core/startos/src/version/v0_3_6_alpha_8.rs 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/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/version/mod.rs b/core/startos/src/version/mod.rs index 10a02d63b..f71cec8b3 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -26,8 +26,9 @@ 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_7::Version; // VERSION_BUMP +pub type Current = v0_3_6_alpha_8::Version; // VERSION_BUMP impl Current { #[instrument(skip(self, db))] @@ -104,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), } @@ -135,6 +137,7 @@ impl Version { 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}"), @@ -158,6 +161,7 @@ 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(), } } 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..b23c205e8 --- /dev/null +++ b/core/startos/src/version/v0_3_6_alpha_8.rs @@ -0,0 +1,125 @@ +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::s9pk::manifest::{DeviceFilter, Manifest}; +use crate::s9pk::merkle_archive::MerkleArchive; +use crate::s9pk::v2::SIG_CONTEXT; +use crate::s9pk::S9pk; +use crate::util::io::create_file; +use crate::{ + install::PKG_ARCHIVE_DIR, s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile, +}; +use crate::{prelude::*, service::LoadDisposition}; + +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"); + + for s9pk_path in s9pk_dir.read_dir()? { + let s9pk_path = s9pk_path?.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(()) + } +} From 176b1c9d206732586e8f6db3b7f331aa59bdff16 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:50:24 -0700 Subject: [PATCH 30/46] allow lxc-net for tor (#2774) * allow lxc-net for tor * /24 --- debian/postinst | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/postinst b/debian/postinst index 6e2e1383a..0db121c56 100755 --- a/debian/postinst +++ b/debian/postinst @@ -88,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 From 020268fe67cf996ac1fe20cd05849a53bf0df91b Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:38:30 -0700 Subject: [PATCH 31/46] don't attempt autoconfig if config is null (#2775) * don't attempt autoconfig if config is null * quiet * fixes --- .../Systems/SystemForEmbassy/index.ts | 1 + core/startos/src/s9pk/v2/pack.rs | 24 +-- core/startos/src/version/v0_3_6_alpha_8.rs | 143 +++++++++--------- 3 files changed, 87 insertions(+), 81 deletions(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index b33e83c0f..b01ca2dc1 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -929,6 +929,7 @@ export class SystemForEmbassy implements System { 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 diff --git a/core/startos/src/s9pk/v2/pack.rs b/core/startos/src/s9pk/v2/pack.rs index 1911152c4..67be558f1 100644 --- a/core/startos/src/s9pk/v2/pack.rs +++ b/core/startos/src/s9pk/v2/pack.rs @@ -1,5 +1,4 @@ -use std::collections::BTreeMap; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -295,7 +294,7 @@ impl TryFrom for ImageConfig { ImageSource::DockerBuild { dockerfile: value.dockerfile, workdir: value.workdir, - build_args: None + build_args: None, } } else if let Some(tag) = value.docker_tag { ImageSource::DockerTag(tag) @@ -345,9 +344,7 @@ impl clap::FromArgMatches for ImageConfig { #[ts(export)] pub enum BuildArg { String(String), - EnvVar { - env: String, - }, + EnvVar { env: String }, } #[derive(Debug, Clone, Deserialize, Serialize, TS)] @@ -361,7 +358,7 @@ pub enum ImageSource { dockerfile: Option, #[serde(skip_serializing_if = "Option::is_none")] #[ts(optional)] - build_args: Option> + build_args: Option>, }, DockerTag(String), } @@ -400,7 +397,7 @@ impl ImageSource { ImageSource::DockerBuild { workdir, dockerfile, - build_args + build_args, } => { let workdir = workdir.as_deref().unwrap_or(Path::new(".")); let dockerfile = dockerfile @@ -424,7 +421,8 @@ impl ImageSource { .arg("-t") .arg(&tag) .arg(&docker_platform) - .arg("--build-arg").arg(format!("ARCH={}", arch)); + .arg("--build-arg") + .arg(format!("ARCH={}", arch)); // add build arguments if let Some(build_args) = build_args { @@ -436,10 +434,12 @@ impl ImageSource { 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("--build-arg") + .arg(format!("{}={}", key, build_arg_value)); } } @@ -580,7 +580,7 @@ fn tar2sqfs(dest: impl AsRef) -> Result { #[cfg(target_os = "linux")] { let mut command = Command::new("tar2sqfs"); - command.arg(&dest); + command.arg("-q").arg(&dest); command } #[cfg(target_os = "macos")] diff --git a/core/startos/src/version/v0_3_6_alpha_8.rs b/core/startos/src/version/v0_3_6_alpha_8.rs index b23c205e8..fcef96a69 100644 --- a/core/startos/src/version/v0_3_6_alpha_8.rs +++ b/core/startos/src/version/v0_3_6_alpha_8.rs @@ -3,15 +3,15 @@ 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; -use crate::{ - install::PKG_ARCHIVE_DIR, s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile, -}; -use crate::{prelude::*, service::LoadDisposition}; lazy_static::lazy_static! { static ref V0_3_6_alpha_8: exver::Version = exver::Version::new( @@ -42,78 +42,83 @@ impl VersionT for Version { async fn post_up(self, ctx: &crate::context::RpcContext) -> Result<(), Error> { let s9pk_dir = ctx.datadir.join(PKG_ARCHIVE_DIR).join("installed"); - for s9pk_path in s9pk_dir.read_dir()? { - let s9pk_path = s9pk_path?.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:?}"); + 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 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 get_archive = async { + let multi_cursor = MultiCursorFile::from(File::open(&s9pk_path).await?); + Ok::<_, Error>(S9pk::archive(&multi_cursor, None).await?) + }; - let mut manifest = previous_manifest.clone(); + 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; + } + }; - 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())?, + 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) - })?, - )?; - } + .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?; + 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?; + } } } From b79c029f21862afea0649b097fa567db180f4667 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:38:52 -0700 Subject: [PATCH 32/46] Feature/registry improvements (#2772) * add build cli script for cross-building cli * sdk alpha.13 * registry improvements --- Makefile | 2 +- core/Cargo.lock | 88 +++++------ core/build-cli.sh | 50 ++++++ core/startos/registry.service | 13 ++ core/startos/src/bins/mod.rs | 10 ++ core/startos/src/lib.rs | 3 +- core/startos/src/logs.rs | 12 +- core/startos/src/registry/admin.rs | 65 ++++++++ core/startos/src/registry/info.rs | 126 +++++++++++++++ core/startos/src/registry/mod.rs | 29 +--- core/startos/src/registry/os/asset/get.rs | 13 +- core/startos/src/registry/package/category.rs | 147 ++++++++++++++++++ core/startos/src/registry/package/mod.rs | 11 ++ core/startos/src/registry/package/signer.rs | 133 ++++++++++++++++ core/startos/src/registry/signer/mod.rs | 2 +- core/startos/startd.service | 2 +- sdk/package/lib/manifest/setupManifest.ts | 13 +- sdk/package/package-lock.json | 4 +- sdk/package/package.json | 2 +- 19 files changed, 636 insertions(+), 89 deletions(-) create mode 100755 core/build-cli.sh create mode 100644 core/startos/registry.service create mode 100644 core/startos/src/registry/info.rs create mode 100644 core/startos/src/registry/package/category.rs create mode 100644 core/startos/src/registry/package/signer.rs diff --git a/Makefile b/Makefile index c13beef94..132270636 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ touch: metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) sudo: - sudo -v + sudo true clean: rm -f system-images/**/*.tar diff --git a/core/Cargo.lock b/core/Cargo.lock index acd2ceb32..32f0939d8 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" [[package]] name = "arrayref" @@ -237,7 +237,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -248,7 +248,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -538,7 +538,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.85", + "syn 2.0.86", "which", ] @@ -847,7 +847,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1255,7 +1255,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1279,7 +1279,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1290,7 +1290,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1321,7 +1321,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1344,7 +1344,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1367,7 +1367,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1589,7 +1589,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -1868,7 +1868,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -2849,9 +2849,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00419de735aac21d53b0de5ce2c03bd3627277cf471300f27ebc89f7d828047" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -3302,7 +3302,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3368,7 +3368,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3575,7 +3575,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3631,7 +3631,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3707,7 +3707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3805,7 +3805,7 @@ checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -3828,7 +3828,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4169,7 +4169,7 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor%2Fno-dyn-ctx#39a872a1294c7d864faca63f072092ce300ffbe5" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor%2Fno-dyn-ctx#021379f21c4d11c5a62c07460f4531ce9b555155" dependencies = [ "async-stream", "async-trait", @@ -4496,7 +4496,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -4570,7 +4570,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5003,7 +5003,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.85", + "syn 2.0.86", "unicode-width", ] @@ -5245,9 +5245,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -5363,22 +5363,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5494,7 +5494,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5768,7 +5768,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -5920,7 +5920,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "termcolor", ] @@ -5990,7 +5990,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6215,7 +6215,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen-shared", ] @@ -6249,7 +6249,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6633,7 +6633,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] @@ -6653,7 +6653,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.86", ] [[package]] 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/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/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/lib.rs b/core/startos/src/lib.rs index d9d626754..83c4d462d 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -115,8 +115,7 @@ pub fn main_api() -> ParentHandler { let api = ParentHandler::new() .subcommand( "git-info", - from_fn(|_: RpcContext| version::git_info()) - .with_about("Display the githash of StartOS CLI"), + from_fn(|_: C| version::git_info()).with_about("Display the githash of StartOS CLI"), ) .subcommand( "echo", diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index 2db7e9952..1cb3327b6 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -360,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) @@ -436,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, @@ -444,7 +440,7 @@ where from_fn_async( move |HandlerArgs { context, - inherited_params: + params: LogsParams { extra, limit, @@ -453,7 +449,7 @@ where before, }, .. - }: HandlerArgs>| { + }: HandlerArgs>| { let f = f.clone(); async move { fetch_logs( diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs index a327a9587..f3cac9f7e 100644 --- a/core/startos/src/registry/admin.rs +++ b/core/startos/src/registry/admin.rs @@ -60,6 +60,13 @@ fn signers_api() -> ParentHandler { "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> { @@ -143,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/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 6a90ab640..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,25 +58,6 @@ 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( @@ -85,13 +67,8 @@ pub fn registry_api() -> ParentHandler { .with_about("List info including registry name and packages") .with_call_remote::(), ) - .subcommand( - "info", - from_fn_async(get_info) - .with_display_serializable() - .with_about("Display registry name, icon, and package categories") - .with_call_remote::(), - ) + .subcommand("info", info::info_api::()) + // set info and categories .subcommand( "os", os::os_api::().with_about("Commands related to OS assets and versions"), diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs index 6c0479964..0d63435f2 100644 --- a/core/startos/src/registry/os/asset/get.rs +++ b/core/startos/src/registry/os/asset/get.rs @@ -40,7 +40,12 @@ pub fn get_api() -> ParentHandler { .with_about("Download img"), ) .subcommand("squashfs", from_fn_async(get_squashfs).no_cli()) - .subcommand("squashfs", from_fn_async(cli_get_os_asset).no_display().with_about("Download squashfs")) + .subcommand( + "squashfs", + from_fn_async(cli_get_os_asset) + .no_display() + .with_about("Download squashfs"), + ) } #[derive(Debug, Deserialize, Serialize, TS)] @@ -104,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", 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/mod.rs b/core/startos/src/registry/package/mod.rs index ef312dc28..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() @@ -29,6 +31,10 @@ pub fn package_api() -> ParentHandler { .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) @@ -40,4 +46,9 @@ pub fn package_api() -> ParentHandler { .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 c203f0dad..137c40f0f 100644 --- a/core/startos/src/registry/signer/mod.rs +++ b/core/startos/src/registry/signer/mod.rs @@ -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/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/sdk/package/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts index 2ae999e62..c529f1ab7 100644 --- a/sdk/package/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -6,6 +6,7 @@ import { } from "../../../base/lib/types/ManifestTypes" import { SDKVersion } from "../StartSdk" import { VersionGraph } from "../version/VersionGraph" +import { execSync } from "child_process" /** * @description Use this function to define critical information about your package @@ -26,6 +27,16 @@ export function setupManifest< 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, @@ -56,7 +67,7 @@ export function buildManifest< ) return { ...manifest, - gitHash: null, + gitHash: gitHash(), osVersion: SDKVersion, version: versions.current.options.version, releaseNotes: versions.current.options.releaseNotes, diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index 2294d800b..2cc87d374 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,12 +1,12 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha9", + "version": "0.3.6-alpha.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha9", + "version": "0.3.6-alpha.13", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/sdk/package/package.json b/sdk/package/package.json index acc523594..661263f70 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.12", + "version": "0.3.6-alpha.13", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", From 6ab6502742c6dc22061bf2fe1e590fd5d7426993 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:52:38 -0700 Subject: [PATCH 33/46] alpha.8 (#2776) --- core/Cargo.lock | 2 +- core/startos/Cargo.toml | 2 +- sdk/base/lib/osBindings/AddCategoryParams.ts | 8 ++++++++ sdk/base/lib/osBindings/CliSetIconParams.ts | 3 +++ sdk/base/lib/osBindings/EditSignerParams.ts | 13 +++++++++++++ sdk/base/lib/osBindings/ListPackageSignersParams.ts | 4 ++++ sdk/base/lib/osBindings/PackageSignerParams.ts | 5 +++++ sdk/base/lib/osBindings/RemoveCategoryParams.ts | 3 +++ sdk/base/lib/osBindings/SetIconParams.ts | 4 ++++ sdk/base/lib/osBindings/SetNameParams.ts | 3 +++ sdk/base/lib/osBindings/index.ts | 8 ++++++++ web/package.json | 2 +- web/patchdb-ui-seed.json | 2 +- .../src/app/modals/os-welcome/os-welcome.page.html | 2 +- 14 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 sdk/base/lib/osBindings/AddCategoryParams.ts create mode 100644 sdk/base/lib/osBindings/CliSetIconParams.ts create mode 100644 sdk/base/lib/osBindings/EditSignerParams.ts create mode 100644 sdk/base/lib/osBindings/ListPackageSignersParams.ts create mode 100644 sdk/base/lib/osBindings/PackageSignerParams.ts create mode 100644 sdk/base/lib/osBindings/RemoveCategoryParams.ts create mode 100644 sdk/base/lib/osBindings/SetIconParams.ts create mode 100644 sdk/base/lib/osBindings/SetNameParams.ts diff --git a/core/Cargo.lock b/core/Cargo.lock index 32f0939d8..490edf223 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5051,7 +5051,7 @@ dependencies = [ [[package]] name = "start-os" -version = "0.3.6-alpha.7" +version = "0.3.6-alpha.8" dependencies = [ "aes", "async-compression", diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 77a710df7..3a9935373 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.7" +version = "0.3.6-alpha.8" license = "MIT" [lib] 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/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/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/base/lib/osBindings/ListPackageSignersParams.ts b/sdk/base/lib/osBindings/ListPackageSignersParams.ts new file mode 100644 index 000000000..73cd6a745 --- /dev/null +++ b/sdk/base/lib/osBindings/ListPackageSignersParams.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 { PackageId } from "./PackageId" + +export type ListPackageSignersParams = { id: PackageId } 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/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/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/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/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index 10c2098bf..acd555219 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -16,6 +16,7 @@ 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" @@ -50,6 +51,7 @@ 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 { CreateSubcontainerFsParams } from "./CreateSubcontainerFsParams" export { CurrentDependencies } from "./CurrentDependencies" @@ -65,6 +67,7 @@ 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 { ExportActionParams } from "./ExportActionParams" export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams" @@ -109,6 +112,7 @@ 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" @@ -133,6 +137,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" @@ -143,6 +148,7 @@ 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" @@ -161,8 +167,10 @@ export { Session } from "./Session" 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" diff --git a/web/package.json b/web/package.json index f1146118d..e8aafc8c7 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.7", + "version": "0.3.6-alpha.8", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", diff --git a/web/patchdb-ui-seed.json b/web/patchdb-ui-seed.json index 3256bb87c..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.7" + "ack-welcome": "0.3.6-alpha.8" } 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 3e4fe52f3..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,7 +12,7 @@

This Release

-

0.3.6-alpha.7

+

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 From 1c90303914090fb9dca42825f072c565339a2592 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Fri, 8 Nov 2024 11:57:42 -0700 Subject: [PATCH 34/46] closes #2340 and #2431, fixes bug with select all for backup (#2780) * closes #2340 and #2431, fixes bug with select all for backup * revefrt mock --- web/package-lock.json | 4 +- .../backup-select/backup-select.page.ts | 4 +- .../diagnostic-routes/home/home.page.html | 10 ++--- .../pages/diagnostic-routes/home/home.page.ts | 2 + .../diagnostic-routes/logs/logs.page.html | 10 +++++ .../pages/diagnostic-routes/logs/logs.page.ts | 45 ++++++++++++++++++- 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 826feb631..764694127 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.6", + "version": "0.3.6-alpha.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.3.6-alpha.6", + "version": "0.3.6-alpha.8", "license": "MIT", "dependencies": { "@angular/animations": "^14.1.0", 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/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('
') + } } From 279c7324c48542265ca87b509b76bf10a6bf94f9 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:38:46 -0700 Subject: [PATCH 35/46] download to directory not filename (#2777) --- core/startos/src/registry/os/asset/get.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs index 0d63435f2..a3da7047c 100644 --- a/core/startos/src/registry/os/asset/get.rs +++ b/core/startos/src/registry/os/asset/get.rs @@ -138,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, @@ -152,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)?; From 25e38bfc98af00ddefa87bbbf759ec4612d9f297 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:39:02 -0700 Subject: [PATCH 36/46] do not mute logs of subcontainer launch dummy (#2781) --- sdk/package/lib/util/SubContainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/package/lib/util/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts index 785c6fea0..7274e22d4 100644 --- a/sdk/package/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -57,7 +57,7 @@ export class SubContainer implements ExecSpawnable { this.leaderExited = false this.leader = cp.spawn("start-cli", ["subcontainer", "launch", rootfs], { killSignal: "SIGKILL", - stdio: "ignore", + stdio: "inherit", }) this.leader.on("exit", () => { this.leaderExited = true From b1e7a717afbc7b553b54046a65c8a05cc0365739 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:39:16 -0700 Subject: [PATCH 37/46] allow updating grub from chroot-and-upgrade (#2778) --- build/lib/scripts/chroot-and-upgrade | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/lib/scripts/chroot-and-upgrade b/build/lib/scripts/chroot-and-upgrade index 3af93f5ef..be2911369 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 From aab2b8fdbc43a967432a91cd80c8c658327db9e6 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:54:51 -0700 Subject: [PATCH 38/46] do not request config action if no config exists (#2779) --- .../src/Adapters/Systems/SystemForEmbassy/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index b01ca2dc1..4bcd3d594 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -337,7 +337,7 @@ export class SystemForEmbassy implements System { await effects.setDataVersion({ version: ExtendedVersion.parseEmver(this.manifest.version).toString(), }) - } else { + } else if (this.manifest.config) { await effects.action.request({ packageId: this.manifest.id, actionId: "config", From c088ab7a79665deef9f06f1d25e833edab592071 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 11 Nov 2024 12:17:44 -0700 Subject: [PATCH 39/46] remove file from input spec (#2782) --- sdk/base/lib/actions/input/builder/value.ts | 82 +++++++++---------- .../lib/test/startosTypeValidation.test.ts | 2 - 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/sdk/base/lib/actions/input/builder/value.ts b/sdk/base/lib/actions/input/builder/value.ts index ad30f1376..78318f868 100644 --- a/sdk/base/lib/actions/input/builder/value.ts +++ b/sdk/base/lib/actions/input/builder/value.ts @@ -689,47 +689,47 @@ export class Value { } }, spec.validator) } - static file(a: { - name: string - description?: string | null - extensions: string[] - required: boolean - }) { - const buildValue = { - type: "file" as const, - description: null, - warning: null, - ...a, - } - return new Value( - () => ({ - ...buildValue, - }), - asRequiredParser(object({ filePath: string }), a), - ) - } - static dynamicFile( - a: LazyBuild< - Store, - { - name: string - description?: string | null - warning?: string | null - extensions: string[] - required: Required - } - >, - ) { - return new Value( - async (options) => ({ - type: "file" as const, - description: null, - warning: null, - ...(await a(options)), - }), - string.optional(), - ) - } + // static file(a: { + // name: string + // description?: string | null + // extensions: string[] + // required: boolean + // }) { + // const buildValue = { + // type: "file" as const, + // description: null, + // warning: null, + // ...a, + // } + // return new Value( + // () => ({ + // ...buildValue, + // }), + // asRequiredParser(object({ filePath: string }), a), + // ) + // } + // static dynamicFile( + // a: LazyBuild< + // Store, + // { + // name: string + // description?: string | null + // warning?: string | null + // extensions: string[] + // required: Required + // } + // >, + // ) { + // return new Value( + // async (options) => ({ + // type: "file" as const, + // description: null, + // warning: null, + // ...(await a(options)), + // }), + // string.optional(), + // ) + // } static union, Type, Store>( a: { name: string diff --git a/sdk/base/lib/test/startosTypeValidation.test.ts b/sdk/base/lib/test/startosTypeValidation.test.ts index 596da3b49..509da0894 100644 --- a/sdk/base/lib/test/startosTypeValidation.test.ts +++ b/sdk/base/lib/test/startosTypeValidation.test.ts @@ -8,12 +8,10 @@ import { ClearServiceInterfacesParams, GetActionInputParams, GetStatusParams, - GetStoreParams, RequestActionParams, RunActionParams, SetDataVersionParams, SetMainStatus, - SetStoreParams, } from ".././osBindings" import { CreateSubcontainerFsParams } from ".././osBindings" import { DestroySubcontainerFsParams } from ".././osBindings" From db6fc661a6451123cbdcb0f3c32d85c1f18b9455 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:53:19 -0700 Subject: [PATCH 40/46] fix: Dependency (#2784) * fix: Dependency * fix: set deps during container init --- .../Systems/SystemForEmbassy/index.ts | 83 ++++++++++++++----- package-lock.json | 2 +- .../ui/src/app/services/api/api.fixures.ts | 10 +-- 3 files changed, 69 insertions(+), 26 deletions(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 4bcd3d594..531b30cd2 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -301,6 +301,20 @@ export class SystemForEmbassy implements System { 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 { @@ -650,7 +664,7 @@ export class SystemForEmbassy implements System { ), ) 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 @@ -673,31 +687,60 @@ 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], + }, + ] + }, + ), }) } 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/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index a2e672b20..844094e3a 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -1191,11 +1191,11 @@ export module Mock { name: 'Datetime', required: false, }), - file: ISB.Value.file({ - name: 'File', - required: false, - extensions: ['png', 'pdf'], - }), + // file: ISB.Value.file({ + // name: 'File', + // required: false, + // extensions: ['png', 'pdf'], + // }), users: ISB.Value.multiselect({ name: 'Users', default: [], From 46179f5c83f0d7e6c274eb6c1cec606100a0434e Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:31:47 -0700 Subject: [PATCH 41/46] attempt to fix webserver lockup (#2788) --- core/startos/Cargo.toml | 2 +- core/startos/src/net/web_server.rs | 50 ++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 3a9935373..df4ac2378 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -198,7 +198,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" 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, + })) } } From 1771797453aa890fca14f21382b10e4cbd1098ca Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:25:43 -0700 Subject: [PATCH 42/46] sdk input spec improvements (#2785) * sdk input spec improvements * more sdk changes * fe changes * alpha.14 * fix tests * separate validator in filehelper * use deeppartial for getinput * fix union type and update ts-matches * alpha.15 * alpha.16 * alpha.17 --------- Co-authored-by: Matt Hill --- .../transformConfigSpec.test.ts.snap | 6 - .../SystemForEmbassy/transformConfigSpec.ts | 2 - sdk/base/lib/actions/index.ts | 45 ++- .../lib/actions/input/builder/inputSpec.ts | 13 +- sdk/base/lib/actions/input/builder/value.ts | 294 +++++++++--------- .../lib/actions/input/builder/variants.ts | 85 +++-- .../lib/actions/input/inputSpecConstants.ts | 22 +- sdk/base/lib/actions/input/inputSpecTypes.ts | 2 - sdk/base/lib/actions/setupActions.ts | 32 +- sdk/base/lib/types.ts | 2 +- sdk/base/package-lock.json | 9 +- sdk/base/package.json | 2 +- sdk/package/lib/StartSdk.ts | 124 ++++---- sdk/package/lib/test/inputSpecBuilder.test.ts | 130 ++++++-- sdk/package/lib/test/output.test.ts | 2 +- sdk/package/lib/util/fileHelper.ts | 61 ++-- sdk/package/package-lock.json | 13 +- sdk/package/package.json | 4 +- sdk/package/scripts/oldSpecToBuilder.ts | 41 +-- .../backup-drives/backup-drives.component.ts | 10 +- .../form-select/form-select.component.html | 5 +- .../server-show/server-show.page.ts | 15 +- .../ui/src/app/services/api/api.fixures.ts | 131 ++++---- .../ui/src/app/services/form.service.ts | 12 +- 24 files changed, 550 insertions(+), 512 deletions(-) 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/transformConfigSpec.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts index 4c074a1bd..1eb2ea508 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts @@ -43,7 +43,6 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec { }), {}, ), - required: false, disabled: false, immutable: false, } @@ -127,7 +126,6 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec { {} as Record, ), disabled: false, - required: true, default: oldVal.default, immutable: false, } diff --git a/sdk/base/lib/actions/index.ts b/sdk/base/lib/actions/index.ts index 2d052e072..4bcbec8b1 100644 --- a/sdk/base/lib/actions/index.ts +++ b/sdk/base/lib/actions/index.ts @@ -1,6 +1,7 @@ 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 @@ -44,36 +45,32 @@ export const runAction = async < }) } } -type GetActionInputType< - A extends Action>, -> = A extends Action ? I : never +type GetActionInputType
> = + A extends Action ? ExtractInputSpecType : never type ActionRequestBase = { reason?: string replayId?: string } -type ActionRequestInput< - T extends Action>, -> = { +type ActionRequestInput> = { kind: "partial" value: Partial> } -export type ActionRequestOptions< - T extends Action>, -> = ActionRequestBase & - ( - | { - when?: Exclude< - T.ActionRequestTrigger, - { condition: "input-not-matches" } - > - input?: ActionRequestInput - } - | { - when: T.ActionRequestTrigger & { condition: "input-not-matches" } - input: ActionRequestInput - } - ) +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 @@ -81,9 +78,7 @@ const _validate: T.ActionRequest = {} as ActionRequestOptions & { severity: T.ActionSeverity } -export const requestAction = < - T extends Action>, ->(options: { +export const requestAction = >(options: { effects: T.Effects packageId: T.PackageId action: T diff --git a/sdk/base/lib/actions/input/builder/inputSpec.ts b/sdk/base/lib/actions/input/builder/inputSpec.ts index 288ce8b3e..611ad8a48 100644 --- a/sdk/base/lib/actions/input/builder/inputSpec.ts +++ b/sdk/base/lib/actions/input/builder/inputSpec.ts @@ -1,5 +1,5 @@ import { ValueSpec } from "../inputSpecTypes" -import { Value } from "./value" +import { PartialValue, Value } from "./value" import { _ } from "../../../util" import { Effects } from "../../../Effects" import { Parser, object } from "ts-matches" @@ -16,6 +16,15 @@ export type ExtractInputSpecType | InputSpec | InputSpec ? B : A +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 } @@ -84,6 +93,8 @@ export class InputSpec, Store = never> { }, 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 diff --git a/sdk/base/lib/actions/input/builder/value.ts b/sdk/base/lib/actions/input/builder/value.ts index 78318f868..053bd0596 100644 --- a/sdk/base/lib/actions/input/builder/value.ts +++ b/sdk/base/lib/actions/input/builder/value.ts @@ -1,6 +1,6 @@ import { InputSpec, LazyBuild } from "./inputSpec" import { List } from "./list" -import { Variants } from "./variants" +import { PartialUnionRes, UnionRes, Variants } from "./variants" import { FilePath, Pattern, @@ -26,37 +26,14 @@ import { string, unknown, } from "ts-matches" +import { DeepPartial } from "../../../types" -export type RequiredDefault = - | false - | { - default: A | null - } - -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 AsRequired = Required extends true + ? T + : T | null | undefined const testForAsRequiredParser = once( - () => object({ required: object({ default: unknown }) }).test, + () => object({ required: literal(true) }).test, ) function asRequiredParser< Type, @@ -69,6 +46,13 @@ function asRequiredParser< return parser.optional() as any } +export type PartialValue = + T extends UnionRes + ? PartialUnionRes + : T extends {} + ? { [P in keyof T]?: PartialValue } + : T + export class Value { protected constructor( public build: LazyBuild, @@ -122,19 +106,19 @@ 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 /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | RandomString | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'World' } - * @example required: { default: { charset: 'abcdefg', len: 16 } } + * 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 /** * @description Mask (aka camouflage) text input with dots: ● ● ● @@ -188,7 +172,6 @@ export class Value { immutable: a.immutable ?? false, generate: a.generate ?? null, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(string, a), ) @@ -200,7 +183,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: DefaultString | null + required: boolean masked?: boolean placeholder?: string | null minLength?: number | null @@ -228,19 +212,16 @@ 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 - /** - * @description Unlike other "required" fields, for textarea this is a simple boolean. - */ - required: boolean + default: string | null + required: Required minLength?: number | null maxLength?: number | null placeholder?: string | null @@ -250,20 +231,23 @@ export class Value { */ 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< @@ -272,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 @@ -280,7 +265,7 @@ export class Value { } >, ) { - return new Value(async (options) => { + return new Value(async (options) => { const a = await getA(options) return { description: null, @@ -293,20 +278,20 @@ 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 Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: number | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 7 } + * @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 @@ -343,7 +328,6 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(number, a), ) @@ -355,7 +339,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: number | null + required: boolean min?: number | null max?: number | null step?: number | null @@ -380,22 +365,21 @@ 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 Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'ffffff' } + * @description optionally provide a default value. + * @type { default: string | null } + * @example default: null + * @example default: 'ffffff' */ + default: string | null required: Required /** * @description Once set, the value can never be changed. @@ -411,9 +395,7 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), - asRequiredParser(string, a), ) } @@ -425,7 +407,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string | null + required: boolean disabled?: false | string } >, @@ -439,22 +422,21 @@ 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 Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: '1985-12-16 18:00:00.000' } + * @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 /** * @description Informs the browser how to behave and which date/time component to display. @@ -481,7 +463,6 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(string, a), ) @@ -493,7 +474,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string | null + required: boolean inputmode?: ValueSpecDatetime["inputmode"] min?: string | null max?: string | null @@ -513,26 +495,21 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static select< - Required extends RequiredDefault, - Values extends Record, - >(a: { + static select>(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** * @description Determines if the field is required. If so, optionally provide a default value from the list of values. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'radio1' } + * @type { (keyof Values & string) | null } + * @example default: null + * @example default: 'radio1' */ - required: Required + default: keyof Values & string /** * @description A mapping of unique radio options to their human readable display format. * @example @@ -551,7 +528,7 @@ export class Value { */ immutable?: boolean }) { - return new Value, never>( + return new Value( () => ({ description: null, warning: null, @@ -559,16 +536,10 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), - asRequiredParser( - anyOf( - ...Object.keys(a.values).map((x: keyof Values & string) => - literal(x), - ), - ), - a, - ) as any, + anyOf( + ...Object.keys(a.values).map((x: keyof Values & string) => literal(x)), + ), ) } static dynamicSelect( @@ -578,13 +549,13 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string values: Record disabled?: false | string | string[] } >, ) { - return new Value(async (options) => { + return new Value(async (options) => { const a = await getA(options) return { description: null, @@ -593,9 +564,8 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } - }, string.optional()) + }, string) } static multiselect>(a: { name: string @@ -605,7 +575,7 @@ export class Value { /** * @description A simple list of which options should be checked by default. */ - default: string[] + default: (keyof Values & string)[] /** * @description A mapping of checkbox options to their human readable display format. * @example @@ -689,11 +659,11 @@ export class Value { } }, spec.validator) } - // static file(a: { + // static file(a: { // name: string // description?: string | null // extensions: string[] - // required: boolean + // required: Required // }) { // const buildValue = { // type: "file" as const, @@ -701,14 +671,14 @@ export class Value { // warning: null, // ...a, // } - // return new Value( + // return new Value, Store>( // () => ({ // ...buildValue, // }), // asRequiredParser(object({ filePath: string }), a), // ) // } - // static dynamicFile( + // static dynamicFile( // a: LazyBuild< // Store, // { @@ -716,43 +686,49 @@ export class Value { // description?: string | null // warning?: string | null // extensions: string[] - // required: Required + // required: boolean // } // >, // ) { - // return new Value( + // return new Value( // async (options) => ({ // type: "file" as const, // description: null, // warning: null, // ...(await a(options)), // }), - // string.optional(), + // object({ filePath: string }).optional(), // ) // } - static union, Type, Store>( + static union< + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, + >( a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value from the list of variants. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'variant1' } + * @description Provide a default value from the list of variants. + * @type { string } + * @example default: 'variant1' */ - required: Required + default: keyof VariantValues & string /** * @description Once set, the value can never be changed. * @default false */ immutable?: boolean }, - aVariants: Variants, + aVariants: Variants, ) { - return new Value, Store>( + return new Value( async (options) => ({ type: "union" as const, description: null, @@ -760,44 +736,50 @@ 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, @@ -805,24 +787,26 @@ export class Value { 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) { diff --git a/sdk/base/lib/actions/input/builder/variants.ts b/sdk/base/lib/actions/input/builder/variants.ts index 6c0f83905..05124f45a 100644 --- a/sdk/base/lib/actions/input/builder/variants.ts +++ b/sdk/base/lib/actions/input/builder/variants.ts @@ -1,6 +1,54 @@ +import { DeepPartial } from "../../../types" import { ValueSpec, ValueSpecUnion } from "../inputSpecTypes" -import { LazyBuild, InputSpec } from "./inputSpec" -import { Parser, anyOf, literals, object } from "ts-matches" +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' } @@ -44,18 +92,24 @@ 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 { @@ -67,26 +121,15 @@ export class Variants { 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< - { - [K in keyof VariantValues]: { - selection: K - // prettier-ignore - value: - VariantValues[K]["spec"] extends (InputSpec | InputSpec) ? B : - never - } - }[keyof VariantValues], - Store - >(async (options) => { + return new Variants(async (options) => { const variants = {} as { [K in keyof VariantValues]: { name: string @@ -118,6 +161,6 @@ export class Variants { ``` */ withStore() { - return this as any as Variants + return this as any as Variants } } diff --git a/sdk/base/lib/actions/input/inputSpecConstants.ts b/sdk/base/lib/actions/input/inputSpecConstants.ts index 3beaefd51..57bf8a79b 100644 --- a/sdk/base/lib/actions/input/inputSpecConstants.ts +++ b/sdk/base/lib/actions/input/inputSpecConstants.ts @@ -10,35 +10,34 @@ import { Variants } from "./builder/variants" 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: [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, }), }) @@ -54,7 +53,7 @@ export const smtpInputSpec = Value.filteredUnion( { name: "SMTP", description: "Optionally provide an SMTP server for sending emails", - required: { default: "disabled" }, + default: "disabled", }, Variants.of({ disabled: { name: "Disabled", spec: InputSpec.of({}) }, @@ -66,6 +65,7 @@ export const smtpInputSpec = Value.filteredUnion( 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: [Patterns.email], diff --git a/sdk/base/lib/actions/input/inputSpecTypes.ts b/sdk/base/lib/actions/input/inputSpecTypes.ts index ee9189ae3..362a56ea1 100644 --- a/sdk/base/lib/actions/input/inputSpecTypes.ts +++ b/sdk/base/lib/actions/input/inputSpecTypes.ts @@ -115,7 +115,6 @@ export type ValueSpecSelect = { description: string | null warning: string | null type: "select" - required: boolean default: string | null disabled: false | string | string[] immutable: boolean @@ -158,7 +157,6 @@ export type ValueSpecUnion = { } > disabled: false | string | string[] - required: boolean default: string | null immutable: boolean } diff --git a/sdk/base/lib/actions/setupActions.ts b/sdk/base/lib/actions/setupActions.ts index 62d6cedd9..081225569 100644 --- a/sdk/base/lib/actions/setupActions.ts +++ b/sdk/base/lib/actions/setupActions.ts @@ -1,5 +1,8 @@ import { InputSpec } from "./input/builder" -import { ExtractInputSpecType } from "./input/builder/inputSpec" +import { + ExtractInputSpecType, + ExtractPartialInputSpecType, +} from "./input/builder/inputSpec" import * as T from "../types" import { once } from "../util" @@ -20,7 +23,10 @@ export type GetInput< > = (options: { effects: T.Effects }) => Promise< - null | void | undefined | (ExtractInputSpecType & Record) + | null + | void + | undefined + | (ExtractPartialInputSpecType & Record) > export type MaybeFn = T | ((options: { effects: T.Effects }) => Promise) @@ -52,15 +58,13 @@ export class Action< | Record | InputSpec | InputSpec, - Type extends - ExtractInputSpecType = ExtractInputSpecType, > { private constructor( readonly id: Id, private readonly metadataFn: MaybeFn, private readonly inputSpec: InputSpecType, - private readonly getInputFn: GetInput, - private readonly runFn: Run, + private readonly getInputFn: GetInput>, + private readonly runFn: Run>, ) {} static withInput< Id extends T.ActionId, @@ -69,15 +73,13 @@ export class Action< | Record | InputSpec | InputSpec, - Type extends - ExtractInputSpecType = ExtractInputSpecType, >( id: Id, metadata: MaybeFn>, inputSpec: InputSpecType, - getInput: GetInput, - run: Run, - ): Action { + getInput: GetInput>, + run: Run>, + ): Action { return new Action( id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })), @@ -90,7 +92,7 @@ export class Action< id: Id, metadata: MaybeFn>, run: Run<{}>, - ): Action { + ): Action { return new Action( id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })), @@ -114,7 +116,7 @@ export class Action< } async run(options: { effects: T.Effects - input: Type + input: ExtractInputSpecType }): Promise { return (await this.runFn(options)) || null } @@ -122,13 +124,13 @@ export class Action< export class Actions< Store, - AllActions extends Record>, + AllActions extends Record>, > { private constructor(private readonly actions: AllActions) {} static of(): Actions { return new Actions({}) } - addAction>( + addAction>( action: A, ): Actions { return new Actions({ ...this.actions, [action.id]: action }) diff --git a/sdk/base/lib/types.ts b/sdk/base/lib/types.ts index d7ab1e51b..ab1acaa87 100644 --- a/sdk/base/lib/types.ts +++ b/sdk/base/lib/types.ts @@ -86,7 +86,7 @@ export namespace ExpectedExports { export type actions = Actions< any, - Record> + Record> > } export type ABI = { diff --git a/sdk/base/package-lock.json b/sdk/base/package-lock.json index 91c470676..4c8f58364 100644 --- a/sdk/base/package-lock.json +++ b/sdk/base/package-lock.json @@ -14,7 +14,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2" }, "devDependencies": { @@ -3897,9 +3897,10 @@ "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==" + "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", diff --git a/sdk/base/package.json b/sdk/base/package.json index e38cc5ac6..509e621de 100644 --- a/sdk/base/package.json +++ b/sdk/base/package.json @@ -27,7 +27,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2" }, "prettier": { diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index f41961d16..28a14e1e5 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -1,7 +1,4 @@ -import { - RequiredDefault, - Value, -} from "../../base/lib/actions/input/builder/value" +import { Value } from "../../base/lib/actions/input/builder/value" import { InputSpec, ExtractInputSpecType, @@ -141,9 +138,7 @@ export class StartSdk { ...startSdkEffectWrapper, action: { run: actions.runAction, - request: < - T extends Action>, - >( + request: >( effects: T.Effects, packageId: T.PackageId, action: T, @@ -157,9 +152,7 @@ export class StartSdk { severity, options: options, }), - requestOwn: < - T extends Action>, - >( + requestOwn: >( effects: T.Effects, action: T, severity: T.ActionSeverity, @@ -1060,14 +1053,14 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | RandomString | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'World' } - * @example required: { default: { charset: 'abcdefg', len: 16 } } + * @description optionally provide a default value. + * @type { string | RandomString | null } + * @example default: null + * @example default: 'World' + * @example default: { charset: 'abcdefg', len: 16 } */ - required: RequiredDefault + default: DefaultString | null + required: boolean /** * @description Mask (aka camouflage) text input with dots: ● ● ● * @default false @@ -1110,15 +1103,12 @@ export class StartSdk { description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - /** - * @description Unlike other "required" fields, for textarea this is a simple boolean. - */ + default: string | null required: boolean minLength?: number | null maxLength?: number | null placeholder?: string | null disabled?: false | string - generate?: null | RandomString } >, ) => Value.dynamicTextarea(getA), @@ -1131,13 +1121,13 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: number | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 7 } + * @description optionally provide a default value. + * @type { number | null } + * @example default: null + * @example default: 7 */ - required: RequiredDefault + default: number | null + required: boolean min?: number | null max?: number | null /** @@ -1167,13 +1157,13 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'ffffff' } + * @description optionally provide a default value. + * @type { string | null } + * @example default: null + * @example default: 'ffffff' */ - required: RequiredDefault + default: string | null + required: boolean disabled?: false | string } >, @@ -1187,13 +1177,13 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: '1985-12-16 18:00:00.000' } + * @description optionally provide a default value. + * @type { string | null } + * @example default: null + * @example default: '1985-12-16 18:00:00.000' */ - required: RequiredDefault + default: string + required: boolean /** * @description Informs the browser how to behave and which date/time component to display. * @default "datetime-local" @@ -1205,7 +1195,7 @@ export class StartSdk { } >, ) => Value.dynamicDatetime(getA), - dynamicSelect: ( + dynamicSelect: >( getA: LazyBuild< Store, { @@ -1214,13 +1204,12 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value from the list of values. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'radio1' } + * @description provide a default value from the list of values. + * @type { default: string } + * @example default: 'radio1' */ - required: RequiredDefault + default: keyof Variants & string + required: boolean /** * @description A mapping of unique radio options to their human readable display format. * @example @@ -1232,7 +1221,7 @@ export class StartSdk { } * ``` */ - values: Record + values: Variants /** * @options * - false - The field can be modified. @@ -1282,27 +1271,37 @@ export class StartSdk { >, ) => Value.dynamicMultiselect(getA), filteredUnion: < - Required extends RequiredDefault, - Type extends Record, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, >( getDisabledFn: LazyBuild, a: { name: string description?: string | null warning?: string | null - required: Required + default: keyof VariantValues & string }, - aVariants: Variants | Variants, + aVariants: + | Variants + | Variants, ) => - Value.filteredUnion( + Value.filteredUnion( getDisabledFn, a, aVariants, ), dynamicUnion: < - Required extends RequiredDefault, - Type extends Record, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, >( getA: LazyBuild< Store, @@ -1312,13 +1311,12 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value from the list of variants. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'variant1' } + * @description provide a default value from the list of variants. + * @type { string } + * @example default: 'variant1' */ - required: Required + default: keyof VariantValues & string + required: boolean /** * @options * - false - The field can be modified. @@ -1329,8 +1327,10 @@ export class StartSdk { disabled: false | string | string[] } >, - aVariants: Variants | Variants, - ) => Value.dynamicUnion(getA, aVariants), + aVariants: + | Variants + | Variants, + ) => Value.dynamicUnion(getA, aVariants), }, Variants: { of: < diff --git a/sdk/package/lib/test/inputSpecBuilder.test.ts b/sdk/package/lib/test/inputSpecBuilder.test.ts index f9d4321a6..195acb40a 100644 --- a/sdk/package/lib/test/inputSpecBuilder.test.ts +++ b/sdk/package/lib/test/inputSpecBuilder.test.ts @@ -17,7 +17,8 @@ describe("builder tests", () => { "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({ @@ -250,7 +259,7 @@ describe("values", () => { const value = Value.union( { name: "Testing", - required: { default: null }, + default: "a", description: null, warning: null, }, @@ -271,7 +280,16 @@ 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", () => { @@ -301,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) @@ -317,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") @@ -333,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) @@ -349,6 +370,7 @@ describe("values", () => { const value = Value.dynamicColor(async () => ({ name: "Testing", required: false, + default: null, description: null, warning: null, })) @@ -414,7 +436,8 @@ describe("values", () => { return { name: "Testing", - required: { default: null }, + required: true, + default: null, inputmode: "date", } }, @@ -436,6 +459,7 @@ describe("values", () => { const value = Value.dynamicTextarea(async () => ({ name: "Testing", required: false, + default: null, description: null, warning: null, minLength: null, @@ -444,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, @@ -454,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, @@ -477,7 +501,7 @@ describe("values", () => { test("select", async () => { const value = Value.dynamicSelect(() => ({ name: "Testing", - required: { default: null }, + default: "a", values: { a: "A", b: "B", @@ -489,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 () => { @@ -529,7 +551,7 @@ describe("values", () => { () => ["a", "c"], { name: "Testing", - required: { default: null }, + default: "a", description: null, warning: null, }, @@ -563,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) @@ -596,7 +638,7 @@ describe("values", () => { () => ({ disabled: ["a", "c"], name: "Testing", - required: { default: null }, + default: "b", description: null, warning: null, }), @@ -630,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) @@ -728,6 +788,7 @@ describe("Nested nullable values", () => { description: "If no name is provided, the name from inputSpec will be used", required: false, + default: null, }), }) const validator = value.validator @@ -743,6 +804,7 @@ describe("Nested nullable values", () => { description: "If no name is provided, the name from inputSpec will be used", required: false, + default: null, warning: null, placeholder: null, integer: false, @@ -765,6 +827,7 @@ describe("Nested nullable values", () => { description: "If no name is provided, the name from inputSpec will be used", required: false, + default: null, warning: null, }), }) @@ -780,7 +843,7 @@ describe("Nested nullable values", () => { name: "Temp Name", description: "If no name is provided, the name from inputSpec will be used", - required: false, + default: "a", warning: null, values: { a: "A", @@ -791,7 +854,7 @@ describe("Nested nullable values", () => { name: "Temp Name", description: "If no name is provided, the name from inputSpec will be used", - required: false, + default: "a", warning: null, values: { a: "A", @@ -799,10 +862,9 @@ 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 = InputSpec.of({ diff --git a/sdk/package/lib/test/output.test.ts b/sdk/package/lib/test/output.test.ts index 37636e52f..53d006274 100644 --- a/sdk/package/lib/test/output.test.ts +++ b/sdk/package/lib/test/output.test.ts @@ -87,7 +87,7 @@ describe("Inputs", () => { dbcache: 5, pruning: { selection: "disabled", - value: {}, + value: { disabled: {} }, }, blockfilters: { blockfilterindex: false, diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index 615bcfdc9..80e5b3564 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -80,7 +80,8 @@ export class FileHelper { protected constructor( readonly path: string, readonly writeData: (dataIn: A) => string, - readonly readData: (stringValue: string) => A, + readonly readData: (stringValue: string) => unknown, + readonly validate: (value: unknown) => A, ) {} /** @@ -97,10 +98,7 @@ export class FileHelper { return null } - /** - * Reads the file from disk and converts it to structured data. - */ - private async readOnce(): Promise { + private async readFile(): Promise { if (!(await exists(this.path))) { return null } @@ -109,6 +107,15 @@ export class FileHelper { ) } + /** + * 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() @@ -156,22 +163,22 @@ export class FileHelper { * 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.readOnce()) || {} + const fileData = (await this.readFile()) || {} const mergeData = merge({}, fileData, data) - return await this.writeFile(mergeData) + return await this.writeFile(this.validate(mergeData)) } /** * Accepts partial structured data and performs a merge with the existing file on disk. */ - async merge(data: Partial) { + async merge(data: T.DeepPartial) { const fileData = - (await this.readOnce()) || + (await this.readFile()) || (() => { throw new Error(`${this.path}: does not exist`) })() const mergeData = merge({}, fileData, data) - return await this.writeFile(mergeData) + return await this.writeFile(this.validate(mergeData)) } /** @@ -179,7 +186,7 @@ export class FileHelper { * Like one behaviour of another dependency or something similar. */ withPath(path: string) { - return new FileHelper(path, this.writeData, this.readData) + return new FileHelper(path, this.writeData, this.readData, this.validate) } /** @@ -190,9 +197,10 @@ export class FileHelper { static raw( path: string, toFile: (dataIn: A) => string, - fromFile: (rawData: string) => A, + fromFile: (rawData: string) => unknown, + validate: (data: unknown) => A, ) { - return new FileHelper(path, toFile, fromFile) + return new FileHelper(path, toFile, fromFile, validate) } /** * Create a File Helper for a .json file. @@ -200,12 +208,9 @@ export class FileHelper { 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)) - }, + (inData) => JSON.stringify(inData, null, 2), + (inString) => JSON.parse(inString), + (data) => shape.unsafeCast(data), ) } /** @@ -217,12 +222,9 @@ export class FileHelper { ) { return new FileHelper( path, - (inData) => { - return TOML.stringify(inData as any) - }, - (inString) => { - return shape.unsafeCast(TOML.parse(inString)) - }, + (inData) => TOML.stringify(inData as any), + (inString) => TOML.parse(inString), + (data) => shape.unsafeCast(data), ) } /** @@ -234,12 +236,9 @@ export class FileHelper { ) { return new FileHelper( path, - (inData) => { - return YAML.stringify(inData, null, 2) - }, - (inString) => { - return shape.unsafeCast(YAML.parse(inString)) - }, + (inData) => YAML.stringify(inData, null, 2), + (inString) => YAML.parse(inString), + (data) => shape.unsafeCast(data), ) } } diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index 2cc87d374..0b06e79db 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,12 +1,12 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.13", + "version": "0.3.6-alpha.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.13", + "version": "0.3.6-alpha.16", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -15,7 +15,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2" }, "devDependencies": { @@ -3918,9 +3918,10 @@ "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==" + "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", diff --git a/sdk/package/package.json b/sdk/package/package.json index 661263f70..bbcab9830 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.13", + "version": "0.3.6-alpha.17", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", @@ -33,7 +33,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2", "@iarna/toml": "^2.2.5", "@noble/curves": "^1.4.0", diff --git a/sdk/package/scripts/oldSpecToBuilder.ts b/sdk/package/scripts/oldSpecToBuilder.ts index 04128f2bd..11ef30340 100644 --- a/sdk/package/scripts/oldSpecToBuilder.ts +++ b/sdk/package/scripts/oldSpecToBuilder.ts @@ -85,6 +85,7 @@ const {InputSpec, 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 {InputSpec, 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 {InputSpec, 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 {InputSpec, 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, @@ -207,14 +194,7 @@ const {InputSpec, 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": { @@ -341,12 +321,7 @@ const {InputSpec, 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}) `, ) 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 743d3546b..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 @@ -268,25 +268,29 @@ const cifsSpec = ISB.InputSpec.of({ '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: 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: 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: 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/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 }}*