mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
Bugfix/sdk misc (#2847)
* misc sdk fixes * version bump * formatting * add missing dependency to root * alpha.16 and beta.17 * beta.18
This commit is contained in:
@@ -36,7 +36,7 @@ let hostSystemId = 0
|
|||||||
export type EffectContext = {
|
export type EffectContext = {
|
||||||
procedureId: string | null
|
procedureId: string | null
|
||||||
callbacks?: CallbackHolder
|
callbacks?: CallbackHolder
|
||||||
constRetry: () => void
|
constRetry?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const rpcRoundFor =
|
const rpcRoundFor =
|
||||||
|
|||||||
@@ -306,7 +306,6 @@ export class RpcListener {
|
|||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
procedureId: null,
|
procedureId: null,
|
||||||
callbacks,
|
callbacks,
|
||||||
constRetry: () => {},
|
|
||||||
})
|
})
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
@@ -337,7 +336,6 @@ export class RpcListener {
|
|||||||
this.callbacks = new CallbackHolder(
|
this.callbacks = new CallbackHolder(
|
||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: null,
|
procedureId: null,
|
||||||
constRetry: () => {},
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const callbacks = this.callbackHolderFor("containerInit")
|
const callbacks = this.callbackHolderFor("containerInit")
|
||||||
@@ -345,7 +343,6 @@ export class RpcListener {
|
|||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: null,
|
procedureId: null,
|
||||||
callbacks,
|
callbacks,
|
||||||
constRetry: () => {},
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
this._system = system
|
this._system = system
|
||||||
@@ -427,7 +424,6 @@ export class RpcListener {
|
|||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
procedureId,
|
procedureId,
|
||||||
callbacks,
|
callbacks,
|
||||||
constRetry: () => {},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (async () => {
|
return (async () => {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class DockerProcedureContainer {
|
|||||||
)
|
)
|
||||||
} else if (volumeMount.type === "assets") {
|
} else if (volumeMount.type === "assets") {
|
||||||
await subcontainer.mount(
|
await subcontainer.mount(
|
||||||
{ type: "assets", id: mount, subpath: null },
|
{ type: "assets", subpath: mount },
|
||||||
mounts[mount],
|
mounts[mount],
|
||||||
)
|
)
|
||||||
} else if (volumeMount.type === "certificate") {
|
} else if (volumeMount.type === "certificate") {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export const polyfillEffects = (
|
|||||||
{
|
{
|
||||||
mounts: [
|
mounts: [
|
||||||
{
|
{
|
||||||
path: "/drive",
|
mountpoint: "/drive",
|
||||||
options: {
|
options: {
|
||||||
type: "volume",
|
type: "volume",
|
||||||
id: input.volumeId,
|
id: input.volumeId,
|
||||||
@@ -212,7 +212,7 @@ export const polyfillEffects = (
|
|||||||
{
|
{
|
||||||
mounts: [
|
mounts: [
|
||||||
{
|
{
|
||||||
path: "/drive",
|
mountpoint: "/drive",
|
||||||
options: {
|
options: {
|
||||||
type: "volume",
|
type: "volume",
|
||||||
id: input.volumeId,
|
id: input.volumeId,
|
||||||
|
|||||||
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -5952,7 +5952,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
version = "0.3.6-alpha.15"
|
version = "0.3.6-alpha.16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.7.5",
|
"aes 0.7.5",
|
||||||
"async-acme",
|
"async-acme",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ keywords = [
|
|||||||
name = "start-os"
|
name = "start-os"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Start9Labs/start-os"
|
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"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
@@ -1021,6 +1021,15 @@ impl ListenerMap {
|
|||||||
fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
||||||
for (bind_addr, listener) in self.listeners.iter() {
|
for (bind_addr, listener) in self.listeners.iter() {
|
||||||
if let Poll::Ready((stream, addr)) = listener.0.poll_accept(cx)? {
|
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 {
|
return Poll::Ready(Ok(Accepted {
|
||||||
stream,
|
stream,
|
||||||
peer: addr,
|
peer: addr,
|
||||||
|
|||||||
@@ -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 {
|
tokio::spawn(async move {
|
||||||
let bind = accepted.bind;
|
let bind = accepted.bind;
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
|
|||||||
@@ -36,6 +36,25 @@ impl<'a, T: Clone> Expected<'a, T> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn check_dir(&mut self, path: impl AsRef<Path>) -> 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(
|
pub fn check_stem(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
|
|||||||
@@ -129,25 +129,16 @@ impl S9pk<TmpSource<PackSource>> {
|
|||||||
tokio_tar::Archive::new(reader.assets().await?)
|
tokio_tar::Archive::new(reader.assets().await?)
|
||||||
.unpack(&asset_dir)
|
.unpack(&asset_dir)
|
||||||
.await?;
|
.await?;
|
||||||
for (asset_id, _) in manifest
|
let sqfs_path = asset_dir.with_extension("squashfs");
|
||||||
.volumes
|
Command::new("mksquashfs")
|
||||||
.iter()
|
.arg(&asset_dir)
|
||||||
.filter(|(_, v)| v.get("type").and_then(|v| v.as_str()) == Some("assets"))
|
.arg(&sqfs_path)
|
||||||
{
|
.invoke(ErrorKind::Filesystem)
|
||||||
let assets_path = asset_dir.join(&asset_id);
|
.await?;
|
||||||
let sqfs_path = assets_path.with_extension("squashfs");
|
archive.insert_path(
|
||||||
Command::new("mksquashfs")
|
"assets.squashfs",
|
||||||
.arg(&assets_path)
|
Entry::file(TmpSource::new(tmp_dir.clone(), PackSource::File(sqfs_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))),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// javascript
|
// javascript
|
||||||
let js_dir = tmp_dir.join("javascript");
|
let js_dir = tmp_dir.join("javascript");
|
||||||
@@ -217,12 +208,6 @@ impl TryFrom<ManifestV1> for Manifest {
|
|||||||
donation_url: value.donation_url,
|
donation_url: value.donation_url,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
images: BTreeMap::new(),
|
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: value
|
||||||
.volumes
|
.volumes
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ pub struct Manifest {
|
|||||||
pub donation_url: Option<Url>,
|
pub donation_url: Option<Url>,
|
||||||
pub description: Description,
|
pub description: Description,
|
||||||
pub images: BTreeMap<ImageId, ImageConfig>,
|
pub images: BTreeMap<ImageId, ImageConfig>,
|
||||||
pub assets: BTreeSet<VolumeId>, // TODO: AssetsId
|
|
||||||
pub volumes: BTreeSet<VolumeId>,
|
pub volumes: BTreeSet<VolumeId>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub alerts: Alerts,
|
pub alerts: Alerts,
|
||||||
@@ -93,8 +92,11 @@ impl Manifest {
|
|||||||
.map_or(false, |mime| mime.starts_with("image/"))
|
.map_or(false, |mime| mime.starts_with("image/"))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for assets in &self.assets {
|
if let Err(e) = expected.check_file(Path::new("assets.squashfs")) {
|
||||||
expected.check_file(Path::new("assets").join(assets).with_extension("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 {
|
for (image_id, config) in &self.images {
|
||||||
let mut check_arch = |arch: &str| {
|
let mut check_arch = |arch: &str| {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ fn priority(s: &str) -> Option<usize> {
|
|||||||
"instructions.md" => Some(3),
|
"instructions.md" => Some(3),
|
||||||
"dependencies" => Some(4),
|
"dependencies" => Some(4),
|
||||||
"javascript.squashfs" => Some(5),
|
"javascript.squashfs" => Some(5),
|
||||||
"assets" => Some(6),
|
"assets.squashfs" => Some(6),
|
||||||
"images" => Some(7),
|
"images" => Some(7),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -694,18 +694,13 @@ pub async fn pack(ctx: CliContext, params: PackParams) -> Result<(), Error> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let assets_dir = params.assets();
|
let assets_dir = params.assets();
|
||||||
for assets in s9pk.as_manifest().assets.clone() {
|
s9pk.as_archive_mut().contents_mut().insert_path(
|
||||||
s9pk.as_archive_mut().contents_mut().insert_path(
|
"assets.squashfs",
|
||||||
Path::new("assets").join(&assets).with_extension("squashfs"),
|
Entry::file(TmpSource::new(
|
||||||
Entry::file(TmpSource::new(
|
tmp_dir.clone(),
|
||||||
tmp_dir.clone(),
|
PackSource::Squashfs(Arc::new(SqfsDir::new(assets_dir, tmp_dir.clone()))),
|
||||||
PackSource::Squashfs(Arc::new(SqfsDir::new(
|
)),
|
||||||
assets_dir.join(&assets),
|
)?;
|
||||||
tmp_dir.clone(),
|
|
||||||
))),
|
|
||||||
)),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
s9pk.load_images(tmp_dir.clone()).await?;
|
s9pk.load_images(tmp_dir.clone()).await?;
|
||||||
|
|
||||||
@@ -816,9 +811,7 @@ pub async fn list_ingredients(_: CliContext, params: PackParams) -> Result<Vec<P
|
|||||||
}
|
}
|
||||||
|
|
||||||
let assets_dir = params.assets();
|
let assets_dir = params.assets();
|
||||||
for assets in manifest.assets {
|
ingredients.push(assets_dir);
|
||||||
ingredients.push(assets_dir.join(assets));
|
|
||||||
}
|
|
||||||
|
|
||||||
for image in manifest.images.values() {
|
for image in manifest.images.values() {
|
||||||
ingredients.extend(image.source.ingredients());
|
ingredients.extend(image.source.ingredients());
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ pub struct PersistentContainer {
|
|||||||
// procedures: Mutex<Vec<(ProcedureName, ProcedureId)>>,
|
// procedures: Mutex<Vec<(ProcedureName, ProcedureId)>>,
|
||||||
js_mount: MountGuard,
|
js_mount: MountGuard,
|
||||||
volumes: BTreeMap<VolumeId, MountGuard>,
|
volumes: BTreeMap<VolumeId, MountGuard>,
|
||||||
assets: BTreeMap<VolumeId, MountGuard>,
|
assets: Vec<MountGuard>,
|
||||||
pub(super) images: BTreeMap<ImageId, Arc<MountGuard>>,
|
pub(super) images: BTreeMap<ImageId, Arc<MountGuard>>,
|
||||||
pub(super) subcontainers: Arc<Mutex<BTreeMap<Guid, Subcontainer>>>,
|
pub(super) subcontainers: Arc<Mutex<BTreeMap<Guid, Subcontainer>>>,
|
||||||
pub(super) state: Arc<watch::Sender<ServiceState>>,
|
pub(super) state: Arc<watch::Sender<ServiceState>>,
|
||||||
@@ -168,35 +168,63 @@ impl PersistentContainer {
|
|||||||
.await?;
|
.await?;
|
||||||
volumes.insert(volume.clone(), mount);
|
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");
|
||||||
let mountpoint = lxc_container
|
tokio::fs::create_dir_all(&mountpoint).await?;
|
||||||
.rootfs_dir()
|
Command::new("chown")
|
||||||
.join("media/startos/assets")
|
.arg("100000:100000")
|
||||||
.join(asset);
|
.arg(&mountpoint)
|
||||||
tokio::fs::create_dir_all(&mountpoint).await?;
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
Command::new("chown")
|
.await?;
|
||||||
.arg("100000:100000")
|
let assets = if let Some(sqfs) = s9pk
|
||||||
.arg(&mountpoint)
|
.as_archive()
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
.contents()
|
||||||
.await?;
|
.get_path("assets.squashfs")
|
||||||
let s9pk_asset_path = Path::new("assets").join(asset).with_extension("squashfs");
|
.and_then(|e| e.as_file())
|
||||||
let sqfs = s9pk
|
{
|
||||||
.as_archive()
|
vec![
|
||||||
.contents()
|
|
||||||
.get_path(&s9pk_asset_path)
|
|
||||||
.and_then(|e| e.as_file())
|
|
||||||
.or_not_found(s9pk_asset_path.display())?;
|
|
||||||
assets.insert(
|
|
||||||
asset.clone(),
|
|
||||||
MountGuard::mount(
|
MountGuard::mount(
|
||||||
&IdMapped::new(LoopDev::from(&**sqfs), 0, 100000, 65536),
|
&IdMapped::new(LoopDev::from(&**sqfs), 0, 100000, 65536),
|
||||||
mountpoint,
|
mountpoint,
|
||||||
MountType::ReadWrite,
|
MountType::ReadWrite,
|
||||||
)
|
)
|
||||||
.await?,
|
.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 mut images = BTreeMap::new();
|
||||||
let image_path = lxc_container.rootfs_dir().join("media/startos/images");
|
let image_path = lxc_container.rootfs_dir().join("media/startos/images");
|
||||||
@@ -432,7 +460,7 @@ impl PersistentContainer {
|
|||||||
for (_, volume) in volumes {
|
for (_, volume) in volumes {
|
||||||
errs.handle(volume.unmount(true).await);
|
errs.handle(volume.unmount(true).await);
|
||||||
}
|
}
|
||||||
for (_, assets) in assets {
|
for assets in assets {
|
||||||
errs.handle(assets.unmount(true).await);
|
errs.handle(assets.unmount(true).await);
|
||||||
}
|
}
|
||||||
for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) {
|
for (_, overlay) in std::mem::take(&mut *subcontainers.lock().await) {
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ mod v0_3_6_alpha_12;
|
|||||||
mod v0_3_6_alpha_13;
|
mod v0_3_6_alpha_13;
|
||||||
mod v0_3_6_alpha_14;
|
mod v0_3_6_alpha_14;
|
||||||
mod v0_3_6_alpha_15;
|
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 {
|
impl Current {
|
||||||
#[instrument(skip(self, db))]
|
#[instrument(skip(self, db))]
|
||||||
@@ -133,7 +134,8 @@ enum Version {
|
|||||||
V0_3_6_alpha_12(Wrapper<v0_3_6_alpha_12::Version>),
|
V0_3_6_alpha_12(Wrapper<v0_3_6_alpha_12::Version>),
|
||||||
V0_3_6_alpha_13(Wrapper<v0_3_6_alpha_13::Version>),
|
V0_3_6_alpha_13(Wrapper<v0_3_6_alpha_13::Version>),
|
||||||
V0_3_6_alpha_14(Wrapper<v0_3_6_alpha_14::Version>),
|
V0_3_6_alpha_14(Wrapper<v0_3_6_alpha_14::Version>),
|
||||||
V0_3_6_alpha_15(Wrapper<v0_3_6_alpha_15::Version>), // VERSION_BUMP
|
V0_3_6_alpha_15(Wrapper<v0_3_6_alpha_15::Version>),
|
||||||
|
V0_3_6_alpha_16(Wrapper<v0_3_6_alpha_16::Version>), // VERSION_BUMP
|
||||||
Other(exver::Version),
|
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_12(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_3_6_alpha_13(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_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) => {
|
Self::Other(v) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("unknown version {v}"),
|
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_12(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_3_6_alpha_13(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_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(),
|
Version::Other(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
core/startos/src/version/v0_3_6_alpha_16.rs
Normal file
36
core/startos/src/version/v0_3_6_alpha_16.rs
Normal file
@@ -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<Self::PreUpRes, Error> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,11 +58,16 @@ check:
|
|||||||
fmt: package/node_modules base/node_modules
|
fmt: package/node_modules base/node_modules
|
||||||
npx prettier . "**/*.ts" --write
|
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
|
cd package && npm ci
|
||||||
|
|
||||||
base/node_modules: base/package.json
|
base/node_modules: base/package-lock.json
|
||||||
cd base && npm ci
|
cd base && npm ci
|
||||||
|
|
||||||
node_modules: package/node_modules base/node_modules
|
node_modules: package/node_modules base/node_modules
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { UrlString } from "./util/getServiceInterface"
|
|||||||
/** Used to reach out from the pure js runtime */
|
/** Used to reach out from the pure js runtime */
|
||||||
|
|
||||||
export type Effects = {
|
export type Effects = {
|
||||||
constRetry: () => void
|
constRetry?: () => void
|
||||||
clearCallbacks: (
|
clearCallbacks: (
|
||||||
options: { only: number[] } | { except: number[] },
|
options: { only: number[] } | { except: number[] },
|
||||||
) => Promise<null>
|
) => Promise<null>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DeepMap } from "deep-equality-data-structures";
|
import { DeepMap } from "deep-equality-data-structures"
|
||||||
import * as P from "./exver"
|
import * as P from "./exver"
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -45,16 +45,16 @@ type Not = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Flavor = {
|
type Flavor = {
|
||||||
type: "Flavor",
|
type: "Flavor"
|
||||||
flavor: string | null,
|
flavor: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlavorNot = {
|
type FlavorNot = {
|
||||||
type: "FlavorNot",
|
type: "FlavorNot"
|
||||||
flavors: Set<string | null>,
|
flavors: Set<string | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
* 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).
|
* for side=+1 the point is like `1.2.3.0.0.**.1` (that is, 1.2.3.0.0.** is less).
|
||||||
*/
|
*/
|
||||||
type VersionRangePoint = {
|
type VersionRangePoint = {
|
||||||
upstream: Version,
|
upstream: Version
|
||||||
downstream: Version,
|
downstream: Version
|
||||||
side: -1 | 1;
|
side: -1 | 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function compareVersionRangePoints(a: VersionRangePoint, b: VersionRangePoint): -1 | 0 | 1 {
|
function compareVersionRangePoints(
|
||||||
let up = a.upstream.compareForSort(b.upstream);
|
a: VersionRangePoint,
|
||||||
|
b: VersionRangePoint,
|
||||||
|
): -1 | 0 | 1 {
|
||||||
|
let up = a.upstream.compareForSort(b.upstream)
|
||||||
if (up != 0) {
|
if (up != 0) {
|
||||||
return up;
|
return up
|
||||||
}
|
}
|
||||||
let down = a.upstream.compareForSort(b.upstream);
|
let down = a.upstream.compareForSort(b.upstream)
|
||||||
if (down != 0) {
|
if (down != 0) {
|
||||||
return down;
|
return down
|
||||||
}
|
}
|
||||||
if (a.side < b.side) {
|
if (a.side < b.side) {
|
||||||
return -1;
|
return -1
|
||||||
} else if (a.side > b.side) {
|
} else if (a.side > b.side) {
|
||||||
return 1;
|
return 1
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjacentVersionRangePoints(a: VersionRangePoint, b: VersionRangePoint): boolean {
|
function adjacentVersionRangePoints(
|
||||||
let up = a.upstream.compareForSort(b.upstream);
|
a: VersionRangePoint,
|
||||||
|
b: VersionRangePoint,
|
||||||
|
): boolean {
|
||||||
|
let up = a.upstream.compareForSort(b.upstream)
|
||||||
if (up != 0) {
|
if (up != 0) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
let down = a.upstream.compareForSort(b.upstream);
|
let down = a.upstream.compareForSort(b.upstream)
|
||||||
if (down != 0) {
|
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 {
|
function flavorAnd(a: FlavorAtom, b: FlavorAtom): FlavorAtom | null {
|
||||||
if (a.type == 'Flavor') {
|
if (a.type == "Flavor") {
|
||||||
if (b.type == 'Flavor') {
|
if (b.type == "Flavor") {
|
||||||
if (a.flavor == b.flavor) {
|
if (a.flavor == b.flavor) {
|
||||||
return a;
|
return a
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (b.flavors.has(a.flavor)) {
|
if (b.flavors.has(a.flavor)) {
|
||||||
return null;
|
return null
|
||||||
} else {
|
} else {
|
||||||
return a;
|
return a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (b.type == 'Flavor') {
|
if (b.type == "Flavor") {
|
||||||
if (a.flavors.has(b.flavor)) {
|
if (a.flavors.has(b.flavor)) {
|
||||||
return null;
|
return null
|
||||||
} else {
|
} else {
|
||||||
return b;
|
return b
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: use Set.union if targeting esnext or later
|
// 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
|
* 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.
|
* combination of flavors and versions we also need tables for flavor negations.
|
||||||
*/
|
*/
|
||||||
type VersionRangeTables = DeepMap<FlavorAtom, VersionRangeTable> | boolean;
|
type VersionRangeTables = DeepMap<FlavorAtom, VersionRangeTable> | boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A truth table for version numbers. This is easiest to picture as a number line, cut up into
|
* A truth table for version numbers. This is easiest to picture as a number line, cut up into
|
||||||
* ranges of versions between version points.
|
* ranges of versions between version points.
|
||||||
*/
|
*/
|
||||||
class VersionRangeTable {
|
class VersionRangeTable {
|
||||||
private constructor(protected points: Array<VersionRangePoint>, protected values: boolean[]) {}
|
private constructor(
|
||||||
|
protected points: Array<VersionRangePoint>,
|
||||||
|
protected values: boolean[],
|
||||||
|
) {}
|
||||||
|
|
||||||
static zip(a: VersionRangeTable, b: VersionRangeTable, func: (a: boolean, b: boolean) => boolean): VersionRangeTable {
|
static zip(
|
||||||
let c = new VersionRangeTable([], []);
|
a: VersionRangeTable,
|
||||||
let i = 0;
|
b: VersionRangeTable,
|
||||||
let j = 0;
|
func: (a: boolean, b: boolean) => boolean,
|
||||||
|
): VersionRangeTable {
|
||||||
|
let c = new VersionRangeTable([], [])
|
||||||
|
let i = 0
|
||||||
|
let j = 0
|
||||||
while (true) {
|
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) {
|
if (c.values.length > 0 && c.values[c.values.length - 1] == next) {
|
||||||
// collapse automatically
|
// collapse automatically
|
||||||
c.points.pop();
|
c.points.pop()
|
||||||
} else {
|
} else {
|
||||||
c.values.push(next);
|
c.values.push(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
// which point do we step over?
|
// which point do we step over?
|
||||||
if (i == a.points.length) {
|
if (i == a.points.length) {
|
||||||
if (j == b.points.length) {
|
if (j == b.points.length) {
|
||||||
// just added the last segment, no point to jump over
|
// just added the last segment, no point to jump over
|
||||||
return c;
|
return c
|
||||||
} else {
|
} else {
|
||||||
// i has reach the end, step over j
|
// i has reach the end, step over j
|
||||||
c.points.push(b.points[j]);
|
c.points.push(b.points[j])
|
||||||
j += 1;
|
j += 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (j == b.points.length) {
|
if (j == b.points.length) {
|
||||||
// j has reached the end, step over i
|
// j has reached the end, step over i
|
||||||
c.points.push(a.points[i]);
|
c.points.push(a.points[i])
|
||||||
i += 1;
|
i += 1
|
||||||
} else {
|
} else {
|
||||||
// depends on which of the next two points is lower
|
// depends on which of the next two points is lower
|
||||||
switch (compareVersionRangePoints(a.points[i], b.points[j])) {
|
switch (compareVersionRangePoints(a.points[i], b.points[j])) {
|
||||||
case -1:
|
case -1:
|
||||||
// i is the lower point
|
// i is the lower point
|
||||||
c.points.push(a.points[i]);
|
c.points.push(a.points[i])
|
||||||
i += 1;
|
i += 1
|
||||||
break;
|
break
|
||||||
case 1:
|
case 1:
|
||||||
// j is the lower point
|
// j is the lower point
|
||||||
c.points.push(b.points[j]);
|
c.points.push(b.points[j])
|
||||||
j += 1;
|
j += 1
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
// step over both
|
// step over both
|
||||||
c.points.push(a.points[i]);
|
c.points.push(a.points[i])
|
||||||
i += 1;
|
i += 1
|
||||||
j += 1;
|
j += 1
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,98 +217,134 @@ class VersionRangeTable {
|
|||||||
*/
|
*/
|
||||||
static eqFlavor(flavor: string | null): VersionRangeTables {
|
static eqFlavor(flavor: string | null): VersionRangeTables {
|
||||||
return new DeepMap([
|
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.
|
// 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.
|
* 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`.
|
* 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([
|
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.
|
// 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`.
|
* Helper for `cmpPoint`.
|
||||||
*/
|
*/
|
||||||
static cmp(version: ExtendedVersion, side: -1 | 1, left: boolean, right: boolean): VersionRangeTables {
|
static cmp(
|
||||||
return VersionRangeTable.cmpPoint(version.flavor, { upstream: version.upstream, downstream: version.downstream, side }, left, right)
|
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) {
|
static not(tables: VersionRangeTables) {
|
||||||
if (tables === true || tables === false) {
|
if (tables === true || tables === false) {
|
||||||
return !tables;
|
return !tables
|
||||||
}
|
}
|
||||||
// because tables are always exhaustive, we can simply invert each range
|
// because tables are always exhaustive, we can simply invert each range
|
||||||
for (let [f, t] of tables) {
|
for (let [f, t] of tables) {
|
||||||
for (let i = 0; i < t.values.length; i++) {
|
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) {
|
if (a_tables === true) {
|
||||||
return b_tables;
|
return b_tables
|
||||||
}
|
}
|
||||||
if (b_tables === true) {
|
if (b_tables === true) {
|
||||||
return a_tables;
|
return a_tables
|
||||||
}
|
}
|
||||||
if (a_tables === false || b_tables == false) {
|
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_a, a] of a_tables) {
|
||||||
for (let [f_b, b] of b_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) {
|
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) {
|
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) {
|
if (prev_c == null) {
|
||||||
c_tables.set(flavor, c);
|
c_tables.set(flavor, c)
|
||||||
} else {
|
} 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 {
|
static or(...in_tables: VersionRangeTables[]): VersionRangeTables {
|
||||||
let out_tables: VersionRangeTables = false;
|
let out_tables: VersionRangeTables = false
|
||||||
for (let tables of in_tables) {
|
for (let tables of in_tables) {
|
||||||
if (tables === false) {
|
if (tables === false) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
if (tables === true) {
|
if (tables === true) {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
if (out_tables === false) {
|
if (out_tables === false) {
|
||||||
out_tables = new DeepMap();
|
out_tables = new DeepMap()
|
||||||
}
|
}
|
||||||
for (let [flavor, table] of tables) {
|
for (let [flavor, table] of tables) {
|
||||||
let prev = out_tables.get(flavor);
|
let prev = out_tables.get(flavor)
|
||||||
if (prev == null) {
|
if (prev == null) {
|
||||||
out_tables.set(flavor, table);
|
out_tables.set(flavor, table)
|
||||||
} else {
|
} 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 {
|
static collapse(tables: VersionRangeTables): boolean | null {
|
||||||
if (tables === true || tables === false) {
|
if (tables === true || tables === false) {
|
||||||
return tables;
|
return tables
|
||||||
} else {
|
} else {
|
||||||
let found = null;
|
let found = null
|
||||||
for (let table of tables.values()) {
|
for (let table of tables.values()) {
|
||||||
for (let x of table.values) {
|
for (let x of table.values) {
|
||||||
if (found == null) {
|
if (found == null) {
|
||||||
found = x;
|
found = x
|
||||||
} else if (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
|
* https://en.wikipedia.org/wiki/Canonical_normal_form#Minterms
|
||||||
*/
|
*/
|
||||||
static minterms(tables: VersionRangeTables): VersionRange {
|
static minterms(tables: VersionRangeTables): VersionRange {
|
||||||
let collapse = VersionRangeTable.collapse(tables);
|
let collapse = VersionRangeTable.collapse(tables)
|
||||||
if (tables === true || collapse === true) {
|
if (tables === true || collapse === true) {
|
||||||
return VersionRange.any()
|
return VersionRange.any()
|
||||||
}
|
}
|
||||||
if (tables == false || collapse === false) {
|
if (tables == false || collapse === false) {
|
||||||
return VersionRange.none()
|
return VersionRange.none()
|
||||||
}
|
}
|
||||||
let sum_terms: VersionRange[] = [];
|
let sum_terms: VersionRange[] = []
|
||||||
for (let [flavor, table] of tables) {
|
for (let [flavor, table] of tables) {
|
||||||
let cmp_flavor = null;
|
let cmp_flavor = null
|
||||||
if (flavor.type == 'Flavor') {
|
if (flavor.type == "Flavor") {
|
||||||
cmp_flavor = flavor.flavor;
|
cmp_flavor = flavor.flavor
|
||||||
}
|
}
|
||||||
for (let i = 0; i < table.values.length; i++) {
|
for (let i = 0; i < table.values.length; i++) {
|
||||||
let term: VersionRange[] = [];
|
let term: VersionRange[] = []
|
||||||
if (!table.values[i]) {
|
if (!table.values[i]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flavor.type == 'FlavorNot') {
|
if (flavor.type == "FlavorNot") {
|
||||||
for (let not_flavor of flavor.flavors) {
|
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 p = null
|
||||||
let q = null;
|
let q = null
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
p = table.points[i - 1];
|
p = table.points[i - 1]
|
||||||
}
|
}
|
||||||
if (i < table.points.length) {
|
if (i < table.points.length) {
|
||||||
q = table.points[i];
|
q = table.points[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p != null && q != null && adjacentVersionRangePoints(p, q)) {
|
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 {
|
} else {
|
||||||
if (p != null && p.side < 0) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {}
|
constructor(public atom: Anchor | And | Or | Not | P.Any | P.None | Flavor) {}
|
||||||
|
|
||||||
toStringParens(parent: "And" | "Or" | "Not") {
|
toStringParens(parent: "And" | "Or" | "Not") {
|
||||||
let needs = true;
|
let needs = true
|
||||||
switch (this.atom.type) {
|
switch (this.atom.type) {
|
||||||
case "And":
|
case "And":
|
||||||
case "Or":
|
case "Or":
|
||||||
needs = parent != this.atom.type;
|
needs = parent != this.atom.type
|
||||||
break
|
break
|
||||||
case "Anchor":
|
case "Anchor":
|
||||||
case "Any":
|
case "Any":
|
||||||
@@ -400,14 +477,14 @@ export class VersionRange {
|
|||||||
break
|
break
|
||||||
case "Not":
|
case "Not":
|
||||||
case "Flavor":
|
case "Flavor":
|
||||||
needs = false;
|
needs = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needs) {
|
if (needs) {
|
||||||
return "(" + this.toString() + ")";
|
return "(" + this.toString() + ")"
|
||||||
} else {
|
} else {
|
||||||
return this.toString();
|
return this.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,39 +596,39 @@ export class VersionRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static and(...xs: Array<VersionRange>) {
|
static and(...xs: Array<VersionRange>) {
|
||||||
let y = VersionRange.any();
|
let y = VersionRange.any()
|
||||||
for (let x of xs) {
|
for (let x of xs) {
|
||||||
if (x.atom.type == 'Any') {
|
if (x.atom.type == "Any") {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
if (x.atom.type == 'None') {
|
if (x.atom.type == "None") {
|
||||||
return x;
|
return x
|
||||||
}
|
}
|
||||||
if (y.atom.type == 'Any') {
|
if (y.atom.type == "Any") {
|
||||||
y = x;
|
y = x
|
||||||
} else {
|
} 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<VersionRange>) {
|
static or(...xs: Array<VersionRange>) {
|
||||||
let y = VersionRange.none();
|
let y = VersionRange.none()
|
||||||
for (let x of xs) {
|
for (let x of xs) {
|
||||||
if (x.atom.type == 'None') {
|
if (x.atom.type == "None") {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
if (x.atom.type == 'Any') {
|
if (x.atom.type == "Any") {
|
||||||
return x;
|
return x
|
||||||
}
|
}
|
||||||
if (y.atom.type == 'None') {
|
if (y.atom.type == "None") {
|
||||||
y = x;
|
y = x
|
||||||
} else {
|
} 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() {
|
static any() {
|
||||||
@@ -567,7 +644,7 @@ export class VersionRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tables(): VersionRangeTables {
|
tables(): VersionRangeTables {
|
||||||
switch(this.atom.type) {
|
switch (this.atom.type) {
|
||||||
case "Anchor":
|
case "Anchor":
|
||||||
switch (this.atom.operator) {
|
switch (this.atom.operator) {
|
||||||
case "=":
|
case "=":
|
||||||
@@ -587,21 +664,33 @@ export class VersionRange {
|
|||||||
case "!=":
|
case "!=":
|
||||||
// `!=1.2.3` is equivalent to `!(>=1.2.3 && <=1.2.3 && #flavor)`
|
// `!=1.2.3` is equivalent to `!(>=1.2.3 && <=1.2.3 && #flavor)`
|
||||||
// **not** equivalent to `(<1.2.3 || >1.2.3) && #flavor`
|
// **not** equivalent to `(<1.2.3 || >1.2.3) && #flavor`
|
||||||
return VersionRangeTable.not(VersionRangeTable.and(
|
return VersionRangeTable.not(
|
||||||
VersionRangeTable.cmp(this.atom.version, -1, false, true),
|
VersionRangeTable.and(
|
||||||
VersionRangeTable.cmp(this.atom.version, 1, true, false),
|
VersionRangeTable.cmp(this.atom.version, -1, false, true),
|
||||||
))
|
VersionRangeTable.cmp(this.atom.version, 1, true, false),
|
||||||
|
),
|
||||||
|
)
|
||||||
case "^":
|
case "^":
|
||||||
// `^1.2.3` is equivalent to `>=1.2.3 && <2.0.0 && #flavor`
|
// `^1.2.3` is equivalent to `>=1.2.3 && <2.0.0 && #flavor`
|
||||||
return VersionRangeTable.and(
|
return VersionRangeTable.and(
|
||||||
VersionRangeTable.cmp(this.atom.version, -1, false, true),
|
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 "~":
|
case "~":
|
||||||
// `~1.2.3` is equivalent to `>=1.2.3 && <1.3.0 && #flavor`
|
// `~1.2.3` is equivalent to `>=1.2.3 && <1.3.0 && #flavor`
|
||||||
return VersionRangeTable.and(
|
return VersionRangeTable.and(
|
||||||
VersionRangeTable.cmp(this.atom.version, -1, false, true),
|
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":
|
case "Flavor":
|
||||||
@@ -609,9 +698,15 @@ export class VersionRange {
|
|||||||
case "Not":
|
case "Not":
|
||||||
return VersionRangeTable.not(this.atom.value.tables())
|
return VersionRangeTable.not(this.atom.value.tables())
|
||||||
case "And":
|
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":
|
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":
|
case "Any":
|
||||||
return true
|
return true
|
||||||
case "None":
|
case "None":
|
||||||
@@ -620,15 +715,15 @@ export class VersionRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
satisfiable(): boolean {
|
satisfiable(): boolean {
|
||||||
return VersionRangeTable.collapse(this.tables()) !== false;
|
return VersionRangeTable.collapse(this.tables()) !== false
|
||||||
}
|
}
|
||||||
|
|
||||||
intersects(other: VersionRange): boolean {
|
intersects(other: VersionRange): boolean {
|
||||||
return VersionRange.and(this, other).satisfiable();
|
return VersionRange.and(this, other).satisfiable()
|
||||||
}
|
}
|
||||||
|
|
||||||
normalize(): VersionRange {
|
normalize(): VersionRange {
|
||||||
return VersionRangeTable.minterms(this.tables());
|
return VersionRangeTable.minterms(this.tables())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export type Manifest = {
|
|||||||
donationUrl: string | null
|
donationUrl: string | null
|
||||||
description: Description
|
description: Description
|
||||||
images: { [key: ImageId]: ImageConfig }
|
images: { [key: ImageId]: ImageConfig }
|
||||||
assets: Array<VolumeId>
|
|
||||||
volumes: Array<VolumeId>
|
volumes: Array<VolumeId>
|
||||||
alerts: Alerts
|
alerts: Alerts
|
||||||
dependencies: Dependencies
|
dependencies: Dependencies
|
||||||
|
|||||||
@@ -80,10 +80,15 @@ describe("ExVer", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test(`VersionRange.parse("=1") invalid`, () => {
|
test(`VersionRange.parse("=1") invalid`, () => {
|
||||||
expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.1:0"))).toEqual(false)
|
expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.1:0"))).toEqual(
|
||||||
expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.0:1"))).toEqual(false)
|
false,
|
||||||
|
)
|
||||||
|
expect(checker.satisfiedBy(ExtendedVersion.parse("1.0.0:1"))).toEqual(
|
||||||
|
false,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
const checker = VersionRange.parse(">=1.2.3:4")
|
const checker = VersionRange.parse(">=1.2.3:4")
|
||||||
test(`VersionRange.parse(">=1.2.3:4") valid`, () => {
|
test(`VersionRange.parse(">=1.2.3:4") valid`, () => {
|
||||||
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
|
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
|
||||||
@@ -306,32 +311,44 @@ describe("ExVer", () => {
|
|||||||
{
|
{
|
||||||
function testNormalization(input: string, expected: string) {
|
function testNormalization(input: string, expected: string) {
|
||||||
test(`"${input}" normalizes to "${expected}"`, () => {
|
test(`"${input}" normalizes to "${expected}"`, () => {
|
||||||
const checker = VersionRange.parse(input).normalize();
|
const checker = VersionRange.parse(input).normalize()
|
||||||
expect(checker.toString()).toEqual(expected);
|
expect(checker.toString()).toEqual(expected)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
testNormalization("=2.0", "=2.0:0");
|
testNormalization("=2.0", "=2.0:0")
|
||||||
testNormalization("=1 && =2", "!");
|
testNormalization("=1 && =2", "!")
|
||||||
testNormalization("!(=1 && =2)", "*");
|
testNormalization("!(=1 && =2)", "*")
|
||||||
testNormalization("!=1 || !=2", "*");
|
testNormalization("!=1 || !=2", "*")
|
||||||
testNormalization("(!=#foo:1 || !=#foo:2) && #foo", "#foo");
|
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(
|
||||||
testNormalization("!(=1 || =2)", "<1:0 || (>1:0 && <2:0) || >2:0 || !#");
|
"!=#foo:1 || !=#bar:2",
|
||||||
testNormalization("=1 && (=2 || =3)", "!");
|
"<#foo:1:0 || >#foo:1:0 || !#foo || <#bar:2:0 || >#bar:2:0 || !#bar",
|
||||||
testNormalization("=1 && (=1 || =2)", "=1:0");
|
)
|
||||||
testNormalization("=#foo:1 && =#bar:1", "!");
|
testNormalization("!(=1 || =2)", "<1:0 || (>1:0 && <2:0) || >2:0 || !#")
|
||||||
testNormalization("!(=#foo:1) && !(=#bar:1)", "<#foo:1:0 || >#foo:1:0 || <#bar:1:0 || >#bar:1:0 || (!#foo && !#bar)");
|
testNormalization("=1 && (=2 || =3)", "!")
|
||||||
testNormalization("!(=#foo:1) && !(=#bar:1) && >2", ">2:0");
|
testNormalization("=1 && (=1 || =2)", "=1:0")
|
||||||
testNormalization("~1.2.3", ">=1.2.3:0 && <1.3.0:0");
|
testNormalization("=#foo:1 && =#bar:1", "!")
|
||||||
testNormalization("^1.2.3", ">=1.2.3:0 && <2.0.0:0");
|
testNormalization(
|
||||||
testNormalization("^1.2.3 && >=1 && >=1.2 && >=1.3", ">=1.3:0 && <2.0.0:0");
|
"!(=#foo:1) && !(=#bar:1)",
|
||||||
testNormalization("(>=1.0 && <1.1) || (>=1.1 && <1.2) || (>=1.2 && <1.3)", ">=1.0:0 && <1.3:0");
|
"<#foo:1:0 || >#foo:1:0 || <#bar:1:0 || >#bar:1:0 || (!#foo && !#bar)",
|
||||||
testNormalization(">1 || <2", "#");
|
)
|
||||||
|
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.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?
|
// 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")
|
const checker = VersionRange.parse("=1 || =2")
|
||||||
|
|
||||||
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
|
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:0"))).toEqual(
|
||||||
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(false) // really?
|
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("2:0"))).toEqual(true)
|
||||||
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
|
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ type EffectsTypeChecker<T extends StringObject = Effects> = {
|
|||||||
describe("startosTypeValidation ", () => {
|
describe("startosTypeValidation ", () => {
|
||||||
test(`checking the params match`, () => {
|
test(`checking the params match`, () => {
|
||||||
typeEquality<EffectsTypeChecker>({
|
typeEquality<EffectsTypeChecker>({
|
||||||
constRetry: {},
|
|
||||||
clearCallbacks: {} as ClearCallbacksParams,
|
clearCallbacks: {} as ClearCallbacksParams,
|
||||||
action: {
|
action: {
|
||||||
clear: {} as ClearActionsParams,
|
clear: {} as ClearActionsParams,
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ export {
|
|||||||
} from "./dependencies/setupDependencies"
|
} from "./dependencies/setupDependencies"
|
||||||
|
|
||||||
export type ExposedStorePaths = string[] & Affine<"ExposedStorePaths">
|
export type ExposedStorePaths = string[] & Affine<"ExposedStorePaths">
|
||||||
declare const HealthProof: unique symbol
|
|
||||||
export type HealthReceipt = {
|
|
||||||
[HealthProof]: never
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DaemonBuildable = {
|
export type DaemonBuildable = {
|
||||||
build(): Promise<{
|
build(): Promise<{
|
||||||
|
|||||||
@@ -84,14 +84,6 @@ export type SDKManifest = {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
readonly images: Record<ImageId, SDKImageInputSpec>
|
readonly images: Record<ImageId, SDKImageInputSpec>
|
||||||
/**
|
|
||||||
* @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.
|
* @description A list of data volumes that will mount to the container. Must contain at least one volume.
|
||||||
* @example ['main']
|
* @example ['main']
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export class GetSystemSmtp {
|
|||||||
*/
|
*/
|
||||||
const() {
|
const() {
|
||||||
return this.effects.getSystemSmtp({
|
return this.effects.getSystemSmtp({
|
||||||
callback: () => this.effects.constRetry(),
|
callback:
|
||||||
|
this.effects.constRetry &&
|
||||||
|
(() => this.effects.constRetry && this.effects.constRetry()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ export function partialDiff<T>(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if (typeof prev === "object" && typeof next === "object") {
|
} else if (typeof prev === "object" && typeof next === "object") {
|
||||||
if (prev === null) {
|
if (prev === null || next === null) return { diff: next }
|
||||||
return { diff: next }
|
|
||||||
}
|
|
||||||
if (next === null) return
|
|
||||||
const res = { diff: {} as Record<keyof T, any> }
|
const res = { diff: {} as Record<keyof T, any> }
|
||||||
for (let key in next) {
|
for (let key in next) {
|
||||||
const diff = partialDiff(prev[key], next[key])
|
const diff = partialDiff(prev[key], next[key])
|
||||||
|
|||||||
@@ -217,7 +217,9 @@ export class GetServiceInterface {
|
|||||||
*/
|
*/
|
||||||
async const() {
|
async const() {
|
||||||
const { id, packageId } = this.opts
|
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({
|
const interfaceFilled = await makeInterfaceFilled({
|
||||||
effects: this.effects,
|
effects: this.effects,
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ export class GetServiceInterfaces {
|
|||||||
*/
|
*/
|
||||||
async const() {
|
async const() {
|
||||||
const { packageId } = this.opts
|
const { packageId } = this.opts
|
||||||
const callback = () => this.effects.constRetry()
|
const callback =
|
||||||
|
this.effects.constRetry &&
|
||||||
|
(() => this.effects.constRetry && this.effects.constRetry())
|
||||||
const interfaceFilled: ServiceInterfaceFilled[] =
|
const interfaceFilled: ServiceInterfaceFilled[] =
|
||||||
await makeManyInterfaceFilled({
|
await makeManyInterfaceFilled({
|
||||||
effects: this.effects,
|
effects: this.effects,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
SyncOptions,
|
SyncOptions,
|
||||||
ServiceInterfaceId,
|
ServiceInterfaceId,
|
||||||
PackageId,
|
PackageId,
|
||||||
HealthReceipt,
|
|
||||||
ServiceInterfaceType,
|
ServiceInterfaceType,
|
||||||
Effects,
|
Effects,
|
||||||
} from "../../base/lib/types"
|
} from "../../base/lib/types"
|
||||||
@@ -27,7 +26,7 @@ import * as patterns from "../../base/lib/util/patterns"
|
|||||||
import { BackupSync, Backups } from "./backup/Backups"
|
import { BackupSync, Backups } from "./backup/Backups"
|
||||||
import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants"
|
import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants"
|
||||||
import { CommandController, Daemons } from "./mainFn/Daemons"
|
import { CommandController, Daemons } from "./mainFn/Daemons"
|
||||||
import { healthCheck, HealthCheckParams } from "./health/HealthCheck"
|
import { HealthCheck } from "./health/HealthCheck"
|
||||||
import { checkPortListening } from "./health/checkFns/checkPortListening"
|
import { checkPortListening } from "./health/checkFns/checkPortListening"
|
||||||
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
||||||
import { List } from "../../base/lib/actions/input/builder/list"
|
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 { setupInit } from "./inits/setupInit"
|
||||||
import * as fs from "node:fs/promises"
|
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
|
// prettier-ignore
|
||||||
type AnyNeverCond<T extends any[], Then, Else> =
|
type AnyNeverCond<T extends any[], Then, Else> =
|
||||||
@@ -231,7 +230,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
|
|||||||
},
|
},
|
||||||
command: T.CommandType,
|
command: T.CommandType,
|
||||||
options: CommandOptions & {
|
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
|
* A name to use to refer to the ephemeral subcontainer for debugging purposes
|
||||||
@@ -420,11 +419,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
|
|||||||
hostnames: string[],
|
hostnames: string[],
|
||||||
algorithm?: T.Algorithm,
|
algorithm?: T.Algorithm,
|
||||||
) => new GetSslCertificate(effects, hostnames, algorithm),
|
) => new GetSslCertificate(effects, hostnames, algorithm),
|
||||||
HealthCheck: {
|
HealthCheck,
|
||||||
of(effects: T.Effects, o: Omit<HealthCheckParams, "effects">) {
|
|
||||||
return healthCheck({ effects, ...o })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
healthCheck: {
|
healthCheck: {
|
||||||
checkPortListening,
|
checkPortListening,
|
||||||
checkWebUrl,
|
checkWebUrl,
|
||||||
@@ -677,9 +672,9 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
|
|||||||
of(
|
of(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
||||||
healthReceipts: HealthReceipt[],
|
healthChecks: HealthCheck[],
|
||||||
) {
|
) {
|
||||||
return Daemons.of<Manifest>({ effects, started, healthReceipts })
|
return Daemons.of<Manifest>({ effects, started, healthChecks })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SubContainer: {
|
SubContainer: {
|
||||||
@@ -699,7 +694,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
|
|||||||
imageId: T.ImageId & keyof Manifest["images"]
|
imageId: T.ImageId & keyof Manifest["images"]
|
||||||
sharedRun?: boolean
|
sharedRun?: boolean
|
||||||
},
|
},
|
||||||
mounts: { options: MountOptions; path: string }[],
|
mounts: { options: MountOptions; mountpoint: string }[],
|
||||||
name: string,
|
name: string,
|
||||||
fn: (subContainer: SubContainer) => Promise<T>,
|
fn: (subContainer: SubContainer) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
@@ -1081,7 +1076,7 @@ export async function runCommand<Manifest extends T.SDKManifest>(
|
|||||||
image: { imageId: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean },
|
image: { imageId: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean },
|
||||||
command: T.CommandType,
|
command: T.CommandType,
|
||||||
options: CommandOptions & {
|
options: CommandOptions & {
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { mountpoint: string; options: MountOptions }[]
|
||||||
},
|
},
|
||||||
name?: string,
|
name?: string,
|
||||||
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> {
|
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> {
|
||||||
|
|||||||
@@ -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 { HealthCheckResult } from "./checkFns/HealthCheckResult"
|
||||||
import { Trigger } from "../trigger"
|
import { Trigger } from "../trigger"
|
||||||
import { TriggerInput } from "../trigger/TriggerInput"
|
import { TriggerInput } from "../trigger/TriggerInput"
|
||||||
import { defaultTrigger } from "../trigger/defaultTrigger"
|
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||||
import { once, asError } from "../util"
|
import { once, asError, Drop } from "../util"
|
||||||
import { object, unknown } from "ts-matches"
|
import { object, unknown } from "ts-matches"
|
||||||
|
|
||||||
export type HealthCheckParams = {
|
export type HealthCheckParams = {
|
||||||
effects: Effects
|
|
||||||
id: HealthCheckId
|
id: HealthCheckId
|
||||||
name: string
|
name: string
|
||||||
trigger?: Trigger
|
trigger?: Trigger
|
||||||
@@ -16,53 +15,110 @@ export type HealthCheckParams = {
|
|||||||
onFirstSuccess?: () => unknown | Promise<unknown>
|
onFirstSuccess?: () => unknown | Promise<unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function healthCheck(o: HealthCheckParams) {
|
export class HealthCheck extends Drop {
|
||||||
new Promise(async () => {
|
private started: number | null = null
|
||||||
const start = performance.now()
|
private setStarted = (started: number | null) => {
|
||||||
let currentValue: TriggerInput = {}
|
this.started = started
|
||||||
const getCurrentValue = () => currentValue
|
}
|
||||||
const gracePeriod = o.gracePeriod ?? 5000
|
private exited = false
|
||||||
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
private exit = () => {
|
||||||
const triggerFirstSuccess = once(() =>
|
this.exited = true
|
||||||
Promise.resolve(
|
}
|
||||||
"onFirstSuccess" in o && o.onFirstSuccess
|
private currentValue: TriggerInput = {}
|
||||||
? o.onFirstSuccess()
|
private promise: Promise<void>
|
||||||
: undefined,
|
private constructor(effects: Effects, o: HealthCheckParams) {
|
||||||
),
|
super()
|
||||||
)
|
this.promise = Promise.resolve().then(async () => {
|
||||||
for (
|
const getCurrentValue = () => this.currentValue
|
||||||
let res = await trigger.next();
|
const gracePeriod = o.gracePeriod ?? 5000
|
||||||
!res.done;
|
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
||||||
res = await trigger.next()
|
const triggerFirstSuccess = once(() =>
|
||||||
) {
|
Promise.resolve(
|
||||||
try {
|
"onFirstSuccess" in o && o.onFirstSuccess
|
||||||
let { result, message } = await o.fn()
|
? o.onFirstSuccess()
|
||||||
if (result === "failure" && performance.now() - start <= gracePeriod)
|
: undefined,
|
||||||
result = "starting"
|
),
|
||||||
await o.effects.setHealth({
|
)
|
||||||
name: o.name,
|
const checkStarted = () =>
|
||||||
id: o.id,
|
[
|
||||||
result,
|
this.started,
|
||||||
message: message || "",
|
new Promise<void>((resolve) => {
|
||||||
})
|
this.setStarted = (started: number | null) => {
|
||||||
currentValue.lastResult = result
|
this.started = started
|
||||||
await triggerFirstSuccess().catch((err) => {
|
resolve()
|
||||||
console.error(asError(err))
|
}
|
||||||
})
|
this.exit = () => {
|
||||||
} catch (e) {
|
this.exited = true
|
||||||
await o.effects.setHealth({
|
resolve()
|
||||||
name: o.name,
|
}
|
||||||
id: o.id,
|
}),
|
||||||
result:
|
] as const
|
||||||
performance.now() - start <= gracePeriod ? "starting" : "failure",
|
let triggered = false
|
||||||
message: asMessage(e) || "",
|
while (!this.exited) {
|
||||||
})
|
const [started, changed] = checkStarted()
|
||||||
currentValue.lastResult = "failure"
|
let race:
|
||||||
|
| [Promise<void>]
|
||||||
|
| [Promise<void>, Promise<IteratorResult<unknown, unknown>>] = [
|
||||||
|
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) {
|
function asMessage(e: unknown) {
|
||||||
if (object({ message: unknown }).test(e)) return String(e.message)
|
if (object({ message: unknown }).test(e)) return String(e.message)
|
||||||
const value = String(e)
|
const value = String(e)
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
import "./checkFns"
|
import "./checkFns"
|
||||||
|
|
||||||
|
export { HealthCheck } from "./HealthCheck"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ISB,
|
ISB,
|
||||||
IST,
|
IST,
|
||||||
types,
|
types,
|
||||||
T,
|
|
||||||
matches,
|
matches,
|
||||||
utils,
|
utils,
|
||||||
} from "../../base/lib"
|
} from "../../base/lib"
|
||||||
@@ -21,10 +20,10 @@ export {
|
|||||||
ISB,
|
ISB,
|
||||||
IST,
|
IST,
|
||||||
types,
|
types,
|
||||||
T,
|
|
||||||
matches,
|
matches,
|
||||||
utils,
|
utils,
|
||||||
}
|
}
|
||||||
|
export * as T from "./types"
|
||||||
export { Daemons } from "./mainFn/Daemons"
|
export { Daemons } from "./mainFn/Daemons"
|
||||||
export { SubContainer } from "./util/SubContainer"
|
export { SubContainer } from "./util/SubContainer"
|
||||||
export { StartSdk } from "./StartSdk"
|
export { StartSdk } from "./StartSdk"
|
||||||
|
|||||||
@@ -7,18 +7,20 @@ import {
|
|||||||
SubContainerHandle,
|
SubContainerHandle,
|
||||||
SubContainer,
|
SubContainer,
|
||||||
} from "../util/SubContainer"
|
} from "../util/SubContainer"
|
||||||
import { splitCommand } from "../util"
|
import { Drop, splitCommand } from "../util"
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as fs from "node:fs/promises"
|
import * as fs from "node:fs/promises"
|
||||||
|
|
||||||
export class CommandController {
|
export class CommandController extends Drop {
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly runningAnswer: Promise<unknown>,
|
readonly runningAnswer: Promise<unknown>,
|
||||||
private state: { exited: boolean },
|
private state: { exited: boolean },
|
||||||
private readonly subcontainer: SubContainer,
|
private readonly subcontainer: SubContainer,
|
||||||
private process: cp.ChildProcess,
|
private process: cp.ChildProcess,
|
||||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
static of<Manifest extends T.SDKManifest>() {
|
static of<Manifest extends T.SDKManifest>() {
|
||||||
return async <A extends string>(
|
return async <A extends string>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
@@ -33,7 +35,7 @@ export class CommandController {
|
|||||||
subcontainerName?: string
|
subcontainerName?: string
|
||||||
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
||||||
sigtermTimeout?: number
|
sigtermTimeout?: number
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { mountpoint: string; options: MountOptions }[]
|
||||||
runAsInit?: boolean
|
runAsInit?: boolean
|
||||||
env?:
|
env?:
|
||||||
| {
|
| {
|
||||||
@@ -68,7 +70,7 @@ export class CommandController {
|
|||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
for (let mount of options.mounts || []) {
|
for (let mount of options.mounts || []) {
|
||||||
await subc.mount(mount.options, mount.path)
|
await subc.mount(mount.options, mount.mountpoint)
|
||||||
}
|
}
|
||||||
return subc
|
return subc
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -135,37 +137,42 @@ export class CommandController {
|
|||||||
return new SubContainerHandle(this.subcontainer)
|
return new SubContainerHandle(this.subcontainer)
|
||||||
}
|
}
|
||||||
async wait({ timeout = NO_TIMEOUT } = {}) {
|
async wait({ timeout = NO_TIMEOUT } = {}) {
|
||||||
|
const self = this.weak()
|
||||||
if (timeout > 0)
|
if (timeout > 0)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.term()
|
self.term()
|
||||||
}, timeout)
|
}, timeout)
|
||||||
try {
|
try {
|
||||||
return await this.runningAnswer
|
return await self.runningAnswer
|
||||||
} finally {
|
} finally {
|
||||||
if (!this.state.exited) {
|
if (!self.state.exited) {
|
||||||
this.process.kill("SIGKILL")
|
self.process.kill("SIGKILL")
|
||||||
}
|
}
|
||||||
await this.subcontainer.destroy().catch((_) => {})
|
await self.subcontainer.destroy().catch((_) => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||||
|
const self = this.weak()
|
||||||
try {
|
try {
|
||||||
if (!this.state.exited) {
|
if (!self.state.exited) {
|
||||||
if (signal !== "SIGKILL") {
|
if (signal !== "SIGKILL") {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.state.exited) this.process.kill("SIGKILL")
|
if (!self.state.exited) self.process.kill("SIGKILL")
|
||||||
}, timeout)
|
}, timeout)
|
||||||
}
|
}
|
||||||
if (!this.process.kill(signal)) {
|
if (!self.process.kill(signal)) {
|
||||||
console.error(
|
console.error(
|
||||||
`failed to send signal ${signal} to pid ${this.process.pid}`,
|
`failed to send signal ${signal} to pid ${this.process.pid}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.runningAnswer
|
await self.runningAnswer
|
||||||
} finally {
|
} finally {
|
||||||
await this.subcontainer.destroy()
|
await self.subcontainer.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onDrop(): void {
|
||||||
|
this.term().catch(console.error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class Daemon {
|
|||||||
command: T.CommandType,
|
command: T.CommandType,
|
||||||
options: {
|
options: {
|
||||||
subcontainerName?: string
|
subcontainerName?: string
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
mounts?: { mountpoint: string; options: MountOptions }[]
|
||||||
env?:
|
env?:
|
||||||
| {
|
| {
|
||||||
[variable: string]: string
|
[variable: string]: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { HealthReceipt, Signals } from "../../../base/lib/types"
|
import { Signals } from "../../../base/lib/types"
|
||||||
|
|
||||||
import { HealthCheckResult } from "../health/checkFns"
|
import { HealthCheckResult } from "../health/checkFns"
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ export { CommandController } from "./CommandController"
|
|||||||
import { HealthDaemon } from "./HealthDaemon"
|
import { HealthDaemon } from "./HealthDaemon"
|
||||||
import { Daemon } from "./Daemon"
|
import { Daemon } from "./Daemon"
|
||||||
import { CommandController } from "./CommandController"
|
import { CommandController } from "./CommandController"
|
||||||
|
import { HealthCheck } from "../health/HealthCheck"
|
||||||
|
|
||||||
export const cpExec = promisify(CP.exec)
|
export const cpExec = promisify(CP.exec)
|
||||||
export const cpExecFile = promisify(CP.execFile)
|
export const cpExecFile = promisify(CP.execFile)
|
||||||
@@ -115,6 +116,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
readonly daemons: Promise<Daemon>[],
|
readonly daemons: Promise<Daemon>[],
|
||||||
readonly ids: Ids[],
|
readonly ids: Ids[],
|
||||||
readonly healthDaemons: HealthDaemon[],
|
readonly healthDaemons: HealthDaemon[],
|
||||||
|
readonly healthChecks: HealthCheck[],
|
||||||
) {}
|
) {}
|
||||||
/**
|
/**
|
||||||
* Returns an empty new Daemons class with the provided inputSpec.
|
* Returns an empty new Daemons class with the provided inputSpec.
|
||||||
@@ -129,7 +131,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
static of<Manifest extends T.SDKManifest>(options: {
|
static of<Manifest extends T.SDKManifest>(options: {
|
||||||
effects: T.Effects
|
effects: T.Effects
|
||||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
|
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
|
||||||
healthReceipts: HealthReceipt[]
|
healthChecks: HealthCheck[]
|
||||||
}) {
|
}) {
|
||||||
return new Daemons<Manifest, never>(
|
return new Daemons<Manifest, never>(
|
||||||
options.effects,
|
options.effects,
|
||||||
@@ -137,6 +139,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
|
options.healthChecks,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -187,28 +190,33 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
daemons,
|
daemons,
|
||||||
ids,
|
ids,
|
||||||
healthDaemons,
|
healthDaemons,
|
||||||
|
this.healthChecks,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async build() {
|
async term() {
|
||||||
const built = {
|
try {
|
||||||
term: async () => {
|
this.healthChecks.forEach((health) => health.stop())
|
||||||
try {
|
for (let result of await Promise.allSettled(
|
||||||
for (let result of await Promise.allSettled(
|
this.healthDaemons.map((x) => x.term({ timeout: x.sigtermTimeout })),
|
||||||
this.healthDaemons.map((x) =>
|
)) {
|
||||||
x.term({ timeout: x.sigtermTimeout }),
|
if (result.status === "rejected") {
|
||||||
),
|
console.error(result.reason)
|
||||||
)) {
|
|
||||||
if (result.status === "rejected") {
|
|
||||||
console.error(result.reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.effects.setMainStatus({ status: "stopped" })
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export class HealthDaemon {
|
|||||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||||
) {
|
) {
|
||||||
this.readyPromise = new Promise((resolve) => (this.resolveReady = resolve))
|
this.readyPromise = new Promise((resolve) => (this.resolveReady = resolve))
|
||||||
this.updateStatus()
|
|
||||||
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()))
|
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +165,8 @@ export class HealthDaemon {
|
|||||||
} as SetHealth)
|
} as SetHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateStatus() {
|
async updateStatus() {
|
||||||
const healths = this.dependencies.map((d) => d._health)
|
const healths = this.dependencies.map((d) => d.running && d._health)
|
||||||
this.changeRunning(healths.every((x) => x.result === "success"))
|
this.changeRunning(healths.every((x) => x && x.result === "success"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as T from "../../../base/lib/types"
|
import * as T from "../../../base/lib/types"
|
||||||
import { MountOptions } from "../util/SubContainer"
|
import { MountOptions } from "../util/SubContainer"
|
||||||
|
|
||||||
type MountArray = { path: string; options: MountOptions }[]
|
type MountArray = { mountpoint: string; options: MountOptions }[]
|
||||||
|
|
||||||
export class Mounts<Manifest extends T.SDKManifest> {
|
export class Mounts<Manifest extends T.SDKManifest> {
|
||||||
private constructor(
|
private constructor(
|
||||||
@@ -12,7 +12,6 @@ export class Mounts<Manifest extends T.SDKManifest> {
|
|||||||
readonly: boolean
|
readonly: boolean
|
||||||
}[],
|
}[],
|
||||||
readonly assets: {
|
readonly assets: {
|
||||||
id: Manifest["assets"][number]
|
|
||||||
subpath: string | null
|
subpath: string | null
|
||||||
mountpoint: string
|
mountpoint: string
|
||||||
}[],
|
}[],
|
||||||
@@ -49,15 +48,12 @@ export class Mounts<Manifest extends T.SDKManifest> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addAssets(
|
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 */
|
/** The path within the asset directory to mount. Use `null` to mount the entire volume */
|
||||||
subpath: string | null,
|
subpath: string | null,
|
||||||
/** Where to mount the asset. e.g. /asset */
|
/** Where to mount the asset. e.g. /asset */
|
||||||
mountpoint: string,
|
mountpoint: string,
|
||||||
) {
|
) {
|
||||||
this.assets.push({
|
this.assets.push({
|
||||||
id,
|
|
||||||
subpath,
|
subpath,
|
||||||
mountpoint,
|
mountpoint,
|
||||||
})
|
})
|
||||||
@@ -102,7 +98,7 @@ export class Mounts<Manifest extends T.SDKManifest> {
|
|||||||
return ([] as MountArray)
|
return ([] as MountArray)
|
||||||
.concat(
|
.concat(
|
||||||
this.volumes.map((v) => ({
|
this.volumes.map((v) => ({
|
||||||
path: v.mountpoint,
|
mountpoint: v.mountpoint,
|
||||||
options: {
|
options: {
|
||||||
type: "volume",
|
type: "volume",
|
||||||
id: v.id,
|
id: v.id,
|
||||||
@@ -113,17 +109,16 @@ export class Mounts<Manifest extends T.SDKManifest> {
|
|||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
this.assets.map((a) => ({
|
this.assets.map((a) => ({
|
||||||
path: a.mountpoint,
|
mountpoint: a.mountpoint,
|
||||||
options: {
|
options: {
|
||||||
type: "assets",
|
type: "assets",
|
||||||
id: a.id,
|
|
||||||
subpath: a.subpath,
|
subpath: a.subpath,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(
|
||||||
this.dependencies.map((d) => ({
|
this.dependencies.map((d) => ({
|
||||||
path: d.mountpoint,
|
mountpoint: d.mountpoint,
|
||||||
options: {
|
options: {
|
||||||
type: "pointer",
|
type: "pointer",
|
||||||
packageId: d.dependencyId,
|
packageId: d.dependencyId,
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ import { execSync } from "child_process"
|
|||||||
export function setupManifest<
|
export function setupManifest<
|
||||||
Id extends string,
|
Id extends string,
|
||||||
VolumesTypes extends VolumeId,
|
VolumesTypes extends VolumeId,
|
||||||
AssetTypes extends VolumeId,
|
|
||||||
Manifest extends {
|
Manifest extends {
|
||||||
id: Id
|
id: Id
|
||||||
assets: AssetTypes[]
|
|
||||||
volumes: VolumesTypes[]
|
volumes: VolumesTypes[]
|
||||||
} & SDKManifest,
|
} & SDKManifest,
|
||||||
>(manifest: Manifest & SDKManifest): Manifest {
|
>(manifest: Manifest & SDKManifest): Manifest {
|
||||||
@@ -31,12 +29,10 @@ export function buildManifest<
|
|||||||
Version extends string,
|
Version extends string,
|
||||||
Dependencies extends Record<string, unknown>,
|
Dependencies extends Record<string, unknown>,
|
||||||
VolumesTypes extends VolumeId,
|
VolumesTypes extends VolumeId,
|
||||||
AssetTypes extends VolumeId,
|
|
||||||
ImagesTypes extends ImageId,
|
ImagesTypes extends ImageId,
|
||||||
Manifest extends {
|
Manifest extends {
|
||||||
dependencies: Dependencies
|
dependencies: Dependencies
|
||||||
id: Id
|
id: Id
|
||||||
assets: AssetTypes[]
|
|
||||||
images: Record<ImagesTypes, SDKImageInputSpec>
|
images: Record<ImagesTypes, SDKImageInputSpec>
|
||||||
volumes: VolumesTypes[]
|
volumes: VolumesTypes[]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export class GetStore<Store, StoreValue> {
|
|||||||
return this.effects.store.get<Store, StoreValue>({
|
return this.effects.store.get<Store, StoreValue>({
|
||||||
...this.options,
|
...this.options,
|
||||||
path: extractJsonPath(this.path),
|
path: extractJsonPath(this.path),
|
||||||
callback: () => this.effects.constRetry(),
|
callback:
|
||||||
|
this.effects.constRetry &&
|
||||||
|
(() => this.effects.constRetry && this.effects.constRetry()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
2
sdk/package/lib/types.ts
Normal file
2
sdk/package/lib/types.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "../../base/lib/types"
|
||||||
|
export { HealthCheck } from "./health"
|
||||||
26
sdk/package/lib/util/Drop.ts
Normal file
26
sdk/package/lib/util/Drop.ts
Normal file
@@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,9 @@ export class GetSslCertificate {
|
|||||||
return this.effects.getSslCertificate({
|
return this.effects.getSslCertificate({
|
||||||
hostnames: this.hostnames,
|
hostnames: this.hostnames,
|
||||||
algorithm: this.algorithm,
|
algorithm: this.algorithm,
|
||||||
callback: () => this.effects.constRetry(),
|
callback:
|
||||||
|
this.effects.constRetry &&
|
||||||
|
(() => this.effects.constRetry && this.effects.constRetry()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -130,14 +130,14 @@ export class SubContainer implements ExecSpawnable {
|
|||||||
static async with<T>(
|
static async with<T>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
image: { imageId: T.ImageId; sharedRun?: boolean },
|
image: { imageId: T.ImageId; sharedRun?: boolean },
|
||||||
mounts: { options: MountOptions; path: string }[],
|
mounts: { options: MountOptions; mountpoint: string }[],
|
||||||
name: string,
|
name: string,
|
||||||
fn: (subContainer: SubContainer) => Promise<T>,
|
fn: (subContainer: SubContainer) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const subContainer = await SubContainer.of(effects, image, name)
|
const subContainer = await SubContainer.of(effects, image, name)
|
||||||
try {
|
try {
|
||||||
for (let mount of mounts) {
|
for (let mount of mounts) {
|
||||||
await subContainer.mount(mount.options, mount.path)
|
await subContainer.mount(mount.options, mount.mountpoint)
|
||||||
}
|
}
|
||||||
return await fn(subContainer)
|
return await fn(subContainer)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -166,7 +166,7 @@ export class SubContainer implements ExecSpawnable {
|
|||||||
? options.subpath
|
? options.subpath
|
||||||
: `/${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(from, { recursive: true })
|
||||||
await fs.mkdir(path, { recursive: true })
|
await fs.mkdir(path, { recursive: true })
|
||||||
@@ -449,7 +449,6 @@ export type MountOptionsVolume = {
|
|||||||
|
|
||||||
export type MountOptionsAssets = {
|
export type MountOptionsAssets = {
|
||||||
type: "assets"
|
type: "assets"
|
||||||
id: string
|
|
||||||
subpath: string | null
|
subpath: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as YAML from "yaml"
|
|||||||
import * as TOML from "@iarna/toml"
|
import * as TOML from "@iarna/toml"
|
||||||
import * as T from "../../../base/lib/types"
|
import * as T from "../../../base/lib/types"
|
||||||
import * as fs from "node:fs/promises"
|
import * as fs from "node:fs/promises"
|
||||||
import { asError } from "../../../base/lib/util"
|
import { asError, partialDiff } from "../../../base/lib/util"
|
||||||
|
|
||||||
const previousPath = /(.+?)\/([^/]*)$/
|
const previousPath = /(.+?)\/([^/]*)$/
|
||||||
|
|
||||||
@@ -101,6 +101,7 @@ function fileMerge(...args: any[]): any {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class FileHelper<A> {
|
export class FileHelper<A> {
|
||||||
|
private consts: (() => void)[] = []
|
||||||
protected constructor(
|
protected constructor(
|
||||||
readonly path: string,
|
readonly path: string,
|
||||||
readonly writeData: (dataIn: A) => string,
|
readonly writeData: (dataIn: A) => string,
|
||||||
@@ -108,27 +109,37 @@ export class FileHelper<A> {
|
|||||||
readonly validate: (value: unknown) => A,
|
readonly validate: (value: unknown) => A,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
private async writeFileRaw(data: string): Promise<null> {
|
||||||
* Accepts structured data and overwrites the existing file on disk.
|
|
||||||
*/
|
|
||||||
private async writeFile(data: A): Promise<null> {
|
|
||||||
const parent = previousPath.exec(this.path)
|
const parent = previousPath.exec(this.path)
|
||||||
if (parent) {
|
if (parent) {
|
||||||
await fs.mkdir(parent[1], { recursive: true })
|
await fs.mkdir(parent[1], { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(this.path, this.writeData(data))
|
await fs.writeFile(this.path, data)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private async readFile(): Promise<unknown> {
|
/**
|
||||||
|
* Accepts structured data and overwrites the existing file on disk.
|
||||||
|
*/
|
||||||
|
private async writeFile(data: A): Promise<null> {
|
||||||
|
return await this.writeFileRaw(this.writeData(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async readFileRaw(): Promise<string | null> {
|
||||||
if (!(await exists(this.path))) {
|
if (!(await exists(this.path))) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return this.readData(
|
return await fs.readFile(this.path).then((data) => data.toString("utf-8"))
|
||||||
await fs.readFile(this.path).then((data) => data.toString("utf-8")),
|
}
|
||||||
)
|
|
||||||
|
private async readFile(): Promise<unknown> {
|
||||||
|
const raw = await this.readFileRaw()
|
||||||
|
if (raw === null) {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
return this.readData(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,7 +154,14 @@ export class FileHelper<A> {
|
|||||||
private async readConst(effects: T.Effects): Promise<A | null> {
|
private async readConst(effects: T.Effects): Promise<A | null> {
|
||||||
const watch = this.readWatch()
|
const watch = this.readWatch()
|
||||||
const res = await watch.next()
|
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
|
return res.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,17 +231,35 @@ export class FileHelper<A> {
|
|||||||
/**
|
/**
|
||||||
* Accepts full structured data and overwrites the existing file on disk if it exists.
|
* Accepts full structured data and overwrites the existing file on disk if it exists.
|
||||||
*/
|
*/
|
||||||
async write(data: A) {
|
async write(effects: T.Effects, data: A) {
|
||||||
return await this.writeFile(this.validate(data))
|
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.
|
* Accepts partial structured data and performs a merge with the existing file on disk.
|
||||||
*/
|
*/
|
||||||
async merge(data: T.DeepPartial<A>) {
|
async merge(effects: T.Effects, data: T.DeepPartial<A>) {
|
||||||
const fileData = (await this.readFile()) || null
|
const fileDataRaw = await this.readFileRaw()
|
||||||
const mergeData = fileMerge(fileData, data)
|
let fileData: any = fileDataRaw === null ? null : this.readData(fileDataRaw)
|
||||||
return await this.writeFile(this.validate(mergeData))
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export * from "../../../base/lib/util"
|
|||||||
export { GetSslCertificate } from "./GetSslCertificate"
|
export { GetSslCertificate } from "./GetSslCertificate"
|
||||||
|
|
||||||
export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname"
|
export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname"
|
||||||
|
export { Drop } from "./Drop"
|
||||||
|
|||||||
23
sdk/package/package-lock.json
generated
23
sdk/package/package-lock.json
generated
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.3.6-beta.14",
|
"version": "0.3.6-beta.18",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.3.6-beta.14",
|
"version": "0.3.6-beta.18",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@noble/curves": "^1.4.0",
|
"@noble/curves": "^1.4.0",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
|
"deep-equality-data-structures": "^1.5.1",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
@@ -1802,6 +1803,15 @@
|
|||||||
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
|
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/deepmerge": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
@@ -3238,6 +3248,15 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"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",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./package/lib/index.js",
|
"main": "./package/lib/index.js",
|
||||||
"types": "./package/lib/index.d.ts",
|
"types": "./package/lib/index.d.ts",
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"ts-matches": "^6.2.1",
|
"ts-matches": "^6.2.1",
|
||||||
"yaml": "^2.2.2",
|
"yaml": "^2.2.2",
|
||||||
|
"deep-equality-data-structures": "^1.5.1",
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@noble/curves": "^1.4.0",
|
"@noble/curves": "^1.4.0",
|
||||||
"@noble/hashes": "^1.4.0"
|
"@noble/hashes": "^1.4.0"
|
||||||
|
|||||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.3.6-alpha.15",
|
"version": "0.3.6-alpha.16",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.3.6-alpha.15",
|
"version": "0.3.6-alpha.16",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^14.1.0",
|
"@angular/animations": "^14.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.3.6-alpha.15",
|
"version": "0.3.6-alpha.16",
|
||||||
"author": "Start9 Labs, Inc",
|
"author": "Start9 Labs, Inc",
|
||||||
"homepage": "https://start9.com/",
|
"homepage": "https://start9.com/",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ export module Mock {
|
|||||||
emulateMissingAs: 'aarch64',
|
emulateMissingAs: 'aarch64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
assets: [],
|
|
||||||
volumes: ['main'],
|
volumes: ['main'],
|
||||||
hardwareRequirements: {
|
hardwareRequirements: {
|
||||||
device: [],
|
device: [],
|
||||||
@@ -173,7 +172,6 @@ export module Mock {
|
|||||||
emulateMissingAs: 'aarch64',
|
emulateMissingAs: 'aarch64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
assets: [],
|
|
||||||
volumes: ['main'],
|
volumes: ['main'],
|
||||||
hardwareRequirements: {
|
hardwareRequirements: {
|
||||||
device: [],
|
device: [],
|
||||||
@@ -223,7 +221,6 @@ export module Mock {
|
|||||||
emulateMissingAs: 'aarch64',
|
emulateMissingAs: 'aarch64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
assets: [],
|
|
||||||
volumes: ['main'],
|
volumes: ['main'],
|
||||||
hardwareRequirements: {
|
hardwareRequirements: {
|
||||||
device: [],
|
device: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user