diff --git a/container-runtime/src/Adapters/EffectCreator.ts b/container-runtime/src/Adapters/EffectCreator.ts index 725e8d22c..ed975f25e 100644 --- a/container-runtime/src/Adapters/EffectCreator.ts +++ b/container-runtime/src/Adapters/EffectCreator.ts @@ -36,7 +36,7 @@ let hostSystemId = 0 export type EffectContext = { procedureId: string | null callbacks?: CallbackHolder - constRetry: () => void + constRetry?: () => void } const rpcRoundFor = diff --git a/container-runtime/src/Adapters/RpcListener.ts b/container-runtime/src/Adapters/RpcListener.ts index c2dc8bafe..dade89ad2 100644 --- a/container-runtime/src/Adapters/RpcListener.ts +++ b/container-runtime/src/Adapters/RpcListener.ts @@ -306,7 +306,6 @@ export class RpcListener { const effects = makeEffects({ procedureId: null, callbacks, - constRetry: () => {}, }) return handleRpc( id, @@ -337,7 +336,6 @@ export class RpcListener { this.callbacks = new CallbackHolder( makeEffects({ procedureId: null, - constRetry: () => {}, }), ) const callbacks = this.callbackHolderFor("containerInit") @@ -345,7 +343,6 @@ export class RpcListener { makeEffects({ procedureId: null, callbacks, - constRetry: () => {}, }), ) this._system = system @@ -427,7 +424,6 @@ export class RpcListener { const effects = makeEffects({ procedureId, callbacks, - constRetry: () => {}, }) return (async () => { diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 806216786..941e21ac8 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -62,7 +62,7 @@ export class DockerProcedureContainer { ) } else if (volumeMount.type === "assets") { await subcontainer.mount( - { type: "assets", id: mount, subpath: null }, + { type: "assets", subpath: mount }, mounts[mount], ) } else if (volumeMount.type === "certificate") { diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts index bec5b092c..0099bea92 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts @@ -170,7 +170,7 @@ export const polyfillEffects = ( { mounts: [ { - path: "/drive", + mountpoint: "/drive", options: { type: "volume", id: input.volumeId, @@ -212,7 +212,7 @@ export const polyfillEffects = ( { mounts: [ { - path: "/drive", + mountpoint: "/drive", options: { type: "volume", id: input.volumeId, diff --git a/core/Cargo.lock b/core/Cargo.lock index db92c1ff9..a68b20515 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5952,7 +5952,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "start-os" -version = "0.3.6-alpha.15" +version = "0.3.6-alpha.16" dependencies = [ "aes 0.7.5", "async-acme", diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index dbcaf2a78..a04df93fd 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.15" # VERSION_BUMP +version = "0.3.6-alpha.16" # VERSION_BUMP license = "MIT" [lib] diff --git a/core/startos/src/net/network_interface.rs b/core/startos/src/net/network_interface.rs index 8c99f0de9..c870629d4 100644 --- a/core/startos/src/net/network_interface.rs +++ b/core/startos/src/net/network_interface.rs @@ -1021,6 +1021,15 @@ impl ListenerMap { fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll> { for (bind_addr, listener) in self.listeners.iter() { if let Poll::Ready((stream, addr)) = listener.0.poll_accept(cx)? { + if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive( + &socket2::TcpKeepalive::new() + .with_time(Duration::from_secs(900)) + .with_interval(Duration::from_secs(60)) + .with_retries(5), + ) { + tracing::error!("Failed to set tcp keepalive: {e}"); + tracing::debug!("{e:?}"); + } return Poll::Ready(Ok(Accepted { stream, peer: addr, diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index cbc1cc916..6317fa8c9 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -258,16 +258,6 @@ impl VHostServer { } } - if let Err(e) = socket2::SockRef::from(&accepted.stream).set_tcp_keepalive( - &socket2::TcpKeepalive::new() - .with_time(Duration::from_secs(900)) - .with_interval(Duration::from_secs(60)) - .with_retries(5), - ) { - tracing::error!("Failed to set tcp keepalive: {e}"); - tracing::debug!("{e:?}"); - } - tokio::spawn(async move { let bind = accepted.bind; if let Err(e) = diff --git a/core/startos/src/s9pk/merkle_archive/expected.rs b/core/startos/src/s9pk/merkle_archive/expected.rs index c9a2fd31b..71088f243 100644 --- a/core/startos/src/s9pk/merkle_archive/expected.rs +++ b/core/startos/src/s9pk/merkle_archive/expected.rs @@ -36,6 +36,25 @@ impl<'a, T: Clone> Expected<'a, T> { )) } } + pub fn check_dir(&mut self, path: impl AsRef) -> Result<(), Error> { + if let Some(dir) = self + .dir + .get_path(path.as_ref()) + .and_then(|e| e.as_directory()) + { + for entry in dir.file_paths(path.as_ref()) { + if !entry.to_string_lossy().ends_with("/") { + self.keep.insert_path(entry, Entry::file(()))?; + } + } + Ok(()) + } else { + Err(Error::new( + eyre!("directory {} missing from archive", path.as_ref().display()), + ErrorKind::ParseS9pk, + )) + } + } pub fn check_stem( &mut self, path: impl AsRef, diff --git a/core/startos/src/s9pk/v2/compat.rs b/core/startos/src/s9pk/v2/compat.rs index db8cbc414..e1253a373 100644 --- a/core/startos/src/s9pk/v2/compat.rs +++ b/core/startos/src/s9pk/v2/compat.rs @@ -129,25 +129,16 @@ impl S9pk> { tokio_tar::Archive::new(reader.assets().await?) .unpack(&asset_dir) .await?; - for (asset_id, _) in manifest - .volumes - .iter() - .filter(|(_, v)| v.get("type").and_then(|v| v.as_str()) == Some("assets")) - { - let assets_path = asset_dir.join(&asset_id); - let sqfs_path = assets_path.with_extension("squashfs"); - Command::new("mksquashfs") - .arg(&assets_path) - .arg(&sqfs_path) - .invoke(ErrorKind::Filesystem) - .await?; - archive.insert_path( - Path::new("assets") - .join(&asset_id) - .with_extension("squashfs"), - Entry::file(TmpSource::new(tmp_dir.clone(), PackSource::File(sqfs_path))), - )?; - } + let sqfs_path = asset_dir.with_extension("squashfs"); + Command::new("mksquashfs") + .arg(&asset_dir) + .arg(&sqfs_path) + .invoke(ErrorKind::Filesystem) + .await?; + archive.insert_path( + "assets.squashfs", + Entry::file(TmpSource::new(tmp_dir.clone(), PackSource::File(sqfs_path))), + )?; // javascript let js_dir = tmp_dir.join("javascript"); @@ -217,12 +208,6 @@ impl TryFrom for Manifest { donation_url: value.donation_url, description: value.description, images: BTreeMap::new(), - assets: value - .volumes - .iter() - .filter(|(_, v)| v.get("type").and_then(|v| v.as_str()) == Some("assets")) - .map(|(id, _)| id.clone()) - .collect(), volumes: value .volumes .iter() diff --git a/core/startos/src/s9pk/v2/manifest.rs b/core/startos/src/s9pk/v2/manifest.rs index 11ea0d9af..6b3b11bd9 100644 --- a/core/startos/src/s9pk/v2/manifest.rs +++ b/core/startos/src/s9pk/v2/manifest.rs @@ -53,7 +53,6 @@ pub struct Manifest { pub donation_url: Option, pub description: Description, pub images: BTreeMap, - pub assets: BTreeSet, // TODO: AssetsId pub volumes: BTreeSet, #[serde(default)] pub alerts: Alerts, @@ -93,8 +92,11 @@ impl Manifest { .map_or(false, |mime| mime.starts_with("image/")) }); } - for assets in &self.assets { - expected.check_file(Path::new("assets").join(assets).with_extension("squashfs"))?; + if let Err(e) = expected.check_file(Path::new("assets.squashfs")) { + // backwards compatibility for alpha s9pks - remove eventually + if expected.check_dir("assets").is_err() { + return Err(e); + } } for (image_id, config) in &self.images { let mut check_arch = |arch: &str| { diff --git a/core/startos/src/s9pk/v2/mod.rs b/core/startos/src/s9pk/v2/mod.rs index ecab202ce..852e7a21d 100644 --- a/core/startos/src/s9pk/v2/mod.rs +++ b/core/startos/src/s9pk/v2/mod.rs @@ -60,7 +60,7 @@ fn priority(s: &str) -> Option { "instructions.md" => Some(3), "dependencies" => Some(4), "javascript.squashfs" => Some(5), - "assets" => Some(6), + "assets.squashfs" => Some(6), "images" => Some(7), _ => None, } diff --git a/core/startos/src/s9pk/v2/pack.rs b/core/startos/src/s9pk/v2/pack.rs index eb3aaa186..f16649ece 100644 --- a/core/startos/src/s9pk/v2/pack.rs +++ b/core/startos/src/s9pk/v2/pack.rs @@ -694,18 +694,13 @@ pub async fn pack(ctx: CliContext, params: PackParams) -> Result<(), Error> { .await?; let assets_dir = params.assets(); - for assets in s9pk.as_manifest().assets.clone() { - s9pk.as_archive_mut().contents_mut().insert_path( - Path::new("assets").join(&assets).with_extension("squashfs"), - Entry::file(TmpSource::new( - tmp_dir.clone(), - PackSource::Squashfs(Arc::new(SqfsDir::new( - assets_dir.join(&assets), - tmp_dir.clone(), - ))), - )), - )?; - } + s9pk.as_archive_mut().contents_mut().insert_path( + "assets.squashfs", + Entry::file(TmpSource::new( + tmp_dir.clone(), + PackSource::Squashfs(Arc::new(SqfsDir::new(assets_dir, tmp_dir.clone()))), + )), + )?; s9pk.load_images(tmp_dir.clone()).await?; @@ -816,9 +811,7 @@ pub async fn list_ingredients(_: CliContext, params: PackParams) -> Result>, js_mount: MountGuard, volumes: BTreeMap, - assets: BTreeMap, + assets: Vec, pub(super) images: BTreeMap>, pub(super) subcontainers: Arc>>, pub(super) state: Arc>, @@ -168,35 +168,63 @@ impl PersistentContainer { .await?; volumes.insert(volume.clone(), mount); } - let mut assets = BTreeMap::new(); - for asset in &s9pk.as_manifest().assets { - let mountpoint = lxc_container - .rootfs_dir() - .join("media/startos/assets") - .join(asset); - tokio::fs::create_dir_all(&mountpoint).await?; - Command::new("chown") - .arg("100000:100000") - .arg(&mountpoint) - .invoke(crate::ErrorKind::Filesystem) - .await?; - let s9pk_asset_path = Path::new("assets").join(asset).with_extension("squashfs"); - let sqfs = s9pk - .as_archive() - .contents() - .get_path(&s9pk_asset_path) - .and_then(|e| e.as_file()) - .or_not_found(s9pk_asset_path.display())?; - assets.insert( - asset.clone(), + + let mountpoint = lxc_container.rootfs_dir().join("media/startos/assets"); + tokio::fs::create_dir_all(&mountpoint).await?; + Command::new("chown") + .arg("100000:100000") + .arg(&mountpoint) + .invoke(crate::ErrorKind::Filesystem) + .await?; + let assets = if let Some(sqfs) = s9pk + .as_archive() + .contents() + .get_path("assets.squashfs") + .and_then(|e| e.as_file()) + { + vec![ MountGuard::mount( &IdMapped::new(LoopDev::from(&**sqfs), 0, 100000, 65536), mountpoint, MountType::ReadWrite, ) .await?, - ); - } + ] + } else if let Some(dir) = s9pk + .as_archive() + .contents() + .get_path("assets") + .and_then(|e| e.as_directory()) + { + // backwards compatibility for alpha s9pks - remove eventually + let mut assets = Vec::new(); + for (asset, entry) in &**dir { + let mountpoint = lxc_container + .rootfs_dir() + .join("media/startos/assets") + .join(Path::new(asset).with_extension("")); + tokio::fs::create_dir_all(&mountpoint).await?; + Command::new("chown") + .arg("100000:100000") + .arg(&mountpoint) + .invoke(crate::ErrorKind::Filesystem) + .await?; + let Some(sqfs) = entry.as_file() else { + continue; + }; + assets.push( + MountGuard::mount( + &IdMapped::new(LoopDev::from(&**sqfs), 0, 100000, 65536), + mountpoint, + MountType::ReadWrite, + ) + .await?, + ); + } + assets + } else { + Vec::new() + }; let mut images = BTreeMap::new(); let image_path = lxc_container.rootfs_dir().join("media/startos/images"); @@ -432,7 +460,7 @@ impl PersistentContainer { for (_, volume) in volumes { errs.handle(volume.unmount(true).await); } - for (_, assets) in assets { + for assets in assets { errs.handle(assets.unmount(true).await); } for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) { diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index eeed45ee5..01f9c5e86 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -35,8 +35,9 @@ mod v0_3_6_alpha_12; mod v0_3_6_alpha_13; mod v0_3_6_alpha_14; mod v0_3_6_alpha_15; +mod v0_3_6_alpha_16; -pub type Current = v0_3_6_alpha_15::Version; // VERSION_BUMP +pub type Current = v0_3_6_alpha_16::Version; // VERSION_BUMP impl Current { #[instrument(skip(self, db))] @@ -133,7 +134,8 @@ enum Version { V0_3_6_alpha_12(Wrapper), V0_3_6_alpha_13(Wrapper), V0_3_6_alpha_14(Wrapper), - V0_3_6_alpha_15(Wrapper), // VERSION_BUMP + V0_3_6_alpha_15(Wrapper), + V0_3_6_alpha_16(Wrapper), // VERSION_BUMP Other(exver::Version), } @@ -172,7 +174,8 @@ impl Version { Self::V0_3_6_alpha_12(v) => DynVersion(Box::new(v.0)), Self::V0_3_6_alpha_13(v) => DynVersion(Box::new(v.0)), Self::V0_3_6_alpha_14(v) => DynVersion(Box::new(v.0)), - Self::V0_3_6_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP + Self::V0_3_6_alpha_15(v) => DynVersion(Box::new(v.0)), + Self::V0_3_6_alpha_16(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP Self::Other(v) => { return Err(Error::new( eyre!("unknown version {v}"), @@ -203,7 +206,8 @@ impl Version { Version::V0_3_6_alpha_12(Wrapper(x)) => x.semver(), Version::V0_3_6_alpha_13(Wrapper(x)) => x.semver(), Version::V0_3_6_alpha_14(Wrapper(x)) => x.semver(), - Version::V0_3_6_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP + Version::V0_3_6_alpha_15(Wrapper(x)) => x.semver(), + Version::V0_3_6_alpha_16(Wrapper(x)) => x.semver(), // VERSION_BUMP Version::Other(x) => x.clone(), } } diff --git a/core/startos/src/version/v0_3_6_alpha_16.rs b/core/startos/src/version/v0_3_6_alpha_16.rs new file mode 100644 index 000000000..84017de7e --- /dev/null +++ b/core/startos/src/version/v0_3_6_alpha_16.rs @@ -0,0 +1,36 @@ +use exver::{PreReleaseSegment, VersionRange}; + +use super::v0_3_5::V0_3_0_COMPAT; +use super::{v0_3_6_alpha_15, VersionT}; +use crate::prelude::*; + +lazy_static::lazy_static! { + static ref V0_3_6_alpha_16: exver::Version = exver::Version::new( + [0, 3, 6], + [PreReleaseSegment::String("alpha".into()), 16.into()] + ); +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Version; + +impl VersionT for Version { + type Previous = v0_3_6_alpha_15::Version; + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) + } + fn semver(self) -> exver::Version { + V0_3_6_alpha_16.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/sdk/Makefile b/sdk/Makefile index 3f8ae533a..8cd295656 100644 --- a/sdk/Makefile +++ b/sdk/Makefile @@ -58,11 +58,16 @@ check: fmt: package/node_modules base/node_modules npx prettier . "**/*.ts" --write +package/package-lock.json: package/package.json + cd package && npm i -package/node_modules: package/package.json +base/package-lock.json: base/package.json + cd base && npm i + +package/node_modules: package/package-lock.json cd package && npm ci -base/node_modules: base/package.json +base/node_modules: base/package-lock.json cd base && npm ci node_modules: package/node_modules base/node_modules diff --git a/sdk/base/lib/Effects.ts b/sdk/base/lib/Effects.ts index 135a2942c..b7c67af12 100644 --- a/sdk/base/lib/Effects.ts +++ b/sdk/base/lib/Effects.ts @@ -28,7 +28,7 @@ import { UrlString } from "./util/getServiceInterface" /** Used to reach out from the pure js runtime */ export type Effects = { - constRetry: () => void + constRetry?: () => void clearCallbacks: ( options: { only: number[] } | { except: number[] }, ) => Promise diff --git a/sdk/base/lib/exver/index.ts b/sdk/base/lib/exver/index.ts index 5ec5bccfd..c855e1534 100644 --- a/sdk/base/lib/exver/index.ts +++ b/sdk/base/lib/exver/index.ts @@ -1,4 +1,4 @@ -import { DeepMap } from "deep-equality-data-structures"; +import { DeepMap } from "deep-equality-data-structures" import * as P from "./exver" // prettier-ignore @@ -45,16 +45,16 @@ type Not = { } type Flavor = { - type: "Flavor", - flavor: string | null, + type: "Flavor" + flavor: string | null } type FlavorNot = { - type: "FlavorNot", - flavors: Set, + type: "FlavorNot" + flavors: Set } -type FlavorAtom = Flavor | FlavorNot; +type FlavorAtom = Flavor | FlavorNot /** * Splits a number line of versions in half, so that every possible semver is either to the left or right. @@ -65,66 +65,75 @@ type FlavorAtom = Flavor | FlavorNot; * for side=+1 the point is like `1.2.3.0.0.**.1` (that is, 1.2.3.0.0.** is less). */ type VersionRangePoint = { - upstream: Version, - downstream: Version, - side: -1 | 1; + upstream: Version + downstream: Version + side: -1 | 1 } -function compareVersionRangePoints(a: VersionRangePoint, b: VersionRangePoint): -1 | 0 | 1 { - let up = a.upstream.compareForSort(b.upstream); +function compareVersionRangePoints( + a: VersionRangePoint, + b: VersionRangePoint, +): -1 | 0 | 1 { + let up = a.upstream.compareForSort(b.upstream) if (up != 0) { - return up; + return up } - let down = a.upstream.compareForSort(b.upstream); + let down = a.upstream.compareForSort(b.upstream) if (down != 0) { - return down; + return down } if (a.side < b.side) { - return -1; + return -1 } else if (a.side > b.side) { - return 1; + return 1 } else { - return 0; + return 0 } } -function adjacentVersionRangePoints(a: VersionRangePoint, b: VersionRangePoint): boolean { - let up = a.upstream.compareForSort(b.upstream); +function adjacentVersionRangePoints( + a: VersionRangePoint, + b: VersionRangePoint, +): boolean { + let up = a.upstream.compareForSort(b.upstream) if (up != 0) { - return false; + return false } - let down = a.upstream.compareForSort(b.upstream); + let down = a.upstream.compareForSort(b.upstream) if (down != 0) { - return false; + return false } - return a.side == -1 && b.side == 1; + return a.side == -1 && b.side == 1 } function flavorAnd(a: FlavorAtom, b: FlavorAtom): FlavorAtom | null { - if (a.type == 'Flavor') { - if (b.type == 'Flavor') { + if (a.type == "Flavor") { + if (b.type == "Flavor") { if (a.flavor == b.flavor) { - return a; + return a } else { - return null; + return null } } else { if (b.flavors.has(a.flavor)) { - return null; + return null } else { - return a; + return a } } } else { - if (b.type == 'Flavor') { + if (b.type == "Flavor") { if (a.flavors.has(b.flavor)) { - return null; + return null } else { - return b; + return b } } else { // TODO: use Set.union if targeting esnext or later - return { type: 'FlavorNot', flavors: new Set([...a.flavors, ...b.flavors]) }; + return { + type: "FlavorNot", + flavors: new Set([...a.flavors, ...b.flavors]), + } } } } @@ -134,62 +143,69 @@ function flavorAnd(a: FlavorAtom, b: FlavorAtom): FlavorAtom | null { * is quite straightforward. But in order to exhaustively enumerate the boolean values of every * combination of flavors and versions we also need tables for flavor negations. */ -type VersionRangeTables = DeepMap | boolean; +type VersionRangeTables = DeepMap | boolean /** * A truth table for version numbers. This is easiest to picture as a number line, cut up into * ranges of versions between version points. */ class VersionRangeTable { - private constructor(protected points: Array, protected values: boolean[]) {} + private constructor( + protected points: Array, + protected values: boolean[], + ) {} - static zip(a: VersionRangeTable, b: VersionRangeTable, func: (a: boolean, b: boolean) => boolean): VersionRangeTable { - let c = new VersionRangeTable([], []); - let i = 0; - let j = 0; + static zip( + a: VersionRangeTable, + b: VersionRangeTable, + func: (a: boolean, b: boolean) => boolean, + ): VersionRangeTable { + let c = new VersionRangeTable([], []) + let i = 0 + let j = 0 while (true) { - let next = func(a.values[i], b.values[j]); + let next = func(a.values[i], b.values[j]) if (c.values.length > 0 && c.values[c.values.length - 1] == next) { // collapse automatically - c.points.pop(); + c.points.pop() } else { - c.values.push(next); + c.values.push(next) } // which point do we step over? if (i == a.points.length) { if (j == b.points.length) { // just added the last segment, no point to jump over - return c; + return c } else { // i has reach the end, step over j - c.points.push(b.points[j]); - j += 1; + c.points.push(b.points[j]) + j += 1 } } else { if (j == b.points.length) { // j has reached the end, step over i - c.points.push(a.points[i]); - i += 1; + c.points.push(a.points[i]) + i += 1 } else { // depends on which of the next two points is lower switch (compareVersionRangePoints(a.points[i], b.points[j])) { case -1: // i is the lower point - c.points.push(a.points[i]); - i += 1; - break; + c.points.push(a.points[i]) + i += 1 + break case 1: // j is the lower point - c.points.push(b.points[j]); - j += 1; - break; + c.points.push(b.points[j]) + j += 1 + break default: // step over both - c.points.push(a.points[i]); - i += 1; - j += 1; - break; + c.points.push(a.points[i]) + i += 1 + j += 1 + break } } } @@ -201,98 +217,134 @@ class VersionRangeTable { */ static eqFlavor(flavor: string | null): VersionRangeTables { return new DeepMap([ - [{ type: 'Flavor', flavor } as FlavorAtom, new VersionRangeTable([], [true])], + [ + { type: "Flavor", flavor } as FlavorAtom, + new VersionRangeTable([], [true]), + ], // make sure the truth table is exhaustive, or `not` will not work properly. - [{ type: 'FlavorNot', flavors: new Set([flavor]) } as FlavorAtom, new VersionRangeTable([], [false])], - ]); + [ + { type: "FlavorNot", flavors: new Set([flavor]) } as FlavorAtom, + new VersionRangeTable([], [false]), + ], + ]) } /** * Creates a version table with exactly two ranges (to the left and right of the given point) and with `false` for any other flavor. * This is easiest to understand by looking at `VersionRange.tables`. */ - static cmpPoint(flavor: string | null, point: VersionRangePoint, left: boolean, right: boolean): VersionRangeTables { + static cmpPoint( + flavor: string | null, + point: VersionRangePoint, + left: boolean, + right: boolean, + ): VersionRangeTables { return new DeepMap([ - [{ type: 'Flavor', flavor } as FlavorAtom, new VersionRangeTable([point], [left, right])], + [ + { type: "Flavor", flavor } as FlavorAtom, + new VersionRangeTable([point], [left, right]), + ], // make sure the truth table is exhaustive, or `not` will not work properly. - [{ type: 'FlavorNot', flavors: new Set([flavor]) } as FlavorAtom, new VersionRangeTable([], [false])], - ]); + [ + { type: "FlavorNot", flavors: new Set([flavor]) } as FlavorAtom, + new VersionRangeTable([], [false]), + ], + ]) } /** * Helper for `cmpPoint`. */ - static cmp(version: ExtendedVersion, side: -1 | 1, left: boolean, right: boolean): VersionRangeTables { - return VersionRangeTable.cmpPoint(version.flavor, { upstream: version.upstream, downstream: version.downstream, side }, left, right) + static cmp( + version: ExtendedVersion, + side: -1 | 1, + left: boolean, + right: boolean, + ): VersionRangeTables { + return VersionRangeTable.cmpPoint( + version.flavor, + { upstream: version.upstream, downstream: version.downstream, side }, + left, + right, + ) } static not(tables: VersionRangeTables) { if (tables === true || tables === false) { - return !tables; + return !tables } // because tables are always exhaustive, we can simply invert each range for (let [f, t] of tables) { for (let i = 0; i < t.values.length; i++) { - t.values[i] = !t.values[i]; + t.values[i] = !t.values[i] } } - return tables; + return tables } - static and(a_tables: VersionRangeTables, b_tables: VersionRangeTables): VersionRangeTables { + static and( + a_tables: VersionRangeTables, + b_tables: VersionRangeTables, + ): VersionRangeTables { if (a_tables === true) { - return b_tables; + return b_tables } if (b_tables === true) { - return a_tables; + return a_tables } if (a_tables === false || b_tables == false) { - return false; + return false } - let c_tables: VersionRangeTables = true; + let c_tables: VersionRangeTables = true for (let [f_a, a] of a_tables) { for (let [f_b, b] of b_tables) { - let flavor = flavorAnd(f_a, f_b); + let flavor = flavorAnd(f_a, f_b) if (flavor == null) { - continue; + continue } - let c = VersionRangeTable.zip(a, b, (a, b) => a && b); + let c = VersionRangeTable.zip(a, b, (a, b) => a && b) if (c_tables === true) { - c_tables = new DeepMap(); + c_tables = new DeepMap() } - let prev_c = c_tables.get(flavor); + let prev_c = c_tables.get(flavor) if (prev_c == null) { - c_tables.set(flavor, c); + c_tables.set(flavor, c) } else { - c_tables.set(flavor, VersionRangeTable.zip(c, prev_c, (a, b) => a || b)); + c_tables.set( + flavor, + VersionRangeTable.zip(c, prev_c, (a, b) => a || b), + ) } } } - return c_tables; + return c_tables } static or(...in_tables: VersionRangeTables[]): VersionRangeTables { - let out_tables: VersionRangeTables = false; + let out_tables: VersionRangeTables = false for (let tables of in_tables) { if (tables === false) { - continue; + continue } if (tables === true) { - return true; + return true } if (out_tables === false) { - out_tables = new DeepMap(); + out_tables = new DeepMap() } for (let [flavor, table] of tables) { - let prev = out_tables.get(flavor); + let prev = out_tables.get(flavor) if (prev == null) { - out_tables.set(flavor, table); + out_tables.set(flavor, table) } else { - out_tables.set(flavor, VersionRangeTable.zip(table, prev, (a, b) => a || b)); + out_tables.set( + flavor, + VersionRangeTable.zip(table, prev, (a, b) => a || b), + ) } } } - return out_tables; + return out_tables } /** @@ -300,19 +352,19 @@ class VersionRangeTable { */ static collapse(tables: VersionRangeTables): boolean | null { if (tables === true || tables === false) { - return tables; + return tables } else { - let found = null; + let found = null for (let table of tables.values()) { for (let x of table.values) { if (found == null) { - found = x; + found = x } else if (found != x) { - return null; + return null } } } - return found; + return found } } @@ -321,65 +373,90 @@ class VersionRangeTable { * https://en.wikipedia.org/wiki/Canonical_normal_form#Minterms */ static minterms(tables: VersionRangeTables): VersionRange { - let collapse = VersionRangeTable.collapse(tables); + let collapse = VersionRangeTable.collapse(tables) if (tables === true || collapse === true) { return VersionRange.any() } if (tables == false || collapse === false) { return VersionRange.none() } - let sum_terms: VersionRange[] = []; + let sum_terms: VersionRange[] = [] for (let [flavor, table] of tables) { - let cmp_flavor = null; - if (flavor.type == 'Flavor') { - cmp_flavor = flavor.flavor; + let cmp_flavor = null + if (flavor.type == "Flavor") { + cmp_flavor = flavor.flavor } for (let i = 0; i < table.values.length; i++) { - let term: VersionRange[] = []; + let term: VersionRange[] = [] if (!table.values[i]) { continue } - if (flavor.type == 'FlavorNot') { + if (flavor.type == "FlavorNot") { for (let not_flavor of flavor.flavors) { - term.push(VersionRange.flavor(not_flavor).not()); + term.push(VersionRange.flavor(not_flavor).not()) } } - let p = null; - let q = null; + let p = null + let q = null if (i > 0) { - p = table.points[i - 1]; + p = table.points[i - 1] } if (i < table.points.length) { - q = table.points[i]; + q = table.points[i] } if (p != null && q != null && adjacentVersionRangePoints(p, q)) { - term.push(VersionRange.anchor('=', new ExtendedVersion(cmp_flavor, p.upstream, p.downstream))); + term.push( + VersionRange.anchor( + "=", + new ExtendedVersion(cmp_flavor, p.upstream, p.downstream), + ), + ) } else { if (p != null && p.side < 0) { - term.push(VersionRange.anchor('>=', new ExtendedVersion(cmp_flavor, p.upstream, p.downstream))); + term.push( + VersionRange.anchor( + ">=", + new ExtendedVersion(cmp_flavor, p.upstream, p.downstream), + ), + ) } if (p != null && p.side >= 0) { - term.push(VersionRange.anchor('>', new ExtendedVersion(cmp_flavor, p.upstream, p.downstream))); + term.push( + VersionRange.anchor( + ">", + new ExtendedVersion(cmp_flavor, p.upstream, p.downstream), + ), + ) } if (q != null && q.side < 0) { - term.push(VersionRange.anchor('<', new ExtendedVersion(cmp_flavor, q.upstream, q.downstream))); + term.push( + VersionRange.anchor( + "<", + new ExtendedVersion(cmp_flavor, q.upstream, q.downstream), + ), + ) } if (q != null && q.side >= 0) { - term.push(VersionRange.anchor('<=', new ExtendedVersion(cmp_flavor, q.upstream, q.downstream))); + term.push( + VersionRange.anchor( + "<=", + new ExtendedVersion(cmp_flavor, q.upstream, q.downstream), + ), + ) } } if (term.length == 0) { - term.push(VersionRange.flavor(cmp_flavor)); + term.push(VersionRange.flavor(cmp_flavor)) } - sum_terms.push(VersionRange.and(...term)); + sum_terms.push(VersionRange.and(...term)) } } - return VersionRange.or(...sum_terms); + return VersionRange.or(...sum_terms) } } @@ -387,11 +464,11 @@ export class VersionRange { constructor(public atom: Anchor | And | Or | Not | P.Any | P.None | Flavor) {} toStringParens(parent: "And" | "Or" | "Not") { - let needs = true; + let needs = true switch (this.atom.type) { case "And": case "Or": - needs = parent != this.atom.type; + needs = parent != this.atom.type break case "Anchor": case "Any": @@ -400,14 +477,14 @@ export class VersionRange { break case "Not": case "Flavor": - needs = false; + needs = false break } if (needs) { - return "(" + this.toString() + ")"; + return "(" + this.toString() + ")" } else { - return this.toString(); + return this.toString() } } @@ -519,39 +596,39 @@ export class VersionRange { } static and(...xs: Array) { - let y = VersionRange.any(); + let y = VersionRange.any() for (let x of xs) { - if (x.atom.type == 'Any') { - continue; + if (x.atom.type == "Any") { + continue } - if (x.atom.type == 'None') { - return x; + if (x.atom.type == "None") { + return x } - if (y.atom.type == 'Any') { - y = x; + if (y.atom.type == "Any") { + y = x } else { - y = new VersionRange({ type: 'And', left: y, right: x}); + y = new VersionRange({ type: "And", left: y, right: x }) } } - return y; + return y } static or(...xs: Array) { - let y = VersionRange.none(); + let y = VersionRange.none() for (let x of xs) { - if (x.atom.type == 'None') { - continue; + if (x.atom.type == "None") { + continue } - if (x.atom.type == 'Any') { - return x; + if (x.atom.type == "Any") { + return x } - if (y.atom.type == 'None') { - y = x; + if (y.atom.type == "None") { + y = x } else { - y = new VersionRange({ type: 'Or', left: y, right: x}); + y = new VersionRange({ type: "Or", left: y, right: x }) } } - return y; + return y } static any() { @@ -567,7 +644,7 @@ export class VersionRange { } tables(): VersionRangeTables { - switch(this.atom.type) { + switch (this.atom.type) { case "Anchor": switch (this.atom.operator) { case "=": @@ -587,21 +664,33 @@ export class VersionRange { case "!=": // `!=1.2.3` is equivalent to `!(>=1.2.3 && <=1.2.3 && #flavor)` // **not** equivalent to `(<1.2.3 || >1.2.3) && #flavor` - return VersionRangeTable.not(VersionRangeTable.and( - VersionRangeTable.cmp(this.atom.version, -1, false, true), - VersionRangeTable.cmp(this.atom.version, 1, true, false), - )) + return VersionRangeTable.not( + VersionRangeTable.and( + VersionRangeTable.cmp(this.atom.version, -1, false, true), + VersionRangeTable.cmp(this.atom.version, 1, true, false), + ), + ) case "^": // `^1.2.3` is equivalent to `>=1.2.3 && <2.0.0 && #flavor` return VersionRangeTable.and( VersionRangeTable.cmp(this.atom.version, -1, false, true), - VersionRangeTable.cmp(this.atom.version.incrementMajor(), -1, true, false), + VersionRangeTable.cmp( + this.atom.version.incrementMajor(), + -1, + true, + false, + ), ) case "~": // `~1.2.3` is equivalent to `>=1.2.3 && <1.3.0 && #flavor` return VersionRangeTable.and( VersionRangeTable.cmp(this.atom.version, -1, false, true), - VersionRangeTable.cmp(this.atom.version.incrementMinor(), -1, true, false), + VersionRangeTable.cmp( + this.atom.version.incrementMinor(), + -1, + true, + false, + ), ) } case "Flavor": @@ -609,9 +698,15 @@ export class VersionRange { case "Not": return VersionRangeTable.not(this.atom.value.tables()) case "And": - return VersionRangeTable.and(this.atom.left.tables(), this.atom.right.tables()) + return VersionRangeTable.and( + this.atom.left.tables(), + this.atom.right.tables(), + ) case "Or": - return VersionRangeTable.or(this.atom.left.tables(), this.atom.right.tables()) + return VersionRangeTable.or( + this.atom.left.tables(), + this.atom.right.tables(), + ) case "Any": return true case "None": @@ -620,15 +715,15 @@ export class VersionRange { } satisfiable(): boolean { - return VersionRangeTable.collapse(this.tables()) !== false; + return VersionRangeTable.collapse(this.tables()) !== false } intersects(other: VersionRange): boolean { - return VersionRange.and(this, other).satisfiable(); + return VersionRange.and(this, other).satisfiable() } normalize(): VersionRange { - return VersionRangeTable.minterms(this.tables()); + return VersionRangeTable.minterms(this.tables()) } } diff --git a/sdk/base/lib/osBindings/Manifest.ts b/sdk/base/lib/osBindings/Manifest.ts index 2c9a2457e..f65daeea8 100644 --- a/sdk/base/lib/osBindings/Manifest.ts +++ b/sdk/base/lib/osBindings/Manifest.ts @@ -26,7 +26,6 @@ export type Manifest = { donationUrl: string | null description: Description images: { [key: ImageId]: ImageConfig } - assets: Array volumes: Array alerts: Alerts dependencies: Dependencies diff --git a/sdk/base/lib/test/exver.test.ts b/sdk/base/lib/test/exver.test.ts index 6bddf3723..776796966 100644 --- a/sdk/base/lib/test/exver.test.ts +++ b/sdk/base/lib/test/exver.test.ts @@ -80,10 +80,15 @@ describe("ExVer", () => { }) test(`VersionRange.parse("=1") invalid`, () => { - expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.1:0"))).toEqual(false) - expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.0:1"))).toEqual(false) + expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.1:0"))).toEqual( + false, + ) + expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.0:1"))).toEqual( + false, + ) }) - } { + } + { const checker = VersionRange.parse(">=1.2.3:4") test(`VersionRange.parse(">=1.2.3:4") valid`, () => { expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true) @@ -306,32 +311,44 @@ describe("ExVer", () => { { function testNormalization(input: string, expected: string) { test(`"${input}" normalizes to "${expected}"`, () => { - const checker = VersionRange.parse(input).normalize(); - expect(checker.toString()).toEqual(expected); - }); + const checker = VersionRange.parse(input).normalize() + expect(checker.toString()).toEqual(expected) + }) } - testNormalization("=2.0", "=2.0:0"); - testNormalization("=1 && =2", "!"); - testNormalization("!(=1 && =2)", "*"); - testNormalization("!=1 || !=2", "*"); - testNormalization("(!=#foo:1 || !=#foo:2) && #foo", "#foo"); - testNormalization("!=#foo:1 || !=#bar:2", "<#foo:1:0 || >#foo:1:0 || !#foo || <#bar:2:0 || >#bar:2:0 || !#bar"); - testNormalization("!(=1 || =2)", "<1:0 || (>1:0 && <2:0) || >2:0 || !#"); - testNormalization("=1 && (=2 || =3)", "!"); - testNormalization("=1 && (=1 || =2)", "=1:0"); - testNormalization("=#foo:1 && =#bar:1", "!"); - testNormalization("!(=#foo:1) && !(=#bar:1)", "<#foo:1:0 || >#foo:1:0 || <#bar:1:0 || >#bar:1:0 || (!#foo && !#bar)"); - testNormalization("!(=#foo:1) && !(=#bar:1) && >2", ">2:0"); - testNormalization("~1.2.3", ">=1.2.3:0 && <1.3.0:0"); - testNormalization("^1.2.3", ">=1.2.3:0 && <2.0.0:0"); - testNormalization("^1.2.3 && >=1 && >=1.2 && >=1.3", ">=1.3:0 && <2.0.0:0"); - testNormalization("(>=1.0 && <1.1) || (>=1.1 && <1.2) || (>=1.2 && <1.3)", ">=1.0:0 && <1.3:0"); - testNormalization(">1 || <2", "#"); + testNormalization("=2.0", "=2.0:0") + testNormalization("=1 && =2", "!") + testNormalization("!(=1 && =2)", "*") + testNormalization("!=1 || !=2", "*") + testNormalization("(!=#foo:1 || !=#foo:2) && #foo", "#foo") + testNormalization( + "!=#foo:1 || !=#bar:2", + "<#foo:1:0 || >#foo:1:0 || !#foo || <#bar:2:0 || >#bar:2:0 || !#bar", + ) + testNormalization("!(=1 || =2)", "<1:0 || (>1:0 && <2:0) || >2:0 || !#") + testNormalization("=1 && (=2 || =3)", "!") + testNormalization("=1 && (=1 || =2)", "=1:0") + testNormalization("=#foo:1 && =#bar:1", "!") + testNormalization( + "!(=#foo:1) && !(=#bar:1)", + "<#foo:1:0 || >#foo:1:0 || <#bar:1:0 || >#bar:1:0 || (!#foo && !#bar)", + ) + testNormalization("!(=#foo:1) && !(=#bar:1) && >2", ">2:0") + testNormalization("~1.2.3", ">=1.2.3:0 && <1.3.0:0") + testNormalization("^1.2.3", ">=1.2.3:0 && <2.0.0:0") + testNormalization( + "^1.2.3 && >=1 && >=1.2 && >=1.3", + ">=1.3:0 && <2.0.0:0", + ) + testNormalization( + "(>=1.0 && <1.1) || (>=1.1 && <1.2) || (>=1.2 && <1.3)", + ">=1.0:0 && <1.3:0", + ) + testNormalization(">1 || <2", "#") - testNormalization("=1 && =1.2 && =1.2.3", "!"); + testNormalization("=1 && =1.2 && =1.2.3", "!") // testNormalization("=1 && =1.2 && =1.2.3", "=1.2.3:0"); TODO: should it be this instead? - testNormalization("=1 || =1.2 || =1.2.3", "=1:0 || =1.2:0 || =1.2.3:0"); + testNormalization("=1 || =1.2 || =1.2.3", "=1:0 || =1.2:0 || =1.2.3:0") // testNormalization("=1 || =1.2 || =1.2.3", "=1:0"); TODO: should it be this instead? } @@ -350,8 +367,12 @@ describe("ExVer", () => { const checker = VersionRange.parse("=1 || =2") expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true) - expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(false) // really? - expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(false) // really? + expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual( + false, + ) // really? + expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual( + false, + ) // really? expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true) expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false) }) diff --git a/sdk/base/lib/test/startosTypeValidation.test.ts b/sdk/base/lib/test/startosTypeValidation.test.ts index dfccb6506..f6656e55d 100644 --- a/sdk/base/lib/test/startosTypeValidation.test.ts +++ b/sdk/base/lib/test/startosTypeValidation.test.ts @@ -45,7 +45,6 @@ type EffectsTypeChecker = { describe("startosTypeValidation ", () => { test(`checking the params match`, () => { typeEquality({ - constRetry: {}, clearCallbacks: {} as ClearCallbacksParams, action: { clear: {} as ClearActionsParams, diff --git a/sdk/base/lib/types.ts b/sdk/base/lib/types.ts index 056f986c2..140655178 100644 --- a/sdk/base/lib/types.ts +++ b/sdk/base/lib/types.ts @@ -20,10 +20,6 @@ export { } from "./dependencies/setupDependencies" export type ExposedStorePaths = string[] & Affine<"ExposedStorePaths"> -declare const HealthProof: unique symbol -export type HealthReceipt = { - [HealthProof]: never -} export type DaemonBuildable = { build(): Promise<{ diff --git a/sdk/base/lib/types/ManifestTypes.ts b/sdk/base/lib/types/ManifestTypes.ts index 76ab921b9..e7b0db48d 100644 --- a/sdk/base/lib/types/ManifestTypes.ts +++ b/sdk/base/lib/types/ManifestTypes.ts @@ -84,14 +84,6 @@ export type SDKManifest = { * ``` */ 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'] diff --git a/sdk/base/lib/util/GetSystemSmtp.ts b/sdk/base/lib/util/GetSystemSmtp.ts index 8e7db00b6..2e560f509 100644 --- a/sdk/base/lib/util/GetSystemSmtp.ts +++ b/sdk/base/lib/util/GetSystemSmtp.ts @@ -9,7 +9,9 @@ export class GetSystemSmtp { */ const() { return this.effects.getSystemSmtp({ - callback: () => this.effects.constRetry(), + callback: + this.effects.constRetry && + (() => this.effects.constRetry && this.effects.constRetry()), }) } /** diff --git a/sdk/base/lib/util/deepMerge.ts b/sdk/base/lib/util/deepMerge.ts index 72392a887..055b3085f 100644 --- a/sdk/base/lib/util/deepMerge.ts +++ b/sdk/base/lib/util/deepMerge.ts @@ -24,10 +24,7 @@ export function partialDiff( return } } else if (typeof prev === "object" && typeof next === "object") { - if (prev === null) { - return { diff: next } - } - if (next === null) return + if (prev === null || next === null) return { diff: next } const res = { diff: {} as Record } for (let key in next) { const diff = partialDiff(prev[key], next[key]) diff --git a/sdk/base/lib/util/getServiceInterface.ts b/sdk/base/lib/util/getServiceInterface.ts index bb57b6c3b..58698a5f9 100644 --- a/sdk/base/lib/util/getServiceInterface.ts +++ b/sdk/base/lib/util/getServiceInterface.ts @@ -217,7 +217,9 @@ export class GetServiceInterface { */ async const() { const { id, packageId } = this.opts - const callback = () => this.effects.constRetry() + const callback = + this.effects.constRetry && + (() => this.effects.constRetry && this.effects.constRetry()) const interfaceFilled = await makeInterfaceFilled({ effects: this.effects, id, diff --git a/sdk/base/lib/util/getServiceInterfaces.ts b/sdk/base/lib/util/getServiceInterfaces.ts index de5a8d015..afb87c6d0 100644 --- a/sdk/base/lib/util/getServiceInterfaces.ts +++ b/sdk/base/lib/util/getServiceInterfaces.ts @@ -51,7 +51,9 @@ export class GetServiceInterfaces { */ async const() { const { packageId } = this.opts - const callback = () => this.effects.constRetry() + const callback = + this.effects.constRetry && + (() => this.effects.constRetry && this.effects.constRetry()) const interfaceFilled: ServiceInterfaceFilled[] = await makeManyInterfaceFilled({ effects: this.effects, diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 7abbc42e2..42229f436 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -19,7 +19,6 @@ import { SyncOptions, ServiceInterfaceId, PackageId, - HealthReceipt, ServiceInterfaceType, Effects, } from "../../base/lib/types" @@ -27,7 +26,7 @@ import * as patterns from "../../base/lib/util/patterns" import { BackupSync, Backups } from "./backup/Backups" import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants" import { CommandController, Daemons } from "./mainFn/Daemons" -import { healthCheck, HealthCheckParams } from "./health/HealthCheck" +import { HealthCheck } from "./health/HealthCheck" import { checkPortListening } from "./health/checkFns/checkPortListening" import { checkWebUrl, runHealthScript } from "./health/checkFns" import { List } from "../../base/lib/actions/input/builder/list" @@ -73,7 +72,7 @@ import * as actions from "../../base/lib/actions" import { setupInit } from "./inits/setupInit" import * as fs from "node:fs/promises" -export const OSVersion = testTypeVersion("0.3.6-alpha.15") +export const OSVersion = testTypeVersion("0.3.6-alpha.16") // prettier-ignore type AnyNeverCond = @@ -231,7 +230,7 @@ export class StartSdk { }, command: T.CommandType, options: CommandOptions & { - mounts?: { path: string; options: MountOptions }[] + mounts?: { mountpoint: string; options: MountOptions }[] }, /** * A name to use to refer to the ephemeral subcontainer for debugging purposes @@ -420,11 +419,7 @@ export class StartSdk { hostnames: string[], algorithm?: T.Algorithm, ) => new GetSslCertificate(effects, hostnames, algorithm), - HealthCheck: { - of(effects: T.Effects, o: Omit) { - return healthCheck({ effects, ...o }) - }, - }, + HealthCheck, healthCheck: { checkPortListening, checkWebUrl, @@ -677,9 +672,9 @@ export class StartSdk { of( effects: Effects, started: (onTerm: () => PromiseLike) => PromiseLike, - healthReceipts: HealthReceipt[], + healthChecks: HealthCheck[], ) { - return Daemons.of({ effects, started, healthReceipts }) + return Daemons.of({ effects, started, healthChecks }) }, }, SubContainer: { @@ -699,7 +694,7 @@ export class StartSdk { imageId: T.ImageId & keyof Manifest["images"] sharedRun?: boolean }, - mounts: { options: MountOptions; path: string }[], + mounts: { options: MountOptions; mountpoint: string }[], name: string, fn: (subContainer: SubContainer) => Promise, ): Promise { @@ -1081,7 +1076,7 @@ export async function runCommand( image: { imageId: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean }, command: T.CommandType, options: CommandOptions & { - mounts?: { path: string; options: MountOptions }[] + mounts?: { mountpoint: string; options: MountOptions }[] }, name?: string, ): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> { diff --git a/sdk/package/lib/health/HealthCheck.ts b/sdk/package/lib/health/HealthCheck.ts index d921100a0..07a9179a5 100644 --- a/sdk/package/lib/health/HealthCheck.ts +++ b/sdk/package/lib/health/HealthCheck.ts @@ -1,13 +1,12 @@ -import { Effects, HealthCheckId, HealthReceipt } from "../../../base/lib/types" +import { Effects, HealthCheckId } from "../../../base/lib/types" import { HealthCheckResult } from "./checkFns/HealthCheckResult" import { Trigger } from "../trigger" import { TriggerInput } from "../trigger/TriggerInput" import { defaultTrigger } from "../trigger/defaultTrigger" -import { once, asError } from "../util" +import { once, asError, Drop } from "../util" import { object, unknown } from "ts-matches" export type HealthCheckParams = { - effects: Effects id: HealthCheckId name: string trigger?: Trigger @@ -16,53 +15,110 @@ export type HealthCheckParams = { onFirstSuccess?: () => unknown | Promise } -export function healthCheck(o: HealthCheckParams) { - new Promise(async () => { - const start = performance.now() - let currentValue: TriggerInput = {} - const getCurrentValue = () => currentValue - const gracePeriod = o.gracePeriod ?? 5000 - const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue) - const triggerFirstSuccess = once(() => - Promise.resolve( - "onFirstSuccess" in o && o.onFirstSuccess - ? o.onFirstSuccess() - : undefined, - ), - ) - for ( - let res = await trigger.next(); - !res.done; - res = await trigger.next() - ) { - try { - let { result, message } = await o.fn() - if (result === "failure" && performance.now() - start <= gracePeriod) - result = "starting" - await o.effects.setHealth({ - name: o.name, - id: o.id, - result, - message: message || "", - }) - currentValue.lastResult = result - await triggerFirstSuccess().catch((err) => { - console.error(asError(err)) - }) - } catch (e) { - await o.effects.setHealth({ - name: o.name, - id: o.id, - result: - performance.now() - start <= gracePeriod ? "starting" : "failure", - message: asMessage(e) || "", - }) - currentValue.lastResult = "failure" +export class HealthCheck extends Drop { + private started: number | null = null + private setStarted = (started: number | null) => { + this.started = started + } + private exited = false + private exit = () => { + this.exited = true + } + private currentValue: TriggerInput = {} + private promise: Promise + private constructor(effects: Effects, o: HealthCheckParams) { + super() + this.promise = Promise.resolve().then(async () => { + const getCurrentValue = () => this.currentValue + const gracePeriod = o.gracePeriod ?? 5000 + const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue) + const triggerFirstSuccess = once(() => + Promise.resolve( + "onFirstSuccess" in o && o.onFirstSuccess + ? o.onFirstSuccess() + : undefined, + ), + ) + const checkStarted = () => + [ + this.started, + new Promise((resolve) => { + this.setStarted = (started: number | null) => { + this.started = started + resolve() + } + this.exit = () => { + this.exited = true + resolve() + } + }), + ] as const + let triggered = false + while (!this.exited) { + const [started, changed] = checkStarted() + let race: + | [Promise] + | [Promise, Promise>] = [ + changed, + ] + if (started) { + race = [...race, trigger.next()] + if (triggered) { + try { + let { result, message } = await o.fn() + if ( + result === "failure" && + performance.now() - started <= gracePeriod + ) + result = "starting" + await effects.setHealth({ + name: o.name, + id: o.id, + result, + message: message || "", + }) + this.currentValue.lastResult = result + await triggerFirstSuccess().catch((err) => { + console.error(asError(err)) + }) + } catch (e) { + await effects.setHealth({ + name: o.name, + id: o.id, + result: + performance.now() - started <= gracePeriod + ? "starting" + : "failure", + message: asMessage(e) || "", + }) + this.currentValue.lastResult = "failure" + } + } + } else triggered = false + const raced = await Promise.race(race) + if (raced) { + if (raced.done) break + triggered = true + } } - } - }) - return {} as HealthReceipt + }) + } + static of(effects: Effects, options: HealthCheckParams): HealthCheck { + return new HealthCheck(effects, options) + } + start() { + if (this.started) return + this.setStarted(performance.now()) + } + stop() { + if (!this.started) return + this.setStarted(null) + } + onDrop(): void { + this.exit() + } } + function asMessage(e: unknown) { if (object({ message: unknown }).test(e)) return String(e.message) const value = String(e) diff --git a/sdk/package/lib/health/index.ts b/sdk/package/lib/health/index.ts index b969037a5..1b9c46595 100644 --- a/sdk/package/lib/health/index.ts +++ b/sdk/package/lib/health/index.ts @@ -1 +1,3 @@ import "./checkFns" + +export { HealthCheck } from "./HealthCheck" diff --git a/sdk/package/lib/index.ts b/sdk/package/lib/index.ts index 3619765e1..dd301c6ee 100644 --- a/sdk/package/lib/index.ts +++ b/sdk/package/lib/index.ts @@ -7,7 +7,6 @@ import { ISB, IST, types, - T, matches, utils, } from "../../base/lib" @@ -21,10 +20,10 @@ export { ISB, IST, types, - T, matches, utils, } +export * as T from "./types" export { Daemons } from "./mainFn/Daemons" export { SubContainer } from "./util/SubContainer" export { StartSdk } from "./StartSdk" diff --git a/sdk/package/lib/mainFn/CommandController.ts b/sdk/package/lib/mainFn/CommandController.ts index d4ea171f0..23f798404 100644 --- a/sdk/package/lib/mainFn/CommandController.ts +++ b/sdk/package/lib/mainFn/CommandController.ts @@ -7,18 +7,20 @@ import { SubContainerHandle, SubContainer, } from "../util/SubContainer" -import { splitCommand } from "../util" +import { Drop, splitCommand } from "../util" import * as cp from "child_process" import * as fs from "node:fs/promises" -export class CommandController { +export class CommandController extends Drop { private constructor( readonly runningAnswer: Promise, private state: { exited: boolean }, private readonly subcontainer: SubContainer, private process: cp.ChildProcess, readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, - ) {} + ) { + super() + } static of() { return async ( effects: T.Effects, @@ -33,7 +35,7 @@ export class CommandController { subcontainerName?: string // Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms sigtermTimeout?: number - mounts?: { path: string; options: MountOptions }[] + mounts?: { mountpoint: string; options: MountOptions }[] runAsInit?: boolean env?: | { @@ -68,7 +70,7 @@ export class CommandController { ) try { for (let mount of options.mounts || []) { - await subc.mount(mount.options, mount.path) + await subc.mount(mount.options, mount.mountpoint) } return subc } catch (e) { @@ -135,37 +137,42 @@ export class CommandController { return new SubContainerHandle(this.subcontainer) } async wait({ timeout = NO_TIMEOUT } = {}) { + const self = this.weak() if (timeout > 0) setTimeout(() => { - this.term() + self.term() }, timeout) try { - return await this.runningAnswer + return await self.runningAnswer } finally { - if (!this.state.exited) { - this.process.kill("SIGKILL") + if (!self.state.exited) { + self.process.kill("SIGKILL") } - await this.subcontainer.destroy().catch((_) => {}) + await self.subcontainer.destroy().catch((_) => {}) } } async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) { + const self = this.weak() try { - if (!this.state.exited) { + if (!self.state.exited) { if (signal !== "SIGKILL") { setTimeout(() => { - if (!this.state.exited) this.process.kill("SIGKILL") + if (!self.state.exited) self.process.kill("SIGKILL") }, timeout) } - if (!this.process.kill(signal)) { + if (!self.process.kill(signal)) { console.error( `failed to send signal ${signal} to pid ${this.process.pid}`, ) } } - await this.runningAnswer + await self.runningAnswer } finally { - await this.subcontainer.destroy() + await self.subcontainer.destroy() } } + onDrop(): void { + this.term().catch(console.error) + } } diff --git a/sdk/package/lib/mainFn/Daemon.ts b/sdk/package/lib/mainFn/Daemon.ts index 864ae4122..8f254286c 100644 --- a/sdk/package/lib/mainFn/Daemon.ts +++ b/sdk/package/lib/mainFn/Daemon.ts @@ -29,7 +29,7 @@ export class Daemon { command: T.CommandType, options: { subcontainerName?: string - mounts?: { path: string; options: MountOptions }[] + mounts?: { mountpoint: string; options: MountOptions }[] env?: | { [variable: string]: string diff --git a/sdk/package/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts index e75ebec93..d471394c7 100644 --- a/sdk/package/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -1,4 +1,4 @@ -import { HealthReceipt, Signals } from "../../../base/lib/types" +import { Signals } from "../../../base/lib/types" import { HealthCheckResult } from "../health/checkFns" @@ -15,6 +15,7 @@ export { CommandController } from "./CommandController" import { HealthDaemon } from "./HealthDaemon" import { Daemon } from "./Daemon" import { CommandController } from "./CommandController" +import { HealthCheck } from "../health/HealthCheck" export const cpExec = promisify(CP.exec) export const cpExecFile = promisify(CP.execFile) @@ -115,6 +116,7 @@ export class Daemons readonly daemons: Promise[], readonly ids: Ids[], readonly healthDaemons: HealthDaemon[], + readonly healthChecks: HealthCheck[], ) {} /** * Returns an empty new Daemons class with the provided inputSpec. @@ -129,7 +131,7 @@ export class Daemons static of(options: { effects: T.Effects started: (onTerm: () => PromiseLike) => PromiseLike - healthReceipts: HealthReceipt[] + healthChecks: HealthCheck[] }) { return new Daemons( options.effects, @@ -137,6 +139,7 @@ export class Daemons [], [], [], + options.healthChecks, ) } /** @@ -187,28 +190,33 @@ export class Daemons daemons, ids, healthDaemons, + this.healthChecks, ) } - async build() { - const built = { - term: async () => { - try { - for (let result of await Promise.allSettled( - this.healthDaemons.map((x) => - x.term({ timeout: x.sigtermTimeout }), - ), - )) { - if (result.status === "rejected") { - console.error(result.reason) - } - } - } finally { - this.effects.setMainStatus({ status: "stopped" }) + async term() { + try { + this.healthChecks.forEach((health) => health.stop()) + for (let result of await Promise.allSettled( + this.healthDaemons.map((x) => x.term({ timeout: x.sigtermTimeout })), + )) { + if (result.status === "rejected") { + console.error(result.reason) } - }, + } + } finally { + this.effects.setMainStatus({ status: "stopped" }) } - this.started(() => built.term()) - return built + } + + async build() { + for (const daemon of this.healthDaemons) { + await daemon.updateStatus() + } + for (const health of this.healthChecks) { + health.start() + } + this.started(() => this.term()) + return this } } diff --git a/sdk/package/lib/mainFn/HealthDaemon.ts b/sdk/package/lib/mainFn/HealthDaemon.ts index 3a1c1000f..5f9ea7f27 100644 --- a/sdk/package/lib/mainFn/HealthDaemon.ts +++ b/sdk/package/lib/mainFn/HealthDaemon.ts @@ -39,7 +39,6 @@ export class HealthDaemon { readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT, ) { this.readyPromise = new Promise((resolve) => (this.resolveReady = resolve)) - this.updateStatus() this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus())) } @@ -166,8 +165,8 @@ export class HealthDaemon { } as SetHealth) } - private async updateStatus() { - const healths = this.dependencies.map((d) => d._health) - this.changeRunning(healths.every((x) => x.result === "success")) + async updateStatus() { + const healths = this.dependencies.map((d) => d.running && d._health) + this.changeRunning(healths.every((x) => x && x.result === "success")) } } diff --git a/sdk/package/lib/mainFn/Mounts.ts b/sdk/package/lib/mainFn/Mounts.ts index 799140871..74d116957 100644 --- a/sdk/package/lib/mainFn/Mounts.ts +++ b/sdk/package/lib/mainFn/Mounts.ts @@ -1,7 +1,7 @@ import * as T from "../../../base/lib/types" import { MountOptions } from "../util/SubContainer" -type MountArray = { path: string; options: MountOptions }[] +type MountArray = { mountpoint: string; options: MountOptions }[] export class Mounts { private constructor( @@ -12,7 +12,6 @@ export class Mounts { readonly: boolean }[], readonly assets: { - id: Manifest["assets"][number] subpath: string | null mountpoint: string }[], @@ -49,15 +48,12 @@ export class Mounts { } addAssets( - /** The ID of the asset directory to mount. This is typically the same as the folder name in your assets directory */ - id: Manifest["assets"][number], /** The path within the asset directory to mount. Use `null` to mount the entire volume */ subpath: string | null, /** Where to mount the asset. e.g. /asset */ mountpoint: string, ) { this.assets.push({ - id, subpath, mountpoint, }) @@ -102,7 +98,7 @@ export class Mounts { return ([] as MountArray) .concat( this.volumes.map((v) => ({ - path: v.mountpoint, + mountpoint: v.mountpoint, options: { type: "volume", id: v.id, @@ -113,17 +109,16 @@ export class Mounts { ) .concat( this.assets.map((a) => ({ - path: a.mountpoint, + mountpoint: a.mountpoint, options: { type: "assets", - id: a.id, subpath: a.subpath, }, })), ) .concat( this.dependencies.map((d) => ({ - path: d.mountpoint, + mountpoint: d.mountpoint, options: { type: "pointer", packageId: d.dependencyId, diff --git a/sdk/package/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts index b0cb1edec..0995b3f51 100644 --- a/sdk/package/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -16,10 +16,8 @@ import { execSync } from "child_process" export function setupManifest< Id extends string, VolumesTypes extends VolumeId, - AssetTypes extends VolumeId, Manifest extends { id: Id - assets: AssetTypes[] volumes: VolumesTypes[] } & SDKManifest, >(manifest: Manifest & SDKManifest): Manifest { @@ -31,12 +29,10 @@ export function buildManifest< Version 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[] }, diff --git a/sdk/package/lib/store/getStore.ts b/sdk/package/lib/store/getStore.ts index 131b7f7f7..cd086c979 100644 --- a/sdk/package/lib/store/getStore.ts +++ b/sdk/package/lib/store/getStore.ts @@ -18,7 +18,9 @@ export class GetStore { return this.effects.store.get({ ...this.options, path: extractJsonPath(this.path), - callback: () => this.effects.constRetry(), + callback: + this.effects.constRetry && + (() => this.effects.constRetry && this.effects.constRetry()), }) } /** diff --git a/sdk/package/lib/types.ts b/sdk/package/lib/types.ts new file mode 100644 index 000000000..0453a0681 --- /dev/null +++ b/sdk/package/lib/types.ts @@ -0,0 +1,2 @@ +export * from "../../base/lib/types" +export { HealthCheck } from "./health" diff --git a/sdk/package/lib/util/Drop.ts b/sdk/package/lib/util/Drop.ts new file mode 100644 index 000000000..e08883580 --- /dev/null +++ b/sdk/package/lib/util/Drop.ts @@ -0,0 +1,26 @@ +export abstract class Drop { + private static weak: { [id: number]: Drop } = {} + private static registry = new FinalizationRegistry((id: number) => { + Drop.weak[id].drop() + }) + private static idCtr: number = 0 + private id: number + private ref: { id: number } | WeakRef<{ id: number }> + protected constructor() { + this.id = Drop.idCtr++ + this.ref = { id: this.id } + Drop.weak[this.id] = this.weak() + Drop.registry.register(this, this.id, this) + } + protected weak(): this { + const weak = Object.assign(Object.create(Object.getPrototypeOf(this)), this) + weak.ref = new WeakRef(this.ref) + return weak + } + abstract onDrop(): void + drop(): void { + this.onDrop() + Drop.registry.unregister(this) + delete Drop.weak[this.id] + } +} diff --git a/sdk/package/lib/util/GetSslCertificate.ts b/sdk/package/lib/util/GetSslCertificate.ts index 8ea870ffb..afc12e6b2 100644 --- a/sdk/package/lib/util/GetSslCertificate.ts +++ b/sdk/package/lib/util/GetSslCertificate.ts @@ -15,7 +15,9 @@ export class GetSslCertificate { return this.effects.getSslCertificate({ hostnames: this.hostnames, algorithm: this.algorithm, - callback: () => this.effects.constRetry(), + callback: + this.effects.constRetry && + (() => this.effects.constRetry && this.effects.constRetry()), }) } /** diff --git a/sdk/package/lib/util/SubContainer.ts b/sdk/package/lib/util/SubContainer.ts index 21dcc75c7..912c3112a 100644 --- a/sdk/package/lib/util/SubContainer.ts +++ b/sdk/package/lib/util/SubContainer.ts @@ -130,14 +130,14 @@ export class SubContainer implements ExecSpawnable { static async with( effects: T.Effects, image: { imageId: T.ImageId; sharedRun?: boolean }, - mounts: { options: MountOptions; path: string }[], + mounts: { options: MountOptions; mountpoint: string }[], name: string, fn: (subContainer: SubContainer) => Promise, ): Promise { const subContainer = await SubContainer.of(effects, image, name) try { for (let mount of mounts) { - await subContainer.mount(mount.options, mount.path) + await subContainer.mount(mount.options, mount.mountpoint) } return await fn(subContainer) } finally { @@ -166,7 +166,7 @@ export class SubContainer implements ExecSpawnable { ? options.subpath : `/${options.subpath}` : "/" - const from = `/media/startos/assets/${options.id}${subpath}` + const from = `/media/startos/assets/${subpath}` await fs.mkdir(from, { recursive: true }) await fs.mkdir(path, { recursive: true }) @@ -449,7 +449,6 @@ export type MountOptionsVolume = { export type MountOptionsAssets = { type: "assets" - id: string subpath: string | null } diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index 74b7b7083..83f46d94d 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -3,7 +3,7 @@ import * as YAML from "yaml" import * as TOML from "@iarna/toml" import * as T from "../../../base/lib/types" import * as fs from "node:fs/promises" -import { asError } from "../../../base/lib/util" +import { asError, partialDiff } from "../../../base/lib/util" const previousPath = /(.+?)\/([^/]*)$/ @@ -101,6 +101,7 @@ function fileMerge(...args: any[]): any { * ``` */ export class FileHelper { + private consts: (() => void)[] = [] protected constructor( readonly path: string, readonly writeData: (dataIn: A) => string, @@ -108,27 +109,37 @@ export class FileHelper { readonly validate: (value: unknown) => A, ) {} - /** - * Accepts structured data and overwrites the existing file on disk. - */ - private async writeFile(data: A): Promise { + private async writeFileRaw(data: string): Promise { const parent = previousPath.exec(this.path) if (parent) { await fs.mkdir(parent[1], { recursive: true }) } - await fs.writeFile(this.path, this.writeData(data)) + await fs.writeFile(this.path, data) return null } - private async readFile(): Promise { + /** + * Accepts structured data and overwrites the existing file on disk. + */ + private async writeFile(data: A): Promise { + return await this.writeFileRaw(this.writeData(data)) + } + + private async readFileRaw(): Promise { if (!(await exists(this.path))) { return null } - return this.readData( - await fs.readFile(this.path).then((data) => data.toString("utf-8")), - ) + return await fs.readFile(this.path).then((data) => data.toString("utf-8")) + } + + private async readFile(): Promise { + const raw = await this.readFileRaw() + if (raw === null) { + return raw + } + return this.readData(raw) } /** @@ -143,7 +154,14 @@ export class FileHelper { private async readConst(effects: T.Effects): Promise { const watch = this.readWatch() const res = await watch.next() - watch.next().then(effects.constRetry) + if (effects.constRetry) { + if (!this.consts.includes(effects.constRetry)) + this.consts.push(effects.constRetry) + watch.next().then(() => { + this.consts = this.consts.filter((a) => a === effects.constRetry) + effects.constRetry && effects.constRetry() + }) + } return res.value } @@ -213,17 +231,35 @@ export class FileHelper { /** * Accepts full structured data and overwrites the existing file on disk if it exists. */ - async write(data: A) { - return await this.writeFile(this.validate(data)) + async write(effects: T.Effects, data: A) { + await this.writeFile(this.validate(data)) + if (effects.constRetry && this.consts.includes(effects.constRetry)) + throw new Error(`Canceled: write after const: ${this.path}`) + return null } /** * Accepts partial structured data and performs a merge with the existing file on disk. */ - async merge(data: T.DeepPartial) { - const fileData = (await this.readFile()) || null - const mergeData = fileMerge(fileData, data) - return await this.writeFile(this.validate(mergeData)) + async merge(effects: T.Effects, data: T.DeepPartial) { + const fileDataRaw = await this.readFileRaw() + let fileData: any = fileDataRaw === null ? null : this.readData(fileDataRaw) + try { + fileData = this.validate(fileData) + } catch (_) {} + const mergeData = this.validate(fileMerge({}, fileData, data)) + const toWrite = this.writeData(mergeData) + if (toWrite !== fileDataRaw) { + this.writeFile(mergeData) + if (effects.constRetry && this.consts.includes(effects.constRetry)) { + const diff = partialDiff(fileData, mergeData as any) + if (!diff) { + return null + } + throw new Error(`Canceled: write after const: ${this.path}`) + } + } + return null } /** diff --git a/sdk/package/lib/util/index.ts b/sdk/package/lib/util/index.ts index 66c73503e..d5f024686 100644 --- a/sdk/package/lib/util/index.ts +++ b/sdk/package/lib/util/index.ts @@ -2,3 +2,4 @@ export * from "../../../base/lib/util" export { GetSslCertificate } from "./GetSslCertificate" export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname" +export { Drop } from "./Drop" diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index c1a6f370b..58aec58ad 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,17 +1,18 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-beta.14", + "version": "0.3.6-beta.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.3.6-beta.14", + "version": "0.3.6-beta.18", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0", + "deep-equality-data-structures": "^1.5.1", "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", @@ -1802,6 +1803,15 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "node_modules/deep-equality-data-structures": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/deep-equality-data-structures/-/deep-equality-data-structures-1.5.1.tgz", + "integrity": "sha512-P7zsL2/AbZIGHDxbo/LLEhCp11AttRp8GvzXOXudqMT/qiGCLo/pyI4lAZvjUZyQnlIbPna3fv8DMsuRvLt4ww==", + "license": "MIT", + "dependencies": { + "object-hash": "^3.0.0" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3238,6 +3248,15 @@ "node": ">=8" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/sdk/package/package.json b/sdk/package/package.json index 27ea8c5b4..ef9116d92 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-beta.14", + "version": "0.3.6-beta.18", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", @@ -35,6 +35,7 @@ "mime-types": "^2.1.35", "ts-matches": "^6.2.1", "yaml": "^2.2.2", + "deep-equality-data-structures": "^1.5.1", "@iarna/toml": "^2.2.5", "@noble/curves": "^1.4.0", "@noble/hashes": "^1.4.0" diff --git a/web/package-lock.json b/web/package-lock.json index 8cea33725..36379429c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.15", + "version": "0.3.6-alpha.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.3.6-alpha.15", + "version": "0.3.6-alpha.16", "license": "MIT", "dependencies": { "@angular/animations": "^14.1.0", diff --git a/web/package.json b/web/package.json index 4808a3945..5b7402d5f 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "startos-ui", - "version": "0.3.6-alpha.15", + "version": "0.3.6-alpha.16", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", 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 bcfedb091..1c9395e23 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -116,7 +116,6 @@ export module Mock { emulateMissingAs: 'aarch64', }, }, - assets: [], volumes: ['main'], hardwareRequirements: { device: [], @@ -173,7 +172,6 @@ export module Mock { emulateMissingAs: 'aarch64', }, }, - assets: [], volumes: ['main'], hardwareRequirements: { device: [], @@ -223,7 +221,6 @@ export module Mock { emulateMissingAs: 'aarch64', }, }, - assets: [], volumes: ['main'], hardwareRequirements: { device: [],