feat: Change the commands

This commit is contained in:
Blu-J
2023-06-08 14:54:14 -06:00
parent b06e46c181
commit 9156d89903
11 changed files with 270 additions and 140 deletions

View File

@@ -22,7 +22,7 @@ import {
} from "./types" } from "./types"
import * as patterns from "./util/patterns" import * as patterns from "./util/patterns"
import { Utils } from "./util/utils" import { Utils } from "./util/utils"
import { DependencyConfig } from "./dependencyConfig/DependencyConfig" import { DependencyConfig, Update } from "./dependencyConfig/DependencyConfig"
import { BackupSet, Backups } from "./backup/Backups" import { BackupSet, Backups } from "./backup/Backups"
import { smtpConfig } from "./config/configConstants" import { smtpConfig } from "./config/configConstants"
import { Daemons } from "./mainFn/Daemons" import { Daemons } from "./mainFn/Daemons"
@@ -231,18 +231,20 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
localConfig, localConfig,
remoteConfig, remoteConfig,
dependencyConfig, dependencyConfig,
update,
}: { }: {
localConfig: Config<LocalConfig, Store> | Config<LocalConfig, never> localConfig: Config<LocalConfig, Store> | Config<LocalConfig, never>
remoteConfig: Config<RemoteConfig, any> | Config<RemoteConfig, never> remoteConfig: Config<RemoteConfig, any> | Config<RemoteConfig, never>
dependencyConfig: (options: { dependencyConfig: (options: {
effects: Effects effects: Effects
localConfig: LocalConfig localConfig: LocalConfig
remoteConfig: RemoteConfig
utils: Utils<Store> utils: Utils<Store>
}) => Promise<void | DeepPartial<RemoteConfig>> }) => Promise<void | DeepPartial<RemoteConfig>>
update?: Update<void | DeepPartial<RemoteConfig>, RemoteConfig>
}) { }) {
return new DependencyConfig<Store, LocalConfig, RemoteConfig>( return new DependencyConfig<Store, LocalConfig, RemoteConfig>(
dependencyConfig, dependencyConfig,
update,
) )
}, },
}, },

View File

@@ -134,15 +134,15 @@ export class Backups<M extends SDKManifest> {
// path: item.dstPath, // path: item.dstPath,
// }) // })
// } // }
await effects // await effects
.runRsync({ // .runRsync({
...item, // ...item,
options: { // options: {
...this.options, // ...this.options,
...item.options, // ...item.options,
}, // },
}) // })
.wait() // .wait()
} }
return return
} }
@@ -158,18 +158,18 @@ export class Backups<M extends SDKManifest> {
// }, // },
// ) // )
// } // }
await effects // await effects
.runRsync({ // .runRsync({
options: { // options: {
...this.options, // ...this.options,
...item.options, // ...item.options,
}, // },
srcVolume: item.dstVolume, // srcVolume: item.dstVolume,
dstVolume: item.srcVolume, // dstVolume: item.srcVolume,
srcPath: item.dstPath, // srcPath: item.dstPath,
dstPath: item.srcPath, // dstPath: item.srcPath,
}) // })
.wait() // .wait()
} }
return return
} }

View File

