mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
addHealthCheck instead of additionalHealthChecks for Daemons (#2962)
* addHealthCheck on Daemons * fix bug that prevents domains without protocols from being deleted * fixes from testing * version bump * add sdk version to UI * fix useEntrypoint * fix dependency health check error display * minor fixes * beta.29 * fixes from testing * beta.30 * set /etc/os-release (#2918) * remove check-monitor from kiosk (#2059) * add units for progress (#2693) * use new progress type * alpha.7 * fix up pwa stuff * fix wormhole-squashfs and prune boot (#2964) * don't exit on expected errors * use bash --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
export type GetOsVersionParams = {
|
||||
sourceVersion: string | null
|
||||
targetVersion: string | null
|
||||
includePrerelease: boolean | null
|
||||
serverId: string | null
|
||||
platform: string | null
|
||||
}
|
||||
|
||||
@@ -32,4 +32,5 @@ export type Manifest = {
|
||||
hardwareRequirements: HardwareRequirements
|
||||
gitHash?: GitHash
|
||||
osVersion: string
|
||||
sdkVersion: string | null
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ProgressUnits } from "./ProgressUnits"
|
||||
|
||||
export type Progress = null | boolean | { done: number; total: number | null }
|
||||
export type Progress =
|
||||
| null
|
||||
| boolean
|
||||
| { done: number; total: number | null; units: ProgressUnits | null }
|
||||
|
||||
3
sdk/base/lib/osBindings/ProgressUnits.ts
Normal file
3
sdk/base/lib/osBindings/ProgressUnits.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 ProgressUnits = "bytes" | "steps"
|
||||
@@ -157,6 +157,7 @@ export { PathOrUrl } from "./PathOrUrl"
|
||||
export { Percentage } from "./Percentage"
|
||||
export { ProcedureId } from "./ProcedureId"
|
||||
export { Progress } from "./Progress"
|
||||
export { ProgressUnits } from "./ProgressUnits"
|
||||
export { Public } from "./Public"
|
||||
export { RecoverySource } from "./RecoverySource"
|
||||
export { RegistryAsset } from "./RegistryAsset"
|
||||
|
||||
@@ -106,7 +106,7 @@ export class UseEntrypoint {
|
||||
export function isUseEntrypoint(
|
||||
command: CommandType,
|
||||
): command is UseEntrypoint {
|
||||
return typeof command === "object" && "ENTRYPOINT" in command
|
||||
return typeof command === "object" && "USE_ENTRYPOINT" in command
|
||||
}
|
||||
|
||||
export type CommandType = string | [string, ...string[]] | UseEntrypoint
|
||||
|
||||
@@ -17,7 +17,6 @@ import * as patterns from "../../base/lib/util/patterns"
|
||||
import { BackupSync, Backups } from "./backup/Backups"
|
||||
import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants"
|
||||
import { Daemon, Daemons } from "./mainFn/Daemons"
|
||||
import { HealthCheck } from "./health/HealthCheck"
|
||||
import { checkPortListening } from "./health/checkFns/checkPortListening"
|
||||
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
||||
import { List } from "../../base/lib/actions/input/builder/list"
|
||||
@@ -25,10 +24,7 @@ import { SetupBackupsParams, setupBackups } from "./backup/setupBackups"
|
||||
import { setupMain } from "./mainFn"
|
||||
import { defaultTrigger } from "./trigger/defaultTrigger"
|
||||
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
||||
import {
|
||||
UpdateServiceInterfaces,
|
||||
setupServiceInterfaces,
|
||||
} from "../../base/lib/interfaces/setupInterfaces"
|
||||
import { setupServiceInterfaces } from "../../base/lib/interfaces/setupInterfaces"
|
||||
import { successFailure } from "./trigger/successFailure"
|
||||
import { MultiHost, Scheme } from "../../base/lib/interfaces/Host"
|
||||
import { ServiceInterfaceBuilder } from "../../base/lib/interfaces/ServiceInterfaceBuilder"
|
||||
@@ -45,17 +41,13 @@ import { splitCommand } from "./util"
|
||||
import { Mounts } from "./mainFn/Mounts"
|
||||
import { setupDependencies } from "../../base/lib/dependencies/setupDependencies"
|
||||
import * as T from "../../base/lib/types"
|
||||
import {
|
||||
ExtendedVersion,
|
||||
testTypeVersion,
|
||||
VersionRange,
|
||||
} from "../../base/lib/exver"
|
||||
import { testTypeVersion } from "../../base/lib/exver"
|
||||
import {
|
||||
CheckDependencies,
|
||||
checkDependencies,
|
||||
} from "../../base/lib/dependencies/dependencies"
|
||||
import { GetSslCertificate } from "./util"
|
||||
import { getDataVersion, setDataVersion, VersionGraph } from "./version"
|
||||
import { getDataVersion, setDataVersion } from "./version"
|
||||
import { MaybeFn } from "../../base/lib/actions/setupActions"
|
||||
import { GetInput } from "../../base/lib/actions/setupActions"
|
||||
import { Run } from "../../base/lib/actions/setupActions"
|
||||
@@ -68,7 +60,7 @@ import {
|
||||
setupOnUninit,
|
||||
} from "../../base/lib/inits"
|
||||
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.6")
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.7")
|
||||
|
||||
// prettier-ignore
|
||||
type AnyNeverCond<T extends any[], Then, Else> =
|
||||
@@ -95,7 +87,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
| "clearServiceInterfaces"
|
||||
| "bind"
|
||||
| "getHostInfo"
|
||||
type MainUsedEffects = "setMainStatus" | "setHealth"
|
||||
type MainUsedEffects = "setMainStatus"
|
||||
type CallbackEffects =
|
||||
| "child"
|
||||
| "constRetry"
|
||||
@@ -129,6 +121,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
shutdown: (effects, ...args) => effects.shutdown(...args),
|
||||
getDependencies: (effects, ...args) => effects.getDependencies(...args),
|
||||
getStatus: (effects, ...args) => effects.getStatus(...args),
|
||||
setHealth: (effects, ...args) => effects.setHealth(...args),
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -454,7 +447,6 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
hostnames: string[],
|
||||
algorithm?: T.Algorithm,
|
||||
) => new GetSslCertificate(effects, hostnames, algorithm),
|
||||
HealthCheck,
|
||||
healthCheck: {
|
||||
checkPortListening,
|
||||
checkWebUrl,
|
||||
@@ -652,19 +644,12 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
successFailure,
|
||||
},
|
||||
Mounts: {
|
||||
of() {
|
||||
return Mounts.of<Manifest>()
|
||||
},
|
||||
of: Mounts.of<Manifest>,
|
||||
},
|
||||
Backups: {
|
||||
volumes: (
|
||||
...volumeNames: Array<Manifest["volumes"][number] & string>
|
||||
) => Backups.withVolumes<Manifest>(...volumeNames),
|
||||
addSets: (
|
||||
...options: BackupSync<Manifest["volumes"][number] & string>[]
|
||||
) => Backups.withSyncs<Manifest>(...options),
|
||||
withOptions: (options?: Partial<SyncOptions>) =>
|
||||
Backups.withOptions<Manifest>(options),
|
||||
ofVolumes: Backups.ofVolumes<Manifest>,
|
||||
ofSyncs: Backups.ofSyncs<Manifest>,
|
||||
withOptions: Backups.withOptions<Manifest>,
|
||||
},
|
||||
InputSpec: {
|
||||
/**
|
||||
@@ -705,10 +690,11 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
||||
Daemons: {
|
||||
of(
|
||||
effects: Effects,
|
||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
||||
healthChecks: HealthCheck[],
|
||||
started:
|
||||
| ((onTerm: () => PromiseLike<void>) => PromiseLike<null>)
|
||||
| null,
|
||||
) {
|
||||
return Daemons.of<Manifest>({ effects, started, healthChecks })
|
||||
return Daemons.of<Manifest>({ effects, started })
|
||||
},
|
||||
},
|
||||
SubContainer: {
|
||||
|
||||
@@ -31,10 +31,10 @@ export class Backups<M extends T.SDKManifest> implements InitScript {
|
||||
private postRestore = async (effects: BackupEffects) => {},
|
||||
) {}
|
||||
|
||||
static withVolumes<M extends T.SDKManifest = never>(
|
||||
static ofVolumes<M extends T.SDKManifest = never>(
|
||||
...volumeNames: Array<M["volumes"][number]>
|
||||
): Backups<M> {
|
||||
return Backups.withSyncs(
|
||||
return Backups.ofSyncs(
|
||||
...volumeNames.map((srcVolume) => ({
|
||||
dataPath: `/media/startos/volumes/${srcVolume}/` as const,
|
||||
backupPath: `/media/startos/backup/volumes/${srcVolume}/` as const,
|
||||
@@ -42,7 +42,7 @@ export class Backups<M extends T.SDKManifest> implements InitScript {
|
||||
)
|
||||
}
|
||||
|
||||
static withSyncs<M extends T.SDKManifest = never>(
|
||||
static ofSyncs<M extends T.SDKManifest = never>(
|
||||
...syncs: BackupSync<M["volumes"][number]>[]
|
||||
) {
|
||||
return syncs.reduce((acc, x) => acc.addSync(x), new Backups<M>())
|
||||
@@ -112,11 +112,9 @@ export class Backups<M extends T.SDKManifest> implements InitScript {
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
addSync(sync: BackupSync<M["volumes"][0]>) {
|
||||
this.backupSet.push({
|
||||
...sync,
|
||||
options: { ...this.options, ...sync.options },
|
||||
})
|
||||
this.backupSet.push(sync)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export function setupBackups<M extends T.SDKManifest>(
|
||||
if (options instanceof Function) {
|
||||
backupsFactory = options
|
||||
} else {
|
||||
backupsFactory = async () => Backups.withVolumes(...options)
|
||||
backupsFactory = async () => Backups.ofVolumes(...options)
|
||||
}
|
||||
const answer: SetupBackupsRes = {
|
||||
get createBackup() {
|
||||
|
||||
@@ -12,7 +12,6 @@ export type HealthCheckParams = {
|
||||
trigger?: Trigger
|
||||
gracePeriod?: number
|
||||
fn(): Promise<HealthCheckResult> | HealthCheckResult
|
||||
onFirstSuccess?: () => unknown | Promise<unknown>
|
||||
}
|
||||
|
||||
export class HealthCheck extends Drop {
|
||||
@@ -32,13 +31,6 @@ export class HealthCheck extends Drop {
|
||||
const getCurrentValue = () => this.currentValue
|
||||
const gracePeriod = o.gracePeriod ?? 10_000
|
||||
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
||||
const triggerFirstSuccess = once(() =>
|
||||
Promise.resolve(
|
||||
"onFirstSuccess" in o && o.onFirstSuccess
|
||||
? o.onFirstSuccess()
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
const checkStarted = () =>
|
||||
[
|
||||
this.started,
|
||||
@@ -78,9 +70,6 @@ export class HealthCheck extends Drop {
|
||||
message: message || "",
|
||||
})
|
||||
this.currentValue.lastResult = result
|
||||
await triggerFirstSuccess().catch((err) => {
|
||||
console.error(asError(err))
|
||||
})
|
||||
} catch (e) {
|
||||
await effects.setHealth({
|
||||
name: o.name,
|
||||
|
||||
@@ -2,44 +2,49 @@ import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
||||
import { NO_TIMEOUT, SIGTERM } from "../../../base/lib/types"
|
||||
|
||||
import * as T from "../../../base/lib/types"
|
||||
import { MountOptions, SubContainer } from "../util/SubContainer"
|
||||
import { SubContainer } from "../util/SubContainer"
|
||||
import { Drop, splitCommand } from "../util"
|
||||
import * as cp from "child_process"
|
||||
import * as fs from "node:fs/promises"
|
||||
import { Mounts } from "./Mounts"
|
||||
import { DaemonCommandType } from "./Daemons"
|
||||
import { DaemonCommandType, ExecCommandOptions, ExecFnOptions } from "./Daemons"
|
||||
|
||||
export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
export class CommandController<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null,
|
||||
> extends Drop {
|
||||
private constructor(
|
||||
readonly runningAnswer: Promise<null>,
|
||||
private state: { exited: boolean },
|
||||
private readonly subcontainer: SubContainer<Manifest>,
|
||||
private readonly subcontainer: C,
|
||||
private process: cp.ChildProcess | AbortController,
|
||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
static of<Manifest extends T.SDKManifest>() {
|
||||
static of<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null,
|
||||
>() {
|
||||
return async (
|
||||
effects: T.Effects,
|
||||
subcontainer: SubContainer<Manifest>,
|
||||
exec: DaemonCommandType,
|
||||
subcontainer: C,
|
||||
exec: DaemonCommandType<Manifest, C>,
|
||||
) => {
|
||||
try {
|
||||
if ("fn" in exec) {
|
||||
const abort = new AbortController()
|
||||
const cell: { ctrl: CommandController<Manifest> } = {
|
||||
ctrl: new CommandController(
|
||||
const cell: { ctrl: CommandController<Manifest, C> } = {
|
||||
ctrl: new CommandController<Manifest, C>(
|
||||
exec.fn(subcontainer, abort).then(async (command) => {
|
||||
if (command && !abort.signal.aborted) {
|
||||
Object.assign(
|
||||
cell.ctrl,
|
||||
await CommandController.of<Manifest>()(
|
||||
effects,
|
||||
subcontainer,
|
||||
command,
|
||||
),
|
||||
)
|
||||
if (subcontainer && command && !abort.signal.aborted) {
|
||||
const newCtrl = (
|
||||
await CommandController.of<
|
||||
Manifest,
|
||||
SubContainer<Manifest>
|
||||
>()(effects, subcontainer, command as ExecCommandOptions)
|
||||
).leak()
|
||||
|
||||
Object.assign(cell.ctrl, newCtrl)
|
||||
return await cell.ctrl.runningAnswer
|
||||
} else {
|
||||
cell.ctrl.state.exited = true
|
||||
@@ -57,7 +62,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
let commands: string[]
|
||||
if (T.isUseEntrypoint(exec.command)) {
|
||||
const imageMeta: T.ImageMetadata = await fs
|
||||
.readFile(`/media/startos/images/${subcontainer.imageId}.json`, {
|
||||
.readFile(`/media/startos/images/${subcontainer!.imageId}.json`, {
|
||||
encoding: "utf8",
|
||||
})
|
||||
.catch(() => "{}")
|
||||
@@ -70,11 +75,11 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
|
||||
let childProcess: cp.ChildProcess
|
||||
if (exec.runAsInit) {
|
||||
childProcess = await subcontainer.launch(commands, {
|
||||
childProcess = await subcontainer!.launch(commands, {
|
||||
env: exec.env,
|
||||
})
|
||||
} else {
|
||||
childProcess = await subcontainer.spawn(commands, {
|
||||
childProcess = await subcontainer!.spawn(commands, {
|
||||
env: exec.env,
|
||||
stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit",
|
||||
})
|
||||
@@ -108,7 +113,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
})
|
||||
})
|
||||
|
||||
return new CommandController(
|
||||
return new CommandController<Manifest, C>(
|
||||
answer,
|
||||
state,
|
||||
subcontainer,
|
||||
@@ -116,7 +121,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
exec.sigtermTimeout,
|
||||
)
|
||||
} catch (e) {
|
||||
await subcontainer.destroy()
|
||||
await subcontainer?.destroy()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
@@ -144,7 +149,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
if (this.process instanceof AbortController) this.process.abort()
|
||||
else this.process.kill("SIGKILL")
|
||||
}
|
||||
await this.subcontainer.destroy()
|
||||
await this.subcontainer?.destroy()
|
||||
}
|
||||
}
|
||||
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||
@@ -178,7 +183,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
||||
])
|
||||
else await this.runningAnswer
|
||||
} finally {
|
||||
await this.subcontainer.destroy()
|
||||
await this.subcontainer?.destroy()
|
||||
}
|
||||
}
|
||||
onDrop(): void {
|
||||
|
||||
@@ -17,14 +17,17 @@ const MAX_TIMEOUT_MS = 30000
|
||||
* and the others state of running, where it will keep a living running command
|
||||
*/
|
||||
|
||||
export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
||||
private commandController: CommandController<Manifest> | null = null
|
||||
export class Daemon<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null = SubContainer<Manifest> | null,
|
||||
> extends Drop {
|
||||
private commandController: CommandController<Manifest, C> | null = null
|
||||
private shouldBeRunning = false
|
||||
protected exitedSuccess = false
|
||||
private onExitFns: ((success: boolean) => void)[] = []
|
||||
protected constructor(
|
||||
private subcontainer: SubContainer<Manifest>,
|
||||
private startCommand: (() => Promise<CommandController<Manifest>>) | null,
|
||||
private subcontainer: C,
|
||||
private startCommand: () => Promise<CommandController<Manifest, C>>,
|
||||
readonly oneshot: boolean = false,
|
||||
) {
|
||||
super()
|
||||
@@ -33,17 +36,20 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
||||
return this.oneshot
|
||||
}
|
||||
static of<Manifest extends T.SDKManifest>() {
|
||||
return async (
|
||||
return async <C extends SubContainer<Manifest> | null>(
|
||||
effects: T.Effects,
|
||||
subcontainer: SubContainer<Manifest>,
|
||||
exec: DaemonCommandType | null,
|
||||
subcontainer: C,
|
||||
exec: DaemonCommandType<Manifest, C>,
|
||||
) => {
|
||||
if (subcontainer.isOwned()) subcontainer = subcontainer.rc()
|
||||
const startCommand = exec
|
||||
? () =>
|
||||
CommandController.of<Manifest>()(effects, subcontainer.rc(), exec)
|
||||
: null
|
||||
return new Daemon(subcontainer, startCommand)
|
||||
let subc: SubContainer<Manifest> | null = subcontainer
|
||||
if (subcontainer && subcontainer.isOwned()) subc = subcontainer.rc()
|
||||
const startCommand = () =>
|
||||
CommandController.of<Manifest, C>()(
|
||||
effects,
|
||||
(subc?.rc() ?? null) as C,
|
||||
exec,
|
||||
)
|
||||
return new Daemon(subc, startCommand)
|
||||
}
|
||||
}
|
||||
async start() {
|
||||
@@ -53,7 +59,7 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
||||
this.shouldBeRunning = true
|
||||
let timeoutCounter = 0
|
||||
;(async () => {
|
||||
while (this.startCommand && this.shouldBeRunning) {
|
||||
while (this.shouldBeRunning) {
|
||||
if (this.commandController)
|
||||
await this.commandController
|
||||
.term({})
|
||||
@@ -106,10 +112,10 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
||||
.catch((e) => console.error(asError(e)))
|
||||
this.commandController = null
|
||||
this.onExitFns = []
|
||||
await this.subcontainer.destroy()
|
||||
await this.subcontainer?.destroy()
|
||||
}
|
||||
subcontainerRc(): SubContainerRc<Manifest> {
|
||||
return this.subcontainer.rc()
|
||||
subcontainerRc(): SubContainerRc<Manifest> | null {
|
||||
return this.subcontainer?.rc() ?? null
|
||||
}
|
||||
onExit(fn: (success: boolean) => void) {
|
||||
this.onExitFns.push(fn)
|
||||
|
||||
@@ -37,9 +37,7 @@ export type Ready = {
|
||||
})
|
||||
* ```
|
||||
*/
|
||||
fn: (
|
||||
subcontainer: SubContainer<Manifest>,
|
||||
) => Promise<HealthCheckResult> | HealthCheckResult
|
||||
fn: () => Promise<HealthCheckResult> | HealthCheckResult
|
||||
/**
|
||||
* A duration in milliseconds to treat a failing health check as "starting"
|
||||
*
|
||||
@@ -65,30 +63,40 @@ export type ExecCommandOptions = {
|
||||
onStderr?: (chunk: Buffer | string | any) => void
|
||||
}
|
||||
|
||||
export type ExecFnOptions = {
|
||||
export type ExecFnOptions<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null,
|
||||
> = {
|
||||
fn: (
|
||||
subcontainer: SubContainer<Manifest>,
|
||||
subcontainer: C,
|
||||
abort: AbortController,
|
||||
) => Promise<ExecCommandOptions | null>
|
||||
) => Promise<C extends null ? null : ExecCommandOptions | null>
|
||||
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
||||
sigtermTimeout?: number
|
||||
}
|
||||
|
||||
export type DaemonCommandType = ExecCommandOptions | ExecFnOptions
|
||||
export type DaemonCommandType<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null,
|
||||
> = ExecFnOptions<Manifest, C> | (C extends null ? never : ExecCommandOptions)
|
||||
|
||||
type NewDaemonParams<Manifest extends T.SDKManifest> = {
|
||||
type NewDaemonParams<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null,
|
||||
> = {
|
||||
/** What to run as the daemon: either an async fn or a commandline command to run in the subcontainer */
|
||||
exec: DaemonCommandType | null
|
||||
/** Information about the subcontainer in which the daemon runs */
|
||||
subcontainer: SubContainer<Manifest>
|
||||
exec: DaemonCommandType<Manifest, C>
|
||||
/** The subcontainer in which the daemon runs */
|
||||
subcontainer: C
|
||||
}
|
||||
|
||||
type AddDaemonParams<
|
||||
Manifest extends T.SDKManifest,
|
||||
Ids extends string,
|
||||
Id extends string,
|
||||
C extends SubContainer<Manifest> | null,
|
||||
> = (
|
||||
| NewDaemonParams<Manifest>
|
||||
| NewDaemonParams<Manifest, C>
|
||||
| {
|
||||
daemon: Daemon<Manifest>
|
||||
}
|
||||
@@ -102,8 +110,15 @@ type AddOneshotParams<
|
||||
Manifest extends T.SDKManifest,
|
||||
Ids extends string,
|
||||
Id extends string,
|
||||
> = NewDaemonParams<Manifest> & {
|
||||
exec: DaemonCommandType
|
||||
C extends SubContainer<Manifest> | null,
|
||||
> = NewDaemonParams<Manifest, C> & {
|
||||
exec: DaemonCommandType<Manifest, C>
|
||||
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||
requires: Exclude<Ids, Id>[]
|
||||
}
|
||||
|
||||
type AddHealthCheckParams<Ids extends string, Id extends string> = {
|
||||
ready: Ready
|
||||
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||
requires: Exclude<Ids, Id>[]
|
||||
}
|
||||
@@ -111,7 +126,7 @@ type AddOneshotParams<
|
||||
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
||||
|
||||
export const runCommand = <Manifest extends T.SDKManifest>() =>
|
||||
CommandController.of<Manifest>()
|
||||
CommandController.of<Manifest, SubContainer<Manifest>>()
|
||||
|
||||
/**
|
||||
* A class for defining and controlling the service daemons
|
||||
@@ -141,11 +156,12 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
{
|
||||
private constructor(
|
||||
readonly effects: T.Effects,
|
||||
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
||||
readonly started:
|
||||
| ((onTerm: () => PromiseLike<void>) => PromiseLike<null>)
|
||||
| null,
|
||||
readonly daemons: Promise<Daemon<Manifest>>[],
|
||||
readonly ids: Ids[],
|
||||
readonly healthDaemons: HealthDaemon<Manifest>[],
|
||||
readonly healthChecks: HealthCheck[],
|
||||
) {}
|
||||
/**
|
||||
* Returns an empty new Daemons class with the provided inputSpec.
|
||||
@@ -154,13 +170,18 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
*
|
||||
* Daemons run in the order they are defined, with latter daemons being capable of
|
||||
* depending on prior daemons
|
||||
* @param options
|
||||
*
|
||||
* @param effects
|
||||
*
|
||||
* @param started
|
||||
* @returns
|
||||
*/
|
||||
static of<Manifest extends T.SDKManifest>(options: {
|
||||
effects: T.Effects
|
||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
|
||||
healthChecks: HealthCheck[]
|
||||
/**
|
||||
* A closure to run once the system is launched. If you are in main, provide the `started` argument you receive from the function arguments
|
||||
*/
|
||||
started: ((onTerm: () => PromiseLike<void>) => PromiseLike<null>) | null
|
||||
}) {
|
||||
return new Daemons<Manifest, never>(
|
||||
options.effects,
|
||||
@@ -168,7 +189,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
options.healthChecks,
|
||||
)
|
||||
}
|
||||
/**
|
||||
@@ -177,19 +197,19 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addDaemon<Id extends string>(
|
||||
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
// prettier-ignore
|
||||
id:
|
||||
"" extends Id ? never :
|
||||
ErrorDuplicateId<Id> extends Id ? never :
|
||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||
Id,
|
||||
options: AddDaemonParams<Manifest, Ids, Id>,
|
||||
options: AddDaemonParams<Manifest, Ids, Id, C>,
|
||||
) {
|
||||
const daemon =
|
||||
"daemon" in options
|
||||
? Promise.resolve(options.daemon)
|
||||
: Daemon.of<Manifest>()(
|
||||
: Daemon.of<Manifest>()<C>(
|
||||
this.effects,
|
||||
options.subcontainer,
|
||||
options.exec,
|
||||
@@ -201,11 +221,10 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
this.ids,
|
||||
options.ready,
|
||||
this.effects,
|
||||
)
|
||||
const daemons = this.daemons.concat(daemon)
|
||||
const daemons = [...this.daemons, daemon]
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
@@ -214,7 +233,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
daemons,
|
||||
ids,
|
||||
healthDaemons,
|
||||
this.healthChecks,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -225,7 +243,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addOneshot<Id extends string>(
|
||||
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||
id: "" extends Id
|
||||
? never
|
||||
: ErrorDuplicateId<Id> extends Id
|
||||
@@ -233,9 +251,9 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
: Id extends Ids
|
||||
? ErrorDuplicateId<Id>
|
||||
: Id,
|
||||
options: AddOneshotParams<Manifest, Ids, Id>,
|
||||
options: AddOneshotParams<Manifest, Ids, Id, C>,
|
||||
) {
|
||||
const daemon = Oneshot.of<Manifest>()(
|
||||
const daemon = Oneshot.of<Manifest>()<C>(
|
||||
this.effects,
|
||||
options.subcontainer,
|
||||
options.exec,
|
||||
@@ -247,11 +265,10 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
this.ids,
|
||||
"EXIT_SUCCESS",
|
||||
this.effects,
|
||||
)
|
||||
const daemons = this.daemons.concat(daemon)
|
||||
const daemons = [...this.daemons, daemon]
|
||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||
return new Daemons<Manifest, Ids | Id>(
|
||||
@@ -260,13 +277,95 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
daemons,
|
||||
ids,
|
||||
healthDaemons,
|
||||
this.healthChecks,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the complete list of daemons, including a new HealthCheck defined here
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
addHealthCheck<Id extends string>(
|
||||
id: "" extends Id
|
||||
? never
|
||||
: ErrorDuplicateId<Id> extends Id
|
||||
? never
|
||||
: Id extends Ids
|
||||
? ErrorDuplicateId<Id>
|
||||
: Id,
|
||||
options: AddHealthCheckParams<Ids, Id>,
|
||||
) {
|
||||
const healthDaemon = new HealthDaemon<Manifest>(
|
||||
null,
|
||||
options.requires
|
||||
.map((x) => this.ids.indexOf(x))
|
||||
.filter((x) => x >= 0)
|
||||
.map((id) => this.healthDaemons[id]),
|
||||
id,
|
||||
options.ready,
|
||||
this.effects,
|
||||
)
|
||||
const daemons = [...this.daemons]
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the entire system until all daemons have returned `ready`.
|
||||
* @param id
|
||||
* @param options
|
||||
* @returns a new Daemons object
|
||||
*/
|
||||
async runUntilSuccess(timeout: number | null) {
|
||||
let resolve = (_: void) => {}
|
||||
const res = new Promise<void>((res, rej) => {
|
||||
resolve = res
|
||||
if (timeout)
|
||||
setTimeout(() => {
|
||||
const notReady = this.healthDaemons
|
||||
.filter((d) => !d.isReady)
|
||||
.map((d) => d.id)
|
||||
rej(new Error(`Timed out waiting for ${notReady}`))
|
||||
}, timeout)
|
||||
})
|
||||
const daemon = Oneshot.of()(this.effects, null, {
|
||||
fn: async () => {
|
||||
resolve()
|
||||
return null
|
||||
},
|
||||
})
|
||||
const healthDaemon = new HealthDaemon<Manifest>(
|
||||
daemon,
|
||||
[...this.healthDaemons],
|
||||
"__RUN_UNTIL_SUCCESS",
|
||||
"EXIT_SUCCESS",
|
||||
this.effects,
|
||||
)
|
||||
const daemons = await new Daemons<Manifest, Ids>(
|
||||
this.effects,
|
||||
this.started,
|
||||
[...this.daemons, daemon],
|
||||
this.ids,
|
||||
[...this.healthDaemons, healthDaemon],
|
||||
).build()
|
||||
try {
|
||||
await res
|
||||
} finally {
|
||||
await daemons.term()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async term() {
|
||||
try {
|
||||
this.healthChecks.forEach((health) => health.stop())
|
||||
for (let result of await Promise.allSettled(
|
||||
this.healthDaemons.map((x) => x.term()),
|
||||
)) {
|
||||
@@ -283,10 +382,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
||||
for (const daemon of this.healthDaemons) {
|
||||
await daemon.init()
|
||||
}
|
||||
for (const health of this.healthChecks) {
|
||||
health.start()
|
||||
}
|
||||
this.started(() => this.term())
|
||||
this.started?.(() => this.term())
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { SetHealth, Effects, SDKManifest } from "../../../base/lib/types"
|
||||
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
||||
import { asError } from "../../../base/lib/util/asError"
|
||||
import { Oneshot } from "./Oneshot"
|
||||
import { SubContainer } from "../util/SubContainer"
|
||||
|
||||
const oncePromise = <T>() => {
|
||||
let resolve: (value: T) => void
|
||||
@@ -30,16 +31,22 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
private running = false
|
||||
private started?: number
|
||||
private resolveReady: (() => void) | undefined
|
||||
private resolvedReady: boolean = false
|
||||
private readyPromise: Promise<void>
|
||||
constructor(
|
||||
private readonly daemon: Promise<Daemon<Manifest>>,
|
||||
private readonly daemon: Promise<Daemon<Manifest>> | null,
|
||||
private readonly dependencies: HealthDaemon<Manifest>[],
|
||||
readonly id: string,
|
||||
readonly ids: string[],
|
||||
readonly ready: Ready | typeof EXIT_SUCCESS,
|
||||
readonly effects: Effects,
|
||||
) {
|
||||
this.readyPromise = new Promise((resolve) => (this.resolveReady = resolve))
|
||||
this.readyPromise = new Promise(
|
||||
(resolve) =>
|
||||
(this.resolveReady = () => {
|
||||
resolve()
|
||||
this.resolvedReady = true
|
||||
}),
|
||||
)
|
||||
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()))
|
||||
}
|
||||
|
||||
@@ -52,7 +59,7 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
this.running = false
|
||||
this.healthCheckCleanup?.()
|
||||
|
||||
await this.daemon.then((d) =>
|
||||
await this.daemon?.then((d) =>
|
||||
d.term({
|
||||
...termOptions,
|
||||
}),
|
||||
@@ -74,11 +81,13 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
this.running = newStatus
|
||||
|
||||
if (newStatus) {
|
||||
;(await this.daemon).start()
|
||||
this.started = performance.now()
|
||||
console.debug(`Launching ${this.id}...`)
|
||||
this.setupHealthCheck()
|
||||
;(await this.daemon)?.start()
|
||||
this.started = performance.now()
|
||||
} else {
|
||||
;(await this.daemon).stop()
|
||||
console.debug(`Stopping ${this.id}...`)
|
||||
;(await this.daemon)?.stop()
|
||||
this.turnOffHealthCheck()
|
||||
|
||||
this.setHealth({ result: "starting", message: null })
|
||||
@@ -88,10 +97,19 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
private healthCheckCleanup: (() => null) | null = null
|
||||
private turnOffHealthCheck() {
|
||||
this.healthCheckCleanup?.()
|
||||
|
||||
this.resolvedReady = false
|
||||
this.readyPromise = new Promise(
|
||||
(resolve) =>
|
||||
(this.resolveReady = () => {
|
||||
resolve()
|
||||
this.resolvedReady = true
|
||||
}),
|
||||
)
|
||||
}
|
||||
private async setupHealthCheck() {
|
||||
const daemon = await this.daemon
|
||||
daemon.onExit((success) => {
|
||||
daemon?.onExit((success) => {
|
||||
if (success && this.ready === "EXIT_SUCCESS") {
|
||||
this.setHealth({ result: "success", message: null })
|
||||
} else if (!success) {
|
||||
@@ -122,28 +140,17 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
!res.done;
|
||||
res = await Promise.race([status, trigger.next()])
|
||||
) {
|
||||
const handle = (await this.daemon).subcontainerRc()
|
||||
|
||||
try {
|
||||
const response: HealthCheckResult = await Promise.resolve(
|
||||
this.ready.fn(handle),
|
||||
).catch((err) => {
|
||||
console.error(asError(err))
|
||||
return {
|
||||
result: "failure",
|
||||
message: "message" in err ? err.message : String(err),
|
||||
}
|
||||
})
|
||||
if (
|
||||
this.resolveReady &&
|
||||
(response.result === "success" || response.result === "disabled")
|
||||
) {
|
||||
this.resolveReady()
|
||||
const response: HealthCheckResult = await Promise.resolve(
|
||||
this.ready.fn(),
|
||||
).catch((err) => {
|
||||
console.error(asError(err))
|
||||
return {
|
||||
result: "failure",
|
||||
message: "message" in err ? err.message : String(err),
|
||||
}
|
||||
await this.setHealth(response)
|
||||
} finally {
|
||||
await handle.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
await this.setHealth(response)
|
||||
}
|
||||
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
|
||||
|
||||
@@ -158,10 +165,18 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
return this.readyPromise
|
||||
}
|
||||
|
||||
get isReady() {
|
||||
return this.resolvedReady
|
||||
}
|
||||
|
||||
private async setHealth(health: HealthCheckResult) {
|
||||
const changed = this._health.result !== health.result
|
||||
this._health = health
|
||||
if (this.resolveReady && health.result === "success") {
|
||||
this.resolveReady()
|
||||
}
|
||||
if (changed) this.healthWatchers.forEach((watcher) => watcher())
|
||||
if (this.ready === "EXIT_SUCCESS") return
|
||||
this.healthWatchers.forEach((watcher) => watcher())
|
||||
const display = this.ready.display
|
||||
if (!display) {
|
||||
return
|
||||
@@ -182,8 +197,18 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
||||
}
|
||||
|
||||
async updateStatus() {
|
||||
const healths = this.dependencies.map((d) => d.running && d._health)
|
||||
this.changeRunning(healths.every((x) => x && x.result === "success"))
|
||||
const healths = this.dependencies.map((d) => ({
|
||||
health: d.running && d._health,
|
||||
id: d.id,
|
||||
}))
|
||||
const waitingOn = healths.filter(
|
||||
(h) => !h.health || h.health.result !== "success",
|
||||
)
|
||||
if (waitingOn.length)
|
||||
console.debug(
|
||||
`daemon ${this.id} waiting on ${waitingOn.map((w) => w.id)}`,
|
||||
)
|
||||
this.changeRunning(!waitingOn.length)
|
||||
}
|
||||
|
||||
async init() {
|
||||
|
||||
@@ -10,19 +10,25 @@ import { DaemonCommandType } from "./Daemons"
|
||||
* unlike Daemon, does not restart on success
|
||||
*/
|
||||
|
||||
export class Oneshot<Manifest extends T.SDKManifest> extends Daemon<Manifest> {
|
||||
export class Oneshot<
|
||||
Manifest extends T.SDKManifest,
|
||||
C extends SubContainer<Manifest> | null = SubContainer<Manifest> | null,
|
||||
> extends Daemon<Manifest, C> {
|
||||
static of<Manifest extends T.SDKManifest>() {
|
||||
return async (
|
||||
return async <C extends SubContainer<Manifest> | null>(
|
||||
effects: T.Effects,
|
||||
subcontainer: SubContainer<Manifest>,
|
||||
exec: DaemonCommandType | null,
|
||||
subcontainer: C,
|
||||
exec: DaemonCommandType<Manifest, C>,
|
||||
) => {
|
||||
if (subcontainer.isOwned()) subcontainer = subcontainer.rc()
|
||||
const startCommand = exec
|
||||
? () =>
|
||||
CommandController.of<Manifest>()(effects, subcontainer.rc(), exec)
|
||||
: null
|
||||
return new Oneshot(subcontainer, startCommand, true)
|
||||
let subc: SubContainer<Manifest> | null = subcontainer
|
||||
if (subcontainer && subcontainer.isOwned()) subc = subcontainer.rc()
|
||||
const startCommand = () =>
|
||||
CommandController.of<Manifest, C>()(
|
||||
effects,
|
||||
(subc?.rc() ?? null) as C,
|
||||
exec,
|
||||
)
|
||||
return new Oneshot<Manifest, C>(subcontainer, startCommand, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "../../../base/lib/types/ManifestTypes"
|
||||
import { OSVersion } from "../StartSdk"
|
||||
import { VersionGraph } from "../version/VersionGraph"
|
||||
import { execSync } from "child_process"
|
||||
import { version as sdkVersion } from "../../package.json"
|
||||
|
||||
/**
|
||||
* @description Use this function to define critical information about your package
|
||||
@@ -55,6 +55,7 @@ export function buildManifest<
|
||||
return {
|
||||
...manifest,
|
||||
osVersion: manifest.osVersion ?? OSVersion,
|
||||
sdkVersion,
|
||||
version: versions.current.options.version,
|
||||
releaseNotes: versions.current.options.releaseNotes,
|
||||
satisfies: versions.current.options.satisfies || [],
|
||||
|
||||
@@ -5,18 +5,18 @@ export abstract class Drop {
|
||||
if (weak) weak.drop()
|
||||
})
|
||||
private static idCtr: number = 0
|
||||
private id: number
|
||||
private ref: { id: number } | WeakRef<{ id: number }>
|
||||
private dropId?: number
|
||||
private dropRef?: { id: number } | WeakRef<{ id: number }>
|
||||
protected constructor() {
|
||||
this.id = Drop.idCtr++
|
||||
this.ref = { id: this.id }
|
||||
this.dropId = Drop.idCtr++
|
||||
this.dropRef = { id: this.dropId }
|
||||
const weak = this.weak()
|
||||
Drop.weak[this.id] = weak
|
||||
Drop.registry.register(this.ref, this.id, this.ref)
|
||||
Drop.weak[this.dropId] = weak
|
||||
Drop.registry.register(this.dropRef, this.dropId, this.dropRef)
|
||||
|
||||
return new Proxy(this, {
|
||||
set(target: any, prop, value) {
|
||||
if (prop === "ref") return false
|
||||
if (prop === "dropRef" || prop == "dropId") return false
|
||||
target[prop] = value
|
||||
;(weak as any)[prop] = value
|
||||
return true
|
||||
@@ -26,13 +26,21 @@ export abstract class Drop {
|
||||
protected register() {}
|
||||
protected weak(): this {
|
||||
const weak = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
|
||||
weak.ref = new WeakRef(this.ref)
|
||||
if (this.dropRef) weak.ref = new WeakRef(this.dropRef)
|
||||
return weak
|
||||
}
|
||||
abstract onDrop(): void
|
||||
drop(): void {
|
||||
if (!this.dropRef || !this.dropId) return
|
||||
this.onDrop()
|
||||
Drop.registry.unregister(this.ref)
|
||||
delete Drop.weak[this.id]
|
||||
this.leak()
|
||||
}
|
||||
leak(): this {
|
||||
if (!this.dropRef || !this.dropId) return this
|
||||
Drop.registry.unregister(this.dropRef)
|
||||
delete Drop.weak[this.dropId]
|
||||
delete this.dropRef
|
||||
delete this.dropId
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,9 +340,17 @@ export class FileHelper<A> {
|
||||
/**
|
||||
* Accepts full structured data and overwrites the existing file on disk if it exists.
|
||||
*/
|
||||
async write(effects: T.Effects, data: T.AllowReadonly<A> | A) {
|
||||
async write(
|
||||
effects: T.Effects,
|
||||
data: T.AllowReadonly<A> | A,
|
||||
options: { allowWriteAfterConst?: boolean } = {},
|
||||
) {
|
||||
await this.writeFile(this.validate(data))
|
||||
if (effects.constRetry && this.consts.includes(effects.constRetry))
|
||||
if (
|
||||
!options.allowWriteAfterConst &&
|
||||
effects.constRetry &&
|
||||
this.consts.includes(effects.constRetry)
|
||||
)
|
||||
throw new Error(`Canceled: write after const: ${this.path}`)
|
||||
return null
|
||||
}
|
||||
@@ -350,7 +358,11 @@ export class FileHelper<A> {
|
||||
/**
|
||||
* Accepts partial structured data and performs a merge with the existing file on disk.
|
||||
*/
|
||||
async merge(effects: T.Effects, data: T.AllowReadonly<T.DeepPartial<A>>) {
|
||||
async merge(
|
||||
effects: T.Effects,
|
||||
data: T.AllowReadonly<T.DeepPartial<A>>,
|
||||
options: { allowWriteAfterConst?: boolean } = {},
|
||||
) {
|
||||
const fileDataRaw = await this.readFileRaw()
|
||||
let fileData: any = fileDataRaw === null ? null : this.readData(fileDataRaw)
|
||||
try {
|
||||
@@ -360,7 +372,11 @@ export class FileHelper<A> {
|
||||
const toWrite = this.writeData(mergeData)
|
||||
if (toWrite !== fileDataRaw) {
|
||||
this.writeFile(mergeData)
|
||||
if (effects.constRetry && this.consts.includes(effects.constRetry)) {
|
||||
if (
|
||||
!options.allowWriteAfterConst &&
|
||||
effects.constRetry &&
|
||||
this.consts.includes(effects.constRetry)
|
||||
) {
|
||||
const diff = partialDiff(fileData, mergeData as any)
|
||||
if (!diff) {
|
||||
return null
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
InitScriptOrFn,
|
||||
UninitFn,
|
||||
UninitScript,
|
||||
UninitScriptOrFn,
|
||||
} from "../../../base/lib/inits"
|
||||
import { Graph, Vertex, once } from "../util"
|
||||
import { IMPOSSIBLE, VersionInfo } from "./VersionInfo"
|
||||
@@ -171,11 +172,11 @@ export class VersionGraph<CurrentVersion extends string>
|
||||
/**
|
||||
* A script to run only on fresh install
|
||||
*/
|
||||
preInstall?: InitScript | InitFn
|
||||
preInstall?: InitScriptOrFn<"install">
|
||||
/**
|
||||
* A script to run only on uninstall
|
||||
*/
|
||||
uninstall?: UninitScript | UninitFn
|
||||
uninstall?: UninitScriptOrFn
|
||||
}) {
|
||||
return new VersionGraph(
|
||||
options.current,
|
||||
|
||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.27",
|
||||
"version": "0.4.0-beta.30",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.27",
|
||||
"version": "0.4.0-beta.30",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.27",
|
||||
"version": "0.4.0-beta.30",
|
||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||
"main": "./package/lib/index.js",
|
||||
"types": "./package/lib/index.d.ts",
|
||||
@@ -22,14 +22,14 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Start9Labs/start-sdk.git"
|
||||
"url": "git+https://github.com/Start9Labs/start-os.git"
|
||||
},
|
||||
"author": "Start9 Labs",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Start9Labs/start-sdk/issues"
|
||||
"url": "https://github.com/Start9Labs/start-os/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
|
||||
"homepage": "https://github.com/Start9Labs/start-os#readme",
|
||||
"dependencies": {
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"mime": "^4.0.7",
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"skipLibCheck": true,
|
||||
"module": "commonjs",
|
||||
"outDir": "../dist",
|
||||
"target": "es2021"
|
||||
"target": "es2021",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["lib/**/*", "../base/lib/util/Hostname.ts"],
|
||||
"exclude": ["lib/**/*.spec.ts", "lib/**/*.gen.ts", "list", "node_modules"]
|
||||
|
||||
Reference in New Issue
Block a user