mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
Merge branch 'next/minor' of github.com:Start9Labs/start-os into next/major
This commit is contained in:
1
sdk/.prettierignore
Normal file
1
sdk/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
/lib/exver/exver.ts
|
||||
38
sdk/Makefile
38
sdk/Makefile
@@ -1,19 +1,28 @@
|
||||
TS_FILES := $(shell find ./**/*.ts )
|
||||
TS_FILES := $(shell git ls-files lib) lib/test/output.ts
|
||||
version = $(shell git tag --sort=committerdate | tail -1)
|
||||
|
||||
.PHONY: test clean bundle fmt buildOutput check
|
||||
|
||||
all: bundle
|
||||
|
||||
test: $(TS_FILES) lib/test/output.ts
|
||||
npm test
|
||||
|
||||
clean:
|
||||
rm -rf dist/* | true
|
||||
rm -rf dist
|
||||
rm -f lib/test/output.ts
|
||||
rm -rf node_modules
|
||||
|
||||
lib/test/output.ts: lib/test/makeOutput.ts scripts/oldSpecToBuilder.ts
|
||||
lib/test/output.ts: node_modules lib/test/makeOutput.ts scripts/oldSpecToBuilder.ts
|
||||
npm run buildOutput
|
||||
|
||||
buildOutput: lib/test/output.ts fmt
|
||||
echo 'done'
|
||||
bundle: dist | test fmt
|
||||
touch dist
|
||||
|
||||
lib/exver/exver.ts: node_modules lib/exver/exver.pegjs
|
||||
npx peggy --allowed-start-rules '*' --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs
|
||||
|
||||
bundle: $(TS_FILES) package.json .FORCE node_modules test fmt
|
||||
dist: $(TS_FILES) package.json node_modules README.md LICENSE
|
||||
npx tsc
|
||||
npx tsc --project tsconfig-cjs.json
|
||||
cp package.json dist/package.json
|
||||
@@ -21,24 +30,19 @@ bundle: $(TS_FILES) package.json .FORCE node_modules test fmt
|
||||
cp LICENSE dist/LICENSE
|
||||
touch dist
|
||||
|
||||
full-bundle:
|
||||
make clean
|
||||
make bundle
|
||||
full-bundle: bundle
|
||||
|
||||
check:
|
||||
npm run check
|
||||
|
||||
fmt: node_modules
|
||||
npx prettier --write "**/*.ts"
|
||||
npx prettier . "**/*.ts" --write
|
||||
|
||||
node_modules: package.json
|
||||
npm install
|
||||
npm ci
|
||||
|
||||
publish: clean bundle package.json README.md LICENSE
|
||||
publish: bundle package.json README.md LICENSE
|
||||
cd dist && npm publish --access=public
|
||||
link: bundle
|
||||
cp package.json dist/package.json
|
||||
cp README.md dist/README.md
|
||||
cp LICENSE dist/LICENSE
|
||||
|
||||
link: bundle
|
||||
cd dist && npm link
|
||||
.FORCE:
|
||||
|
||||
@@ -5,4 +5,4 @@ module.exports = {
|
||||
testEnvironment: "node",
|
||||
rootDir: "./lib/",
|
||||
modulePathIgnorePatterns: ["./dist/"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
) {}
|
||||
|
||||
@@ -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,21 +20,20 @@ import {
|
||||
MaybePromise,
|
||||
ServiceInterfaceId,
|
||||
PackageId,
|
||||
ValidIfNoStupidEscape,
|
||||
} from "./types"
|
||||
import * as patterns from "./util/patterns"
|
||||
import { DependencyConfig, Update } from "./dependencyConfig/DependencyConfig"
|
||||
import { DependencyConfig, Update } from "./dependencies/DependencyConfig"
|
||||
import { BackupSet, Backups } from "./backup/Backups"
|
||||
import { smtpConfig } from "./config/configConstants"
|
||||
import { Daemons } from "./mainFn/Daemons"
|
||||
import { healthCheck } from "./health/HealthCheck"
|
||||
import { healthCheck, HealthCheckParams } from "./health/HealthCheck"
|
||||
import { checkPortListening } from "./health/checkFns/checkPortListening"
|
||||
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
||||
import { List } from "./config/builder/list"
|
||||
import { Migration } from "./inits/migrations/Migration"
|
||||
import { Install, InstallFn } from "./inits/setupInstall"
|
||||
import { setupActions } from "./actions/setupActions"
|
||||
import { setupDependencyConfig } from "./dependencyConfig/setupDependencyConfig"
|
||||
import { setupDependencyConfig } from "./dependencies/setupDependencyConfig"
|
||||
import { SetupBackupsParams, setupBackups } from "./backup/setupBackups"
|
||||
import { setupInit } from "./inits/setupInit"
|
||||
import {
|
||||
@@ -59,7 +57,7 @@ import {
|
||||
} from "./interfaces/setupInterfaces"
|
||||
import { successFailure } from "./trigger/successFailure"
|
||||
import { HealthReceipt } from "./health/HealthReceipt"
|
||||
import { MultiHost, Scheme, SingleHost, StaticHost } from "./interfaces/Host"
|
||||
import { MultiHost, Scheme } from "./interfaces/Host"
|
||||
import { ServiceInterfaceBuilder } from "./interfaces/ServiceInterfaceBuilder"
|
||||
import { GetSystemSmtp } from "./util/GetSystemSmtp"
|
||||
import nullIfEmpty from "./util/nullIfEmpty"
|
||||
@@ -74,9 +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 "."
|
||||
import { GetSslCertificate } from "./util/GetSslCertificate"
|
||||
|
||||
export const SDKVersion = testTypeVersion("0.3.6")
|
||||
|
||||
// prettier-ignore
|
||||
type AnyNeverCond<T extends any[], Then, Else> =
|
||||
@@ -86,22 +89,37 @@ type AnyNeverCond<T extends any[], Then, Else> =
|
||||
never
|
||||
|
||||
export type ServiceInterfaceType = "ui" | "p2p" | "api"
|
||||
export type MainEffects = Effects & { _type: "main" }
|
||||
export type MainEffects = Effects & {
|
||||
_type: "main"
|
||||
clearCallbacks: () => Promise<void>
|
||||
}
|
||||
export type Signals = NodeJS.Signals
|
||||
export const SIGTERM: Signals = "SIGTERM"
|
||||
export const SIGKILL: Signals = "SIGTERM"
|
||||
export const SIGKILL: Signals = "SIGKILL"
|
||||
export const NO_TIMEOUT = -1
|
||||
|
||||
function removeConstType<E>() {
|
||||
return <T>(t: T) => t as T & (E extends MainEffects ? {} : { const: never })
|
||||
function removeCallbackTypes<E extends Effects>(effects: E) {
|
||||
return <T extends object>(t: T) => {
|
||||
if ("_type" in effects && effects._type === "main") {
|
||||
return t as E extends MainEffects ? T : Omit<T, "const" | "watch">
|
||||
} else {
|
||||
if ("const" in t) {
|
||||
delete t.const
|
||||
}
|
||||
if ("watch" in t) {
|
||||
delete t.watch
|
||||
}
|
||||
return t as E extends MainEffects ? T : Omit<T, "const" | "watch">
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>>() {
|
||||
@@ -124,28 +142,26 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
}
|
||||
|
||||
return {
|
||||
checkAllDependencies,
|
||||
serviceInterface: {
|
||||
getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) =>
|
||||
removeConstType<E>()(
|
||||
removeCallbackTypes<E>(effects)(
|
||||
getServiceInterface(effects, {
|
||||
id,
|
||||
packageId: null,
|
||||
}),
|
||||
),
|
||||
get: <E extends Effects>(
|
||||
effects: E,
|
||||
opts: { id: ServiceInterfaceId; packageId: PackageId },
|
||||
) => removeConstType<E>()(getServiceInterface(effects, opts)),
|
||||
) =>
|
||||
removeCallbackTypes<E>(effects)(getServiceInterface(effects, opts)),
|
||||
getAllOwn: <E extends Effects>(effects: E) =>
|
||||
removeConstType<E>()(
|
||||
getServiceInterfaces(effects, {
|
||||
packageId: null,
|
||||
}),
|
||||
),
|
||||
removeCallbackTypes<E>(effects)(getServiceInterfaces(effects, {})),
|
||||
getAll: <E extends Effects>(
|
||||
effects: E,
|
||||
opts: { packageId: PackageId },
|
||||
) => removeConstType<E>()(getServiceInterfaces(effects, opts)),
|
||||
) =>
|
||||
removeCallbackTypes<E>(effects)(getServiceInterfaces(effects, opts)),
|
||||
},
|
||||
|
||||
store: {
|
||||
@@ -154,7 +170,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
packageId: string,
|
||||
path: PathBuilder<Store, StoreValue>,
|
||||
) =>
|
||||
removeConstType<E>()(
|
||||
removeCallbackTypes<E>(effects)(
|
||||
getStore<Store, StoreValue>(effects, path, {
|
||||
packageId,
|
||||
}),
|
||||
@@ -162,7 +178,10 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
getOwn: <E extends Effects, StoreValue = unknown>(
|
||||
effects: E,
|
||||
path: PathBuilder<Store, StoreValue>,
|
||||
) => removeConstType<E>()(getStore<Store, StoreValue>(effects, path)),
|
||||
) =>
|
||||
removeCallbackTypes<E>(effects)(
|
||||
getStore<Store, StoreValue>(effects, path),
|
||||
),
|
||||
setOwn: <E extends Effects, Path extends PathBuilder<Store, unknown>>(
|
||||
effects: E,
|
||||
path: Path,
|
||||
@@ -175,22 +194,25 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
},
|
||||
|
||||
host: {
|
||||
static: (effects: Effects, id: string) =>
|
||||
new StaticHost({ id, effects }),
|
||||
single: (effects: Effects, id: string) =>
|
||||
new SingleHost({ id, effects }),
|
||||
// static: (effects: Effects, id: string) =>
|
||||
// new StaticHost({ id, effects }),
|
||||
// single: (effects: Effects, id: string) =>
|
||||
// new SingleHost({ id, effects }),
|
||||
multi: (effects: Effects, id: string) => new MultiHost({ id, effects }),
|
||||
},
|
||||
nullIfEmpty,
|
||||
runCommand: async <A extends string>(
|
||||
effects: Effects,
|
||||
imageId: Manifest["images"][number],
|
||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||
image: {
|
||||
id: keyof Manifest["images"] & T.ImageId
|
||||
sharedRun?: boolean
|
||||
},
|
||||
command: T.CommandType,
|
||||
options: CommandOptions & {
|
||||
mounts?: { path: string; options: MountOptions }[]
|
||||
},
|
||||
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
|
||||
return runCommand<Manifest>(effects, imageId, command, options)
|
||||
return runCommand<Manifest>(effects, image, command, options)
|
||||
},
|
||||
|
||||
createAction: <
|
||||
@@ -235,7 +257,16 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
},
|
||||
) => new ServiceInterfaceBuilder({ ...options, effects }),
|
||||
getSystemSmtp: <E extends Effects>(effects: E) =>
|
||||
removeConstType<E>()(new GetSystemSmtp(effects)),
|
||||
removeCallbackTypes<E>(effects)(new GetSystemSmtp(effects)),
|
||||
|
||||
getSslCerificate: <E extends Effects>(
|
||||
effects: E,
|
||||
hostnames: string[],
|
||||
algorithm?: T.Algorithm,
|
||||
) =>
|
||||
removeCallbackTypes<E>(effects)(
|
||||
new GetSslCertificate(effects, hostnames, algorithm),
|
||||
),
|
||||
|
||||
createDynamicAction: <
|
||||
ConfigType extends
|
||||
@@ -262,7 +293,9 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
)
|
||||
},
|
||||
HealthCheck: {
|
||||
of: healthCheck,
|
||||
of(o: HealthCheckParams<Manifest>) {
|
||||
return healthCheck<Manifest>(o)
|
||||
},
|
||||
},
|
||||
Dependency: {
|
||||
of(data: Dependency["data"]) {
|
||||
@@ -284,7 +317,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||
>(
|
||||
spec: ConfigType,
|
||||
write: Save<Store, Type, Manifest>,
|
||||
write: Save<Type>,
|
||||
read: Read<Manifest, Store, Type>,
|
||||
) => setupConfig<Store, ConfigType, Manifest, Type>(spec, write, read),
|
||||
setupConfigRead: <
|
||||
@@ -301,7 +334,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
| Config<Record<string, never>, never>,
|
||||
>(
|
||||
_configSpec: ConfigSpec,
|
||||
fn: Save<Store, ConfigSpec, Manifest>,
|
||||
fn: Save<ConfigSpec>,
|
||||
) => fn,
|
||||
setupDependencyConfig: <Input extends Record<string, any>>(
|
||||
config: Config<Input, Store> | Config<Input, never>,
|
||||
@@ -327,7 +360,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
([
|
||||
id,
|
||||
{
|
||||
data: { versionSpec, ...x },
|
||||
data: { versionRange, ...x },
|
||||
},
|
||||
]) => ({
|
||||
id,
|
||||
@@ -340,7 +373,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
: {
|
||||
kind: "exists",
|
||||
}),
|
||||
versionSpec: versionSpec.range,
|
||||
versionRange: versionRange.toString(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
@@ -391,7 +424,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
setupProperties:
|
||||
(
|
||||
fn: (options: { effects: Effects }) => Promise<T.SdkPropertiesReturn>,
|
||||
): T.ExpectedExports.Properties =>
|
||||
): T.ExpectedExports.properties =>
|
||||
(options) =>
|
||||
fn(options).then(nullifyProperties),
|
||||
setupUninstall: (fn: UninstallFn<Manifest, Store>) =>
|
||||
@@ -424,9 +457,6 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
spec: Spec,
|
||||
) => Config.of<Spec, Store>(spec),
|
||||
},
|
||||
Checker: {
|
||||
parse: Checker.parse,
|
||||
},
|
||||
Daemons: {
|
||||
of(config: {
|
||||
effects: Effects
|
||||
@@ -466,13 +496,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
>(dependencyConfig, update)
|
||||
},
|
||||
},
|
||||
EmVer: {
|
||||
from: EmVer.from,
|
||||
parse: EmVer.parse,
|
||||
},
|
||||
List: {
|
||||
text: List.text,
|
||||
number: List.number,
|
||||
obj: <Type extends Record<string, any>>(
|
||||
a: {
|
||||
name: string
|
||||
@@ -515,33 +540,10 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
}
|
||||
>,
|
||||
) => List.dynamicText<Store>(getA),
|
||||
dynamicNumber: (
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
/** Default = [] */
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string
|
||||
spec: {
|
||||
integer: boolean
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
}
|
||||
}
|
||||
>,
|
||||
) => List.dynamicNumber<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),
|
||||
@@ -736,16 +738,16 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function runCommand<Manifest extends SDKManifest>(
|
||||
export async function runCommand<Manifest extends T.Manifest>(
|
||||
effects: Effects,
|
||||
imageId: Manifest["images"][number],
|
||||
image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean },
|
||||
command: string | [string, ...string[]],
|
||||
options: CommandOptions & {
|
||||
mounts?: { path: string; options: MountOptions }[]
|
||||
},
|
||||
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> {
|
||||
const commands = splitCommand(command)
|
||||
const overlay = await Overlay.of(effects, imageId)
|
||||
const overlay = await Overlay.of(effects, image)
|
||||
try {
|
||||
for (let mount of options.mounts || []) {
|
||||
await overlay.mount(mount.options, mount.path)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||
import * as T from "../types"
|
||||
|
||||
import * as child_process from "child_process"
|
||||
import { promises as fsPromises } from "fs"
|
||||
|
||||
export type BACKUP = "BACKUP"
|
||||
export const DEFAULT_OPTIONS: T.BackupOptions = {
|
||||
delete: true,
|
||||
@@ -37,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(
|
||||
@@ -56,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 })
|
||||
@@ -91,58 +93,22 @@ export class Backups<M extends SDKManifest> {
|
||||
)
|
||||
return this
|
||||
}
|
||||
build() {
|
||||
build(pathMaker: T.PathMaker) {
|
||||
const createBackup: T.ExpectedExports.createBackup = async ({
|
||||
effects,
|
||||
}) => {
|
||||
// const previousItems = (
|
||||
// await effects
|
||||
// .readDir({
|
||||
// volumeId: Backups.BACKUP,
|
||||
// path: ".",
|
||||
// })
|
||||
// .catch(() => [])
|
||||
// ).map((x) => `${x}`)
|
||||
// const backupPaths = this.backupSet
|
||||
// .filter((x) => x.dstVolume === Backups.BACKUP)
|
||||
// .map((x) => x.dstPath)
|
||||
// .map((x) => x.replace(/\.\/([^]*)\//, "$1"))
|
||||
// const filteredItems = previousItems.filter(
|
||||
// (x) => backupPaths.indexOf(x) === -1,
|
||||
// )
|
||||
// for (const itemToRemove of filteredItems) {
|
||||
// effects.console.error(`Trying to remove ${itemToRemove}`)
|
||||
// await effects
|
||||
// .removeDir({
|
||||
// volumeId: Backups.BACKUP,
|
||||
// path: itemToRemove,
|
||||
// })
|
||||
// .catch(() =>
|
||||
// effects.removeFile({
|
||||
// volumeId: Backups.BACKUP,
|
||||
// path: itemToRemove,
|
||||
// }),
|
||||
// )
|
||||
// .catch(() => {
|
||||
// console.warn(`Failed to remove ${itemToRemove} from backup volume`)
|
||||
// })
|
||||
// }
|
||||
for (const item of this.backupSet) {
|
||||
// if (notEmptyPath(item.dstPath)) {
|
||||
// await effects.createDir({
|
||||
// volumeId: item.dstVolume,
|
||||
// path: item.dstPath,
|
||||
// })
|
||||
// }
|
||||
// await effects
|
||||
// .runRsync({
|
||||
// ...item,
|
||||
// options: {
|
||||
// ...this.options,
|
||||
// ...item.options,
|
||||
// },
|
||||
// })
|
||||
// .wait()
|
||||
const rsyncResults = await runRsync(
|
||||
{
|
||||
dstPath: item.dstPath,
|
||||
dstVolume: item.dstVolume,
|
||||
options: { ...this.options, ...item.options },
|
||||
srcPath: item.srcPath,
|
||||
srcVolume: item.srcVolume,
|
||||
},
|
||||
pathMaker,
|
||||
)
|
||||
await rsyncResults.wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -150,26 +116,17 @@ export class Backups<M extends SDKManifest> {
|
||||
effects,
|
||||
}) => {
|
||||
for (const item of this.backupSet) {
|
||||
// if (notEmptyPath(item.srcPath)) {
|
||||
// await new Promise((resolve, reject) => fs.mkdir(items.src)).createDir(
|
||||
// {
|
||||
// volumeId: item.srcVolume,
|
||||
// path: item.srcPath,
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
// await effects
|
||||
// .runRsync({
|
||||
// options: {
|
||||
// ...this.options,
|
||||
// ...item.options,
|
||||
// },
|
||||
// srcVolume: item.dstVolume,
|
||||
// dstVolume: item.srcVolume,
|
||||
// srcPath: item.dstPath,
|
||||
// dstPath: item.srcPath,
|
||||
// })
|
||||
// .wait()
|
||||
const rsyncResults = await runRsync(
|
||||
{
|
||||
dstPath: item.dstPath,
|
||||
dstVolume: item.dstVolume,
|
||||
options: { ...this.options, ...item.options },
|
||||
srcPath: item.srcPath,
|
||||
srcVolume: item.srcVolume,
|
||||
},
|
||||
pathMaker,
|
||||
)
|
||||
await rsyncResults.wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -179,3 +136,73 @@ export class Backups<M extends SDKManifest> {
|
||||
function notEmptyPath(file: string) {
|
||||
return ["", ".", "./"].indexOf(file) === -1
|
||||
}
|
||||
async function runRsync(
|
||||
rsyncOptions: {
|
||||
srcVolume: string
|
||||
dstVolume: string
|
||||
srcPath: string
|
||||
dstPath: string
|
||||
options: T.BackupOptions
|
||||
},
|
||||
pathMaker: T.PathMaker,
|
||||
): Promise<{
|
||||
id: () => Promise<string>
|
||||
wait: () => Promise<null>
|
||||
progress: () => Promise<number>
|
||||
}> {
|
||||
const { srcVolume, dstVolume, srcPath, dstPath, options } = rsyncOptions
|
||||
|
||||
const command = "rsync"
|
||||
const args: string[] = []
|
||||
if (options.delete) {
|
||||
args.push("--delete")
|
||||
}
|
||||
if (options.force) {
|
||||
args.push("--force")
|
||||
}
|
||||
if (options.ignoreExisting) {
|
||||
args.push("--ignore-existing")
|
||||
}
|
||||
for (const exclude of options.exclude) {
|
||||
args.push(`--exclude=${exclude}`)
|
||||
}
|
||||
args.push("-actAXH")
|
||||
args.push("--info=progress2")
|
||||
args.push("--no-inc-recursive")
|
||||
args.push(pathMaker({ volume: srcVolume, path: srcPath }))
|
||||
args.push(pathMaker({ volume: dstVolume, path: dstPath }))
|
||||
const spawned = child_process.spawn(command, args, { detached: true })
|
||||
let percentage = 0.0
|
||||
spawned.stdout.on("data", (data: unknown) => {
|
||||
const lines = String(data).replace("\r", "\n").split("\n")
|
||||
for (const line of lines) {
|
||||
const parsed = /$([0-9.]+)%/.exec(line)?.[1]
|
||||
if (!parsed) continue
|
||||
percentage = Number.parseFloat(parsed)
|
||||
}
|
||||
})
|
||||
|
||||
spawned.stderr.on("data", (data: unknown) => {
|
||||
console.error(String(data))
|
||||
})
|
||||
|
||||
const id = async () => {
|
||||
const pid = spawned.pid
|
||||
if (pid === undefined) {
|
||||
throw new Error("rsync process has no pid")
|
||||
}
|
||||
return String(pid)
|
||||
}
|
||||
const waitPromise = new Promise<null>((resolve, reject) => {
|
||||
spawned.on("exit", (code: any) => {
|
||||
if (code === 0) {
|
||||
resolve(null)
|
||||
} else {
|
||||
reject(new Error(`rsync exited with code ${code}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
const wait = () => waitPromise
|
||||
const progress = () => Promise.resolve(percentage)
|
||||
return { id, wait, progress }
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Backups } from "./Backups"
|
||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||
import { ExpectedExports } 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().createBackup(options)
|
||||
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().restoreBackup(options)
|
||||
await backup.build(options.pathMaker).restoreBackup(options)
|
||||
}
|
||||
}) as ExpectedExports.restoreBackup
|
||||
}) as T.ExpectedExports.restoreBackup
|
||||
},
|
||||
}
|
||||
return answer
|
||||
|
||||
@@ -125,96 +125,6 @@ export class List<Type, Store> {
|
||||
return built
|
||||
}, arrayOf(string))
|
||||
}
|
||||
static number(
|
||||
a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
/** Default = [] */
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
},
|
||||
aSpec: {
|
||||
integer: boolean
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
},
|
||||
) {
|
||||
return new List<number[], never>(() => {
|
||||
const spec = {
|
||||
type: "number" as const,
|
||||
placeholder: null,
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
units: null,
|
||||
...aSpec,
|
||||
}
|
||||
const built: ValueSpecListOf<"number"> = {
|
||||
description: null,
|
||||
warning: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
default: [],
|
||||
type: "list" as const,
|
||||
disabled: false,
|
||||
...a,
|
||||
spec,
|
||||
}
|
||||
return built
|
||||
}, arrayOf(number))
|
||||
}
|
||||
static dynamicNumber<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
/** Default = [] */
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string
|
||||
spec: {
|
||||
integer: boolean
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
}
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new List<number[], Store>(async (options) => {
|
||||
const { spec: aSpec, ...a } = await getA(options)
|
||||
const spec = {
|
||||
type: "number" as const,
|
||||
placeholder: null,
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
units: null,
|
||||
...aSpec,
|
||||
}
|
||||
return {
|
||||
description: null,
|
||||
warning: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
default: [],
|
||||
type: "list" as const,
|
||||
disabled: false,
|
||||
...a,
|
||||
spec,
|
||||
}
|
||||
}, arrayOf(number))
|
||||
}
|
||||
static obj<Type extends Record<string, any>, Store>(
|
||||
a: {
|
||||
name: string
|
||||
|
||||
@@ -69,8 +69,8 @@ export class Variants<Type, Store> {
|
||||
const validator = anyOf(
|
||||
...Object.entries(a).map(([name, { spec }]) =>
|
||||
object({
|
||||
unionSelectKey: literals(name),
|
||||
unionValueKey: spec.validator,
|
||||
selection: literals(name),
|
||||
value: spec.validator,
|
||||
}),
|
||||
),
|
||||
) as Parser<unknown, any>
|
||||
@@ -78,9 +78,9 @@ export class Variants<Type, Store> {
|
||||
return new Variants<
|
||||
{
|
||||
[K in keyof VariantValues]: {
|
||||
unionSelectKey: K
|
||||
selection: K
|
||||
// prettier-ignore
|
||||
unionValueKey:
|
||||
value:
|
||||
VariantValues[K]["spec"] extends (Config<infer B, Store> | Config<infer B, never>) ? B :
|
||||
never
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||
import { Dependency } from "../types"
|
||||
import * as T from "../types"
|
||||
|
||||
export type ConfigDependencies<T extends SDKManifest> = {
|
||||
exists(id: keyof T["dependencies"]): Dependency
|
||||
running(id: keyof T["dependencies"], healthChecks: string[]): Dependency
|
||||
export type ConfigDependencies<T extends T.Manifest> = {
|
||||
exists(id: keyof T["dependencies"]): T.Dependencies[number]
|
||||
running(
|
||||
id: keyof T["dependencies"],
|
||||
healthChecks: string[],
|
||||
): 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 Dependency
|
||||
} as T.Dependencies[number]
|
||||
},
|
||||
|
||||
running(id: keyof T["dependencies"], healthChecks: string[]) {
|
||||
@@ -21,6 +23,6 @@ export const configDependenciesSet = <
|
||||
id,
|
||||
kind: "running",
|
||||
healthChecks,
|
||||
} as Dependency
|
||||
} as T.Dependencies[number]
|
||||
},
|
||||
})
|
||||
|
||||
@@ -15,70 +15,93 @@ export type ValueType =
|
||||
export type ValueSpec = ValueSpecOf<ValueType>
|
||||
/** core spec types. These types provide the metadata for performing validations */
|
||||
// prettier-ignore
|
||||
export type ValueSpecOf<T extends ValueType> = T extends "text"
|
||||
? ValueSpecText
|
||||
: T extends "textarea"
|
||||
? ValueSpecTextarea
|
||||
: T extends "number"
|
||||
? ValueSpecNumber
|
||||
: T extends "color"
|
||||
? ValueSpecColor
|
||||
: T extends "datetime"
|
||||
? ValueSpecDatetime
|
||||
: T extends "toggle"
|
||||
? ValueSpecToggle
|
||||
: T extends "select"
|
||||
? ValueSpecSelect
|
||||
: T extends "multiselect"
|
||||
? ValueSpecMultiselect
|
||||
: T extends "list"
|
||||
? ValueSpecList
|
||||
: T extends "object"
|
||||
? ValueSpecObject
|
||||
: T extends "file"
|
||||
? ValueSpecFile
|
||||
: T extends "union"
|
||||
? ValueSpecUnion
|
||||
: never
|
||||
export type ValueSpecOf<T extends ValueType> =
|
||||
T extends "text" ? ValueSpecText :
|
||||
T extends "textarea" ? ValueSpecTextarea :
|
||||
T extends "number" ? ValueSpecNumber :
|
||||
T extends "color" ? ValueSpecColor :
|
||||
T extends "datetime" ? ValueSpecDatetime :
|
||||
T extends "toggle" ? ValueSpecToggle :
|
||||
T extends "select" ? ValueSpecSelect :
|
||||
T extends "multiselect" ? ValueSpecMultiselect :
|
||||
T extends "list" ? ValueSpecList :
|
||||
T extends "object" ? ValueSpecObject :
|
||||
T extends "file" ? ValueSpecFile :
|
||||
T extends "union" ? ValueSpecUnion :
|
||||
never
|
||||
|
||||
export type ValueSpecText = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "text"
|
||||
patterns: Pattern[]
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
masked: boolean
|
||||
|
||||
inputmode: "text" | "email" | "tel" | "url"
|
||||
placeholder: string | null
|
||||
|
||||
export interface ValueSpecText extends ListValueSpecText, WithStandalone {
|
||||
required: boolean
|
||||
default: DefaultString | null
|
||||
disabled: false | string
|
||||
generate: null | RandomString
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecTextarea extends WithStandalone {
|
||||
export type ValueSpecTextarea = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "textarea"
|
||||
placeholder: string | null
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
required: boolean
|
||||
disabled: false | string
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
|
||||
export type FilePath = {
|
||||
filePath: string
|
||||
}
|
||||
export interface ValueSpecNumber extends ListValueSpecNumber, WithStandalone {
|
||||
export type ValueSpecNumber = {
|
||||
type: "number"
|
||||
min: number | null
|
||||
max: number | null
|
||||
integer: boolean
|
||||
step: number | null
|
||||
units: string | null
|
||||
placeholder: string | null
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
required: boolean
|
||||
default: number | null
|
||||
disabled: false | string
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecColor extends WithStandalone {
|
||||
export type ValueSpecColor = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "color"
|
||||
required: boolean
|
||||
default: string | null
|
||||
disabled: false | string
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecDatetime extends WithStandalone {
|
||||
export type ValueSpecDatetime = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "datetime"
|
||||
required: boolean
|
||||
inputmode: "date" | "time" | "datetime-local"
|
||||
@@ -86,10 +109,14 @@ export interface ValueSpecDatetime extends WithStandalone {
|
||||
max: string | null
|
||||
default: string | null
|
||||
disabled: false | string
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecSelect extends SelectBase, WithStandalone {
|
||||
export type ValueSpecSelect = {
|
||||
values: Record<string, string>
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "select"
|
||||
required: boolean
|
||||
default: string | null
|
||||
@@ -99,10 +126,16 @@ export interface ValueSpecSelect extends SelectBase, WithStandalone {
|
||||
* string[] means that the options are disabled
|
||||
*/
|
||||
disabled: false | string | string[]
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecMultiselect extends SelectBase, WithStandalone {
|
||||
export type ValueSpecMultiselect = {
|
||||
values: Record<string, string>
|
||||
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "multiselect"
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
@@ -113,17 +146,25 @@ export interface ValueSpecMultiselect extends SelectBase, WithStandalone {
|
||||
*/
|
||||
disabled: false | string | string[]
|
||||
default: string[]
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecToggle extends WithStandalone {
|
||||
export type ValueSpecToggle = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "toggle"
|
||||
default: boolean | null
|
||||
disabled: false | string
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecUnion extends WithStandalone {
|
||||
export type ValueSpecUnion = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "union"
|
||||
variants: Record<
|
||||
string,
|
||||
@@ -140,39 +181,37 @@ export interface ValueSpecUnion extends WithStandalone {
|
||||
disabled: false | string | string[]
|
||||
required: boolean
|
||||
default: string | null
|
||||
/** Immutable means it can only be configed at the first config then never again */
|
||||
/** Immutable means it can only be configured at the first config then never again */
|
||||
immutable: boolean
|
||||
}
|
||||
export interface ValueSpecFile extends WithStandalone {
|
||||
export type ValueSpecFile = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "file"
|
||||
extensions: string[]
|
||||
required: boolean
|
||||
}
|
||||
export interface ValueSpecObject extends WithStandalone {
|
||||
type: "object"
|
||||
spec: InputSpec
|
||||
}
|
||||
export interface WithStandalone {
|
||||
export type ValueSpecObject = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "object"
|
||||
spec: InputSpec
|
||||
}
|
||||
export interface SelectBase {
|
||||
values: Record<string, string>
|
||||
}
|
||||
export type ListValueSpecType = "text" | "number" | "object"
|
||||
export type ListValueSpecType = "text" | "object"
|
||||
/** represents a spec for the values of a list */
|
||||
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "text"
|
||||
? ListValueSpecText
|
||||
: T extends "number"
|
||||
? ListValueSpecNumber
|
||||
: T extends "object"
|
||||
? ListValueSpecObject
|
||||
: never
|
||||
// prettier-ignore
|
||||
export type ListValueSpecOf<T extends ListValueSpecType> =
|
||||
T extends "text" ? ListValueSpecText :
|
||||
T extends "object" ? ListValueSpecObject :
|
||||
never
|
||||
/** represents a spec for a list */
|
||||
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>
|
||||
export interface ValueSpecListOf<T extends ListValueSpecType>
|
||||
extends WithStandalone {
|
||||
export type ValueSpecListOf<T extends ListValueSpecType> = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "list"
|
||||
spec: ListValueSpecOf<T>
|
||||
minLength: number | null
|
||||
@@ -180,19 +219,17 @@ export interface ValueSpecListOf<T extends ListValueSpecType>
|
||||
disabled: false | string
|
||||
default:
|
||||
| string[]
|
||||
| number[]
|
||||
| DefaultString[]
|
||||
| Record<string, unknown>[]
|
||||
| readonly string[]
|
||||
| readonly number[]
|
||||
| readonly DefaultString[]
|
||||
| readonly Record<string, unknown>[]
|
||||
}
|
||||
export interface Pattern {
|
||||
export type Pattern = {
|
||||
regex: string
|
||||
description: string
|
||||
}
|
||||
export interface ListValueSpecText {
|
||||
export type ListValueSpecText = {
|
||||
type: "text"
|
||||
patterns: Pattern[]
|
||||
minLength: number | null
|
||||
@@ -203,16 +240,7 @@ export interface ListValueSpecText {
|
||||
inputmode: "text" | "email" | "tel" | "url"
|
||||
placeholder: string | null
|
||||
}
|
||||
export interface ListValueSpecNumber {
|
||||
type: "number"
|
||||
min: number | null
|
||||
max: number | null
|
||||
integer: boolean
|
||||
step: number | null
|
||||
units: string | null
|
||||
placeholder: string | null
|
||||
}
|
||||
export interface ListValueSpecObject {
|
||||
export type ListValueSpecObject = {
|
||||
type: "object"
|
||||
/** this is a mapped type of the config object at this level, replacing the object's values with specs on those values */
|
||||
spec: InputSpec
|
||||
@@ -242,8 +270,3 @@ export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
||||
return "spec" in t && t.spec.type === s
|
||||
}
|
||||
export const unionSelectKey = "unionSelectKey" as const
|
||||
export type UnionSelectKey = typeof unionSelectKey
|
||||
|
||||
export const unionValueKey = "unionValueKey" as const
|
||||
export type UnionValueKey = typeof unionValueKey
|
||||
|
||||
@@ -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"
|
||||
@@ -11,30 +11,27 @@ export type DependenciesReceipt = void & {
|
||||
}
|
||||
|
||||
export type Save<
|
||||
Store,
|
||||
A extends
|
||||
| Record<string, any>
|
||||
| Config<Record<string, any>, any>
|
||||
| Config<Record<string, never>, never>,
|
||||
Manifest extends SDKManifest,
|
||||
> = (options: {
|
||||
effects: Effects
|
||||
effects: T.Effects
|
||||
input: ExtractConfigType<A> & Record<string, any>
|
||||
dependencies: D.ConfigDependencies<Manifest>
|
||||
}) => Promise<{
|
||||
dependenciesReceipt: DependenciesReceipt
|
||||
interfacesReceipt: InterfacesReceipt
|
||||
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
|
||||
@@ -49,11 +46,11 @@ 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>,
|
||||
write: Save<Store, Type, Manifest>,
|
||||
write: Save<Type>,
|
||||
read: Read<Manifest, Store, Type>,
|
||||
) {
|
||||
const validator = spec.validator
|
||||
@@ -66,14 +63,13 @@ export function setupConfig<
|
||||
await effects.clearBindings()
|
||||
await effects.clearServiceInterfaces()
|
||||
const { restart } = await write({
|
||||
input: JSON.parse(JSON.stringify(input)),
|
||||
input: JSON.parse(JSON.stringify(input)) as any,
|
||||
effects,
|
||||
dependencies: D.configDependenciesSet<Manifest>(),
|
||||
})
|
||||
if (restart) {
|
||||
await effects.restart()
|
||||
}
|
||||
}) as ExpectedExports.setConfig,
|
||||
}) as T.ExpectedExports.setConfig,
|
||||
getConfig: (async ({ effects }) => {
|
||||
const configValue = nullIfEmpty((await read({ effects })) || null)
|
||||
return {
|
||||
@@ -82,7 +78,7 @@ export function setupConfig<
|
||||
}),
|
||||
config: configValue,
|
||||
}
|
||||
}) as ExpectedExports.getConfig,
|
||||
}) as T.ExpectedExports.getConfig,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
131
sdk/lib/dependencies/dependencies.ts
Normal file
131
sdk/lib/dependencies/dependencies.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
Effects,
|
||||
PackageId,
|
||||
DependencyRequirement,
|
||||
SetHealth,
|
||||
CheckDependenciesResult,
|
||||
} from "../types"
|
||||
|
||||
export type CheckAllDependencies = {
|
||||
notInstalled: () => Promise<CheckDependenciesResult[]>
|
||||
notRunning: () => Promise<CheckDependenciesResult[]>
|
||||
configNotSatisfied: () => Promise<CheckDependenciesResult[]>
|
||||
healthErrors: () => Promise<{ [id: string]: SetHealth[] }>
|
||||
|
||||
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()
|
||||
const resultsPromise = dependenciesPromise.then((dependencies) =>
|
||||
effects.checkDependencies({
|
||||
packageIds: dependencies.map((dep) => dep.id),
|
||||
}),
|
||||
)
|
||||
|
||||
const dependenciesByIdPromise = dependenciesPromise.then((d) =>
|
||||
d.reduce(
|
||||
(acc, dep) => {
|
||||
acc[dep.id] = dep
|
||||
return acc
|
||||
},
|
||||
{} as { [id: PackageId]: DependencyRequirement },
|
||||
),
|
||||
)
|
||||
|
||||
const healthErrors = async () => {
|
||||
const results = await resultsPromise
|
||||
const dependenciesById = await dependenciesByIdPromise
|
||||
const answer: { [id: PackageId]: SetHealth[] } = {}
|
||||
for (const result of results) {
|
||||
const dependency = dependenciesById[result.packageId]
|
||||
if (!dependency) continue
|
||||
if (dependency.kind !== "running") continue
|
||||
|
||||
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 () => {
|
||||
const results = await resultsPromise
|
||||
const dependenciesById = await dependenciesByIdPromise
|
||||
return results.filter((x) => {
|
||||
const dependency = dependenciesById[x.packageId]
|
||||
if (!dependency) return false
|
||||
if (dependency.kind !== "running") return false
|
||||
return !x.isRunning
|
||||
})
|
||||
}
|
||||
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 throwIfHealthError = () =>
|
||||
healthErrors()
|
||||
.then(entries)
|
||||
.then(first)
|
||||
.then((x) => {
|
||||
if (!x) return
|
||||
const [id, healthChecks] = x
|
||||
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])
|
||||
throw new Error(`Package ${results[0].packageId} is not running`)
|
||||
})
|
||||
|
||||
const throwIfNotInstalled = () =>
|
||||
notInstalled().then((results) => {
|
||||
if (results[0])
|
||||
throw new Error(`Package ${results[0].packageId} is not installed`)
|
||||
})
|
||||
const throwIfNotValid = async () =>
|
||||
Promise.all([
|
||||
throwIfNotRunning(),
|
||||
throwIfNotInstalled(),
|
||||
throwIfConfigNotSatisfied(),
|
||||
throwIfHealthError(),
|
||||
]).then(sinkVoid)
|
||||
|
||||
const isValid = () =>
|
||||
throwIfNotValid().then(
|
||||
() => true,
|
||||
() => false,
|
||||
)
|
||||
|
||||
return {
|
||||
notRunning,
|
||||
notInstalled,
|
||||
configNotSatisfied,
|
||||
healthErrors,
|
||||
throwIfNotRunning,
|
||||
throwIfConfigNotSatisfied,
|
||||
throwIfNotValid,
|
||||
throwIfNotInstalled,
|
||||
throwIfHealthError,
|
||||
isValid,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
import * as matches from "ts-matches"
|
||||
|
||||
const starSub = /((\d+\.)*\d+)\.\*/
|
||||
// prettier-ignore
|
||||
export type ValidEmVer = `${number}${`.${number}` | ""}${`.${number}` | ""}${`-${string}` | ""}`;
|
||||
// prettier-ignore
|
||||
export type ValidEmVerRange = `${'>=' | '<='| '<' | '>' | ''}${'^' | '~' | ''}${number | '*'}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`-${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
99
sdk/lib/exver/exver.pegjs
Normal 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
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
443
sdk/lib/exver/index.ts
Normal 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")
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { InterfaceReceipt } from "../interfaces/interfaceReceipt"
|
||||
import { Daemon, Effects } from "../types"
|
||||
import { Effects } from "../types"
|
||||
import { CheckResult } from "./checkFns/CheckResult"
|
||||
import { HealthReceipt } from "./HealthReceipt"
|
||||
import { Trigger } from "../trigger"
|
||||
@@ -7,17 +6,26 @@ import { TriggerInput } from "../trigger/TriggerInput"
|
||||
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||
import { once } from "../util/once"
|
||||
import { Overlay } from "../util/Overlay"
|
||||
import { object, unknown } from "ts-matches"
|
||||
import * as T from "../types"
|
||||
|
||||
export function healthCheck(o: {
|
||||
export type HealthCheckParams<Manifest extends T.Manifest> = {
|
||||
effects: Effects
|
||||
name: string
|
||||
imageId: string
|
||||
image: {
|
||||
id: keyof Manifest["images"] & T.ImageId
|
||||
sharedRun?: boolean
|
||||
}
|
||||
trigger?: Trigger
|
||||
fn(overlay: Overlay): Promise<CheckResult> | CheckResult
|
||||
onFirstSuccess?: () => unknown | Promise<unknown>
|
||||
}) {
|
||||
}
|
||||
|
||||
export function healthCheck<Manifest extends T.Manifest>(
|
||||
o: HealthCheckParams<Manifest>,
|
||||
) {
|
||||
new Promise(async () => {
|
||||
const overlay = await Overlay.of(o.effects, o.imageId)
|
||||
const overlay = await Overlay.of(o.effects, o.image)
|
||||
try {
|
||||
let currentValue: TriggerInput = {
|
||||
hadSuccess: false,
|
||||
@@ -66,8 +74,7 @@ export function healthCheck(o: {
|
||||
return {} as HealthReceipt
|
||||
}
|
||||
function asMessage(e: unknown) {
|
||||
if (typeof e === "object" && e != null && "message" in e)
|
||||
return String(e.message)
|
||||
if (object({ message: unknown }).test(e)) return String(e.message)
|
||||
const value = String(e)
|
||||
if (value.length == null) return null
|
||||
return value
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
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 "./dependencyConfig"
|
||||
export * as manifest from "./manifest"
|
||||
export * as dependencyConfig from "./dependencies"
|
||||
export * as types from "./types"
|
||||
export * as T from "./types"
|
||||
export * as yaml from "yaml"
|
||||
export * as matches from "ts-matches"
|
||||
|
||||
export * as util from "./util/index.browser"
|
||||
export * as utils from "./util/index.browser"
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
export { Daemons } from "./mainFn/Daemons"
|
||||
export { EmVer } from "./emverLite/mod"
|
||||
export { Overlay } from "./util/Overlay"
|
||||
export { StartSdk } from "./StartSdk"
|
||||
export { setupManifest } from "./manifest/setupManifest"
|
||||
export { FileHelper } from "./util/fileHelper"
|
||||
export { setupExposeStore } from "./store/setupExposeStore"
|
||||
export { pathBuilder } from "./store/PathBuilder"
|
||||
export { S9pk } from "./s9pk"
|
||||
export { VersionRange, ExtendedVersion, Version } from "./exver"
|
||||
|
||||
export * as actions from "./actions"
|
||||
export * as backup from "./backup"
|
||||
export * as config from "./config"
|
||||
export * as CB from "./config/builder"
|
||||
export * as CT from "./config/configTypes"
|
||||
export * as dependencyConfig from "./dependencyConfig"
|
||||
export * as dependencyConfig from "./dependencies"
|
||||
export * as daemons from "./mainFn/Daemons"
|
||||
export * as health from "./health"
|
||||
export * as healthFns from "./health/checkFns"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { object, string } from "ts-matches"
|
||||
import { number, object, string } from "ts-matches"
|
||||
import { Effects } from "../types"
|
||||
import { Origin } from "./Origin"
|
||||
import { AddSslOptions } from "../../../core/startos/bindings/AddSslOptions"
|
||||
import { Security } from "../../../core/startos/bindings/Security"
|
||||
import { BindOptions } from "../../../core/startos/bindings/BindOptions"
|
||||
import { AlpnInfo } from "../../../core/startos/bindings/AlpnInfo"
|
||||
import { AddSslOptions, BindParams } from ".././osBindings"
|
||||
import { Security } from ".././osBindings"
|
||||
import { BindOptions } from ".././osBindings"
|
||||
import { AlpnInfo } from ".././osBindings"
|
||||
|
||||
export { AddSslOptions, Security, BindOptions }
|
||||
|
||||
const knownProtocols = {
|
||||
export const knownProtocols = {
|
||||
http: {
|
||||
secure: null,
|
||||
defaultPort: 80,
|
||||
@@ -70,18 +70,16 @@ type BindOptionsByKnownProtocol =
|
||||
| {
|
||||
protocol: ProtocolsWithSslVariants
|
||||
preferredExternalPort?: number
|
||||
scheme?: Scheme
|
||||
addSsl?: Partial<AddSslOptions>
|
||||
}
|
||||
| {
|
||||
protocol: NotProtocolsWithSslVariants
|
||||
preferredExternalPort?: number
|
||||
scheme?: Scheme
|
||||
addSsl?: AddSslOptions
|
||||
}
|
||||
type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions
|
||||
export type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions
|
||||
|
||||
export type HostKind = "static" | "single" | "multi"
|
||||
export type HostKind = BindParams["kind"]
|
||||
|
||||
const hasStringProtocol = object({
|
||||
protocol: string,
|
||||
@@ -110,81 +108,84 @@ export class Host {
|
||||
private async bindPortForUnknown(
|
||||
internalPort: number,
|
||||
options: {
|
||||
scheme: Scheme
|
||||
preferredExternalPort: number
|
||||
addSsl: AddSslOptions | null
|
||||
secure: { ssl: boolean } | null
|
||||
},
|
||||
) {
|
||||
await this.options.effects.bind({
|
||||
const binderOptions = {
|
||||
kind: this.options.kind,
|
||||
id: this.options.id,
|
||||
internalPort: internalPort,
|
||||
internalPort,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
await this.options.effects.bind(binderOptions)
|
||||
|
||||
return new Origin(this, options)
|
||||
return new Origin(this, internalPort, null, null)
|
||||
}
|
||||
|
||||
private async bindPortForKnown(
|
||||
options: BindOptionsByKnownProtocol,
|
||||
internalPort: number,
|
||||
) {
|
||||
const scheme =
|
||||
options.scheme === undefined ? options.protocol : options.scheme
|
||||
const protoInfo = knownProtocols[options.protocol]
|
||||
const preferredExternalPort =
|
||||
options.preferredExternalPort ||
|
||||
knownProtocols[options.protocol].defaultPort
|
||||
const addSsl = this.getAddSsl(options, protoInfo)
|
||||
const sslProto = this.getSslProto(options, protoInfo)
|
||||
const addSsl =
|
||||
sslProto && "alpn" in protoInfo
|
||||
? {
|
||||
// addXForwardedHeaders: null,
|
||||
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
||||
scheme: sslProto,
|
||||
alpn: protoInfo.alpn,
|
||||
...("addSsl" in options ? options.addSsl : null),
|
||||
}
|
||||
: null
|
||||
|
||||
const secure: Security | null = !protoInfo.secure ? null : { ssl: false }
|
||||
|
||||
const newOptions = {
|
||||
scheme,
|
||||
preferredExternalPort,
|
||||
addSsl,
|
||||
secure,
|
||||
}
|
||||
|
||||
await this.options.effects.bind({
|
||||
kind: this.options.kind,
|
||||
id: this.options.id,
|
||||
internalPort,
|
||||
...newOptions,
|
||||
preferredExternalPort,
|
||||
addSsl,
|
||||
secure,
|
||||
})
|
||||
|
||||
return new Origin(this, newOptions)
|
||||
return new Origin(this, internalPort, options.protocol, sslProto)
|
||||
}
|
||||
|
||||
private getAddSsl(
|
||||
private getSslProto(
|
||||
options: BindOptionsByKnownProtocol,
|
||||
protoInfo: KnownProtocols[keyof KnownProtocols],
|
||||
): AddSslOptions | null {
|
||||
if ("noAddSsl" in options && options.noAddSsl) return null
|
||||
if ("withSsl" in protoInfo && protoInfo.withSsl)
|
||||
return {
|
||||
// addXForwardedHeaders: null,
|
||||
preferredExternalPort: knownProtocols[protoInfo.withSsl].defaultPort,
|
||||
scheme: protoInfo.withSsl,
|
||||
alpn: protoInfo.alpn,
|
||||
...("addSsl" in options ? options.addSsl : null),
|
||||
}
|
||||
) {
|
||||
if (inObject("noAddSsl", options) && options.noAddSsl) return null
|
||||
if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export class StaticHost extends Host {
|
||||
constructor(options: { effects: Effects; id: string }) {
|
||||
super({ ...options, kind: "static" })
|
||||
}
|
||||
function inObject<Key extends string>(
|
||||
key: Key,
|
||||
obj: any,
|
||||
): obj is { [K in Key]: unknown } {
|
||||
return key in obj
|
||||
}
|
||||
|
||||
export class SingleHost extends Host {
|
||||
constructor(options: { effects: Effects; id: string }) {
|
||||
super({ ...options, kind: "single" })
|
||||
}
|
||||
}
|
||||
// export class StaticHost extends Host {
|
||||
// constructor(options: { effects: Effects; id: string }) {
|
||||
// super({ ...options, kind: "static" })
|
||||
// }
|
||||
// }
|
||||
|
||||
// export class SingleHost extends Host {
|
||||
// constructor(options: { effects: Effects; id: string }) {
|
||||
// super({ ...options, kind: "single" })
|
||||
// }
|
||||
// }
|
||||
|
||||
export class MultiHost extends Host {
|
||||
constructor(options: { effects: Effects; id: string }) {
|
||||
|
||||
@@ -6,7 +6,9 @@ import { ServiceInterfaceBuilder } from "./ServiceInterfaceBuilder"
|
||||
export class Origin<T extends Host> {
|
||||
constructor(
|
||||
readonly host: T,
|
||||
readonly options: BindOptions,
|
||||
readonly internalPort: number,
|
||||
readonly scheme: string | null,
|
||||
readonly sslScheme: string | null,
|
||||
) {}
|
||||
|
||||
build({ username, path, search, schemeOverride }: BuildOptions): AddressInfo {
|
||||
@@ -20,18 +22,9 @@ export class Origin<T extends Host> {
|
||||
|
||||
return {
|
||||
hostId: this.host.options.id,
|
||||
bindOptions: {
|
||||
...this.options,
|
||||
scheme: schemeOverride ? schemeOverride.noSsl : this.options.scheme,
|
||||
addSsl: this.options.addSsl
|
||||
? {
|
||||
...this.options.addSsl,
|
||||
scheme: schemeOverride
|
||||
? schemeOverride.ssl
|
||||
: this.options.addSsl.scheme,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
internalPort: this.internalPort,
|
||||
scheme: schemeOverride ? schemeOverride.noSsl : this.scheme,
|
||||
sslScheme: schemeOverride ? schemeOverride.ssl : this.sslScheme,
|
||||
suffix: `${path}${qp}`,
|
||||
username,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
156
sdk/lib/mainFn/CommandController.ts
Normal file
156
sdk/lib/mainFn/CommandController.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
||||
import { NO_TIMEOUT, SIGKILL, SIGTERM } from "../StartSdk"
|
||||
|
||||
import * as T from "../types"
|
||||
import { MountOptions, Overlay } from "../util/Overlay"
|
||||
import { splitCommand } from "../util/splitCommand"
|
||||
import { cpExecFile, cpExec } from "./Daemons"
|
||||
|
||||
export class CommandController {
|
||||
private constructor(
|
||||
readonly runningAnswer: Promise<unknown>,
|
||||
readonly overlay: Overlay,
|
||||
readonly pid: number | undefined,
|
||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||
) {}
|
||||
static of<Manifest extends T.Manifest>() {
|
||||
return async <A extends string>(
|
||||
effects: T.Effects,
|
||||
imageId: {
|
||||
id: keyof Manifest["images"] & T.ImageId
|
||||
sharedRun?: boolean
|
||||
},
|
||||
command: T.CommandType,
|
||||
options: {
|
||||
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
||||
sigtermTimeout?: number
|
||||
mounts?: { path: string; options: MountOptions }[]
|
||||
overlay?: Overlay
|
||||
env?:
|
||||
| {
|
||||
[variable: string]: string
|
||||
}
|
||||
| undefined
|
||||
cwd?: string | undefined
|
||||
user?: string | undefined
|
||||
onStdout?: (x: Buffer) => void
|
||||
onStderr?: (x: Buffer) => void
|
||||
},
|
||||
) => {
|
||||
const commands = splitCommand(command)
|
||||
const overlay = options.overlay || (await Overlay.of(effects, imageId))
|
||||
for (let mount of options.mounts || []) {
|
||||
await overlay.mount(mount.options, mount.path)
|
||||
}
|
||||
const childProcess = await overlay.spawn(commands, {
|
||||
env: options.env,
|
||||
})
|
||||
const answer = new Promise<null>((resolve, reject) => {
|
||||
childProcess.stdout.on(
|
||||
"data",
|
||||
options.onStdout ??
|
||||
((data: any) => {
|
||||
console.log(data.toString())
|
||||
}),
|
||||
)
|
||||
childProcess.stderr.on(
|
||||
"data",
|
||||
options.onStderr ??
|
||||
((data: any) => {
|
||||
console.error(data.toString())
|
||||
}),
|
||||
)
|
||||
|
||||
childProcess.on("exit", (code: any) => {
|
||||
if (code === 0) {
|
||||
return resolve(null)
|
||||
}
|
||||
return reject(new Error(`${commands[0]} exited with code ${code}`))
|
||||
})
|
||||
})
|
||||
|
||||
const pid = childProcess.pid
|
||||
|
||||
return new CommandController(answer, overlay, pid, options.sigtermTimeout)
|
||||
}
|
||||
}
|
||||
async wait(timeout: number = NO_TIMEOUT) {
|
||||
if (timeout > 0)
|
||||
setTimeout(() => {
|
||||
this.term()
|
||||
}, timeout)
|
||||
try {
|
||||
return await this.runningAnswer
|
||||
} finally {
|
||||
if (this.pid !== undefined) {
|
||||
await cpExecFile("pkill", ["-9", "-s", String(this.pid)]).catch(
|
||||
(_) => {},
|
||||
)
|
||||
}
|
||||
await this.overlay.destroy().catch((_) => {})
|
||||
}
|
||||
}
|
||||
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||
if (this.pid === undefined) return
|
||||
try {
|
||||
await cpExecFile("pkill", [
|
||||
`-${signal.replace("SIG", "")}`,
|
||||
"-s",
|
||||
String(this.pid),
|
||||
])
|
||||
|
||||
const didTimeout = await waitSession(this.pid, timeout)
|
||||
if (didTimeout) {
|
||||
await cpExecFile("pkill", [`-9`, "-s", String(this.pid)]).catch(
|
||||
(_) => {},
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
await this.overlay.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function waitSession(
|
||||
sid: number,
|
||||
timeout = NO_TIMEOUT,
|
||||
interval = 100,
|
||||
): Promise<boolean> {
|
||||
let nextInterval = interval * 2
|
||||
if (timeout >= 0 && timeout < nextInterval) {
|
||||
nextInterval = timeout
|
||||
}
|
||||
let nextTimeout = timeout
|
||||
if (timeout > 0) {
|
||||
if (timeout >= interval) {
|
||||
nextTimeout -= interval
|
||||
} else {
|
||||
nextTimeout = 0
|
||||
}
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let next: NodeJS.Timeout | null = null
|
||||
if (timeout !== 0) {
|
||||
next = setTimeout(() => {
|
||||
waitSession(sid, nextTimeout, nextInterval).then(resolve, reject)
|
||||
}, interval)
|
||||
}
|
||||
cpExecFile("ps", [`--sid=${sid}`, "-o", "--pid="]).then(
|
||||
(_) => {
|
||||
if (timeout === 0) {
|
||||
resolve(true)
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
if (next) {
|
||||
clearTimeout(next)
|
||||
}
|
||||
if (typeof e === "object" && e && "code" in e && e.code) {
|
||||
resolve(false)
|
||||
} else {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
79
sdk/lib/mainFn/Daemon.ts
Normal file
79
sdk/lib/mainFn/Daemon.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import * as T from "../types"
|
||||
import { MountOptions, Overlay } from "../util/Overlay"
|
||||
import { CommandController } from "./CommandController"
|
||||
|
||||
const TIMEOUT_INCREMENT_MS = 1000
|
||||
const MAX_TIMEOUT_MS = 30000
|
||||
/**
|
||||
* This is a wrapper around CommandController that has a state of off, where the command shouldn't be running
|
||||
* and the others state of running, where it will keep a living running command
|
||||
*/
|
||||
|
||||
export class Daemon {
|
||||
private commandController: CommandController | null = null
|
||||
private shouldBeRunning = false
|
||||
private constructor(private startCommand: () => Promise<CommandController>) {}
|
||||
static of<Manifest extends T.Manifest>() {
|
||||
return async <A extends string>(
|
||||
effects: T.Effects,
|
||||
imageId: {
|
||||
id: keyof Manifest["images"] & T.ImageId
|
||||
sharedRun?: boolean
|
||||
},
|
||||
command: T.CommandType,
|
||||
options: {
|
||||
mounts?: { path: string; options: MountOptions }[]
|
||||
overlay?: Overlay
|
||||
env?:
|
||||
| {
|
||||
[variable: string]: string
|
||||
}
|
||||
| undefined
|
||||
cwd?: string | undefined
|
||||
user?: string | undefined
|
||||
onStdout?: (x: Buffer) => void
|
||||
onStderr?: (x: Buffer) => void
|
||||
sigtermTimeout?: number
|
||||
},
|
||||
) => {
|
||||
const startCommand = () =>
|
||||
CommandController.of<Manifest>()(effects, imageId, command, options)
|
||||
return new Daemon(startCommand)
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.commandController) {
|
||||
return
|
||||
}
|
||||
this.shouldBeRunning = true
|
||||
let timeoutCounter = 0
|
||||
new Promise(async () => {
|
||||
while (this.shouldBeRunning) {
|
||||
this.commandController = await this.startCommand()
|
||||
await this.commandController.wait().catch((err) => console.error(err))
|
||||
await new Promise((resolve) => setTimeout(resolve, timeoutCounter))
|
||||
timeoutCounter += TIMEOUT_INCREMENT_MS
|
||||
timeoutCounter = Math.max(MAX_TIMEOUT_MS, timeoutCounter)
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
async term(termOptions?: {
|
||||
signal?: NodeJS.Signals | undefined
|
||||
timeout?: number | undefined
|
||||
}) {
|
||||
return this.stop(termOptions)
|
||||
}
|
||||
async stop(termOptions?: {
|
||||
signal?: NodeJS.Signals | undefined
|
||||
timeout?: number | undefined
|
||||
}) {
|
||||
this.shouldBeRunning = false
|
||||
await this.commandController
|
||||
?.term(termOptions)
|
||||
.catch((e) => console.error(e))
|
||||
this.commandController = null
|
||||
}
|
||||
}
|
||||
@@ -1,11 +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, ValidIfNoStupidEscape } from "../types"
|
||||
import * as T from "../types"
|
||||
import { Mounts } from "./Mounts"
|
||||
import { CommandOptions, MountOptions, Overlay } from "../util/Overlay"
|
||||
import { splitCommand } from "../util/splitCommand"
|
||||
@@ -13,117 +13,39 @@ import { splitCommand } from "../util/splitCommand"
|
||||
import { promisify } from "node:util"
|
||||
import * as CP from "node:child_process"
|
||||
|
||||
const cpExec = promisify(CP.exec)
|
||||
const cpExecFile = promisify(CP.execFile)
|
||||
async function psTree(pid: number, overlay: Overlay): Promise<number[]> {
|
||||
const { stdout } = await cpExec(`pstree -p ${pid}`)
|
||||
const regex: RegExp = /\((\d+)\)/g
|
||||
return [...stdout.toString().matchAll(regex)].map(([_all, pid]) =>
|
||||
parseInt(pid),
|
||||
)
|
||||
export { Daemon } from "./Daemon"
|
||||
export { CommandController } from "./CommandController"
|
||||
import { HealthDaemon } from "./HealthDaemon"
|
||||
import { Daemon } from "./Daemon"
|
||||
import { CommandController } from "./CommandController"
|
||||
|
||||
export const cpExec = promisify(CP.exec)
|
||||
export const cpExecFile = promisify(CP.execFile)
|
||||
export type Ready = {
|
||||
display: string | null
|
||||
fn: () => Promise<CheckResult> | CheckResult
|
||||
trigger?: Trigger
|
||||
}
|
||||
type Daemon<
|
||||
Manifest extends SDKManifest,
|
||||
|
||||
type DaemonsParams<
|
||||
Manifest extends T.Manifest,
|
||||
Ids extends string,
|
||||
Command extends string,
|
||||
Id extends string,
|
||||
> = {
|
||||
id: "" extends Id ? never : Id
|
||||
command: ValidIfNoStupidEscape<Command> | [string, ...string[]]
|
||||
imageId: Manifest["images"][number]
|
||||
command: T.CommandType
|
||||
image: { id: keyof Manifest["images"] & T.ImageId; sharedRun?: boolean }
|
||||
mounts: Mounts<Manifest>
|
||||
env?: Record<string, string>
|
||||
ready: {
|
||||
display: string | null
|
||||
fn: () => Promise<CheckResult> | CheckResult
|
||||
trigger?: Trigger
|
||||
}
|
||||
ready: Ready
|
||||
requires: Exclude<Ids, Id>[]
|
||||
sigtermTimeout?: number
|
||||
}
|
||||
|
||||
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
||||
|
||||
export const runDaemon =
|
||||
<Manifest extends SDKManifest>() =>
|
||||
async <A extends string>(
|
||||
effects: Effects,
|
||||
imageId: Manifest["images"][number],
|
||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||
options: CommandOptions & {
|
||||
mounts?: { path: string; options: MountOptions }[]
|
||||
overlay?: Overlay
|
||||
},
|
||||
): Promise<DaemonReturned> => {
|
||||
const commands = splitCommand(command)
|
||||
const overlay = options.overlay || (await Overlay.of(effects, imageId))
|
||||
for (let mount of options.mounts || []) {
|
||||
await overlay.mount(mount.options, mount.path)
|
||||
}
|
||||
const childProcess = await overlay.spawn(commands, {
|
||||
env: options.env,
|
||||
})
|
||||
const answer = new Promise<null>((resolve, reject) => {
|
||||
childProcess.stdout.on("data", (data: any) => {
|
||||
console.log(data.toString())
|
||||
})
|
||||
childProcess.stderr.on("data", (data: any) => {
|
||||
console.error(data.toString())
|
||||
})
|
||||
|
||||
childProcess.on("exit", (code: any) => {
|
||||
if (code === 0) {
|
||||
return resolve(null)
|
||||
}
|
||||
return reject(new Error(`${commands[0]} exited with code ${code}`))
|
||||
})
|
||||
})
|
||||
|
||||
const pid = childProcess.pid
|
||||
return {
|
||||
async wait() {
|
||||
const pids = pid ? await psTree(pid, overlay) : []
|
||||
try {
|
||||
return await answer
|
||||
} finally {
|
||||
for (const process of pids) {
|
||||
cpExecFile("kill", [`-9`, String(process)]).catch((_) => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) {
|
||||
const pids = pid ? await psTree(pid, overlay) : []
|
||||
try {
|
||||
childProcess.kill(signal)
|
||||
|
||||
if (timeout > NO_TIMEOUT) {
|
||||
const didTimeout = await Promise.race([
|
||||
new Promise((resolve) => setTimeout(resolve, timeout)).then(
|
||||
() => true,
|
||||
),
|
||||
answer.then(() => false),
|
||||
])
|
||||
if (didTimeout) {
|
||||
childProcess.kill(SIGKILL)
|
||||
}
|
||||
} else {
|
||||
await answer
|
||||
}
|
||||
} finally {
|
||||
await overlay.destroy()
|
||||
}
|
||||
|
||||
try {
|
||||
for (const process of pids) {
|
||||
await cpExecFile("kill", [`-${signal}`, String(process)])
|
||||
}
|
||||
} finally {
|
||||
for (const process of pids) {
|
||||
cpExecFile("kill", [`-9`, String(process)]).catch((_) => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
export const runCommand = <Manifest extends T.Manifest>() =>
|
||||
CommandController.of<Manifest>()
|
||||
|
||||
/**
|
||||
* A class for defining and controlling the service daemons
|
||||
@@ -148,11 +70,13 @@ 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?: Daemon<Manifest, Ids, "command", Ids>[],
|
||||
readonly daemons: Promise<Daemon>[],
|
||||
readonly ids: Ids[],
|
||||
readonly healthDaemons: HealthDaemon[],
|
||||
) {}
|
||||
/**
|
||||
* Returns an empty new Daemons class with the provided config.
|
||||
@@ -164,12 +88,18 @@ 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[]
|
||||
}) {
|
||||
return new Daemons<Manifest, never>(config.effects, config.started)
|
||||
return new Daemons<Manifest, never>(
|
||||
config.effects,
|
||||
config.started,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Returns the complete list of daemons, including the one defined here
|
||||
@@ -184,73 +114,57 @@ export class Daemons<Manifest extends SDKManifest, Ids extends string> {
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
newDaemon: Omit<Daemon<Manifest, Ids, Command, Id>, "id">,
|
||||
options: DaemonsParams<Manifest, Ids, Command, Id>,
|
||||
) {
|
||||
const daemons = ((this?.daemons ?? []) as any[]).concat({
|
||||
...newDaemon,
|
||||
id,
|
||||
const daemonIndex = this.daemons.length
|
||||
const daemon = Daemon.of()(this.effects, options.image, options.command, {
|
||||
...options,
|
||||
mounts: options.mounts.build(),
|
||||
})
|
||||
return new Daemons<Manifest, Ids | Id>(this.effects, this.started, daemons)
|
||||
const healthDaemon = new HealthDaemon(
|
||||
daemon,
|
||||
daemonIndex,
|
||||
options.requires
|
||||
.map((x) => this.ids.indexOf(id as any))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
this.ids,
|
||||
options.ready,
|
||||
this.effects,
|
||||
options.sigtermTimeout,
|
||||
)
|
||||
const daemons = this.daemons.concat(daemon)
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
this.effects,
|
||||
this.started,
|
||||
daemons,
|
||||
ids,
|
||||
healthDaemons,
|
||||
)
|
||||
}
|
||||
|
||||
async build() {
|
||||
const daemonsStarted = {} as Record<Ids, Promise<DaemonReturned>>
|
||||
const { effects } = this
|
||||
const daemons = this.daemons ?? []
|
||||
for (const daemon of daemons) {
|
||||
const requiredPromise = Promise.all(
|
||||
daemon.requires?.map((id) => daemonsStarted[id]) ?? [],
|
||||
)
|
||||
daemonsStarted[daemon.id] = requiredPromise.then(async () => {
|
||||
const { command, imageId } = daemon
|
||||
this.updateMainHealth()
|
||||
this.healthDaemons.forEach((x) =>
|
||||
x.addWatcher(() => this.updateMainHealth()),
|
||||
)
|
||||
const built = {
|
||||
term: async (options?: { signal?: Signals; timeout?: number }) => {
|
||||
try {
|
||||
await Promise.all(this.healthDaemons.map((x) => x.term(options)))
|
||||
} finally {
|
||||
this.effects.setMainStatus({ status: "stopped" })
|
||||
}
|
||||
},
|
||||
}
|
||||
this.started(() => built.term())
|
||||
return built
|
||||
}
|
||||
|
||||
const child = runDaemon<Manifest>()(effects, imageId, command, {
|
||||
env: daemon.env,
|
||||
mounts: daemon.mounts.build(),
|
||||
})
|
||||
let currentInput: TriggerInput = {}
|
||||
const getCurrentInput = () => currentInput
|
||||
const trigger = (daemon.ready.trigger ?? defaultTrigger)(
|
||||
getCurrentInput,
|
||||
)
|
||||
return new Promise(async (resolve) => {
|
||||
for (
|
||||
let res = await trigger.next();
|
||||
!res.done;
|
||||
res = await trigger.next()
|
||||
) {
|
||||
const response = await Promise.resolve(daemon.ready.fn()).catch(
|
||||
(err) =>
|
||||
({
|
||||
status: "failure",
|
||||
message: "message" in err ? err.message : String(err),
|
||||
}) as CheckResult,
|
||||
)
|
||||
currentInput.lastResult = response.status || null
|
||||
if (!currentInput.hadSuccess && response.status === "success") {
|
||||
currentInput.hadSuccess = true
|
||||
resolve(child)
|
||||
}
|
||||
}
|
||||
resolve(child)
|
||||
})
|
||||
})
|
||||
}
|
||||
return {
|
||||
async term(options?: { signal?: Signals; timeout?: number }) {
|
||||
await Promise.all(
|
||||
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
||||
x.then((x) => x.term(options)),
|
||||
),
|
||||
)
|
||||
},
|
||||
async wait() {
|
||||
await Promise.all(
|
||||
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
||||
x.then((x) => x.wait()),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
private updateMainHealth() {
|
||||
this.effects.setMainStatus({ status: "running" })
|
||||
}
|
||||
}
|
||||
|
||||
159
sdk/lib/mainFn/HealthDaemon.ts
Normal file
159
sdk/lib/mainFn/HealthDaemon.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { CheckResult } from "../health/checkFns"
|
||||
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||
import { Ready } from "./Daemons"
|
||||
import { Daemon } from "./Daemon"
|
||||
import { Effects } from "../types"
|
||||
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
||||
|
||||
const oncePromise = <T>() => {
|
||||
let resolve: (value: T) => void
|
||||
const promise = new Promise<T>((res) => {
|
||||
resolve = res
|
||||
})
|
||||
return { resolve: resolve!, promise }
|
||||
}
|
||||
|
||||
/**
|
||||
* Wanted a structure that deals with controlling daemons by their health status
|
||||
* States:
|
||||
* -- Waiting for dependencies to be success
|
||||
* -- Running: Daemon is running and the status is in the health
|
||||
*
|
||||
*/
|
||||
export class HealthDaemon {
|
||||
#health: CheckResult = { status: "starting", message: null }
|
||||
#healthWatchers: Array<() => unknown> = []
|
||||
#running = false
|
||||
#hadSuccess = false
|
||||
constructor(
|
||||
readonly daemon: Promise<Daemon>,
|
||||
readonly daemonIndex: number,
|
||||
readonly dependencies: HealthDaemon[],
|
||||
readonly id: string,
|
||||
readonly ids: string[],
|
||||
readonly ready: Ready,
|
||||
readonly effects: Effects,
|
||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||
) {
|
||||
this.updateStatus()
|
||||
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()))
|
||||
}
|
||||
|
||||
/** Run after we want to do cleanup */
|
||||
async term(termOptions?: {
|
||||
signal?: NodeJS.Signals | undefined
|
||||
timeout?: number | undefined
|
||||
}) {
|
||||
this.#healthWatchers = []
|
||||
this.#running = false
|
||||
this.#healthCheckCleanup?.()
|
||||
|
||||
await this.daemon.then((d) =>
|
||||
d.stop({
|
||||
timeout: this.sigtermTimeout,
|
||||
...termOptions,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/** Want to add another notifier that the health might have changed */
|
||||
addWatcher(watcher: () => unknown) {
|
||||
this.#healthWatchers.push(watcher)
|
||||
}
|
||||
|
||||
get health() {
|
||||
return Object.freeze(this.#health)
|
||||
}
|
||||
|
||||
private async changeRunning(newStatus: boolean) {
|
||||
if (this.#running === newStatus) return
|
||||
|
||||
this.#running = newStatus
|
||||
|
||||
if (newStatus) {
|
||||
;(await this.daemon).start()
|
||||
this.setupHealthCheck()
|
||||
} else {
|
||||
;(await this.daemon).stop()
|
||||
this.turnOffHealthCheck()
|
||||
|
||||
this.setHealth({ status: "starting", message: null })
|
||||
}
|
||||
}
|
||||
|
||||
#healthCheckCleanup: (() => void) | null = null
|
||||
private turnOffHealthCheck() {
|
||||
this.#healthCheckCleanup?.()
|
||||
}
|
||||
private async setupHealthCheck() {
|
||||
if (this.#healthCheckCleanup) return
|
||||
const trigger = (this.ready.trigger ?? defaultTrigger)(() => ({
|
||||
hadSuccess: this.#hadSuccess,
|
||||
lastResult: this.#health.status,
|
||||
}))
|
||||
|
||||
const { promise: status, resolve: setStatus } = oncePromise<{
|
||||
done: true
|
||||
}>()
|
||||
new Promise(async () => {
|
||||
for (
|
||||
let res = await Promise.race([status, trigger.next()]);
|
||||
!res.done;
|
||||
res = await Promise.race([status, trigger.next()])
|
||||
) {
|
||||
const response: CheckResult = await Promise.resolve(
|
||||
this.ready.fn(),
|
||||
).catch((err) => {
|
||||
console.error(err)
|
||||
return {
|
||||
status: "failure",
|
||||
message: "message" in err ? err.message : String(err),
|
||||
}
|
||||
})
|
||||
this.setHealth(response)
|
||||
if (response.status === "success") {
|
||||
this.#hadSuccess = true
|
||||
}
|
||||
}
|
||||
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
|
||||
|
||||
this.#healthCheckCleanup = () => {
|
||||
setStatus({ done: true })
|
||||
this.#healthCheckCleanup = null
|
||||
}
|
||||
}
|
||||
|
||||
private setHealth(health: CheckResult) {
|
||||
this.#health = health
|
||||
this.#healthWatchers.forEach((watcher) => watcher())
|
||||
const display = this.ready.display
|
||||
const status = health.status
|
||||
if (!display) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
status === "success" ||
|
||||
status === "disabled" ||
|
||||
status === "starting"
|
||||
) {
|
||||
this.effects.setHealth({
|
||||
result: status,
|
||||
message: health.message,
|
||||
id: this.id,
|
||||
name: display,
|
||||
})
|
||||
} else {
|
||||
this.effects.setHealth({
|
||||
result: health.status,
|
||||
message: health.message || "",
|
||||
id: this.id,
|
||||
name: display,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private async updateStatus() {
|
||||
const healths = this.dependencies.map((d) => d.#health)
|
||||
this.changeRunning(healths.every((x) => x.status === "success"))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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
|
||||
/**
|
||||
* Used to ensure that the main function is running with the valid proofs.
|
||||
* We first do the folowing order of things
|
||||
@@ -17,12 +18,12 @@ import { MainEffects } from "../StartSdk"
|
||||
* @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
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { ValidEmVer } from "../emverLite/mod"
|
||||
import { ValidateExVer, ValidateExVers } from "../exver"
|
||||
import {
|
||||
ActionMetadata,
|
||||
HardwareRequirements,
|
||||
ImageConfig,
|
||||
ImageId,
|
||||
ImageSource,
|
||||
} from "../types"
|
||||
|
||||
export interface 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 */
|
||||
@@ -22,13 +19,12 @@ 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.*/
|
||||
readonly license: string // name of license
|
||||
/** A list of normie (hosted, SaaS, custodial, etc) services this services intends to replace */
|
||||
readonly replaces: Readonly<string[]>
|
||||
/** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this),
|
||||
* any scripts necessary for configuration, backups, actions, or health checks (more below). This key
|
||||
* must exist. But could be embedded into the source repository
|
||||
@@ -51,36 +47,49 @@ export type SDKManifest = {
|
||||
}
|
||||
|
||||
/** Defines the os images needed to run the container processes */
|
||||
readonly images: string[]
|
||||
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 interface ManifestDependency {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,20 +1,71 @@
|
||||
import { SDKManifest, ManifestVersion } from "./ManifestTypes"
|
||||
import * as T from "../types"
|
||||
import { ImageConfig, ImageId, VolumeId } from "../osBindings"
|
||||
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 string,
|
||||
AssetTypes extends string,
|
||||
ImagesTypes extends string,
|
||||
Manifest extends SDKManifest & {
|
||||
VolumesTypes extends VolumeId,
|
||||
AssetTypes extends VolumeId,
|
||||
ImagesTypes extends ImageId,
|
||||
Manifest extends SDKManifest<Version, Satisfies> & {
|
||||
dependencies: Dependencies
|
||||
id: Id
|
||||
version: Version
|
||||
assets: AssetTypes[]
|
||||
images: ImagesTypes[]
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
7
sdk/lib/osBindings/AcceptSigners.ts
Normal file
7
sdk/lib/osBindings/AcceptSigners.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
|
||||
|
||||
export type AcceptSigners =
|
||||
| { signer: AnyVerifyingKey }
|
||||
| { any: Array<AcceptSigners> }
|
||||
| { all: Array<AcceptSigners> }
|
||||
3
sdk/lib/osBindings/ActionId.ts
Normal file
3
sdk/lib/osBindings/ActionId.ts
Normal 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 ActionId = string
|
||||
12
sdk/lib/osBindings/ActionMetadata.ts
Normal file
12
sdk/lib/osBindings/ActionMetadata.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AllowedStatuses } from "./AllowedStatuses"
|
||||
|
||||
export type ActionMetadata = {
|
||||
name: string
|
||||
description: string
|
||||
warning: string | null
|
||||
input: any
|
||||
disabled: boolean
|
||||
allowedStatuses: AllowedStatuses
|
||||
group: string | null
|
||||
}
|
||||
4
sdk/lib/osBindings/AddAdminParams.ts
Normal file
4
sdk/lib/osBindings/AddAdminParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Guid } from "./Guid"
|
||||
|
||||
export type AddAdminParams = { signer: Guid }
|
||||
11
sdk/lib/osBindings/AddAssetParams.ts
Normal file
11
sdk/lib/osBindings/AddAssetParams.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AnySignature } from "./AnySignature"
|
||||
import type { Blake3Commitment } from "./Blake3Commitment"
|
||||
|
||||
export type AddAssetParams = {
|
||||
version: string
|
||||
platform: string
|
||||
url: string
|
||||
signature: AnySignature
|
||||
commitment: Blake3Commitment
|
||||
}
|
||||
9
sdk/lib/osBindings/AddPackageParams.ts
Normal file
9
sdk/lib/osBindings/AddPackageParams.ts
Normal 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 { AnySignature } from "./AnySignature"
|
||||
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
|
||||
|
||||
export type AddPackageParams = {
|
||||
url: string
|
||||
commitment: MerkleArchiveCommitment
|
||||
signature: AnySignature
|
||||
}
|
||||
7
sdk/lib/osBindings/AddSslOptions.ts
Normal file
7
sdk/lib/osBindings/AddSslOptions.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AlpnInfo } from "./AlpnInfo"
|
||||
|
||||
export type AddSslOptions = {
|
||||
preferredExternalPort: number
|
||||
alpn: AlpnInfo | null
|
||||
}
|
||||
8
sdk/lib/osBindings/AddVersionParams.ts
Normal file
8
sdk/lib/osBindings/AddVersionParams.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AddVersionParams = {
|
||||
version: string
|
||||
headline: string
|
||||
releaseNotes: string
|
||||
sourceVersion: string
|
||||
}
|
||||
11
sdk/lib/osBindings/AddressInfo.ts
Normal file
11
sdk/lib/osBindings/AddressInfo.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HostId } from "./HostId"
|
||||
|
||||
export type AddressInfo = {
|
||||
username: string | null
|
||||
hostId: HostId
|
||||
internalPort: number
|
||||
scheme: string | null
|
||||
sslScheme: string | null
|
||||
suffix: string
|
||||
}
|
||||
9
sdk/lib/osBindings/Alerts.ts
Normal file
9
sdk/lib/osBindings/Alerts.ts
Normal 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.
|
||||
|
||||
export type Alerts = {
|
||||
install: string | null
|
||||
uninstall: string | null
|
||||
restore: string | null
|
||||
start: string | null
|
||||
stop: string | null
|
||||
}
|
||||
3
sdk/lib/osBindings/Algorithm.ts
Normal file
3
sdk/lib/osBindings/Algorithm.ts
Normal 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 Algorithm = "ecdsa" | "ed25519"
|
||||
5
sdk/lib/osBindings/AllPackageData.ts
Normal file
5
sdk/lib/osBindings/AllPackageData.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageDataEntry } from "./PackageDataEntry"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type AllPackageData = { [key: PackageId]: PackageDataEntry }
|
||||
3
sdk/lib/osBindings/AllowedStatuses.ts
Normal file
3
sdk/lib/osBindings/AllowedStatuses.ts
Normal 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 AllowedStatuses = "onlyRunning" | "onlyStopped" | "any"
|
||||
4
sdk/lib/osBindings/AlpnInfo.ts
Normal file
4
sdk/lib/osBindings/AlpnInfo.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MaybeUtf8String } from "./MaybeUtf8String"
|
||||
|
||||
export type AlpnInfo = "reflect" | { specified: Array<MaybeUtf8String> }
|
||||
3
sdk/lib/osBindings/AnySignature.ts
Normal file
3
sdk/lib/osBindings/AnySignature.ts
Normal 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 AnySignature = string
|
||||
3
sdk/lib/osBindings/AnySigningKey.ts
Normal file
3
sdk/lib/osBindings/AnySigningKey.ts
Normal 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 AnySigningKey = string
|
||||
3
sdk/lib/osBindings/AnyVerifyingKey.ts
Normal file
3
sdk/lib/osBindings/AnyVerifyingKey.ts
Normal 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 AnyVerifyingKey = string
|
||||
3
sdk/lib/osBindings/ApiState.ts
Normal file
3
sdk/lib/osBindings/ApiState.ts
Normal 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 ApiState = "error" | "initializing" | "running"
|
||||
7
sdk/lib/osBindings/AttachParams.ts
Normal file
7
sdk/lib/osBindings/AttachParams.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { EncryptedWire } from "./EncryptedWire"
|
||||
|
||||
export type AttachParams = {
|
||||
startOsPassword: EncryptedWire | null
|
||||
guid: string
|
||||
}
|
||||
3
sdk/lib/osBindings/BackupProgress.ts
Normal file
3
sdk/lib/osBindings/BackupProgress.ts
Normal 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 BackupProgress = { complete: boolean }
|
||||
7
sdk/lib/osBindings/BackupTargetFS.ts
Normal file
7
sdk/lib/osBindings/BackupTargetFS.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BlockDev } from "./BlockDev"
|
||||
import type { Cifs } from "./Cifs"
|
||||
|
||||
export type BackupTargetFS =
|
||||
| ({ type: "disk" } & BlockDev)
|
||||
| ({ type: "cifs" } & Cifs)
|
||||
3
sdk/lib/osBindings/Base64.ts
Normal file
3
sdk/lib/osBindings/Base64.ts
Normal 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 Base64 = string
|
||||
5
sdk/lib/osBindings/BindInfo.ts
Normal file
5
sdk/lib/osBindings/BindInfo.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BindOptions } from "./BindOptions"
|
||||
import type { LanInfo } from "./LanInfo"
|
||||
|
||||
export type BindInfo = { options: BindOptions; lan: LanInfo }
|
||||
9
sdk/lib/osBindings/BindOptions.ts
Normal file
9
sdk/lib/osBindings/BindOptions.ts
Normal 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 { AddSslOptions } from "./AddSslOptions"
|
||||
import type { Security } from "./Security"
|
||||
|
||||
export type BindOptions = {
|
||||
preferredExternalPort: number
|
||||
addSsl: AddSslOptions | null
|
||||
secure: Security | null
|
||||
}
|
||||
14
sdk/lib/osBindings/BindParams.ts
Normal file
14
sdk/lib/osBindings/BindParams.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AddSslOptions } from "./AddSslOptions"
|
||||
import type { HostId } from "./HostId"
|
||||
import type { HostKind } from "./HostKind"
|
||||
import type { Security } from "./Security"
|
||||
|
||||
export type BindParams = {
|
||||
kind: HostKind
|
||||
id: HostId
|
||||
internalPort: number
|
||||
preferredExternalPort: number
|
||||
addSsl: AddSslOptions | null
|
||||
secure: Security | null
|
||||
}
|
||||
4
sdk/lib/osBindings/Blake3Commitment.ts
Normal file
4
sdk/lib/osBindings/Blake3Commitment.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Base64 } from "./Base64"
|
||||
|
||||
export type Blake3Commitment = { hash: Base64; size: number }
|
||||
3
sdk/lib/osBindings/BlockDev.ts
Normal file
3
sdk/lib/osBindings/BlockDev.ts
Normal 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 BlockDev = { logicalname: string }
|
||||
3
sdk/lib/osBindings/CallbackId.ts
Normal file
3
sdk/lib/osBindings/CallbackId.ts
Normal 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 CallbackId = number
|
||||
4
sdk/lib/osBindings/Category.ts
Normal file
4
sdk/lib/osBindings/Category.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Description } from "./Description"
|
||||
|
||||
export type Category = { name: string; description: Description }
|
||||
4
sdk/lib/osBindings/CheckDependenciesParam.ts
Normal file
4
sdk/lib/osBindings/CheckDependenciesParam.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CheckDependenciesParam = { packageIds?: Array<PackageId> }
|
||||
13
sdk/lib/osBindings/CheckDependenciesResult.ts
Normal file
13
sdk/lib/osBindings/CheckDependenciesResult.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HealthCheckId } from "./HealthCheckId"
|
||||
import type { HealthCheckResult } from "./HealthCheckResult"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CheckDependenciesResult = {
|
||||
packageId: PackageId
|
||||
isInstalled: boolean
|
||||
isRunning: boolean
|
||||
configSatisfied: boolean
|
||||
healthChecks: { [key: HealthCheckId]: HealthCheckResult }
|
||||
version: string | null
|
||||
}
|
||||
8
sdk/lib/osBindings/Cifs.ts
Normal file
8
sdk/lib/osBindings/Cifs.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Cifs = {
|
||||
hostname: string
|
||||
path: string
|
||||
username: string
|
||||
password: string | null
|
||||
}
|
||||
6
sdk/lib/osBindings/ContactInfo.ts
Normal file
6
sdk/lib/osBindings/ContactInfo.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ContactInfo =
|
||||
| { email: string }
|
||||
| { matrix: string }
|
||||
| { website: string }
|
||||
4
sdk/lib/osBindings/CreateOverlayedImageParams.ts
Normal file
4
sdk/lib/osBindings/CreateOverlayedImageParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ImageId } from "./ImageId"
|
||||
|
||||
export type CreateOverlayedImageParams = { imageId: ImageId }
|
||||
5
sdk/lib/osBindings/CurrentDependencies.ts
Normal file
5
sdk/lib/osBindings/CurrentDependencies.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CurrentDependencyInfo } from "./CurrentDependencyInfo"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CurrentDependencies = { [key: PackageId]: CurrentDependencyInfo }
|
||||
9
sdk/lib/osBindings/CurrentDependencyInfo.ts
Normal file
9
sdk/lib/osBindings/CurrentDependencyInfo.ts
Normal 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 CurrentDependencyInfo = {
|
||||
title: string | null
|
||||
icon: DataUrl | null
|
||||
versionRange: string
|
||||
configSatisfied: boolean
|
||||
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
|
||||
3
sdk/lib/osBindings/DataUrl.ts
Normal file
3
sdk/lib/osBindings/DataUrl.ts
Normal 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 DataUrl = string
|
||||
8
sdk/lib/osBindings/DepInfo.ts
Normal file
8
sdk/lib/osBindings/DepInfo.ts
Normal file
@@ -0,0 +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
|
||||
s9pk: PathOrUrl | null
|
||||
}
|
||||
5
sdk/lib/osBindings/Dependencies.ts
Normal file
5
sdk/lib/osBindings/Dependencies.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DepInfo } from "./DepInfo"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type Dependencies = { [key: PackageId]: DepInfo }
|
||||
3
sdk/lib/osBindings/DependencyKind.ts
Normal file
3
sdk/lib/osBindings/DependencyKind.ts
Normal 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 DependencyKind = "exists" | "running"
|
||||
9
sdk/lib/osBindings/DependencyMetadata.ts
Normal file
9
sdk/lib/osBindings/DependencyMetadata.ts
Normal 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
|
||||
}
|
||||
12
sdk/lib/osBindings/DependencyRequirement.ts
Normal file
12
sdk/lib/osBindings/DependencyRequirement.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HealthCheckId } from "./HealthCheckId"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type DependencyRequirement =
|
||||
| {
|
||||
kind: "running"
|
||||
id: PackageId
|
||||
healthChecks: Array<HealthCheckId>
|
||||
versionRange: string
|
||||
}
|
||||
| { kind: "exists"; id: PackageId; versionRange: string }
|
||||
3
sdk/lib/osBindings/Description.ts
Normal file
3
sdk/lib/osBindings/Description.ts
Normal 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 Description = { short: string; long: string }
|
||||
4
sdk/lib/osBindings/DestroyOverlayedImageParams.ts
Normal file
4
sdk/lib/osBindings/DestroyOverlayedImageParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Guid } from "./Guid"
|
||||
|
||||
export type DestroyOverlayedImageParams = { guid: Guid }
|
||||
3
sdk/lib/osBindings/Duration.ts
Normal file
3
sdk/lib/osBindings/Duration.ts
Normal 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 Duration = string
|
||||
3
sdk/lib/osBindings/EchoParams.ts
Normal file
3
sdk/lib/osBindings/EchoParams.ts
Normal 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 EchoParams = { message: string }
|
||||
3
sdk/lib/osBindings/EncryptedWire.ts
Normal file
3
sdk/lib/osBindings/EncryptedWire.ts
Normal 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 EncryptedWire = { encrypted: any }
|
||||
9
sdk/lib/osBindings/ExecuteAction.ts
Normal file
9
sdk/lib/osBindings/ExecuteAction.ts
Normal 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 { ActionId } from "./ActionId"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type ExecuteAction = {
|
||||
packageId?: PackageId
|
||||
actionId: ActionId
|
||||
input: any
|
||||
}
|
||||
10
sdk/lib/osBindings/ExportActionParams.ts
Normal file
10
sdk/lib/osBindings/ExportActionParams.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionId } from "./ActionId"
|
||||
import type { ActionMetadata } from "./ActionMetadata"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type ExportActionParams = {
|
||||
packageId?: PackageId
|
||||
id: ActionId
|
||||
metadata: ActionMetadata
|
||||
}
|
||||
15
sdk/lib/osBindings/ExportServiceInterfaceParams.ts
Normal file
15
sdk/lib/osBindings/ExportServiceInterfaceParams.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AddressInfo } from "./AddressInfo"
|
||||
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
|
||||
import type { ServiceInterfaceType } from "./ServiceInterfaceType"
|
||||
|
||||
export type ExportServiceInterfaceParams = {
|
||||
id: ServiceInterfaceId
|
||||
name: string
|
||||
description: string
|
||||
hasPrimary: boolean
|
||||
disabled: boolean
|
||||
masked: boolean
|
||||
addressInfo: AddressInfo
|
||||
type: ServiceInterfaceType
|
||||
}
|
||||
3
sdk/lib/osBindings/ExposeForDependentsParams.ts
Normal file
3
sdk/lib/osBindings/ExposeForDependentsParams.ts
Normal 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 ExposeForDependentsParams = { paths: string[] }
|
||||
14
sdk/lib/osBindings/FullIndex.ts
Normal file
14
sdk/lib/osBindings/FullIndex.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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"
|
||||
import type { Guid } from "./Guid"
|
||||
import type { OsIndex } from "./OsIndex"
|
||||
import type { PackageIndex } from "./PackageIndex"
|
||||
import type { SignerInfo } from "./SignerInfo"
|
||||
|
||||
export type FullIndex = {
|
||||
name: string | null
|
||||
icon: DataUrl | null
|
||||
package: PackageIndex
|
||||
os: OsIndex
|
||||
signers: { [key: Guid]: SignerInfo }
|
||||
}
|
||||
5
sdk/lib/osBindings/FullProgress.ts
Normal file
5
sdk/lib/osBindings/FullProgress.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { NamedProgress } from "./NamedProgress"
|
||||
import type { Progress } from "./Progress"
|
||||
|
||||
export type FullProgress = { overall: Progress; phases: Array<NamedProgress> }
|
||||
4
sdk/lib/osBindings/GetConfiguredParams.ts
Normal file
4
sdk/lib/osBindings/GetConfiguredParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type GetConfiguredParams = { packageId?: PackageId }
|
||||
10
sdk/lib/osBindings/GetHostInfoParams.ts
Normal file
10
sdk/lib/osBindings/GetHostInfoParams.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CallbackId } from "./CallbackId"
|
||||
import type { HostId } from "./HostId"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type GetHostInfoParams = {
|
||||
hostId: HostId
|
||||
packageId?: PackageId
|
||||
callback?: CallbackId
|
||||
}
|
||||
3
sdk/lib/osBindings/GetOsAssetParams.ts
Normal file
3
sdk/lib/osBindings/GetOsAssetParams.ts
Normal 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 GetOsAssetParams = { version: string; platform: string }
|
||||
8
sdk/lib/osBindings/GetOsVersionParams.ts
Normal file
8
sdk/lib/osBindings/GetOsVersionParams.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GetOsVersionParams = {
|
||||
source: string | null
|
||||
target: string | null
|
||||
serverId: string | null
|
||||
arch: string | null
|
||||
}
|
||||
11
sdk/lib/osBindings/GetPackageParams.ts
Normal file
11
sdk/lib/osBindings/GetPackageParams.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageDetailLevel } from "./PackageDetailLevel"
|
||||
import type { PackageId } from "./PackageId"
|
||||
import type { Version } from "./Version"
|
||||
|
||||
export type GetPackageParams = {
|
||||
id: PackageId | null
|
||||
version: string | null
|
||||
sourceVersion: Version | null
|
||||
otherVersions: PackageDetailLevel
|
||||
}
|
||||
10
sdk/lib/osBindings/GetPackageResponse.ts
Normal file
10
sdk/lib/osBindings/GetPackageResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageInfoShort } from "./PackageInfoShort"
|
||||
import type { PackageVersionInfo } from "./PackageVersionInfo"
|
||||
import type { Version } from "./Version"
|
||||
|
||||
export type GetPackageResponse = {
|
||||
categories: string[]
|
||||
best: { [key: Version]: PackageVersionInfo }
|
||||
otherVersions?: { [key: Version]: PackageInfoShort }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user