@@ -7,55 +7,39 @@ import { Utils, utils } from "../util/utils"
import { deepEqual } from "../util/deepEqual" import { deepEqual } from "../util/deepEqual"
import { deepMerge } from "../util/deepMerge" import { deepMerge } from "../util/deepMerge"
export type Update<QueryResults, RemoteConfig> = (options: {
remoteConfig: RemoteConfig
queryResults: QueryResults
}) => Promise<RemoteConfig>
export class DependencyConfig< export class DependencyConfig<
Store, Store,
Input extends Record<string, any>, Input extends Record<string, any>,
RemoteConfig extends Record<string, any>, RemoteConfig extends Record<string, any>,
> { > {
static defaultUpdate = async (options: {
queryResults: unknown
remoteConfig: unknown
}): Promise<unknown> => {
return deepMerge({}, options.remoteConfig, options.queryResults || {})
}
constructor( constructor(
readonly dependencyConfig: (options: { readonly dependencyConfig: (options: {
effects: Effects effects: Effects
localConfig: Input localConfig: Input
remoteConfig: RemoteConfig
utils: Utils<Store> utils: Utils<Store>
}) => Promise<void | DeepPartial<RemoteConfig>>, }) => Promise<void | DeepPartial<RemoteConfig>>,
readonly update: Update<
void | DeepPartial<RemoteConfig>,
RemoteConfig
> = DependencyConfig.defaultUpdate as any,
) {} ) {}
async check( async query(options: { effects: Effects; localConfig: unknown }) {
options: Parameters<DependencyConfigType["check"]>[0], return this.dependencyConfig({
): ReturnType<DependencyConfigType["check"]> {
const origConfig = JSON.parse(JSON.stringify(options.localConfig))
const newOptions = {
...options,
utils: utils<Store>(options.effects),
localConfig: options.localConfig as Input, localConfig: options.localConfig as Input,
remoteConfig: options.remoteConfig as RemoteConfig, effects: options.effects,
}
if (
!deepEqual(
origConfig,
deepMerge(
{},
options.localConfig,
await this.dependencyConfig(newOptions),
),
)
)
throw new Error(`Check failed`)
}
async autoConfigure(
options: Parameters<DependencyConfigType["autoConfigure"]>[0],
): ReturnType<DependencyConfigType["autoConfigure"]> {
const newOptions = {
...options,
utils: utils<Store>(options.effects), utils: utils<Store>(options.effects),
localConfig: options.localConfig as Input, })
remoteConfig: options.remoteConfig as any,
}
return deepMerge(
{},
options.remoteConfig,
await this.dependencyConfig(newOptions),
)
} }
} }

View File

