From 4a27128a1c116c311df367bef7aae866cfa011c2 Mon Sep 17 00:00:00 2001 From: J H Date: Wed, 20 Mar 2024 20:28:31 -0600 Subject: [PATCH] chore: Update the types for changes that Matt wanted with sdk + examples --- .../startos/bindings/DependencyRequirement.ts | 3 +- core/startos/src/db/model/package.rs | 2 +- .../src/service/service_effect_handler.rs | 81 ++++++++++-------- sdk/lib/Dependency.ts | 18 ++++ sdk/lib/StartSdk.ts | 84 +++++++++++++++++-- sdk/lib/emverLite/mod.ts | 73 +++++++++------- sdk/lib/inits/setupInit.ts | 13 ++- sdk/lib/manifest/ManifestTypes.ts | 28 ++----- sdk/lib/test/configBuilder.test.ts | 3 +- sdk/lib/test/output.sdk.ts | 3 +- sdk/lib/test/setupDependencyConfig.test.ts | 4 +- sdk/lib/types.ts | 3 +- 12 files changed, 212 insertions(+), 103 deletions(-) create mode 100644 sdk/lib/Dependency.ts diff --git a/core/startos/bindings/DependencyRequirement.ts b/core/startos/bindings/DependencyRequirement.ts index 0179c0e84..52bc25cd9 100644 --- a/core/startos/bindings/DependencyRequirement.ts +++ b/core/startos/bindings/DependencyRequirement.ts @@ -1,4 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { DependencyKind } from "./DependencyKind"; -export interface DependencyRequirement { id: string, kind: DependencyKind, healthChecks: string[], } \ No newline at end of file +export type DependencyRequirement = { "kind": "running", id: string, healthChecks: string[], versionSpec: string, url: string, } | { "kind": "exists", id: string, versionSpec: string, url: string, }; \ No newline at end of file diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 97e90b894..3c7f3b7c7 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -412,7 +412,7 @@ pub struct StaticDependencyInfo { pub icon: DataUrl<'static>, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "kebab-case")] #[serde(tag = "kind")] pub enum CurrentDependencyInfo { diff --git a/core/startos/src/service/service_effect_handler.rs b/core/startos/src/service/service_effect_handler.rs index e8082e99e..65217ecd5 100644 --- a/core/startos/src/service/service_effect_handler.rs +++ b/core/startos/src/service/service_effect_handler.rs @@ -1,10 +1,10 @@ use std::collections::BTreeSet; use std::ffi::OsString; +use std::net::Ipv4Addr; use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::{Arc, Weak}; -use std::net::Ipv4Addr; use clap::builder::ValueParserFactory; use clap::Parser; @@ -765,8 +765,8 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result, +enum DependencyRequirement { + Running { + #[ts(type = "string")] + id: PackageId, + #[ts(type = "string[]")] + #[serde(rename = "healthChecks")] + health_checks: BTreeSet, + #[serde(rename = "versionSpec")] + version_spec: String, + url: String, + }, + Exists { + #[ts(type = "string")] + id: PackageId, + #[serde(rename = "versionSpec")] + version_spec: String, + url: String, + }, } // filebrowser:exists,bitcoind:running:foo+bar+baz impl FromStr for DependencyRequirement { type Err = Error; fn from_str(s: &str) -> Result { match s.split_once(':') { - Some((id, "e")) | Some((id, "exists")) => Ok(Self { + Some((id, "e")) | Some((id, "exists")) => Ok(Self::Exists { id: id.parse()?, - kind: DependencyKind::Exists, - health_checks: BTreeSet::new(), + url: "".to_string(), + version_spec: "*".to_string(), }), Some((id, rest)) => { - let health_checks = match rest.split_once(":") { + let health_checks = match rest.split_once(':') { Some(("r", rest)) | Some(("running", rest)) => rest .split('+') .map(|id| id.parse().map_err(Error::from)) @@ -1108,16 +1119,18 @@ impl FromStr for DependencyRequirement { )), }, }?; - Ok(Self { + Ok(Self::Running { id: id.parse()?, - kind: DependencyKind::Running, health_checks, + url: "".to_string(), + version_spec: "*".to_string(), }) } - None => Ok(Self { + None => Ok(Self::Running { id: s.parse()?, - kind: DependencyKind::Running, health_checks: BTreeSet::new(), + url: "".to_string(), + version_spec: "*".to_string(), }), } } @@ -1149,23 +1162,19 @@ async fn set_dependencies( let dependencies = CurrentDependencies( dependencies .into_iter() - .map( - |DependencyRequirement { - id, - kind, - health_checks, - }| { - ( - id, - match kind { - DependencyKind::Exists => CurrentDependencyInfo::Exists, - DependencyKind::Running => { - CurrentDependencyInfo::Running { health_checks } - } - }, - ) - }, - ) + .map(|dependency| match dependency { + DependencyRequirement::Exists { + id, + url, + version_spec, + } => (id, CurrentDependencyInfo::Exists), + DependencyRequirement::Running { + id, + health_checks, + url, + version_spec, + } => (id, CurrentDependencyInfo::Running { health_checks }), + }) .collect(), ); for (dep, entry) in db.as_public_mut().as_package_data_mut().as_entries_mut()? { diff --git a/sdk/lib/Dependency.ts b/sdk/lib/Dependency.ts new file mode 100644 index 000000000..71cc05890 --- /dev/null +++ b/sdk/lib/Dependency.ts @@ -0,0 +1,18 @@ +import { Checker } from "./emverLite/mod" + +export class Dependency { + constructor( + readonly data: + | { + type: "running" + versionSpec: Checker + url: string + healthChecks: string[] + } + | { + type: "exists" + versionSpec: Checker + url: string + }, + ) {} +} diff --git a/sdk/lib/StartSdk.ts b/sdk/lib/StartSdk.ts index 660950b6c..77c791f55 100644 --- a/sdk/lib/StartSdk.ts +++ b/sdk/lib/StartSdk.ts @@ -50,7 +50,11 @@ import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall" import { setupMain } from "./mainFn" import { defaultTrigger } from "./trigger/defaultTrigger" import { changeOnFirstSuccess, cooldownTrigger } from "./trigger" -import setupConfig, { Read, Save } from "./config/setupConfig" +import setupConfig, { + DependenciesReceipt, + Read, + Save, +} from "./config/setupConfig" import { InterfacesReceipt, SetInterfaces, @@ -72,6 +76,9 @@ import { getStore } from "./store/getStore" import { CommandOptions, MountOptions, Overlay } from "./util/Overlay" import { splitCommand } from "./util/splitCommand" import { Mounts } from "./mainFn/Mounts" +import { Dependency } from "./Dependency" +import * as T from "./types" +import { Checker, EmVer } from "./emverLite/mod" // prettier-ignore type AnyNeverCond = @@ -104,6 +111,20 @@ export class StartSdk { } build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) { + type DependencyType = { + [K in keyof { + [K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false + ? K + : never + }]: Dependency + } & { + [K in keyof { + [K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true + ? K + : never + }]?: Dependency + } + return { serviceInterface: { getOwn: (effects: E, id: ServiceInterfaceId) => @@ -233,6 +254,11 @@ export class StartSdk { HealthCheck: { of: healthCheck, }, + Dependency: { + of(data: Dependency["data"]) { + return new Dependency({ ...data }) + }, + }, healthCheck: { checkPortListening, checkWebUrl, @@ -278,12 +304,48 @@ export class StartSdk { > | null }, ) => setupDependencyConfig(config, autoConfigs), + setupDependencies: >( + fn: (options: { + effects: Effects + input: Input | null + }) => Promise, + ) => { + return async (options: { effects: Effects; input: Input }) => { + const dependencyType = await fn(options) + return await options.effects.setDependencies({ + dependencies: Object.entries(dependencyType).map( + ([ + id, + { + data: { versionSpec, ...x }, + }, + ]) => ({ + id, + ...x, + ...(x.type === "running" + ? { + kind: "running", + healthChecks: x.healthChecks, + } + : { + kind: "exists", + }), + versionSpec: versionSpec.range, + }), + ), + }) + } + }, setupExports: (fn: SetupExports) => fn, setupInit: ( migrations: Migrations, install: Install, uninstall: Uninstall, setInterfaces: SetInterfaces, + setDependencies: (options: { + effects: Effects + input: any + }) => Promise, setupExports: SetupExports, ) => setupInit( @@ -292,6 +354,7 @@ export class StartSdk { uninstall, setInterfaces, setupExports, + setDependencies, ), setupInstall: (fn: InstallFn) => Install.of(fn), setupInterfaces: < @@ -346,6 +409,9 @@ export class StartSdk { spec: Spec, ) => Config.of(spec), }, + Checker: { + parse: Checker.parse, + }, Daemons: { of(config: { effects: Effects @@ -360,13 +426,17 @@ export class StartSdk { LocalConfig extends Record, RemoteConfig extends Record, >({ - localConfig, - remoteConfig, + localConfigSpec, + remoteConfigSpec, dependencyConfig, update, }: { - localConfig: Config | Config - remoteConfig: Config | Config + localConfigSpec: + | Config + | Config + remoteConfigSpec: + | Config + | Config dependencyConfig: (options: { effects: Effects localConfig: LocalConfig @@ -381,6 +451,10 @@ export class StartSdk { >(dependencyConfig, update) }, }, + EmVer: { + from: EmVer.from, + parse: EmVer.parse, + }, List: { text: List.text, number: List.number, diff --git a/sdk/lib/emverLite/mod.ts b/sdk/lib/emverLite/mod.ts index f672613aa..dd68360ac 100644 --- a/sdk/lib/emverLite/mod.ts +++ b/sdk/lib/emverLite/mod.ts @@ -163,7 +163,7 @@ export class EmVer { } toString() { - return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}` + return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}` as ValidEmVer } } @@ -179,6 +179,7 @@ export class Checker { * @returns */ static parse(range: string | Checker): Checker { + console.log(`Parser (${range})`) if (range instanceof Checker) { return range } @@ -193,10 +194,12 @@ export class Checker { return new Checker((version) => { EmVer.from(version) return true - }) + }, range) } + if (range.startsWith("!!")) return Checker.parse(range.substring(2)) if (range.startsWith("!")) { - return Checker.parse(range.substring(1)).not() + const tempValue = Checker.parse(range.substring(1)) + return new Checker((x) => !tempValue.check(x), range) } const starSubMatches = starSub.exec(range) if (starSubMatches != null) { @@ -210,7 +213,7 @@ export class Checker { !v.greaterThan(emVarUpper) && !v.equals(emVarUpper) ) - }) + }, range) } switch (range.substring(0, 2)) { @@ -219,14 +222,14 @@ export class Checker { return new Checker((version) => { const v = EmVer.from(version) return v.greaterThanOrEqual(emVar) - }) + }, range) } case "<=": { const emVar = EmVer.parse(range.substring(2)) return new Checker((version) => { const v = EmVer.from(version) return v.lessThanOrEqual(emVar) - }) + }, range) } } @@ -236,21 +239,21 @@ export class Checker { return new Checker((version) => { const v = EmVer.from(version) return v.greaterThan(emVar) - }) + }, range) } case "<": { const emVar = EmVer.parse(range.substring(1)) return new Checker((version) => { const v = EmVer.from(version) return v.lessThan(emVar) - }) + }, range) } case "=": { const emVar = EmVer.parse(range.substring(1)) return new Checker((version) => { const v = EmVer.from(version) return v.equals(emVar) - }) + }, `=${emVar.toString()}`) } } throw new Error("Couldn't parse range: " + range) @@ -261,40 +264,53 @@ export class Checker { * a pattern */ public readonly check: (value: ValidEmVer | EmVer) => boolean, + private readonly _range: string, ) {} + get range() { + return this._range as ValidEmVerRange + } + /** * Used when we want the `and` condition with another checker */ public and(...others: (Checker | string)[]): Checker { - return new Checker((value) => { - if (!this.check(value)) { - return false - } - for (const other of others) { - if (!Checker.parse(other).check(value)) { + const othersCheck = others.map(Checker.parse) + return new Checker( + (value) => { + if (!this.check(value)) { return false } - } - return true - }) + for (const other of othersCheck) { + if (!other.check(value)) { + return false + } + } + return true + }, + othersCheck.map((x) => x._range).join(" && "), + ) } /** * Used when we want the `or` condition with another checker */ public or(...others: (Checker | string)[]): Checker { - return new Checker((value) => { - if (this.check(value)) { - return true - } - for (const other of others) { - if (Checker.parse(other).check(value)) { + const othersCheck = others.map(Checker.parse) + return new Checker( + (value) => { + if (this.check(value)) { return true } - } - return false - }) + for (const other of othersCheck) { + if (other.check(value)) { + return true + } + } + return false + }, + othersCheck.map((x) => x._range).join(" || "), + ) } /** @@ -302,6 +318,7 @@ export class Checker { * @returns */ public not(): Checker { - return new Checker((value) => !this.check(value)) + let newRange = `!${this._range}` + return Checker.parse(newRange) } } diff --git a/sdk/lib/inits/setupInit.ts b/sdk/lib/inits/setupInit.ts index 80e1a202e..af085e17b 100644 --- a/sdk/lib/inits/setupInit.ts +++ b/sdk/lib/inits/setupInit.ts @@ -1,6 +1,12 @@ +import { DependenciesReceipt } from "../config/setupConfig" import { SetInterfaces } from "../interfaces/setupInterfaces" import { SDKManifest } from "../manifest/ManifestTypes" -import { ExpectedExports, ExposeUiPaths, ExposeUiPathsAll } from "../types" +import { + Effects, + ExpectedExports, + ExposeUiPaths, + ExposeUiPathsAll, +} from "../types" import { Migrations } from "./migrations/setupMigrations" import { SetupExports } from "./setupExports" import { Install } from "./setupInstall" @@ -12,6 +18,10 @@ export function setupInit( uninstall: Uninstall, setInterfaces: SetInterfaces, setupExports: SetupExports, + setDependencies: (options: { + effects: Effects + input: any + }) => Promise, ): { init: ExpectedExports.init uninit: ExpectedExports.uninit @@ -27,6 +37,7 @@ export function setupInit( const { services, ui } = await setupExports(opts) await opts.effects.exposeForDependents({ paths: services }) await opts.effects.exposeUi(forExpose(ui)) + await setDependencies({ effects: opts.effects, input: null }) }, uninit: async (opts) => { await migrations.uninit(opts) diff --git a/sdk/lib/manifest/ManifestTypes.ts b/sdk/lib/manifest/ManifestTypes.ts index 4b25ff1d0..ed8703bf2 100644 --- a/sdk/lib/manifest/ManifestTypes.ts +++ b/sdk/lib/manifest/ManifestTypes.ts @@ -75,31 +75,13 @@ export type SDKManifest = { } export interface ManifestDependency { - /** The range of versions that would satisfy the dependency - * - * ie: >=3.4.5 <4.0.0 - */ - version: string /** * A human readable explanation on what the dependency is used for */ description: string | null - requirement: - | { - type: "opt-in" - /** - * The human readable explanation on how to opt-in to the dependency - */ - how: string - } - | { - type: "opt-out" - /** - * The human readable explanation on how to opt-out to the dependency - */ - how: string - } - | { - type: "required" - } + /** + * Determines if the dependency is optional or not. Times that optional that are good include such situations + * such as being able to toggle other services or to use a different service for the same purpose. + */ + optional: boolean } diff --git a/sdk/lib/test/configBuilder.test.ts b/sdk/lib/test/configBuilder.test.ts index ef85ee366..cd14d6a18 100644 --- a/sdk/lib/test/configBuilder.test.ts +++ b/sdk/lib/test/configBuilder.test.ts @@ -414,8 +414,7 @@ describe("values", () => { dependencies: { remoteTest: { description: "", - requirement: { how: "", type: "opt-in" }, - version: "1.0", + optional: true, }, }, }), diff --git a/sdk/lib/test/output.sdk.ts b/sdk/lib/test/output.sdk.ts index e69ef2a68..a0bab1f6e 100644 --- a/sdk/lib/test/output.sdk.ts +++ b/sdk/lib/test/output.sdk.ts @@ -35,8 +35,7 @@ export const sdk = StartSdk.of() dependencies: { remoteTest: { description: "", - requirement: { how: "", type: "opt-in" }, - version: "1.0", + optional: false, }, }, }), diff --git a/sdk/lib/test/setupDependencyConfig.test.ts b/sdk/lib/test/setupDependencyConfig.test.ts index 4fac5d063..5fa4a0ddf 100644 --- a/sdk/lib/test/setupDependencyConfig.test.ts +++ b/sdk/lib/test/setupDependencyConfig.test.ts @@ -16,8 +16,8 @@ describe("setupDependencyConfig", () => { }), }) const remoteTest = sdk.DependencyConfig.of({ - localConfig: testConfig, - remoteConfig: testConfig2, + localConfigSpec: testConfig, + remoteConfigSpec: testConfig2, dependencyConfig: async ({}) => {}, }) sdk.setupDependencyConfig(testConfig, { diff --git a/sdk/lib/types.ts b/sdk/lib/types.ts index add74a624..c6ab16e64 100644 --- a/sdk/lib/types.ts +++ b/sdk/lib/types.ts @@ -600,7 +600,8 @@ export type KnownError = export type Dependency = { id: PackageId - kind: DependencyKind + versionSpec: string + url: string } & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] }) export type Dependencies = Array