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:
Aiden McClelland
2025-03-16 09:04:10 -06:00
committed by GitHub
parent e662b2f393
commit 05162ca350
50 changed files with 756 additions and 442 deletions

View 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]
}
}

View File

@@ -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()),
})
}
/**

View File

@@ -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
}

View File

@@ -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
}
/**

View File

@@ -2,3 +2,4 @@ export * from "../../../base/lib/util"
export { GetSslCertificate } from "./GetSslCertificate"
export { hostnameInfoToAddress } from "../../../base/lib/util/Hostname"
export { Drop } from "./Drop"