@@ -1,4 +1,5 @@
import { Effects } from "../../types" import { Effects } from "../../types"
import { createUtils } from "../../util"
import { CheckResult } from "./CheckResult" import { CheckResult } from "./CheckResult"
export function containsAddress(x: string, port: number) { export function containsAddress(x: string, port: number) {
const readPorts = x const readPorts = x
@@ -26,11 +27,15 @@ export async function checkPortListening(
timeout?: number timeout?: number
}, },
): Promise<CheckResult> { ): Promise<CheckResult> {
const utils = createUtils(effects)
return Promise.race<CheckResult>([ return Promise.race<CheckResult>([
Promise.resolve().then(async () => { Promise.resolve().then(async () => {
const hasAddress = const hasAddress =
containsAddress(await effects.runCommand(`cat /proc/net/tcp`), port) || containsAddress(
containsAddress(await effects.runCommand("cat /proc/net/udp"), port) await utils.runCommand(`cat /proc/net/tcp`, {}),
port,
) ||
containsAddress(await utils.runCommand("cat /proc/net/udp", {}), port)
if (hasAddress) { if (hasAddress) {
return { status: "passing", message: options.successMessage } return { status: "passing", message: options.successMessage }
} }

View File

@@ -1,6 +1,7 @@
import { Effects } from "../../types" import { Effects } from "../../types"
import { CheckResult } from "./CheckResult" import { CheckResult } from "./CheckResult"
import { timeoutPromise } from "./index" import { timeoutPromise } from "./index"
import fetch from "node-fetch"
/** /**
* This is a helper function to check if a web url is reachable. * This is a helper function to check if a web url is reachable.
@@ -17,7 +18,7 @@ export const checkWebUrl = async (
errorMessage = `Error while fetching URL: ${url}`, errorMessage = `Error while fetching URL: ${url}`,
} = {}, } = {},
): Promise<CheckResult> => { ): Promise<CheckResult> => {
return Promise.race([effects.fetch(url), timeoutPromise(timeout)]) return Promise.race([fetch(url), timeoutPromise(timeout)])
.then((x) => ({ .then((x) => ({
status: "passing" as const, status: "passing" as const,
message: successMessage, message: successMessage,

View File

@@ -1,4 +1,5 @@
import { CommandType, Effects } from "../../types" import { CommandType, Effects } from "../../types"
import { createUtils } from "../../util"
import { CheckResult } from "./CheckResult" import { CheckResult } from "./CheckResult"
import { timeoutPromise } from "./index" import { timeoutPromise } from "./index"
@@ -9,9 +10,9 @@ import { timeoutPromise } from "./index"
* @param param0 * @param param0
* @returns * @returns
*/ */
export const runHealthScript = async <A extends string>( export const runHealthScript = async (
effects: Effects, effects: Effects,
runCommand: CommandType<A>, runCommand: string,
{ {
timeout = 30000, timeout = 30000,
errorMessage = `Error while running command: ${runCommand}`, errorMessage = `Error while running command: ${runCommand}`,
@@ -19,8 +20,9 @@ export const runHealthScript = async <A extends string>(
`Have ran script ${runCommand} and the result: ${res}`, `Have ran script ${runCommand} and the result: ${res}`,
} = {}, } = {},
): Promise<CheckResult> => { ): Promise<CheckResult> => {
const utils = createUtils(effects)
const res = await Promise.race([ const res = await Promise.race([
effects.runCommand(runCommand, { timeoutMillis: timeout }), utils.runCommand(runCommand, { timeout }),
timeoutPromise(timeout), timeoutPromise(timeout),
]).catch((e) => { ]).catch((e) => {
console.warn(errorMessage) console.warn(errorMessage)

View File

@@ -4,9 +4,10 @@ import { Trigger } from "../trigger"
import { TriggerInput } from "../trigger/TriggerInput" import { TriggerInput } from "../trigger/TriggerInput"
import { defaultTrigger } from "../trigger/defaultTrigger" import { defaultTrigger } from "../trigger/defaultTrigger"
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types" import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
import { createUtils } from "../util"
type Daemon<Ids extends string, Command extends string, Id extends string> = { type Daemon<Ids extends string, Command extends string, Id extends string> = {
id: "" extends Id ? never : Id id: "" extends Id ? never : Id
command: ValidIfNoStupidEscape<Command> | [string, ...string[]] command: string
env?: Record<string, string> env?: Record<string, string>
ready: { ready: {
display: string | null display: string | null
@@ -95,8 +96,9 @@ export class Daemons<Ids extends string> {
) )
daemonsStarted[daemon.id] = requiredPromise.then(async () => { daemonsStarted[daemon.id] = requiredPromise.then(async () => {
const { command } = daemon const { command } = daemon
const utils = createUtils(effects)
const child = effects.runDaemon(command, { env: daemon.env }) const child = utils.runDaemon(command, { env: daemon.env })
let currentInput: TriggerInput = {} let currentInput: TriggerInput = {}
const getCurrentInput = () => currentInput const getCurrentInput = () => currentInput
const trigger = (daemon.ready.trigger ?? defaultTrigger)( const trigger = (daemon.ready.trigger ?? defaultTrigger)(

View File

@@ -97,16 +97,11 @@ export type VersionString = string
* this is used to make sure that other dependencies have the values that this service could use. * this is used to make sure that other dependencies have the values that this service could use.
*/ */
export type DependencyConfig = { export type DependencyConfig = {
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */ /** During autoconfigure, we have access to effects and local data. We are going to figure out all the data that we need and send it to update. For the sdk it is the desired delta */
check(options: { query(options: { effects: Effects; localConfig: unknown }): Promise<unknown>
effects: Effects /** This is the second part. Given the query results off the previous function, we will determine what to change the remote config to. In our sdk normall we are going to use the previous as a deep merge. */
localConfig: unknown update(options: {
remoteConfig: unknown queryResults: unknown
}): Promise<void>
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
autoConfigure(options: {
effects: Effects
localConfig: unknown
remoteConfig: unknown remoteConfig: unknown
}): Promise<unknown> }): Promise<unknown>
} }

View File

@@ -6,6 +6,7 @@ import {
checkWebUrl, checkWebUrl,
} from "../health/checkFns" } from "../health/checkFns"
import { import {
DaemonReturned,
Effects, Effects,
EnsureStorePath, EnsureStorePath,
ExtractStore, ExtractStore,
@@ -34,6 +35,8 @@ import {
getNetworkInterfaces, getNetworkInterfaces,
} from "./getNetworkInterfaces" } from "./getNetworkInterfaces"
import { exec } from "node:child_process"
export type Utils<Store, WrapperOverWrite = { const: never }> = { export type Utils<Store, WrapperOverWrite = { const: never }> = {
checkPortListening( checkPortListening(
port: number, port: number,
@@ -91,6 +94,14 @@ export type Utils<Store, WrapperOverWrite = { const: never }> = {
} }
nullIfEmpty: typeof nullIfEmpty nullIfEmpty: typeof nullIfEmpty
readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]> readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]>
runDaemon: (
command: string,
options: { env?: Record<string, string> },
) => Promise<DaemonReturned>
runCommand: (
command: string,
options: { env?: Record<string, string>; timeout?: number },
) => Promise<string>
store: { store: {
get: <Path extends string>( get: <Path extends string>(
packageId: string, packageId: string,
@@ -111,71 +122,113 @@ export type Utils<Store, WrapperOverWrite = { const: never }> = {
} }
export const utils = <Store = never, WrapperOverWrite = { const: never }>( export const utils = <Store = never, WrapperOverWrite = { const: never }>(
effects: Effects, effects: Effects,
): Utils<Store, WrapperOverWrite> => ({ ): Utils<Store, WrapperOverWrite> => {
createInterface: (options: { return {
name: string createInterface: (options: {
id: string name: string
description: string id: string
hasPrimary: boolean description: string
disabled: boolean hasPrimary: boolean
ui: boolean disabled: boolean
username: null | string ui: boolean
path: string username: null | string
search: Record<string, string> path: string
}) => new NetworkInterfaceBuilder({ ...options, effects }), search: Record<string, string>
getSystemSmtp: () => }) => new NetworkInterfaceBuilder({ ...options, effects }),
new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite, getSystemSmtp: () =>
new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite,
host: { host: {
static: (id: string) => new StaticHost({ id, effects }), static: (id: string) => new StaticHost({ id, effects }),
single: (id: string) => new SingleHost({ id, effects }), single: (id: string) => new SingleHost({ id, effects }),
multi: (id: string) => new MultiHost({ id, effects }), multi: (id: string) => new MultiHost({ id, effects }),
}, },
readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects), readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects),
writeFile: <A>(fileHelper: FileHelper<A>, data: A) => writeFile: <A>(fileHelper: FileHelper<A>, data: A) =>
fileHelper.write(data, effects), fileHelper.write(data, effects),
nullIfEmpty, nullIfEmpty,
networkInterface: { networkInterface: {
getOwn: (interfaceId: InterfaceId) => getOwn: (interfaceId: InterfaceId) =>
getNetworkInterface(effects, { interfaceId }) as GetNetworkInterface & getNetworkInterface(effects, { interfaceId }) as GetNetworkInterface &
WrapperOverWrite, WrapperOverWrite,
get: (opts: { interfaceId: InterfaceId; packageId: PackageId }) => get: (opts: { interfaceId: InterfaceId; packageId: PackageId }) =>
getNetworkInterface(effects, opts) as GetNetworkInterface & getNetworkInterface(effects, opts) as GetNetworkInterface &
WrapperOverWrite, WrapperOverWrite,
getAllOwn: () => getAllOwn: () =>
getNetworkInterfaces(effects, {}) as GetNetworkInterfaces & getNetworkInterfaces(effects, {}) as GetNetworkInterfaces &
WrapperOverWrite, WrapperOverWrite,
getAll: (opts: { packageId: PackageId }) => getAll: (opts: { packageId: PackageId }) =>
getNetworkInterfaces(effects, opts) as GetNetworkInterfaces & getNetworkInterfaces(effects, opts) as GetNetworkInterfaces &
WrapperOverWrite, WrapperOverWrite,
}, },
store: { store: {
get: <Path extends string = never>( get: <Path extends string = never>(
packageId: string, packageId: string,
path: EnsureStorePath<Store, Path>, path: EnsureStorePath<Store, Path>,
) => ) =>
getStore<Store, Path>(effects, path as any, { getStore<Store, Path>(effects, path as any, {
packageId, packageId,
}) as any, }) as any,
getOwn: <Path extends string>(path: EnsureStorePath<Store, Path>) => getOwn: <Path extends string>(path: EnsureStorePath<Store, Path>) =>
getStore<Store, Path>(effects, path as any) as any, getStore<Store, Path>(effects, path as any) as any,
setOwn: <Path extends string | never>( setOwn: <Path extends string | never>(
path: EnsureStorePath<Store, Path>, path: EnsureStorePath<Store, Path>,
value: ExtractStore<Store, Path>, value: ExtractStore<Store, Path>,
) => effects.store.set<Store, Path>({ value, path: path as any }), ) => effects.store.set<Store, Path>({ value, path: path as any }),
}, },
checkPortListening: checkPortListening.bind(null, effects),
checkWebUrl: checkWebUrl.bind(null, effects),
mountDependencies: < runDaemon: async (
In extends command: string,
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>> options: { env?: Record<string, string> },
| Record<VolumeName, Record<NamedPath, Path>> ): Promise<DaemonReturned> => {
| Record<NamedPath, Path> let childProcess: null | {
| Path, kill(signal?: number | string): void
>( } = null
value: In, const answer = new Promise<string>(
) => mountDependencies(effects, value), (resolve, reject) =>
}) (childProcess = exec(command, options, (error, stdout, stderr) => {
if (error) return reject(error.toString())
if (stderr) return reject(stderr.toString())
return resolve(stdout.toString())
})),
)
return {
wait() {
return answer
},
async term() {
childProcess?.kill()
},
}
},
runCommand: async (
command: string,
options: { env?: Record<string, string>; timeout?: number },
): Promise<string> => {
const answer = new Promise<string>((resolve, reject) =>
exec(command, options, (error, stdout, stderr) => {
if (error) return reject(error.toString())
if (stderr) return reject(stderr.toString())
return resolve(stdout.toString())
}),
)
return answer
},
checkPortListening: checkPortListening.bind(null, effects),
checkWebUrl: checkWebUrl.bind(null, effects),
mountDependencies: <
In extends
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
| Record<VolumeName, Record<NamedPath, Path>>
| Record<NamedPath, Path>
| Path,
>(
value: In,
) => mountDependencies(effects, value),
}
}
function noop(): void {} function noop(): void {}

85
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"node-fetch": "^3.3.1",
"ts-matches": "^5.4.1", "ts-matches": "^5.4.1",
"yaml": "^2.2.2" "yaml": "^2.2.2"
}, },
@@ -2205,6 +2206,14 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2471,6 +2480,28 @@
"bser": "2.1.1" "bser": "2.1.1"
} }
}, },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2496,6 +2527,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3719,6 +3761,41 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
"integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-int64": { "node_modules/node-int64": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -5107,6 +5184,14 @@
"makeerror": "1.0.12" "makeerror": "1.0.12"
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"engines": {
"node": ">= 8"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -21,6 +21,7 @@
"homepage": "https://github.com/Start9Labs/start-sdk#readme", "homepage": "https://github.com/Start9Labs/start-sdk#readme",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"node-fetch": "^3.3.1",
"ts-matches": "^5.4.1", "ts-matches": "^5.4.1",
"yaml": "^2.2.2" "yaml": "^2.2.2"
}, },