sideload wip, websockets, styling, multiple todos (#2865)

* sideload wip, websockets, styling, multiple todos

* sideload

* misc backend updates

* chore: comments

* prep for license and instructions display

* comment for Matt

* s9pk updates and 040 sdk

* fix dependency error for actions

* 0.4.0-beta.1

* beta.2

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: waterplea <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
Matt Hill
2025-04-10 13:51:05 -06:00
committed by GitHub
parent ab4336cfd7
commit fc2be42418
88 changed files with 773 additions and 965 deletions

View File

@@ -66,7 +66,9 @@ export async function checkDependencies<
return dep.requirement.kind !== "running" || dep.result.isRunning
}
const actionsSatisfied = (packageId: DependencyId) =>
Object.keys(find(packageId).result.requestedActions).length === 0
Object.entries(find(packageId).result.requestedActions).filter(
([_, req]) => req.active && req.request.severity === "critical",
).length === 0
const healthCheckSatisfied = (
packageId: DependencyId,
healthCheckId?: HealthCheckId,
@@ -129,7 +131,9 @@ export async function checkDependencies<
}
const throwIfActionsNotSatisfied = (packageId: DependencyId) => {
const dep = find(packageId)
const reqs = Object.keys(dep.result.requestedActions)
const reqs = Object.entries(dep.result.requestedActions)
.filter(([_, req]) => req.active && req.request.severity === "critical")
.map(([id, _]) => id)
if (reqs.length) {
throw new Error(
`The following action requests have not been fulfilled: ${reqs.join(", ")}`,

View File

@@ -1,8 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PasswordType } from "./PasswordType"
export type LoginParams = {
password: PasswordType | null
ephemeral: boolean
metadata: any
}
export type LoginParams = { password: PasswordType | null; ephemeral: boolean }

View File

@@ -14,7 +14,7 @@ export type PackageVersionInfo = {
icon: DataUrl
description: Description
releaseNotes: string
gitHash: GitHash
gitHash: GitHash | null
license: string
wrapperRepo: string
upstreamRepo: string

View File

@@ -1,3 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Session = { loggedIn: string; lastActive: string; metadata: any }
export type Session = {
loggedIn: string
lastActive: string
userAgent: string | null
}

View File

@@ -1,5 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Sessions = {
[key: string]: { loggedIn: string; lastActive: string; metadata: any }
[key: string]: {
loggedIn: string
lastActive: string
userAgent: string | null
}
}

View File

@@ -1,6 +1,14 @@
import { DataUrl, Manifest, MerkleArchiveCommitment } from "../osBindings"
import {
DataUrl,
DependencyMetadata,
Manifest,
MerkleArchiveCommitment,
PackageId,
} from "../osBindings"
import { ArrayBufferReader, MerkleArchive } from "./merkleArchive"
import mime from "mime-types"
import { DirectoryContents } from "./merkleArchive/directoryContents"
import { FileContents } from "./merkleArchive/fileContents"
const magicAndVersion = new Uint8Array([59, 59, 2])
@@ -65,4 +73,63 @@ export class S9pk {
).toString("base64")
)
}
async dependencyMetadataFor(id: PackageId) {
const entry = this.archive.contents.getPath([
"dependencies",
id,
"metadata.json",
])
if (!entry) return null
return JSON.parse(
new TextDecoder().decode(await entry.verifiedFileContents()),
) as { title: string }
}
async dependencyIconFor(id: PackageId) {
const dir = this.archive.contents.getPath(["dependencies", id])
if (!dir || !(dir.contents instanceof DirectoryContents)) return null
const iconName = Object.keys(dir.contents.contents).find(
(name) =>
name.startsWith("icon.") &&
(mime.contentType(name) || null)?.startsWith("image/"),
)
if (!iconName) return null
return (
`data:${mime.contentType(iconName)};base64,` +
Buffer.from(
await dir.contents.getPath([iconName])!.verifiedFileContents(),
).toString("base64")
)
}
async dependencyMetadata(): Promise<Record<PackageId, DependencyMetadata>> {
return Object.fromEntries(
await Promise.all(
Object.entries(this.manifest.dependencies).map(async ([id, info]) => [
id,
{
...(await this.dependencyMetadataFor(id)),
icon: await this.dependencyIconFor(id),
description: info.description,
optional: info.optional,
},
]),
),
)
}
async instructions(): Promise<string> {
const file = this.archive.contents.getPath(["instructions.md"])
if (!file || !(file.contents instanceof FileContents))
throw new Error("instructions.md not found in archive")
return new TextDecoder().decode(await file.verifiedFileContents())
}
async license(): Promise<string> {
const file = this.archive.contents.getPath(["LICENSE.md"])
if (!file || !(file.contents instanceof FileContents))
throw new Error("instructions.md not found in archive")
return new TextDecoder().decode(await file.verifiedFileContents())
}
}

View File

