mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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:
@@ -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(", ")}`,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -14,7 +14,7 @@ export type PackageVersionInfo = {
|
||||
icon: DataUrl
|
||||
description: Description
|
||||
releaseNotes: string
|
||||
gitHash: GitHash
|
||||
gitHash: GitHash | null
|
||||
license: string
|
||||
wrapperRepo: string
|
||||
upstreamRepo: string
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user