Feature/fe new registry (#2647)

* bugfixes

* update fe types

* implement new registry types in marketplace and ui

* fix marketplace types to have default params

* add alt implementation toggle

* merge cleanup

* more cleanup and notes

* fix build

* cleanup sync with next/minor

* add exver JS parser

* parse ValidExVer to string

* update types to interface

* add VersionRange and comparative functions

* Parse ExtendedVersion from string

* add conjunction, disjunction, and inversion logic

* consider flavor in satisfiedBy fn

* consider prerelease for ordering

* add compare fn for sorting

* rename fns for consistency

* refactoring

* update compare fn to return null if flavors don't match

* begin simplifying dependencies

* under construction

* wip

* add dependency metadata to CurrentDependencyInfo

* ditch inheritance for recursive VersionRange constructor. Recursive 'satisfiedBy' fn wip

* preprocess manifest

* misc fixes

* use sdk version as osVersion in manifest

* chore: Change the type to just validate and not generate all solutions.

* add publishedAt

* fix pegjs exports

* integrate exver into sdk

* misc fixes

* complete satisfiedBy fn

* refactor - use greaterThanOrEqual and lessThanOrEqual fns

* fix tests

* update dependency details

* update types

* remove interim types

* rename alt implementation to flavor

* cleanup os update

* format exver.ts

* add s9pk parsing endpoints

* fix build

* update to exver

* exver and bug fixes

* update static endpoints + cleanup

* cleanup

* update static proxy verification

* make mocks more robust; fix dep icon fallback; cleanup

* refactor alert versions and update fixtures

* registry bugfixes

* misc fixes

* cleanup unused

* convert patchdb ui seed to camelCase

* update otherVersions type

* change otherVersions: null to 'none'

* refactor and complete feature

* improve static endpoints

* fix install params

* mask systemd-networkd-wait-online

* fix static file fetching

* include non-matching versions in otherVersions

* convert release notes to modal and clean up displayExver

* alert for no other versions

* Fix ack-instructions casing

* fix indeterminate loader on service install

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Shadowy Super Coder <musashidisciple@proton.me>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
Co-authored-by: J H <dragondef@gmail.com>
Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Lucy
2024-07-22 20:48:12 -04:00
committed by GitHub
parent 0fbb18b315
commit a535fc17c3
196 changed files with 7002 additions and 2162 deletions

View File

@@ -1,17 +1,17 @@
import { Checker } from "./emverLite/mod"
import { VersionRange } from "./exver"
export class Dependency {
constructor(
readonly data:
| {
type: "running"
versionSpec: Checker
versionRange: VersionRange
registryUrl: string
healthChecks: string[]
}
| {
type: "exists"
versionSpec: Checker
versionRange: VersionRange
registryUrl: string
},
) {}

View File

@@ -1,4 +1,3 @@
import { ManifestVersion, SDKManifest } from "./manifest/ManifestTypes"
import { RequiredDefault, Value } from "./config/builder/value"
import { Config, ExtractConfigType, LazyBuild } from "./config/builder/config"
import {
@@ -21,7 +20,6 @@ import {
MaybePromise,
ServiceInterfaceId,
PackageId,
ValidIfNoStupidEscape,
} from "./types"
import * as patterns from "./util/patterns"
import { DependencyConfig, Update } from "./dependencies/DependencyConfig"
@@ -74,12 +72,14 @@ 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"
import { testTypeVersion, ValidateExVer } from "./exver"
import { ExposedStorePaths } from "./store/setupExposeStore"
import { PathBuilder, extractJsonPath, pathBuilder } from "./store/PathBuilder"
import { checkAllDependencies } from "./dependencies/dependencies"
import { health } from "."
export const SDKVersion = testTypeVersion("0.3.6")
// prettier-ignore
type AnyNeverCond<T extends any[], Then, Else> =
T extends [] ? Else :
@@ -98,12 +98,12 @@ function removeConstType<E>() {
return <T>(t: T) => t as T & (E extends MainEffects ? {} : { const: never })
}
export class StartSdk<Manifest extends SDKManifest, Store> {
export class StartSdk<Manifest extends T.Manifest, Store> {
private constructor(readonly manifest: Manifest) {}
static of() {
return new StartSdk<never, never>(null as never)
}
withManifest<Manifest extends SDKManifest = never>(manifest: Manifest) {
withManifest<Manifest extends T.Manifest = never>(manifest: Manifest) {
return new StartSdk<Manifest, Store>(manifest)
}
withStore<Store extends Record<string, any>>() {
@@ -191,7 +191,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
id: keyof Manifest["images"] & T.ImageId
sharedRun?: boolean
},
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
command: T.CommandType,
options: CommandOptions & {
mounts?: { path: string; options: MountOptions }[]
},
@@ -335,7 +335,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
([
id,
{
data: { versionSpec, ...x },
data: { versionRange, ...x },
},
]) => ({
id,
@@ -348,7 +348,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
: {
kind: "exists",
}),
versionSpec: versionSpec.range,
versionRange: versionRange.toString(),
}),
),
})
@@ -432,9 +432,6 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
spec: Spec,
) => Config.of<Spec, Store>(spec),
},
Checker: {
parse: Checker.parse,
},
Daemons: {
of(config: {
effects: Effects
@@ -474,10 +471,6 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
>(dependencyConfig, update)
},
},
EmVer: {
from: EmVer.from,
parse: EmVer.parse,
},
List: {
text: List.text,
obj: <Type extends Record<string, any>>(
@@ -524,8 +517,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
) => List.dynamicText<Store>(getA),
},
Migration: {
of: <Version extends ManifestVersion>(options: {
version: Version
of: <Version extends string>(options: {
version: Version & ValidateExVer<Version>
up: (opts: { effects: Effects }) => Promise<void>
down: (opts: { effects: Effects }) => Promise<void>
}) => Migration.of<Manifest, Store, Version>(options),
@@ -720,7 +713,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
}
}
export async function runCommand<Manifest extends SDKManifest>(
export async function runCommand<Manifest extends T.Manifest>(
effects: Effects,
image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean },
command: string | [string, ...string[]],

View File

@@ -1,12 +1,13 @@
import * as T from "../types"
import { Config, ExtractConfigType } from "../config/builder/config"
import { SDKManifest } from "../manifest/ManifestTypes"
import { ActionMetadata, ActionResult, Effects, ExportedAction } from "../types"
export type MaybeFn<Manifest extends SDKManifest, Store, Value> =
export type MaybeFn<Manifest extends T.Manifest, Store, Value> =
| Value
| ((options: { effects: Effects }) => Promise<Value> | Value)
export class CreatedAction<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
ConfigType extends
| Record<string, any>
@@ -30,7 +31,7 @@ export class CreatedAction<
) {}
static of<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
ConfigType extends
| Record<string, any>

View File

@@ -1,8 +1,8 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import * as T from "../types"
import { Effects, ExpectedExports } from "../types"
import { CreatedAction } from "./createAction"
export function setupActions<Manifest extends SDKManifest, Store>(
export function setupActions<Manifest extends T.Manifest, Store>(
...createdActions: CreatedAction<Manifest, Store, any>[]
) {
const myActions = async (options: { effects: Effects }) => {

View File

@@ -1,5 +1,3 @@
import { recursive } from "ts-matches"
import { SDKManifest } from "../manifest/ManifestTypes"
import * as T from "../types"
import * as child_process from "child_process"
@@ -41,14 +39,14 @@ export type BackupSet<Volumes extends string> = {
* ).build()q
* ```
*/
export class Backups<M extends SDKManifest> {
export class Backups<M extends T.Manifest> {
static BACKUP: BACKUP = "BACKUP"
private constructor(
private options = DEFAULT_OPTIONS,
private backupSet = [] as BackupSet<M["volumes"][number]>[],
) {}
static volumes<M extends SDKManifest = never>(
static volumes<M extends T.Manifest = never>(
...volumeNames: Array<M["volumes"][0]>
): Backups<M> {
return new Backups<M>().addSets(
@@ -60,12 +58,12 @@ export class Backups<M extends SDKManifest> {
})),
)
}
static addSets<M extends SDKManifest = never>(
static addSets<M extends T.Manifest = never>(
...options: BackupSet<M["volumes"][0]>[]
) {
return new Backups().addSets(...options)
}
static with_options<M extends SDKManifest = never>(
static with_options<M extends T.Manifest = never>(
options?: Partial<T.BackupOptions>,
) {
return new Backups({ ...DEFAULT_OPTIONS, ...options })

View File

@@ -1,13 +1,13 @@
import { Backups } from "./Backups"
import { SDKManifest } from "../manifest/ManifestTypes"
import { ExpectedExports, PathMaker } from "../types"
import * as T from "../types"
import { _ } from "../util"
export type SetupBackupsParams<M extends SDKManifest> = Array<
export type SetupBackupsParams<M extends T.Manifest> = Array<
M["volumes"][number] | Backups<M>
>
export function setupBackups<M extends SDKManifest>(
export function setupBackups<M extends T.Manifest>(
...args: _<SetupBackupsParams<M>>
) {
const backups = Array<Backups<M>>()
@@ -21,22 +21,22 @@ export function setupBackups<M extends SDKManifest>(
}
backups.push(Backups.volumes(...volumes))
const answer: {
createBackup: ExpectedExports.createBackup
restoreBackup: ExpectedExports.restoreBackup
createBackup: T.ExpectedExports.createBackup
restoreBackup: T.ExpectedExports.restoreBackup
} = {
get createBackup() {
return (async (options) => {
for (const backup of backups) {
await backup.build(options.pathMaker).createBackup(options)
}
}) as ExpectedExports.createBackup
}) as T.ExpectedExports.createBackup
},
get restoreBackup() {
return (async (options) => {
for (const backup of backups) {
await backup.build(options.pathMaker).restoreBackup(options)
}
}) as ExpectedExports.restoreBackup
}) as T.ExpectedExports.restoreBackup
},
}
return answer

View File

@@ -1,22 +1,21 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import { Dependencies } from "../types"
import * as T from "../types"
export type ConfigDependencies<T extends SDKManifest> = {
exists(id: keyof T["dependencies"]): Dependencies[number]
export type ConfigDependencies<T extends T.Manifest> = {
exists(id: keyof T["dependencies"]): T.Dependencies[number]
running(
id: keyof T["dependencies"],
healthChecks: string[],
): Dependencies[number]
): T.Dependencies[number]
}
export const configDependenciesSet = <
T extends SDKManifest,
T extends T.Manifest,
>(): ConfigDependencies<T> => ({
exists(id: keyof T["dependencies"]) {
return {
id,
kind: "exists",
} as Dependencies[number]
} as T.Dependencies[number]
},
running(id: keyof T["dependencies"], healthChecks: string[]) {
@@ -24,6 +23,6 @@ export const configDependenciesSet = <
id,
kind: "running",
healthChecks,
} as Dependencies[number]
} as T.Dependencies[number]
},
})

View File

@@ -1,5 +1,5 @@
import { Effects, ExpectedExports } from "../types"
import { SDKManifest } from "../manifest/ManifestTypes"
import * as T from "../types"
import * as D from "./configDependencies"
import { Config, ExtractConfigType } from "./builder/config"
import nullIfEmpty from "../util/nullIfEmpty"
@@ -16,7 +16,7 @@ export type Save<
| Config<Record<string, any>, any>
| Config<Record<string, never>, never>,
> = (options: {
effects: Effects
effects: T.Effects
input: ExtractConfigType<A> & Record<string, any>
}) => Promise<{
dependenciesReceipt: DependenciesReceipt
@@ -24,14 +24,14 @@ export type Save<
restart: boolean
}>
export type Read<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
A extends
| Record<string, any>
| Config<Record<string, any>, any>
| Config<Record<string, any>, never>,
> = (options: {
effects: Effects
effects: T.Effects
}) => Promise<void | (ExtractConfigType<A> & Record<string, any>)>
/**
* We want to setup a config export with a get and set, this
@@ -46,7 +46,7 @@ export function setupConfig<
| Record<string, any>
| Config<any, any>
| Config<any, never>,
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
>(
spec: Config<Type, Store> | Config<Type, never>,
@@ -69,7 +69,7 @@ export function setupConfig<
if (restart) {
await effects.restart()
}
}) as ExpectedExports.setConfig,
}) as T.ExpectedExports.setConfig,
getConfig: (async ({ effects }) => {
const configValue = nullIfEmpty((await read({ effects })) || null)
return {
@@ -78,7 +78,7 @@ export function setupConfig<
}),
config: configValue,
}
}) as ExpectedExports.getConfig,
}) as T.ExpectedExports.getConfig,
}
}

View File

@@ -1,11 +1,6 @@
import {
DependencyConfig as DependencyConfigType,
DeepPartial,
Effects,
} from "../types"
import * as T from "../types"
import { deepEqual } from "../util/deepEqual"
import { deepMerge } from "../util/deepMerge"
import { SDKManifest } from "../manifest/ManifestTypes"
export type Update<QueryResults, RemoteConfig> = (options: {
remoteConfig: RemoteConfig
@@ -13,7 +8,7 @@ export type Update<QueryResults, RemoteConfig> = (options: {
}) => Promise<RemoteConfig>
export class DependencyConfig<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
Input extends Record<string, any>,
RemoteConfig extends Record<string, any>,
@@ -26,16 +21,16 @@ export class DependencyConfig<
}
constructor(
readonly dependencyConfig: (options: {
effects: Effects
effects: T.Effects
localConfig: Input
}) => Promise<void | DeepPartial<RemoteConfig>>,
}) => Promise<void | T.DeepPartial<RemoteConfig>>,
readonly update: Update<
void | DeepPartial<RemoteConfig>,
void | T.DeepPartial<RemoteConfig>,
RemoteConfig
> = DependencyConfig.defaultUpdate as any,
) {}
async query(options: { effects: Effects; localConfig: unknown }) {
async query(options: { effects: T.Effects; localConfig: unknown }) {
return this.dependencyConfig({
localConfig: options.localConfig as Input,
effects: options.effects,

View File

@@ -3,20 +3,23 @@ import {
PackageId,
DependencyRequirement,
SetHealth,
CheckDependencyResult,
CheckDependenciesResult,
} from "../types"
export type CheckAllDependencies = {
notRunning: () => Promise<CheckDependencyResult[]>
notInstalled: () => Promise<CheckDependencyResult[]>
notInstalled: () => Promise<CheckDependenciesResult[]>
notRunning: () => Promise<CheckDependenciesResult[]>
configNotSatisfied: () => Promise<CheckDependenciesResult[]>
healthErrors: () => Promise<{ [id: string]: SetHealth[] }>
throwIfNotRunning: () => Promise<void>
throwIfNotValid: () => Promise<undefined>
throwIfNotInstalled: () => Promise<void>
throwIfError: () => Promise<void>
isValid: () => Promise<boolean>
throwIfNotRunning: () => Promise<void>
throwIfNotInstalled: () => Promise<void>
throwIfConfigNotSatisfied: () => Promise<void>
throwIfHealthError: () => Promise<void>
throwIfNotValid: () => Promise<void>
}
export function checkAllDependencies(effects: Effects): CheckAllDependencies {
const dependenciesPromise = effects.getDependencies()
@@ -45,14 +48,16 @@ export function checkAllDependencies(effects: Effects): CheckAllDependencies {
if (!dependency) continue
if (dependency.kind !== "running") continue
const healthChecks = result.healthChecks
.filter((x) => dependency.healthChecks.includes(x.id))
const healthChecks = Object.entries(result.healthChecks)
.map(([id, hc]) => ({ ...hc, id }))
.filter((x) => !!x.message)
if (healthChecks.length === 0) continue
answer[result.packageId] = healthChecks
}
return answer
}
const configNotSatisfied = () =>
resultsPromise.then((x) => x.filter((x) => !x.configSatisfied))
const notInstalled = () =>
resultsPromise.then((x) => x.filter((x) => !x.isInstalled))
const notRunning = async () => {
@@ -68,7 +73,7 @@ export function checkAllDependencies(effects: Effects): CheckAllDependencies {
const entries = <B>(x: { [k: string]: B }) => Object.entries(x)
const first = <A>(x: A[]): A | undefined => x[0]
const sinkVoid = <A>(x: A) => void 0
const throwIfError = () =>
const throwIfHealthError = () =>
healthErrors()
.then(entries)
.then(first)
@@ -78,6 +83,14 @@ export function checkAllDependencies(effects: Effects): CheckAllDependencies {
if (healthChecks.length > 0)
throw `Package ${id} has the following errors: ${healthChecks.map((x) => x.message).join(", ")}`
})
const throwIfConfigNotSatisfied = () =>
configNotSatisfied().then((results) => {
throw new Error(
`Package ${results[0].packageId} does not have a valid configuration`,
)
})
const throwIfNotRunning = () =>
notRunning().then((results) => {
if (results[0])
@@ -93,7 +106,8 @@ export function checkAllDependencies(effects: Effects): CheckAllDependencies {
Promise.all([
throwIfNotRunning(),
throwIfNotInstalled(),
throwIfError(),
throwIfConfigNotSatisfied(),
throwIfHealthError(),
]).then(sinkVoid)
const isValid = () =>
@@ -105,11 +119,13 @@ export function checkAllDependencies(effects: Effects): CheckAllDependencies {
return {
notRunning,
notInstalled,
configNotSatisfied,
healthErrors,
throwIfNotRunning,
throwIfConfigNotSatisfied,
throwIfNotValid,
throwIfNotInstalled,
throwIfError,
throwIfHealthError,
isValid,
}
}

View File

@@ -1,12 +1,12 @@
import { Config } from "../config/builder/config"
import { SDKManifest } from "../manifest/ManifestTypes"
import { ExpectedExports } from "../types"
import * as T from "../types"
import { DependencyConfig } from "./DependencyConfig"
export function setupDependencyConfig<
Store,
Input extends Record<string, any>,
Manifest extends SDKManifest,
Manifest extends T.Manifest,
>(
_config: Config<Input, Store> | Config<Input, never>,
autoConfigs: {
@@ -17,6 +17,6 @@ export function setupDependencyConfig<
any
> | null
},
): ExpectedExports.dependencyConfig {
): T.ExpectedExports.dependencyConfig {
return autoConfigs
}

View File

@@ -1,323 +0,0 @@
import * as matches from "ts-matches"
const starSub = /((\d+\.)*\d+)\.\*/
// prettier-ignore
export type ValidEmVer = string;
// prettier-ignore
export type ValidEmVerRange = string;
function incrementLastNumber(list: number[]) {
const newList = [...list]
newList[newList.length - 1]++
return newList
}
/**
* Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*`
* and return a checker, that has the check function for checking that a version is in the valid
* @param range
* @returns
*/
export function rangeOf(range: string | Checker): Checker {
return Checker.parse(range)
}
/**
* Used to create a checker that will `and` all the ranges passed in
* @param ranges
* @returns
*/
export function rangeAnd(...ranges: (string | Checker)[]): Checker {
if (ranges.length === 0) {
throw new Error("No ranges given")
}
const [firstCheck, ...rest] = ranges
return Checker.parse(firstCheck).and(...rest)
}
/**
* Used to create a checker that will `or` all the ranges passed in
* @param ranges
* @returns
*/
export function rangeOr(...ranges: (string | Checker)[]): Checker {
if (ranges.length === 0) {
throw new Error("No ranges given")
}
const [firstCheck, ...rest] = ranges
return Checker.parse(firstCheck).or(...rest)
}
/**
* This will negate the checker, so given a checker that checks for >= 1.0.0, it will check for < 1.0.0
* @param range
* @returns
*/
export function notRange(range: string | Checker): Checker {
return rangeOf(range).not()
}
/**
* EmVer is a set of versioning of any pattern like 1 or 1.2 or 1.2.3 or 1.2.3.4 or ..
*/
export class EmVer {
/**
* Convert the range, should be 1.2.* or * into a emver
* Or an already made emver
* IsUnsafe
*/
static from(range: string | EmVer): EmVer {
if (range instanceof EmVer) {
return range
}
return EmVer.parse(range)
}
/**
* Convert the range, should be 1.2.* or * into a emver
* IsUnsafe
*/
static parse(rangeExtra: string): EmVer {
const [range, extra] = rangeExtra.split("-")
const values = range.split(".").map((x) => parseInt(x))
for (const value of values) {
if (isNaN(value)) {
throw new Error(`Couldn't parse range: ${range}`)
}
}
return new EmVer(values, extra)
}
private constructor(
public readonly values: number[],
readonly extra: string | null,
) {}
/**
* Used when we need a new emver that has the last number incremented, used in the 1.* like things
*/
public withLastIncremented() {
return new EmVer(incrementLastNumber(this.values), null)
}
public greaterThan(other: EmVer): boolean {
for (const i in this.values) {
if (other.values[i] == null) {
return true
}
if (this.values[i] > other.values[i]) {
return true
}
if (this.values[i] < other.values[i]) {
return false
}
}
return false
}
public equals(other: EmVer): boolean {
if (other.values.length !== this.values.length) {
return false
}
for (const i in this.values) {
if (this.values[i] !== other.values[i]) {
return false
}
}
return true
}
public greaterThanOrEqual(other: EmVer): boolean {
return this.greaterThan(other) || this.equals(other)
}
public lessThanOrEqual(other: EmVer): boolean {
return !this.greaterThan(other)
}
public lessThan(other: EmVer): boolean {
return !this.greaterThanOrEqual(other)
}
/**
* Return a enum string that describes (used for switching/iffs)
* to know comparison
* @param other
* @returns
*/
public compare(other: EmVer) {
if (this.equals(other)) {
return "equal" as const
} else if (this.greaterThan(other)) {
return "greater" as const
} else {
return "less" as const
}
}
/**
* Used when sorting emver's in a list using the sort method
* @param other
* @returns
*/
public compareForSort(other: EmVer) {
return matches
.matches(this.compare(other))
.when("equal", () => 0 as const)
.when("greater", () => 1 as const)
.when("less", () => -1 as const)
.unwrap()
}
toString() {
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}` as ValidEmVer
}
}
/**
* A checker is a function that takes a version and returns true if the version matches the checker.
* Used when we are doing range checking, like saying ">=1.0.0".check("1.2.3") will be true
*/
export class Checker {
/**
* Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*`
* and return a checker, that has the check function for checking that a version is in the valid
* @param range
* @returns
*/
static parse(range: string | Checker): Checker {
if (range instanceof Checker) {
return range
}
range = range.trim()
if (range.indexOf("||") !== -1) {
return rangeOr(...range.split("||").map((x) => Checker.parse(x)))
}
if (range.indexOf("&&") !== -1) {
return rangeAnd(...range.split("&&").map((x) => Checker.parse(x)))
}
if (range === "*") {
return new Checker((version) => {
EmVer.from(version)
return true
}, range)
}
if (range.startsWith("!!")) return Checker.parse(range.substring(2))
if (range.startsWith("!")) {
const tempValue = Checker.parse(range.substring(1))
return new Checker((x) => !tempValue.check(x), range)
}
const starSubMatches = starSub.exec(range)
if (starSubMatches != null) {
const emVarLower = EmVer.parse(starSubMatches[1])
const emVarUpper = emVarLower.withLastIncremented()
return new Checker((version) => {
const v = EmVer.from(version)
return (
(v.greaterThan(emVarLower) || v.equals(emVarLower)) &&
!v.greaterThan(emVarUpper) &&
!v.equals(emVarUpper)
)
}, range)
}
switch (range.substring(0, 2)) {
case ">=": {
const emVar = EmVer.parse(range.substring(2))
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)
}
}
switch (range.substring(0, 1)) {
case ">": {
const emVar = EmVer.parse(range.substring(1))
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)
}
constructor(
/**
* Check is the function that will be given a emver or unparsed emver and should give if it follows
* 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 {
const othersCheck = others.map(Checker.parse)
return new Checker(
(value) => {
if (!this.check(value)) {
return false
}
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 {
const othersCheck = others.map(Checker.parse)
return new Checker(
(value) => {
if (this.check(value)) {
return true
}
for (const other of othersCheck) {
if (other.check(value)) {
return true
}
}
return false
},
othersCheck.map((x) => x._range).join(" || "),
)
}
/**
* A useful example is making sure we don't match an exact version, like !=1.2.3
* @returns
*/
public not(): Checker {
let newRange = `!${this._range}`
return Checker.parse(newRange)
}
}

99
sdk/lib/exver/exver.pegjs Normal file
View File

@@ -0,0 +1,99 @@
// #flavor:0.1.2-beta.1:0
// !( >=1:1 && <= 2:2)
VersionRange
= first:VersionRangeAtom rest:(_ ((Or / And) _)? VersionRangeAtom)*
Or = "||"
And = "&&"
VersionRangeAtom
= Parens
/ Anchor
/ Not
/ Any
/ None
Parens
= "(" _ expr:VersionRange _ ")" { return { type: "Parens", expr } }
Anchor
= operator:CmpOp? _ version:VersionSpec { return { type: "Anchor", operator, version } }
VersionSpec
= flavor:Flavor? upstream:Version downstream:( ":" Version )? { return { flavor: flavor || null, upstream, downstream: downstream ? downstream[1] : { number: [0], prerelease: [] } } }
Not = "!" _ value:VersionRangeAtom { return { type: "Not", value: value }}
Any = "*" { return { type: "Any" } }
None = "!" { return { type: "None" } }
CmpOp
= ">=" { return ">="; }
/ "<=" { return "<="; }
/ ">" { return ">"; }
/ "<" { return "<"; }
/ "=" { return "="; }
/ "!=" { return "!="; }
/ "^" { return "^"; }
/ "~" { return "~"; }
ExtendedVersion
= flavor:Flavor? upstream:Version ":" downstream:Version {
return { flavor: flavor || null, upstream, downstream }
}
EmVer
= major:Digit "." minor:Digit "." patch:Digit ("." revision:Digit)? {
return {
flavor: null,
upstream: {
number: [major, minor, patch],
prerelease: [],
},
downstream: {
number: [revision || 0],
prerelease: [],
},
}
}
Flavor
= "#" flavor:Lowercase ":" { return flavor }
Lowercase
= [a-z]+ { return text() }
String
= [a-zA-Z]+ { return text(); }
Version
= number:VersionNumber prerelease: PreRelease? {
return {
number,
prerelease: prerelease || []
};
}
PreRelease
= "-" first:PreReleaseSegment rest:("." PreReleaseSegment)* {
return [first].concat(rest.map(r => r[1]));
}
PreReleaseSegment
= "."? segment:(Digit / String) {
return segment;
}
VersionNumber
= first:Digit rest:("." Digit)* {
return [first].concat(rest.map(r => r[1]));
}
Digit
= [0-9]+ { return parseInt(text(), 10); }
_ "whitespace"
= [ \t\n\r]*

2507
sdk/lib/exver/exver.ts Normal file

File diff suppressed because it is too large Load Diff

443
sdk/lib/exver/index.ts Normal file
View File

@@ -0,0 +1,443 @@
import * as P from "./exver"
// prettier-ignore
export type ValidateVersion<T extends String> =
T extends `-${infer A}` ? never :
T extends `${infer A}-${infer B}` ? ValidateVersion<A> & ValidateVersion<B> :
T extends `${bigint}` ? unknown :
T extends `${bigint}.${infer A}` ? ValidateVersion<A> :
never
// prettier-ignore
export type ValidateExVer<T extends string> =
T extends `#${string}:${infer A}:${infer B}` ? ValidateVersion<A> & ValidateVersion<B> :
T extends `${infer A}:${infer B}` ? ValidateVersion<A> & ValidateVersion<B> :
never
// prettier-ignore
export type ValidateExVers<T> =
T extends [] ? unknown :
T extends [infer A, ...infer B] ? ValidateExVer<A & string> & ValidateExVers<B> :
never
type Anchor = {
type: "Anchor"
operator: P.CmpOp
version: ExtendedVersion
}
type And = {
type: "And"
left: VersionRange
right: VersionRange
}
type Or = {
type: "Or"
left: VersionRange
right: VersionRange
}
type Not = {
type: "Not"
value: VersionRange
}
export class VersionRange {
private constructor(private atom: Anchor | And | Or | Not | P.Any | P.None) {}
toString(): string {
switch (this.atom.type) {
case "Anchor":
return `${this.atom.operator}${this.atom.version}`
case "And":
return `(${this.atom.left.toString()}) && (${this.atom.right.toString()})`
case "Or":
return `(${this.atom.left.toString()}) || (${this.atom.right.toString()})`
case "Not":
return `!(${this.atom.value.toString()})`
case "Any":
return "*"
case "None":
return "!"
}
}
/**
* Returns a boolean indicating whether a given version satisfies the VersionRange
* !( >= 1:1 <= 2:2) || <=#bitcoin:1.2.0-alpha:0
*/
satisfiedBy(version: ExtendedVersion): boolean {
switch (this.atom.type) {
case "Anchor":
const otherVersion = this.atom.version
switch (this.atom.operator) {
case "=":
return version.equals(otherVersion)
case ">":
return version.greaterThan(otherVersion)
case "<":
return version.lessThan(otherVersion)
case ">=":
return version.greaterThanOrEqual(otherVersion)
case "<=":
return version.lessThanOrEqual(otherVersion)
case "!=":
return !version.equals(otherVersion)
case "^":
const nextMajor = this.atom.version.incrementMajor()
if (
version.greaterThanOrEqual(otherVersion) &&
version.lessThan(nextMajor)
) {
return true
} else {
return false
}
case "~":
const nextMinor = this.atom.version.incrementMinor()
if (
version.greaterThanOrEqual(otherVersion) &&
version.lessThan(nextMinor)
) {
return true
} else {
return false
}
}
case "And":
return (
this.atom.left.satisfiedBy(version) &&
this.atom.right.satisfiedBy(version)
)
case "Or":
return (
this.atom.left.satisfiedBy(version) ||
this.atom.right.satisfiedBy(version)
)
case "Not":
return !this.atom.value.satisfiedBy(version)
case "Any":
return true
case "None":
return false
}
}
private static parseAtom(atom: P.VersionRangeAtom): VersionRange {
switch (atom.type) {
case "Not":
return new VersionRange({
type: "Not",
value: VersionRange.parseAtom(atom.value),
})
case "Parens":
return VersionRange.parseRange(atom.expr)
case "Anchor":
return new VersionRange({
type: "Anchor",
operator: atom.operator || "^",
version: new ExtendedVersion(
atom.version.flavor,
new Version(
atom.version.upstream.number,
atom.version.upstream.prerelease,
),
new Version(
atom.version.downstream.number,
atom.version.downstream.prerelease,
),
),
})
default:
return new VersionRange(atom)
}
}
private static parseRange(range: P.VersionRange): VersionRange {
let result = VersionRange.parseAtom(range[0])
for (const next of range[1]) {
switch (next[1]?.[0]) {
case "||":
result = new VersionRange({
type: "Or",
left: result,
right: VersionRange.parseAtom(next[2]),
})
break
case "&&":
default:
result = new VersionRange({
type: "And",
left: result,
right: VersionRange.parseAtom(next[2]),
})
break
}
}
return result
}
static parse(range: string): VersionRange {
return VersionRange.parseRange(
P.parse(range, { startRule: "VersionRange" }),
)
}
and(right: VersionRange) {
return new VersionRange({ type: "And", left: this, right })
}
or(right: VersionRange) {
return new VersionRange({ type: "Or", left: this, right })
}
not() {
return new VersionRange({ type: "Not", value: this })
}
static anchor(operator: P.CmpOp, version: ExtendedVersion) {
return new VersionRange({ type: "Anchor", operator, version })
}
static any() {
return new VersionRange({ type: "Any" })
}
static none() {
return new VersionRange({ type: "None" })
}
}
export class Version {
constructor(
public number: number[],
public prerelease: (string | number)[],
) {}
toString(): string {
return `${this.number.join(".")}${this.prerelease.length > 0 ? `-${this.prerelease.join(".")}` : ""}`
}
compare(other: Version): "greater" | "equal" | "less" {
const numLen = Math.max(this.number.length, other.number.length)
for (let i = 0; i < numLen; i++) {
if ((this.number[i] || 0) > (other.number[i] || 0)) {
return "greater"
} else if ((this.number[i] || 0) < (other.number[i] || 0)) {
return "less"
}
}
if (this.prerelease.length === 0 && other.prerelease.length !== 0) {
return "greater"
} else if (this.prerelease.length !== 0 && other.prerelease.length === 0) {
return "less"
}
const prereleaseLen = Math.max(this.number.length, other.number.length)
for (let i = 0; i < prereleaseLen; i++) {
if (typeof this.prerelease[i] === typeof other.prerelease[i]) {
if (this.prerelease[i] > other.prerelease[i]) {
return "greater"
} else if (this.prerelease[i] < other.prerelease[i]) {
return "less"
}
} else {
switch (`${typeof this.prerelease[1]}:${typeof other.prerelease[i]}`) {
case "number:string":
return "less"
case "string:number":
return "greater"
case "number:undefined":
case "string:undefined":
return "greater"
case "undefined:number":
case "undefined:string":
return "less"
}
}
}
return "equal"
}
static parse(version: string): Version {
const parsed = P.parse(version, { startRule: "Version" })
return new Version(parsed.number, parsed.prerelease)
}
}
// #flavor:0.1.2-beta.1:0
export class ExtendedVersion {
constructor(
public flavor: string | null,
public upstream: Version,
public downstream: Version,
) {}
toString(): string {
return `${this.flavor ? `#${this.flavor}:` : ""}${this.upstream.toString()}:${this.downstream.toString()}`
}
compare(other: ExtendedVersion): "greater" | "equal" | "less" | null {
if (this.flavor !== other.flavor) {
return null
}
const upstreamCmp = this.upstream.compare(other.upstream)
if (upstreamCmp !== "equal") {
return upstreamCmp
}
return this.downstream.compare(other.downstream)
}
compareLexicographic(other: ExtendedVersion): "greater" | "equal" | "less" {
if ((this.flavor || "") > (other.flavor || "")) {
return "greater"
} else if ((this.flavor || "") > (other.flavor || "")) {
return "less"
} else {
return this.compare(other)!
}
}
compareForSort(other: ExtendedVersion): 1 | 0 | -1 {
switch (this.compareLexicographic(other)) {
case "greater":
return 1
case "equal":
return 0
case "less":
return -1
}
}
greaterThan(other: ExtendedVersion): boolean {
return this.compare(other) === "greater"
}
greaterThanOrEqual(other: ExtendedVersion): boolean {
return ["greater", "equal"].includes(this.compare(other) as string)
}
equals(other: ExtendedVersion): boolean {
return this.compare(other) === "equal"
}
lessThan(other: ExtendedVersion): boolean {
return this.compare(other) === "less"
}
lessThanOrEqual(other: ExtendedVersion): boolean {
return ["less", "equal"].includes(this.compare(other) as string)
}
static parse(extendedVersion: string): ExtendedVersion {
const parsed = P.parse(extendedVersion, { startRule: "ExtendedVersion" })
return new ExtendedVersion(
parsed.flavor,
new Version(parsed.upstream.number, parsed.upstream.prerelease),
new Version(parsed.downstream.number, parsed.downstream.prerelease),
)
}
static parseEmver(extendedVersion: string): ExtendedVersion {
const parsed = P.parse(extendedVersion, { startRule: "EmVer" })
return new ExtendedVersion(
parsed.flavor,
new Version(parsed.upstream.number, parsed.upstream.prerelease),
new Version(parsed.downstream.number, parsed.downstream.prerelease),
)
}
/**
* Returns an ExtendedVersion with the Upstream major version version incremented by 1
* and sets subsequent digits to zero.
* If no non-zero upstream digit can be found the last upstream digit will be incremented.
*/
incrementMajor(): ExtendedVersion {
const majorIdx = this.upstream.number.findIndex((num: number) => num !== 0)
const majorNumber = this.upstream.number.map((num, idx): number => {
if (idx > majorIdx) {
return 0
} else if (idx === majorIdx) {
return num + 1
}
return num
})
const incrementedUpstream = new Version(majorNumber, [])
const updatedDownstream = new Version([0], [])
return new ExtendedVersion(
this.flavor,
incrementedUpstream,
updatedDownstream,
)
}
/**
* Returns an ExtendedVersion with the Upstream minor version version incremented by 1
* also sets subsequent digits to zero.
* If no non-zero upstream digit can be found the last digit will be incremented.
*/
incrementMinor(): ExtendedVersion {
const majorIdx = this.upstream.number.findIndex((num: number) => num !== 0)
let minorIdx = majorIdx === -1 ? majorIdx : majorIdx + 1
const majorNumber = this.upstream.number.map((num, idx): number => {
if (idx > minorIdx) {
return 0
} else if (idx === minorIdx) {
return num + 1
}
return num
})
const incrementedUpstream = new Version(majorNumber, [])
const updatedDownstream = new Version([0], [])
return new ExtendedVersion(
this.flavor,
incrementedUpstream,
updatedDownstream,
)
}
}
export const testTypeExVer = <T extends string>(t: T & ValidateExVer<T>) => t
export const testTypeVersion = <T extends string>(t: T & ValidateVersion<T>) =>
t
function tests() {
testTypeVersion("1.2.3")
testTypeVersion("1")
testTypeVersion("12.34.56")
testTypeVersion("1.2-3")
testTypeVersion("1-3")
// @ts-expect-error
testTypeVersion("-3")
// @ts-expect-error
testTypeVersion("1.2.3:1")
// @ts-expect-error
testTypeVersion("#cat:1:1")
testTypeExVer("1.2.3:1.2.3")
testTypeExVer("1.2.3.4.5.6.7.8.9.0:1")
testTypeExVer("100:1")
testTypeExVer("#cat:1:1")
testTypeExVer("1.2.3.4.5.6.7.8.9.11.22.33:1")
testTypeExVer("1-0:1")
testTypeExVer("1-0:1")
// @ts-expect-error
testTypeExVer("1.2-3")
// @ts-expect-error
testTypeExVer("1-3")
// @ts-expect-error
testTypeExVer("1.2.3.4.5.6.7.8.9.0.10:1" as string)
// @ts-expect-error
testTypeExVer("1.-2:1")
// @ts-expect-error
testTypeExVer("1..2.3:3")
}

View File

@@ -1,5 +1,4 @@
import { InterfaceReceipt } from "../interfaces/interfaceReceipt"
import { Daemon, Effects, SDKManifest } from "../types"
import { Effects } from "../types"
import { CheckResult } from "./checkFns/CheckResult"
import { HealthReceipt } from "./HealthReceipt"
import { Trigger } from "../trigger"
@@ -8,9 +7,9 @@ import { defaultTrigger } from "../trigger/defaultTrigger"
import { once } from "../util/once"
import { Overlay } from "../util/Overlay"
import { object, unknown } from "ts-matches"
import { T } from ".."
import * as T from "../types"
export type HealthCheckParams<Manifest extends SDKManifest> = {
export type HealthCheckParams<Manifest extends T.Manifest> = {
effects: Effects
name: string
image: {
@@ -22,7 +21,7 @@ export type HealthCheckParams<Manifest extends SDKManifest> = {
onFirstSuccess?: () => unknown | Promise<unknown>
}
export function healthCheck<Manifest extends SDKManifest>(
export function healthCheck<Manifest extends T.Manifest>(
o: HealthCheckParams<Manifest>,
) {
new Promise(async () => {

View File

@@ -1,12 +1,10 @@
export { EmVer } from "./emverLite/mod"
export { setupManifest } from "./manifest/setupManifest"
export { setupExposeStore } from "./store/setupExposeStore"
export { S9pk } from "./s9pk"
export { VersionRange, ExtendedVersion, Version } from "./exver"
export * as config from "./config"
export * as CB from "./config/builder"
export * as CT from "./config/configTypes"
export * as dependencyConfig from "./dependencies"
export * as manifest from "./manifest"
export * as types from "./types"
export * as T from "./types"
export * as yaml from "yaml"

View File

@@ -1,5 +1,4 @@
export { Daemons } from "./mainFn/Daemons"
export { EmVer } from "./emverLite/mod"
export { Overlay } from "./util/Overlay"
export { StartSdk } from "./StartSdk"
export { setupManifest } from "./manifest/setupManifest"
@@ -7,6 +6,7 @@ export { FileHelper } from "./util/fileHelper"
export { setupExposeStore } from "./store/setupExposeStore"
export { pathBuilder } from "./store/PathBuilder"
export { S9pk } from "./s9pk"
export { VersionRange, ExtendedVersion, Version } from "./exver"
export * as actions from "./actions"
export * as backup from "./backup"

View File

@@ -1,35 +1,35 @@
import { ManifestVersion, SDKManifest } from "../../manifest/ManifestTypes"
import { Effects } from "../../types"
import { ValidateExVer } from "../../exver"
import * as T from "../../types"
export class Migration<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
Version extends ManifestVersion,
Version extends string,
> {
constructor(
readonly options: {
version: Version
up: (opts: { effects: Effects }) => Promise<void>
down: (opts: { effects: Effects }) => Promise<void>
version: Version & ValidateExVer<Version>
up: (opts: { effects: T.Effects }) => Promise<void>
down: (opts: { effects: T.Effects }) => Promise<void>
},
) {}
static of<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
Version extends ManifestVersion,
Version extends string,
>(options: {
version: Version
up: (opts: { effects: Effects }) => Promise<void>
down: (opts: { effects: Effects }) => Promise<void>
version: Version & ValidateExVer<Version>
up: (opts: { effects: T.Effects }) => Promise<void>
down: (opts: { effects: T.Effects }) => Promise<void>
}) {
return new Migration<Manifest, Store, Version>(options)
}
async up(opts: { effects: Effects }) {
async up(opts: { effects: T.Effects }) {
this.up(opts)
}
async down(opts: { effects: Effects }) {
async down(opts: { effects: T.Effects }) {
this.down(opts)
}
}

View File

@@ -1,27 +1,31 @@
import { EmVer } from "../../emverLite/mod"
import { SDKManifest } from "../../manifest/ManifestTypes"
import { ExpectedExports } from "../../types"
import { ExtendedVersion } from "../../exver"
import * as T from "../../types"
import { once } from "../../util/once"
import { Migration } from "./Migration"
export class Migrations<Manifest extends SDKManifest, Store> {
export class Migrations<Manifest extends T.Manifest, Store> {
private constructor(
readonly manifest: SDKManifest,
readonly manifest: T.Manifest,
readonly migrations: Array<Migration<Manifest, Store, any>>,
) {}
private sortedMigrations = once(() => {
const migrationsAsVersions = (
this.migrations as Array<Migration<Manifest, Store, any>>
).map((x) => [EmVer.parse(x.options.version), x] as const)
)
.map((x) => [ExtendedVersion.parse(x.options.version), x] as const)
.filter(([v, _]) => v.flavor === this.currentVersion().flavor)
migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0]))
return migrationsAsVersions
})
private currentVersion = once(() => EmVer.parse(this.manifest.version))
private currentVersion = once(() =>
ExtendedVersion.parse(this.manifest.version),
)
static of<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
Migrations extends Array<Migration<Manifest, Store, any>>,
>(manifest: SDKManifest, ...migrations: EnsureUniqueId<Migrations>) {
>(manifest: T.Manifest, ...migrations: EnsureUniqueId<Migrations>) {
return new Migrations(
manifest,
migrations as Array<Migration<Manifest, Store, any>>,
@@ -30,11 +34,11 @@ export class Migrations<Manifest extends SDKManifest, Store> {
async init({
effects,
previousVersion,
}: Parameters<ExpectedExports.init>[0]) {
}: Parameters<T.ExpectedExports.init>[0]) {
if (!!previousVersion) {
const previousVersionEmVer = EmVer.parse(previousVersion)
const previousVersionExVer = ExtendedVersion.parse(previousVersion)
for (const [_, migration] of this.sortedMigrations()
.filter((x) => x[0].greaterThan(previousVersionEmVer))
.filter((x) => x[0].greaterThan(previousVersionExVer))
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
await migration.up({ effects })
}
@@ -43,12 +47,12 @@ export class Migrations<Manifest extends SDKManifest, Store> {
async uninit({
effects,
nextVersion,
}: Parameters<ExpectedExports.uninit>[0]) {
}: Parameters<T.ExpectedExports.uninit>[0]) {
if (!!nextVersion) {
const nextVersionEmVer = EmVer.parse(nextVersion)
const nextVersionExVer = ExtendedVersion.parse(nextVersion)
const reversed = [...this.sortedMigrations()].reverse()
for (const [_, migration] of reversed
.filter((x) => x[0].greaterThan(nextVersionEmVer))
.filter((x) => x[0].greaterThan(nextVersionExVer))
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
await migration.down({ effects })
}
@@ -57,10 +61,10 @@ export class Migrations<Manifest extends SDKManifest, Store> {
}
export function setupMigrations<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
Migrations extends Array<Migration<Manifest, Store, any>>,
>(manifest: SDKManifest, ...migrations: EnsureUniqueId<Migrations>) {
>(manifest: T.Manifest, ...migrations: EnsureUniqueId<Migrations>) {
return Migrations.of<Manifest, Store, Migrations>(manifest, ...migrations)
}

View File

@@ -1,25 +1,25 @@
import { DependenciesReceipt } from "../config/setupConfig"
import { SetInterfaces } from "../interfaces/setupInterfaces"
import { SDKManifest } from "../manifest/ManifestTypes"
import { ExposedStorePaths } from "../store/setupExposeStore"
import { Effects, ExpectedExports } from "../types"
import * as T from "../types"
import { Migrations } from "./migrations/setupMigrations"
import { Install } from "./setupInstall"
import { Uninstall } from "./setupUninstall"
export function setupInit<Manifest extends SDKManifest, Store>(
export function setupInit<Manifest extends T.Manifest, Store>(
migrations: Migrations<Manifest, Store>,
install: Install<Manifest, Store>,
uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setDependencies: (options: {
effects: Effects
effects: T.Effects
input: any
}) => Promise<DependenciesReceipt>,
exposedStore: ExposedStorePaths,
): {
init: ExpectedExports.init
uninit: ExpectedExports.uninit
init: T.ExpectedExports.init
uninit: T.ExpectedExports.uninit
} {
return {
init: async (opts) => {

View File

@@ -1,12 +1,11 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects, ExpectedExports } from "../types"
import * as T from "../types"
export type InstallFn<Manifest extends SDKManifest, Store> = (opts: {
effects: Effects
export type InstallFn<Manifest extends T.Manifest, Store> = (opts: {
effects: T.Effects
}) => Promise<void>
export class Install<Manifest extends SDKManifest, Store> {
export class Install<Manifest extends T.Manifest, Store> {
private constructor(readonly fn: InstallFn<Manifest, Store>) {}
static of<Manifest extends SDKManifest, Store>(
static of<Manifest extends T.Manifest, Store>(
fn: InstallFn<Manifest, Store>,
) {
return new Install(fn)
@@ -15,7 +14,7 @@ export class Install<Manifest extends SDKManifest, Store> {
async init({
effects,
previousVersion,
}: Parameters<ExpectedExports.init>[0]) {
}: Parameters<T.ExpectedExports.init>[0]) {
if (!previousVersion)
await this.fn({
effects,
@@ -23,7 +22,7 @@ export class Install<Manifest extends SDKManifest, Store> {
}
}
export function setupInstall<Manifest extends SDKManifest, Store>(
export function setupInstall<Manifest extends T.Manifest, Store>(
fn: InstallFn<Manifest, Store>,
) {
return Install.of(fn)

View File

@@ -1,12 +1,11 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects, ExpectedExports } from "../types"
import * as T from "../types"
export type UninstallFn<Manifest extends SDKManifest, Store> = (opts: {
effects: Effects
export type UninstallFn<Manifest extends T.Manifest, Store> = (opts: {
effects: T.Effects
}) => Promise<void>
export class Uninstall<Manifest extends SDKManifest, Store> {
export class Uninstall<Manifest extends T.Manifest, Store> {
private constructor(readonly fn: UninstallFn<Manifest, Store>) {}
static of<Manifest extends SDKManifest, Store>(
static of<Manifest extends T.Manifest, Store>(
fn: UninstallFn<Manifest, Store>,
) {
return new Uninstall(fn)
@@ -15,7 +14,7 @@ export class Uninstall<Manifest extends SDKManifest, Store> {
async uninit({
effects,
nextVersion,
}: Parameters<ExpectedExports.uninit>[0]) {
}: Parameters<T.ExpectedExports.uninit>[0]) {
if (!nextVersion)
await this.fn({
effects,
@@ -23,7 +22,7 @@ export class Uninstall<Manifest extends SDKManifest, Store> {
}
}
export function setupUninstall<Manifest extends SDKManifest, Store>(
export function setupUninstall<Manifest extends T.Manifest, Store>(
fn: UninstallFn<Manifest, Store>,
) {
return Uninstall.of(fn)

View File

@@ -1,17 +1,17 @@
import { Config } from "../config/builder/config"
import { SDKManifest } from "../manifest/ManifestTypes"
import { AddressInfo, Effects } from "../types"
import * as T from "../types"
import { AddressReceipt } from "./AddressReceipt"
export type InterfacesReceipt = Array<AddressInfo[] & AddressReceipt>
export type InterfacesReceipt = Array<T.AddressInfo[] & AddressReceipt>
export type SetInterfaces<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
ConfigInput extends Record<string, any>,
Output extends InterfacesReceipt,
> = (opts: { effects: Effects; input: null | ConfigInput }) => Promise<Output>
> = (opts: { effects: T.Effects; input: null | ConfigInput }) => Promise<Output>
export type SetupInterfaces = <
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Store,
ConfigInput extends Record<string, any>,
Output extends InterfacesReceipt,

View File

@@ -1,7 +1,7 @@
import { DEFAULT_SIGTERM_TIMEOUT } from "."
import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk"
import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects, ImageId, ValidIfNoStupidEscape } from "../types"
import * as T from "../types"
import { MountOptions, Overlay } from "../util/Overlay"
import { splitCommand } from "../util/splitCommand"
import { cpExecFile, cpExec } from "./Daemons"
@@ -13,14 +13,14 @@ export class CommandController {
readonly pid: number | undefined,
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
) {}
static of<Manifest extends SDKManifest>() {
static of<Manifest extends T.Manifest>() {
return async <A extends string>(
effects: Effects,
effects: T.Effects,
imageId: {
id: keyof Manifest["images"] & ImageId
id: keyof Manifest["images"] & T.ImageId
sharedRun?: boolean
},
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
command: T.CommandType,
options: {
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
sigtermTimeout?: number

View File

@@ -1,5 +1,4 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects, ImageId, ValidIfNoStupidEscape } from "../types"
import * as T from "../types"
import { MountOptions, Overlay } from "../util/Overlay"
import { CommandController } from "./CommandController"
@@ -14,14 +13,14 @@ export class Daemon {
private commandController: CommandController | null = null
private shouldBeRunning = false
private constructor(private startCommand: () => Promise<CommandController>) {}
static of<Manifest extends SDKManifest>() {
static of<Manifest extends T.Manifest>() {
return async <A extends string>(
effects: Effects,
effects: T.Effects,
imageId: {
id: keyof Manifest["images"] & ImageId
id: keyof Manifest["images"] & T.ImageId
sharedRun?: boolean
},
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
command: T.CommandType,
options: {
mounts?: { path: string; options: MountOptions }[]
overlay?: Overlay

View File

@@ -1,16 +1,11 @@
import { NO_TIMEOUT, SIGKILL, SIGTERM, Signals } from "../StartSdk"
import { HealthReceipt } from "../health/HealthReceipt"
import { CheckResult } from "../health/checkFns"
import { SDKManifest } from "../manifest/ManifestTypes"
import { Trigger } from "../trigger"
import { TriggerInput } from "../trigger/TriggerInput"
import { defaultTrigger } from "../trigger/defaultTrigger"
import {
DaemonReturned,
Effects,
ImageId,
ValidIfNoStupidEscape,
} from "../types"
import * as T from "../types"
import { Mounts } from "./Mounts"
import { CommandOptions, MountOptions, Overlay } from "../util/Overlay"
import { splitCommand } from "../util/splitCommand"
@@ -33,13 +28,13 @@ export type Ready = {
}
type DaemonsParams<
Manifest extends SDKManifest,
Manifest extends T.Manifest,
Ids extends string,
Command extends string,
Id extends string,
> = {
command: ValidIfNoStupidEscape<Command> | [string, ...string[]]
image: { id: keyof Manifest["images"] & ImageId; sharedRun?: boolean }
command: T.CommandType
image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean }
mounts: Mounts<Manifest>
env?: Record<string, string>
ready: Ready
@@ -49,7 +44,7 @@ type DaemonsParams<
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
export const runCommand = <Manifest extends SDKManifest>() =>
export const runCommand = <Manifest extends T.Manifest>() =>
CommandController.of<Manifest>()
/**
@@ -75,9 +70,9 @@ Daemons.of({
})
```
*/
export class Daemons<Manifest extends SDKManifest, Ids extends string> {
export class Daemons<Manifest extends T.Manifest, Ids extends string> {
private constructor(
readonly effects: Effects,
readonly effects: T.Effects,
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<void>,
readonly daemons: Promise<Daemon>[],
readonly ids: Ids[],
@@ -93,8 +88,8 @@ export class Daemons<Manifest extends SDKManifest, Ids extends string> {
* @param config
* @returns
*/
static of<Manifest extends SDKManifest>(config: {
effects: Effects
static of<Manifest extends T.Manifest>(config: {
effects: T.Effects
started: (onTerm: () => PromiseLike<void>) => PromiseLike<void>
healthReceipts: HealthReceipt[]
}) {

View File

@@ -1,10 +1,9 @@
import { SDKManifest } from "../manifest/ManifestTypes"
import { Effects } from "../types"
import * as T from "../types"
import { MountOptions } from "../util/Overlay"
type MountArray = { path: string; options: MountOptions }[]
export class Mounts<Manifest extends SDKManifest> {
export class Mounts<Manifest extends T.Manifest> {
private constructor(
readonly volumes: {
id: Manifest["volumes"][number]
@@ -26,7 +25,7 @@ export class Mounts<Manifest extends SDKManifest> {
}[],
) {}
static of<Manifest extends SDKManifest>() {
static of<Manifest extends T.Manifest>() {
return new Mounts<Manifest>([], [], [])
}
@@ -58,7 +57,7 @@ export class Mounts<Manifest extends SDKManifest> {
return this
}
addDependency<DependencyManifest extends SDKManifest>(
addDependency<DependencyManifest extends T.Manifest>(
dependencyId: keyof Manifest["dependencies"] & string,
volumeId: DependencyManifest["volumes"][number],
subpath: string | null,

View File

@@ -1,10 +1,10 @@
import { ExpectedExports } from "../types"
import * as T from "../types"
import { Daemons } from "./Daemons"
import "../interfaces/ServiceInterfaceBuilder"
import "../interfaces/Origin"
import "./Daemons"
import { SDKManifest } from "../manifest/ManifestTypes"
import { MainEffects } from "../StartSdk"
export const DEFAULT_SIGTERM_TIMEOUT = 30_000
@@ -18,12 +18,12 @@ export const DEFAULT_SIGTERM_TIMEOUT = 30_000
* @param fn
* @returns
*/
export const setupMain = <Manifest extends SDKManifest, Store>(
export const setupMain = <Manifest extends T.Manifest, Store>(
fn: (o: {
effects: MainEffects
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
}) => Promise<Daemons<Manifest, any>>,
): ExpectedExports.main => {
): T.ExpectedExports.main => {
return async (options) => {
const result = await fn(options)
return result

View File

@@ -1,20 +1,16 @@
import { ValidEmVer } from "../emverLite/mod"
import { ActionMetadata, ImageConfig, ImageId } from "../types"
import { ValidateExVer, ValidateExVers } from "../exver"
import {
ActionMetadata,
HardwareRequirements,
ImageConfig,
ImageId,
ImageSource,
} from "../types"
export type Container = {
/** This should be pointing to a docker container name */
image: string
/** These should match the manifest data volumes */
mounts: Record<string, string>
/** Default is 64mb */
shmSizeMb?: `${number}${"mb" | "gb" | "b" | "kb"}`
/** if more than 30s to shutdown */
sigtermTimeout?: `${number}${"s" | "m" | "h"}`
}
export type ManifestVersion = ValidEmVer
export type SDKManifest = {
export type SDKManifest<
Version extends string,
Satisfies extends string[] = [],
> = {
/** The package identifier used by the OS. This must be unique amongst all other known packages */
readonly id: string
/** A human readable service title */
@@ -23,7 +19,8 @@ export type SDKManifest = {
* - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of
* the service
*/
readonly version: ManifestVersion
readonly version: Version & ValidateExVer<Version>
readonly satisfies?: Satisfies & ValidateExVers<Satisfies>
/** Release notes for the update - can be a string, paragraph or URL */
readonly releaseNotes: string
/** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/
@@ -50,36 +47,49 @@ export type SDKManifest = {
}
/** Defines the os images needed to run the container processes */
readonly images: Record<ImageId, ImageConfig>
readonly images: Record<ImageId, SDKImageConfig>
/** This denotes readonly asset directories that should be available to mount to the container.
* Assuming that there will be three files with names along the lines:
* icon.* : the icon that will be this packages icon on the ui
* LICENSE : What the license is for this service
* Instructions : to be seen in the ui section of the package
* */
* These directories are expected to be found in `assets/<id>` at pack time.
**/
readonly assets: string[]
/** This denotes any data volumes that should be available to mount to the container */
readonly volumes: string[]
readonly alerts: {
readonly install: string | null
readonly update: string | null
readonly uninstall: string | null
readonly restore: string | null
readonly start: string | null
readonly stop: string | null
readonly alerts?: {
readonly install?: string | null
readonly update?: string | null
readonly uninstall?: string | null
readonly restore?: string | null
readonly start?: string | null
readonly stop?: string | null
}
readonly hasConfig?: boolean
readonly dependencies: Readonly<Record<string, ManifestDependency>>
readonly hardwareRequirements?: {
readonly device?: { display?: RegExp; processor?: RegExp }
readonly ram?: number | null
readonly arch?: string[] | null
}
}
export type SDKImageConfig = {
source: Exclude<ImageSource, "packed">
arch?: string[]
emulateMissingAs?: string | null
}
export type ManifestDependency = {
/**
* A human readable explanation on what the dependency is used for
*/
description: string | null
readonly description: string | null
/**
* Determines if the dependency is optional or not. Times that optional that are good include such situations
* such as being able to toggle other services or to use a different service for the same purpose.
*/
optional: boolean
readonly optional: boolean
/**
* A url or local path for an s9pk that satisfies this dependency
*/
readonly s9pk: string
}

View File

@@ -1,21 +1,71 @@
import * as T from "../types"
import { ImageConfig, ImageId, VolumeId } from "../osBindings"
import { SDKManifest, ManifestVersion } from "./ManifestTypes"
import { SDKManifest, SDKImageConfig } from "./ManifestTypes"
import { SDKVersion } from "../StartSdk"
export function setupManifest<
Id extends string,
Version extends ManifestVersion,
Version extends string,
Dependencies extends Record<string, unknown>,
VolumesTypes extends VolumeId,
AssetTypes extends VolumeId,
ImagesTypes extends ImageId,
Manifest extends SDKManifest & {
Manifest extends SDKManifest<Version, Satisfies> & {
dependencies: Dependencies
id: Id
version: Version
assets: AssetTypes[]
images: Record<ImagesTypes, ImageConfig>
images: Record<ImagesTypes, SDKImageConfig>
volumes: VolumesTypes[]
},
>(manifest: Manifest): Manifest {
return manifest
Satisfies extends string[] = [],
>(manifest: Manifest & { version: Version }): Manifest & T.Manifest {
const images = Object.entries(manifest.images).reduce(
(images, [k, v]) => {
v.arch = v.arch || ["aarch64", "x86_64"]
if (v.emulateMissingAs === undefined)
v.emulateMissingAs = v.arch[0] || null
images[k] = v as ImageConfig
return images
},
{} as { [k: string]: ImageConfig },
)
return {
...manifest,
gitHash: null,
osVersion: SDKVersion,
satisfies: manifest.satisfies || [],
images,
alerts: {
install: manifest.alerts?.install || null,
update: manifest.alerts?.update || null,
uninstall: manifest.alerts?.uninstall || null,
restore: manifest.alerts?.restore || null,
start: manifest.alerts?.start || null,
stop: manifest.alerts?.stop || null,
},
hasConfig: manifest.hasConfig === undefined ? true : manifest.hasConfig,
hardwareRequirements: {
device: Object.fromEntries(
Object.entries(manifest.hardwareRequirements?.device || {}).map(
([k, v]) => [k, v.source],
),
),
ram: manifest.hardwareRequirements?.ram || null,
arch:
manifest.hardwareRequirements?.arch === undefined
? Object.values(images).reduce(
(arch, config) => {
if (config.emulateMissingAs) {
return arch
}
if (arch === null) {
return config.arch
}
return arch.filter((a) => config.arch.includes(a))
},
null as string[] | null,
)
: manifest.hardwareRequirements?.arch,
},
}
}

View File

@@ -1,4 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { HealthCheckId } from "./HealthCheckId"
import type { HealthCheckResult } from "./HealthCheckResult"
import type { PackageId } from "./PackageId"
@@ -6,6 +7,7 @@ export type CheckDependenciesResult = {
packageId: PackageId
isInstalled: boolean
isRunning: boolean
healthChecks: Array<HealthCheckResult>
configSatisfied: boolean
healthChecks: { [key: HealthCheckId]: HealthCheckResult }
version: string | null
}

View File

@@ -2,9 +2,8 @@
import type { DataUrl } from "./DataUrl"
export type CurrentDependencyInfo = {
title: string
icon: DataUrl
registryUrl: string
versionSpec: string
title: string | null
icon: DataUrl | null
versionRange: string
configSatisfied: boolean
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })

View File

@@ -1,3 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PathOrUrl } from "./PathOrUrl"
export type DepInfo = { description: string | null; optional: boolean }
export type DepInfo = {
description: string | null
optional: boolean
s9pk: PathOrUrl | null
}

View File

@@ -0,0 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DataUrl } from "./DataUrl"
export type DependencyMetadata = {
title: string | null
icon: DataUrl | null
description: string | null
optional: boolean
}

View File

@@ -5,7 +5,6 @@ export type DependencyRequirement =
kind: "running"
id: string
healthChecks: string[]
versionSpec: string
registryUrl: string
versionRange: string
}
| { kind: "exists"; id: string; versionSpec: string; registryUrl: string }
| { kind: "exists"; id: string; versionRange: string }

View File

@@ -6,6 +6,7 @@ import type { PackageIndex } from "./PackageIndex"
import type { SignerInfo } from "./SignerInfo"
export type FullIndex = {
name: string | null
icon: DataUrl | null
package: PackageIndex
os: OsIndex

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type GetVersionParams = {
export type GetOsVersionParams = {
source: string | null
target: string | null
serverId: string | null

View File

@@ -7,5 +7,5 @@ export type GetPackageParams = {
id: PackageId | null
version: string | null
sourceVersion: Version | null
otherVersions: PackageDetailLevel | null
otherVersions: PackageDetailLevel
}

View File

@@ -1,7 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type HardwareRequirements = {
device: { [key: string]: string }
device: { device?: string; processor?: string }
ram: number | null
arch: string[] | null
}

View File

@@ -0,0 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackageId } from "./PackageId"
import type { Version } from "./Version"
export type InstallParams = {
registry: string
id: PackageId
version: Version
}

View File

@@ -13,6 +13,7 @@ export type Manifest = {
id: PackageId
title: string
version: Version
satisfies: Array<Version>
releaseNotes: string
license: string
wrapperRepo: string

View File

@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PackageDetailLevel = "short" | "full"
export type PackageDetailLevel = "none" | "short" | "full"

View File

@@ -1,8 +1,11 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Alerts } from "./Alerts"
import type { DataUrl } from "./DataUrl"
import type { DependencyMetadata } from "./DependencyMetadata"
import type { Description } from "./Description"
import type { HardwareRequirements } from "./HardwareRequirements"
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
import type { PackageId } from "./PackageId"
import type { RegistryAsset } from "./RegistryAsset"
export type PackageVersionInfo = {
@@ -16,6 +19,9 @@ export type PackageVersionInfo = {
upstreamRepo: string
supportSite: string
marketingSite: string
donationUrl: string | null
alerts: Alerts
dependencyMetadata: { [key: PackageId]: DependencyMetadata }
osVersion: string
hardwareRequirements: HardwareRequirements
sourceVersion: string | null

View File

@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PathOrUrl = string

View File

@@ -3,6 +3,7 @@ import type { AnySignature } from "./AnySignature"
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
export type RegistryAsset<Commitment> = {
publishedAt: string
url: string
commitment: Commitment
signatures: { [key: AnyVerifyingKey]: AnySignature }

View File

@@ -0,0 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Category } from "./Category"
import type { DataUrl } from "./DataUrl"
export type RegistryInfo = {
name: string | null
icon: DataUrl | null
categories: { [key: string]: Category }
}

View File

@@ -37,6 +37,7 @@ export { CurrentDependencyInfo } from "./CurrentDependencyInfo"
export { DataUrl } from "./DataUrl"
export { Dependencies } from "./Dependencies"
export { DependencyKind } from "./DependencyKind"
export { DependencyMetadata } from "./DependencyMetadata"
export { DependencyRequirement } from "./DependencyRequirement"
export { DepInfo } from "./DepInfo"
export { Description } from "./Description"
@@ -51,6 +52,7 @@ export { FullIndex } from "./FullIndex"
export { FullProgress } from "./FullProgress"
export { GetHostInfoParams } from "./GetHostInfoParams"
export { GetOsAssetParams } from "./GetOsAssetParams"
export { GetOsVersionParams } from "./GetOsVersionParams"
export { GetPackageParams } from "./GetPackageParams"
export { GetPackageResponseFull } from "./GetPackageResponseFull"
export { GetPackageResponse } from "./GetPackageResponse"
@@ -61,7 +63,6 @@ export { GetSslCertificateParams } from "./GetSslCertificateParams"
export { GetSslKeyParams } from "./GetSslKeyParams"
export { GetStoreParams } from "./GetStoreParams"
export { GetSystemSmtpParams } from "./GetSystemSmtpParams"
export { GetVersionParams } from "./GetVersionParams"
export { Governor } from "./Governor"
export { Guid } from "./Guid"
export { HardwareRequirements } from "./HardwareRequirements"
@@ -82,6 +83,7 @@ export { InstalledState } from "./InstalledState"
export { InstalledVersionParams } from "./InstalledVersionParams"
export { InstallingInfo } from "./InstallingInfo"
export { InstallingState } from "./InstallingState"
export { InstallParams } from "./InstallParams"
export { IpHostname } from "./IpHostname"
export { IpInfo } from "./IpInfo"
export { LanInfo } from "./LanInfo"
@@ -109,11 +111,13 @@ export { PackageVersionInfo } from "./PackageVersionInfo"
export { ParamsMaybePackageId } from "./ParamsMaybePackageId"
export { ParamsPackageId } from "./ParamsPackageId"
export { PasswordType } from "./PasswordType"
export { PathOrUrl } from "./PathOrUrl"
export { ProcedureId } from "./ProcedureId"
export { Progress } from "./Progress"
export { Public } from "./Public"
export { RecoverySource } from "./RecoverySource"
export { RegistryAsset } from "./RegistryAsset"
export { RegistryInfo } from "./RegistryInfo"
export { RemoveActionParams } from "./RemoveActionParams"
export { RemoveAddressParams } from "./RemoveAddressParams"
export { RemoveVersionParams } from "./RemoveVersionParams"

View File

@@ -369,7 +369,7 @@ describe("values", () => {
setupManifest({
id: "testOutput",
title: "",
version: "1.0",
version: "1.0.0:0",
releaseNotes: "",
license: "",
replaces: [],
@@ -395,9 +395,10 @@ describe("values", () => {
stop: null,
},
dependencies: {
remoteTest: {
"remote-test": {
description: "",
optional: true,
s9pk: "https://example.com/remote-test.s9pk",
},
},
}),

View File

@@ -1,253 +0,0 @@
import { EmVer, notRange, rangeAnd, rangeOf, rangeOr } from "../emverLite/mod"
describe("EmVer", () => {
{
{
const checker = rangeOf("*")
test("rangeOf('*')", () => {
checker.check("1")
checker.check("1.2")
checker.check("1.2.3")
checker.check("1.2.3.4")
checker.check("1.2.3.4.5")
checker.check("1.2.3.4.5.6")
expect(checker.check("1")).toEqual(true)
expect(checker.check("1.2")).toEqual(true)
expect(checker.check("1.2.3.4")).toEqual(true)
})
test("rangeOf('*') invalid", () => {
expect(() => checker.check("a")).toThrow()
expect(() => checker.check("")).toThrow()
expect(() => checker.check("1..3")).toThrow()
})
}
{
const checker = rangeOf(">1.2.3.4")
test(`rangeOf(">1.2.3.4") valid`, () => {
expect(checker.check("2-beta123")).toEqual(true)
expect(checker.check("2")).toEqual(true)
expect(checker.check("1.2.3.5")).toEqual(true)
expect(checker.check("1.2.3.4.1")).toEqual(true)
})
test(`rangeOf(">1.2.3.4") invalid`, () => {
expect(checker.check("1.2.3.4")).toEqual(false)
expect(checker.check("1.2.3")).toEqual(false)
expect(checker.check("1")).toEqual(false)
})
}
{
const checker = rangeOf("=1.2.3")
test(`rangeOf("=1.2.3") valid`, () => {
expect(checker.check("1.2.3")).toEqual(true)
})
test(`rangeOf("=1.2.3") invalid`, () => {
expect(checker.check("2")).toEqual(false)
expect(checker.check("1.2.3.1")).toEqual(false)
expect(checker.check("1.2")).toEqual(false)
})
}
{
const checker = rangeOf(">=1.2.3.4")
test(`rangeOf(">=1.2.3.4") valid`, () => {
expect(checker.check("2")).toEqual(true)
expect(checker.check("1.2.3.5")).toEqual(true)
expect(checker.check("1.2.3.4.1")).toEqual(true)
expect(checker.check("1.2.3.4")).toEqual(true)
})
test(`rangeOf(">=1.2.3.4") invalid`, () => {
expect(checker.check("1.2.3")).toEqual(false)
expect(checker.check("1")).toEqual(false)
})
}
{
const checker = rangeOf("<1.2.3.4")
test(`rangeOf("<1.2.3.4") invalid`, () => {
expect(checker.check("2")).toEqual(false)
expect(checker.check("1.2.3.5")).toEqual(false)
expect(checker.check("1.2.3.4.1")).toEqual(false)
expect(checker.check("1.2.3.4")).toEqual(false)
})
test(`rangeOf("<1.2.3.4") valid`, () => {
expect(checker.check("1.2.3")).toEqual(true)
expect(checker.check("1")).toEqual(true)
})
}
{
const checker = rangeOf("<=1.2.3.4")
test(`rangeOf("<=1.2.3.4") invalid`, () => {
expect(checker.check("2")).toEqual(false)
expect(checker.check("1.2.3.5")).toEqual(false)
expect(checker.check("1.2.3.4.1")).toEqual(false)
})
test(`rangeOf("<=1.2.3.4") valid`, () => {
expect(checker.check("1.2.3")).toEqual(true)
expect(checker.check("1")).toEqual(true)
expect(checker.check("1.2.3.4")).toEqual(true)
})
}
{
const checkA = rangeOf(">1")
const checkB = rangeOf("<=2")
const checker = rangeAnd(checkA, checkB)
test(`simple and(checkers) valid`, () => {
expect(checker.check("2")).toEqual(true)
expect(checker.check("1.1")).toEqual(true)
})
test(`simple and(checkers) invalid`, () => {
expect(checker.check("2.1")).toEqual(false)
expect(checker.check("1")).toEqual(false)
expect(checker.check("0")).toEqual(false)
})
}
{
const checkA = rangeOf("<1")
const checkB = rangeOf("=2")
const checker = rangeOr(checkA, checkB)
test(`simple or(checkers) valid`, () => {
expect(checker.check("2")).toEqual(true)
expect(checker.check("0.1")).toEqual(true)
})
test(`simple or(checkers) invalid`, () => {
expect(checker.check("2.1")).toEqual(false)
expect(checker.check("1")).toEqual(false)
expect(checker.check("1.1")).toEqual(false)
})
}
{
const checker = rangeOf("1.2.*")
test(`rangeOf(1.2.*) valid`, () => {
expect(checker.check("1.2")).toEqual(true)
expect(checker.check("1.2.1")).toEqual(true)
})
test(`rangeOf(1.2.*) invalid`, () => {
expect(checker.check("1.3")).toEqual(false)
expect(checker.check("1.3.1")).toEqual(false)
expect(checker.check("1.1.1")).toEqual(false)
expect(checker.check("1.1")).toEqual(false)
expect(checker.check("1")).toEqual(false)
expect(checker.check("2")).toEqual(false)
})
}
{
const checker = notRange(rangeOf("1.2.*"))
test(`notRange(rangeOf(1.2.*)) valid`, () => {
expect(checker.check("1.3")).toEqual(true)
expect(checker.check("1.3.1")).toEqual(true)
expect(checker.check("1.1.1")).toEqual(true)
expect(checker.check("1.1")).toEqual(true)
expect(checker.check("1")).toEqual(true)
expect(checker.check("2")).toEqual(true)
})
test(`notRange(rangeOf(1.2.*)) invalid `, () => {
expect(checker.check("1.2")).toEqual(false)
expect(checker.check("1.2.1")).toEqual(false)
})
}
{
const checker = rangeOf("!1.2.*")
test(`!(rangeOf(1.2.*)) valid`, () => {
expect(checker.check("1.3")).toEqual(true)
expect(checker.check("1.3.1")).toEqual(true)
expect(checker.check("1.1.1")).toEqual(true)
expect(checker.check("1.1")).toEqual(true)
expect(checker.check("1")).toEqual(true)
expect(checker.check("2")).toEqual(true)
})
test(`!(rangeOf(1.2.*)) invalid `, () => {
expect(checker.check("1.2")).toEqual(false)
expect(checker.check("1.2.1")).toEqual(false)
})
}
{
test(`no and ranges`, () => {
expect(() => rangeAnd()).toThrow()
})
test(`no or ranges`, () => {
expect(() => rangeOr()).toThrow()
})
}
{
const checker = rangeOf("!>1.2.3.4")
test(`rangeOf("!>1.2.3.4") invalid`, () => {
expect(checker.check("2")).toEqual(false)
expect(checker.check("1.2.3.5")).toEqual(false)
expect(checker.check("1.2.3.4.1")).toEqual(false)
})
test(`rangeOf("!>1.2.3.4") valid`, () => {
expect(checker.check("1.2.3.4")).toEqual(true)
expect(checker.check("1.2.3")).toEqual(true)
expect(checker.check("1")).toEqual(true)
})
}
{
test(">1 && =1.2", () => {
const checker = rangeOf(">1 && =1.2")
expect(checker.check("1.2")).toEqual(true)
expect(checker.check("1.2.1")).toEqual(false)
})
test("=1 || =2", () => {
const checker = rangeOf("=1 || =2")
expect(checker.check("1")).toEqual(true)
expect(checker.check("2")).toEqual(true)
expect(checker.check("3")).toEqual(false)
})
test(">1 && =1.2 || =2", () => {
const checker = rangeOf(">1 && =1.2 || =2")
expect(checker.check("1.2")).toEqual(true)
expect(checker.check("1")).toEqual(false)
expect(checker.check("2")).toEqual(true)
expect(checker.check("3")).toEqual(false)
})
test("&& before || order of operationns: <1.5 && >1 || >1.5 && <3", () => {
const checker = rangeOf("<1.5 && >1 || >1.5 && <3")
expect(checker.check("1.1")).toEqual(true)
expect(checker.check("2")).toEqual(true)
expect(checker.check("1.5")).toEqual(false)
expect(checker.check("1")).toEqual(false)
expect(checker.check("3")).toEqual(false)
})
test("Compare function on the emver", () => {
const a = EmVer.from("1.2.3")
const b = EmVer.from("1.2.4")
expect(a.compare(b)).toEqual("less")
expect(b.compare(a)).toEqual("greater")
expect(a.compare(a)).toEqual("equal")
})
test("Compare for sort function on the emver", () => {
const a = EmVer.from("1.2.3")
const b = EmVer.from("1.2.4")
expect(a.compareForSort(b)).toEqual(-1)
expect(b.compareForSort(a)).toEqual(1)
expect(a.compareForSort(a)).toEqual(0)
})
}
}
})

View File

@@ -0,0 +1,355 @@
import { VersionRange, ExtendedVersion } from "../exver"
describe("ExVer", () => {
{
{
const checker = VersionRange.parse("*")
test("VersionRange.parse('*')", () => {
checker.satisfiedBy(ExtendedVersion.parse("1:0"))
checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.5"))
checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.5.6"))
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
})
test("VersionRange.parse('*') invalid", () => {
expect(() => checker.satisfiedBy(ExtendedVersion.parse("a"))).toThrow()
expect(() => checker.satisfiedBy(ExtendedVersion.parse(""))).toThrow()
expect(() =>
checker.satisfiedBy(ExtendedVersion.parse("1..3")),
).toThrow()
})
}
{
const checker = VersionRange.parse(">1.2.3:4")
test(`VersionRange.parse(">1.2.3:4") valid`, () => {
expect(
checker.satisfiedBy(ExtendedVersion.parse("2-beta.123:0")),
).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
true,
)
})
test(`VersionRange.parse(">1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
})
}
{
const checker = VersionRange.parse("=1.2.3")
test(`VersionRange.parse("=1.2.3") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
})
test(`VersionRange.parse("=1.2.3") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:1"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse(">=1.2.3:4")
test(`VersionRange.parse(">=1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
})
test(`VersionRange.parse(">=1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
})
}
{
const checker = VersionRange.parse("<1.2.3:4")
test(`VersionRange.parse("<1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
false,
)
})
test(`VersionRange.parse("<1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
})
}
{
const checker = VersionRange.parse("<=1.2.3:4")
test(`VersionRange.parse("<=1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
false,
)
})
test(`VersionRange.parse("<=1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
})
}
{
const checkA = VersionRange.parse(">1")
const checkB = VersionRange.parse("<=2")
const checker = checkA.and(checkB)
test(`simple and(checkers) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
})
test(`simple and(checkers) invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
})
}
{
const checkA = VersionRange.parse("<1")
const checkB = VersionRange.parse("=2")
const checker = checkA.or(checkB)
test(`simple or(checkers) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("0.1:0"))).toEqual(
true,
)
})
test(`simple or(checkers) invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse("~1.2")
test(`VersionRange.parse(~1.2) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
true,
)
})
test(`VersionRange.parse(~1.2) invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
})
}
{
const checker = VersionRange.parse("~1.2").not()
test(`VersionRange.parse(~1.2).not() valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
})
test(`VersionRange.parse(~1.2).not() invalid `, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse("!~1.2")
test(`!(VersionRange.parse(~1.2)) valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.3.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
})
test(`!(VersionRange.parse(~1.2)) invalid `, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
false,
)
})
}
{
const checker = VersionRange.parse("!>1.2.3:4")
test(`VersionRange.parse("!>1.2.3:4") invalid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:5"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4.1"))).toEqual(
false,
)
})
test(`VersionRange.parse("!>1.2.3:4") valid`, () => {
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:4"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.3:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
})
}
{
test(">1 && =1.2", () => {
const checker = VersionRange.parse(">1 && =1.2")
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2.1:0"))).toEqual(
false,
)
})
test("=1 || =2", () => {
const checker = VersionRange.parse("=1 || =2")
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
})
test(">1 && =1.2 || =2", () => {
const checker = VersionRange.parse(">1 && =1.2 || =2")
expect(checker.satisfiedBy(ExtendedVersion.parse("1.2:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
})
test("&& before || order of operationns: <1.5 && >1 || >1.5 && <3", () => {
const checker = VersionRange.parse("<1.5 && >1 || >1.5 && <3")
expect(checker.satisfiedBy(ExtendedVersion.parse("1.1:0"))).toEqual(
true,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("2:0"))).toEqual(true)
expect(checker.satisfiedBy(ExtendedVersion.parse("1.5:0"))).toEqual(
false,
)
expect(checker.satisfiedBy(ExtendedVersion.parse("1:0"))).toEqual(false)
expect(checker.satisfiedBy(ExtendedVersion.parse("3:0"))).toEqual(false)
})
test("Compare function on the emver", () => {
const a = ExtendedVersion.parse("1.2.3:0")
const b = ExtendedVersion.parse("1.2.4:0")
expect(a.compare(b)).toEqual("less")
expect(b.compare(a)).toEqual("greater")
expect(a.compare(a)).toEqual("equal")
})
test("Compare for sort function on the emver", () => {
const a = ExtendedVersion.parse("1.2.3:0")
const b = ExtendedVersion.parse("1.2.4:0")
expect(a.compareForSort(b)).toEqual(-1)
expect(b.compareForSort(a)).toEqual(1)
expect(a.compareForSort(a)).toEqual(0)
})
}
}
})

View File

@@ -7,7 +7,7 @@ export const sdk = StartSdk.of()
setupManifest({
id: "testOutput",
title: "",
version: "1.0",
version: "1.0:0",
releaseNotes: "",
license: "",
replaces: [],
@@ -33,9 +33,10 @@ export const sdk = StartSdk.of()
stop: null,
},
dependencies: {
remoteTest: {
"remote-test": {
description: "",
optional: false,
s9pk: "https://example.com/remote-test.s9pk",
},
},
}),

View File

@@ -21,7 +21,7 @@ describe("setupDependencyConfig", () => {
dependencyConfig: async ({}) => {},
})
sdk.setupDependencyConfig(testConfig, {
remoteTest,
"remote-test": remoteTest,
})
})
})

View File

@@ -1,42 +0,0 @@
import { getHostname } from "../util/getServiceInterface"
import { splitCommand } from "../util/splitCommand"
describe("splitCommand ", () => {
const inputToExpected = [
["cat", ["cat"]],
[["cat"], ["cat"]],
[
["cat", "hello all my homies"],
["cat", "hello all my homies"],
],
["cat hello world", ["cat", "hello", "world"]],
["cat hello 'big world'", ["cat", "hello", "big world"]],
[`cat hello "big world"`, ["cat", "hello", "big world"]],
[
`cat hello "big world's are the greatest"`,
["cat", "hello", "big world's are the greatest"],
],
// Too many spaces
["cat ", ["cat"]],
[["cat "], ["cat "]],
[
["cat ", "hello all my homies "],
["cat ", "hello all my homies "],
],
["cat hello world ", ["cat", "hello", "world"]],
[
" cat hello 'big world' ",
["cat", "hello", "big world"],
],
[
` cat hello "big world" `,
["cat", "hello", "big world"],
],
]
for (const [input, expectValue] of inputToExpected) {
test(`should return ${expectValue} for ${input}`, () => {
expect(splitCommand(input as any)).toEqual(expectValue)
})
}
})

View File

@@ -12,6 +12,7 @@ import {
LanInfo,
BindParams,
Manifest,
CheckDependenciesResult,
} from "./osBindings"
import { MainEffects, ServiceInterfaceType, Signals } from "./StartSdk"
@@ -154,14 +155,6 @@ export type DependencyConfig = {
}): Promise<unknown>
}
export type ValidIfNoStupidEscape<A> = A extends
| `${string}'"'"'${string}`
| `${string}\\"${string}`
? never
: "" extends A & ""
? never
: A
export type ConfigRes = {
/** This should be the previous config, that way during set config we start with the previous */
config?: null | Record<string, unknown>
@@ -188,9 +181,7 @@ export type SmtpValue = {
password: string | null | undefined
}
export type CommandType<A extends string> =
| ValidIfNoStupidEscape<A>
| [string, ...string[]]
export type CommandType = string | [string, ...string[]]
export type DaemonReturned = {
wait(): Promise<unknown>
@@ -470,7 +461,7 @@ export type Effects = {
*/
checkDependencies(options: {
packageIds: PackageId[] | null
}): Promise<CheckDependencyResult[]>
}): Promise<CheckDependenciesResult[]>
/** Exists could be useful during the runtime to know if some service exists, option dep */
exists(options: { packageId: PackageId }): Promise<boolean>
/** Exists could be useful during the runtime to know if some service is running, option dep */
@@ -554,12 +545,3 @@ export type Dependencies = Array<DependencyRequirement>
export type DeepPartial<T> = T extends {}
? { [P in keyof T]?: DeepPartial<T[P]> }
: T
export type CheckDependencyResult = {
packageId: PackageId
isInstalled: boolean
isRunning: boolean
healthChecks: SetHealth[]
version: string | null
}
export type CheckResults = CheckDependencyResult[]

View File

@@ -1,17 +1,8 @@
import { arrayOf, string } from "ts-matches"
import { ValidIfNoStupidEscape } from "../types"
export const splitCommand = (
command: string | [string, ...string[]],
): string[] => {
if (arrayOf(string).test(command)) return command
return String(command)
.split('"')
.flatMap((x, i) =>
i % 2 !== 0
? [x]
: x.split("'").flatMap((x, i) => (i % 2 !== 0 ? [x] : x.split(" "))),
)
.map((x) => x.trim())
.filter(Boolean)
return ["sh", "-c", command]
}