@@ -30,7 +30,12 @@ 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"
import { Install, InstallFn } from "./inits/setupInstall"
import {
Install,
InstallFn,
PostInstall,
PreInstall,
} from "./inits/setupInstall"
import { SetupBackupsParams, setupBackups } from "./backup/setupBackups"
import { UninstallFn, setupUninstall } from "./inits/setupUninstall"
import { setupMain } from "./mainFn"
@@ -571,12 +576,24 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
setupDependencies: setupDependencies<Manifest>,
setupInit: setupInit<Manifest, Store>,
/**
* @description Use this function to execute arbitrary logic *once*, on initial install only.
* @description Use this function to execute arbitrary logic *once*, on initial install *before* interfaces, actions, and dependencies are updated.
* @example
* In the this example, we initialize a config file
*
* ```
const preInstall = sdk.setupPreInstall(async ({ effects }) => {
await configFile.write(effects, { name: 'World' })
})
* ```
*/
setupPreInstall: (fn: InstallFn<Manifest, Store>) => PreInstall.of(fn),
/**
* @description Use this function to execute arbitrary logic *once*, on initial install *after* interfaces, actions, and dependencies are updated.
* @example
* In the this example, we bootstrap our Store with a random, 16-char admin password.
*
* ```
const install = sdk.setupInstall(async ({ effects }) => {
const postInstall = sdk.setupPostInstall(async ({ effects }) => {
await sdk.store.setOwn(
effects,
sdk.StorePath.adminPassword,
@@ -588,10 +605,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
})
* ```
*/
setupInstall: (
fn: InstallFn<Manifest, Store>,
preFn?: InstallFn<Manifest, Store>,
) => Install.of(fn, preFn),
setupPostInstall: (fn: InstallFn<Manifest, Store>) => PostInstall.of(fn),
/**
* @description Use this function to determine how this service will be hosted and served. The function executes on service install, service update, and inputSpec save.
*

View File

@@ -5,12 +5,13 @@ import { ExposedStorePaths } from "../../../base/lib/types"
import * as T from "../../../base/lib/types"
import { StorePath } from "../util"
import { VersionGraph } from "../version/VersionGraph"
import { Install } from "./setupInstall"
import { PostInstall, PreInstall } from "./setupInstall"
import { Uninstall } from "./setupUninstall"
export function setupInit<Manifest extends T.SDKManifest, Store>(
versions: VersionGraph<string>,
install: Install<Manifest, Store>,
preInstall: PreInstall<Manifest, Store>,
postInstall: PostInstall<Manifest, Store>,
uninstall: Uninstall<Manifest, Store>,
setServiceInterfaces: UpdateServiceInterfaces<any>,
setDependencies: (options: {
@@ -34,7 +35,7 @@ export function setupInit<Manifest extends T.SDKManifest, Store>(
to: versions.currentVersion(),
})
} else {
await install.install(opts)
await postInstall.postInstall(opts)
await opts.effects.setDataVersion({
version: versions.current.options.version,
})
@@ -61,7 +62,7 @@ export function setupInit<Manifest extends T.SDKManifest, Store>(
path: "" as StorePath,
value: initStore,
})
await install.preInstall(opts)
await preInstall.preInstall(opts)
}
await setServiceInterfaces({
...opts,

View File

@@ -4,34 +4,57 @@ export type InstallFn<Manifest extends T.SDKManifest, Store> = (opts: {
effects: T.Effects
}) => Promise<null | void | undefined>
export class Install<Manifest extends T.SDKManifest, Store> {
private constructor(
readonly fn: InstallFn<Manifest, Store>,
readonly preFn?: InstallFn<Manifest, Store>,
) {}
protected constructor(readonly fn: InstallFn<Manifest, Store>) {}
}
export class PreInstall<Manifest extends T.SDKManifest, Store> extends Install<
Manifest,
Store
> {
private constructor(fn: InstallFn<Manifest, Store>) {
super(fn)
}
static of<Manifest extends T.SDKManifest, Store>(
fn: InstallFn<Manifest, Store>,
preFn?: InstallFn<Manifest, Store>,
) {
return new Install(fn, preFn)
return new PreInstall(fn)
}
async install({ effects }: Parameters<T.ExpectedExports.packageInit>[0]) {
async preInstall({ effects }: Parameters<T.ExpectedExports.packageInit>[0]) {
await this.fn({
effects,
})
}
}
async preInstall({ effects }: Parameters<T.ExpectedExports.packageInit>[0]) {
this.preFn &&
(await this.preFn({
effects,
}))
export function setupPreInstall<Manifest extends T.SDKManifest, Store>(
fn: InstallFn<Manifest, Store>,
) {
return PreInstall.of(fn)
}
export class PostInstall<Manifest extends T.SDKManifest, Store> extends Install<
Manifest,
Store
> {
private constructor(fn: InstallFn<Manifest, Store>) {
super(fn)
}
static of<Manifest extends T.SDKManifest, Store>(
fn: InstallFn<Manifest, Store>,
) {
return new PostInstall(fn)
}
async postInstall({ effects }: Parameters<T.ExpectedExports.packageInit>[0]) {
await this.fn({
effects,
})
}
}
export function setupInstall<Manifest extends T.SDKManifest, Store>(
export function setupPostInstall<Manifest extends T.SDKManifest, Store>(
fn: InstallFn<Manifest, Store>,
preFn?: InstallFn<Manifest, Store>,
) {
return Install.of(fn, preFn)
return PostInstall.of(fn)
}

View File

@@ -1,12 +1,12 @@
{
"name": "@start9labs/start-sdk",
"version": "0.3.6-beta.20",
"version": "0.4.0-beta.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@start9labs/start-sdk",
"version": "0.3.6-beta.20",
"version": "0.4.0-beta.2",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@start9labs/start-sdk",
"version": "0.3.6-beta.20",
"version": "0.4.0-beta.2",
"description": "Software development kit to facilitate packaging services for StartOS",
"main": "./package/lib/index.js",
"types": "./package/lib/index.d.ts",