mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
Bugfix/sdk misc (#2847)
* misc sdk fixes * version bump * formatting * add missing dependency to root * alpha.16 and beta.17 * beta.18
This commit is contained in:
26
sdk/package/lib/util/Drop.ts
Normal file
26
sdk/package/lib/util/Drop.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export abstract class Drop {
|
||||
private static weak: { [id: number]: Drop } = {}
|
||||
private static registry = new FinalizationRegistry((id: number) => {
|
||||
Drop.weak[id].drop()
|
||||
})
|
||||
private static idCtr: number = 0
|
||||
private id: number
|
||||
private ref: { id: number } | WeakRef<{ id: number }>
|
||||
protected constructor() {
|
||||
this.id = Drop.idCtr++
|
||||
this.ref = { id: this.id }
|
||||
Drop.weak[this.id] = this.weak()
|
||||
Drop.registry.register(this, this.id, this)
|
||||
}
|
||||
protected weak(): this {
|
||||
const weak = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
|
||||
weak.ref = new WeakRef(this.ref)
|
||||
return weak
|
||||
}
|
||||
abstract onDrop(): void
|
||||
drop(): void {
|
||||
this.onDrop()
|
||||
Drop.registry.unregister(this)
|
||||
delete Drop.weak[this.id]
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,9 @@ export class GetSslCertificate {
|
||||
return this.effects.getSslCertificate({
|
||||
hostnames: this.hostnames,
|
||||
algorithm: this.algorithm,
|
||||
callback: () => this.effects.constRetry(),
|
||||
callback:
|
||||
this.effects.constRetry &&
|
||||
(() => this.effects.constRetry && this.effects.constRetry()),
|
||||
})
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -130,14 +130,14 @@ export class SubContainer implements ExecSpawnable {
|
||||
static async with<T>(
|
||||
effects: T.Effects,
|
||||
image: { imageId: T.ImageId; sharedRun?: boolean },
|
||||
mounts: { options: MountOptions; path: string }[],
|
||||
mounts: { options: MountOptions; mountpoint: string }[],
|
||||
name: string,
|
||||
fn: (subContainer: SubContainer) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const subContainer = await SubContainer.of(effects, image, name)
|
||||
try {
|
||||
for (let mount of mounts) {
|
||||
await subContainer.mount(mount.options, mount.path)
|
||||
await subContainer.mount(mount.options, mount.mountpoint)
|
||||
}
|
||||
return await fn(subContainer)
|
||||
} finally {
|
||||
@@ -166,7 +166,7 @@ export class SubContainer implements ExecSpawnable {
|
||||
? options.subpath
|
||||
: `/${options.subpath}`
|
||||
: "/"
|
||||
const from = `/media/startos/assets/${options.id}${subpath}`
|
||||
const from = `/media/startos/assets/${subpath}`
|
||||
|
||||
await fs.mkdir(from, { recursive: true })
|
||||
await fs.mkdir(path, { recursive: true })
|
||||
@@ -449,7 +449,6 @@ export type MountOptionsVolume = {
|
||||
|
||||
export type MountOptionsAssets = {
|
||||
type: "assets"
|
||||
id: string
|
||||
subpath: string | null
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as YAML from "yaml"
|
||||
import * as TOML from "@iarna/toml"
|
||||
import * as T from "../../../base/lib/types"
|
||||
import * as fs from "node:fs/promises"
|
||||
import { asError } from "../../../base/lib/util"
|
||||
import { asError, partialDiff } from "../../../base/lib/util"
|
||||
|
||||
const previousPath = /(.+?)\/([^/]*)$/
|
||||
|
||||
@@ -101,6 +101,7 @@ function fileMerge(...args: any[]): any {
|
||||
* ```
|
||||
*/
|
||||
export class FileHelper<A> {
|
||||
private consts: (() => void)[] = []
|
||||
protected constructor(
|
||||
readonly path: string,
|
||||
readonly writeData: (dataIn: A) => string,
|
||||
@@ -108,27 +109,37 @@ export class FileHelper<A> {
|
||||
readonly validate: (value: unknown) => A,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Accepts structured data and overwrites the existing file on disk.
|
||||
*/
|
||||
private async writeFile(data: A): Promise<null> {
|
||||
private async writeFileRaw(data: string): Promise<null> {
|
||||
const parent = previousPath.exec(this.path)
|
||||
if (parent) {
|
||||
await fs.mkdir(parent[1], { recursive: true })
|
||||
}
|
||||
|
||||
await fs.writeFile(this.path, this.writeData(data))
|
||||
await fs.writeFile(this.path, data)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private async readFile(): Promise<unknown> {
|
||||
/**
|
||||
* Accepts structured data and overwrites the existing file on disk.
|
||||
*/
|
||||
private async writeFile(data: A): Promise<null> {
|
||||
return await this.writeFileRaw(this.writeData(data))
|
||||
}
|
||||
|
||||
private async readFileRaw(): Promise<string | null> {
|
||||
if (!(await exists(this.path))) {
|
||||
return null
|
||||
}
|
||||
return this.readData(
|
||||
await fs.readFile(this.path).then((data) => data.toString("utf-8")),
|
||||
)
|
||||
return await fs.readFile(this.path).then((data) => data.toString("utf-8"))
|
||||
}
|
||||
|
||||
private async readFile(): Promise<unknown> {
|
||||
const raw = await this.readFileRaw()
|
||||
if (raw === null) {
|
||||
return raw
|
||||
}
|
||||
return this.readData(raw)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +154,14 @@ export class FileHelper<A> {
|
||||
private async readConst(effects: T.Effects): Promise<A | null> {
|
||||
const watch = this.readWatch()
|
||||
const res = await watch.next()
|
||||
watch.next().then(effects.constRetry)
|
||||
if (effects.constRetry) {
|
||||
if (!this.consts.includes(effects.constRetry))
|
||||
this.consts.push(effects.constRetry)
|
||||
watch.next().then(() => {
|
||||
this.consts = this.consts.filter((a) => a === effects.constRetry)
|
||||
effects.constRetry && effects.constRetry()
|
||||
})
|
||||
}
|
||||
return res.value
|
||||
}
|
||||
|
||||
@@ -213,17 +231,35 @@ export class FileHelper<A> {
|
||||
/**
|
||||
* Accepts full structured data and overwrites the existing file on disk if it exists.
|
||||
*/
|
||||
async write(data: A) {
|
||||
return await this.writeFile(this.validate(data))
|
||||
async write(effects: T.Effects, data: A) {
|
||||
await this.writeFile(this.validate(data))
|
||||
if (effects.constRetry && this.consts.includes(effects.constRetry))
|
||||
throw new Error(`Canceled: write after const: ${this.path}`)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts partial structured data and performs a merge with the existing file on disk.
|
||||
*/
|
||||
async merge(data: T.DeepPartial<A>) {
|
||||
const fileData = (await this.readFile()) || null
|
||||
const mergeData = fileMerge(fileData, data)
|
||||
return await this.writeFile(this.validate(mergeData))
|
||||
async merge(effects: T.Effects, data: T.DeepPartial<A>) {
|
||||
const fileDataRaw = await this.readFileRaw()
|
||||
let fileData: any = fileDataRaw === null ? null : this.readData(fileDataRaw)
|
||||
try {
|
||||
fileData = this.validate(fileData)
|
||||
} catch (_) {}
|
||||
const mergeData = this.validate(fileMerge({}, fileData, data))
|
||||
const toWrite = this.writeData(mergeData)
|
||||
if (toWrite !== fileDataRaw) {
|
||||
this.writeFile(mergeData)
|
||||
if (effects.constRetry && this.consts.includes(effects.constRetry)) {
|
||||
const diff = partialDiff(fileData, mergeData as any)
|
||||
if (!diff) {
|
||||
return null
|
||||
}
|
||||
throw new Error(`Canceled: write after const: ${this.path}`)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "../../../base/lib/util"
|
||||
export { GetSslCertificate } from "./GetSslCertificate"
|
||||
|
||||
export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname"
|
||||
export { Drop } from "./Drop"
|
||||
|
||||
Reference in New Issue
Block a user