mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-30 20:24:47 +00:00
feat: Dependencies
This commit is contained in:
@@ -47,6 +47,7 @@ import { setupMain } from "./mainFn"
|
|||||||
import { defaultTrigger } from "./trigger/defaultTrigger"
|
import { defaultTrigger } from "./trigger/defaultTrigger"
|
||||||
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
||||||
import setupConfig, { Read, Save } from "./config/setupConfig"
|
import setupConfig, { Read, Save } from "./config/setupConfig"
|
||||||
|
import { setupDependencyMounts } from "./dependency/setupDependencyMounts"
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type AnyNeverCond<T extends any[], Then, Else> =
|
type AnyNeverCond<T extends any[], Then, Else> =
|
||||||
@@ -254,6 +255,7 @@ export class StartSdk<Manifest extends SDKManifest, Store, Vault> {
|
|||||||
_configSpec: ConfigSpec,
|
_configSpec: ConfigSpec,
|
||||||
fn: Save<Store, Vault, ConfigSpec, Manifest>,
|
fn: Save<Store, Vault, ConfigSpec, Manifest>,
|
||||||
) => fn,
|
) => fn,
|
||||||
|
setupDependencyMounts,
|
||||||
setupInit: (
|
setupInit: (
|
||||||
migrations: Migrations<Store, Vault>,
|
migrations: Migrations<Store, Vault>,
|
||||||
install: Install<Store, Vault>,
|
install: Install<Store, Vault>,
|
||||||
|
|||||||
43
lib/dependency/mountDependencies.ts
Normal file
43
lib/dependency/mountDependencies.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Effects } from "../types"
|
||||||
|
import {
|
||||||
|
Path,
|
||||||
|
ManifestId,
|
||||||
|
VolumeName,
|
||||||
|
NamedPath,
|
||||||
|
matchPath,
|
||||||
|
} from "./setupDependencyMounts"
|
||||||
|
|
||||||
|
export type MountDependenciesOut<A> =
|
||||||
|
// prettier-ignore
|
||||||
|
A extends Path ? string : A extends Record<string, unknown> ? {
|
||||||
|
[P in keyof A]: MountDependenciesOut<A[P]>;
|
||||||
|
} : never
|
||||||
|
export async function mountDependencies<
|
||||||
|
In extends
|
||||||
|
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||||
|
| Record<VolumeName, Record<NamedPath, Path>>
|
||||||
|
| Record<NamedPath, Path>
|
||||||
|
| Path,
|
||||||
|
>(effects: Effects, value: In): Promise<MountDependenciesOut<In>> {
|
||||||
|
if (matchPath.test(value)) {
|
||||||
|
const mountPath = `${value.manifest.id}/${value.volume}/${value.name}`
|
||||||
|
|
||||||
|
return (await effects.mount({
|
||||||
|
location: {
|
||||||
|
path: mountPath,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
packageId: value.manifest.id,
|
||||||
|
path: value.path,
|
||||||
|
readonly: value.readonly,
|
||||||
|
volumeId: value.volume,
|
||||||
|
},
|
||||||
|
})) as MountDependenciesOut<In>
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
mountDependencies(effects, value),
|
||||||
|
]),
|
||||||
|
) as Record<string, unknown> as MountDependenciesOut<In>
|
||||||
|
}
|
||||||
70
lib/dependency/setupDependencyMounts.ts
Normal file
70
lib/dependency/setupDependencyMounts.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { boolean, object, string } from "ts-matches"
|
||||||
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
|
import { deepMerge } from "../util/deepMerge"
|
||||||
|
|
||||||
|
export type VolumeName = string
|
||||||
|
export type NamedPath = string
|
||||||
|
export type ManifestId = string
|
||||||
|
|
||||||
|
export const matchPath = object({
|
||||||
|
name: string,
|
||||||
|
volume: string,
|
||||||
|
path: string,
|
||||||
|
manifest: object({
|
||||||
|
id: string,
|
||||||
|
}),
|
||||||
|
readonly: boolean,
|
||||||
|
})
|
||||||
|
export type Path = typeof matchPath._TYPE
|
||||||
|
export type BuildPath<M extends Path> = {
|
||||||
|
[PId in M["manifest"]["id"]]: {
|
||||||
|
[V in M["volume"]]: {
|
||||||
|
[N in M["name"]]: M
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type ValidIfNotInNested<
|
||||||
|
Building,
|
||||||
|
M extends Path,
|
||||||
|
> = Building extends BuildPath<M> ? never : M
|
||||||
|
class SetupDependencyMounts<Building> {
|
||||||
|
private constructor(readonly building: Building) {}
|
||||||
|
|
||||||
|
static of() {
|
||||||
|
return new SetupDependencyMounts({})
|
||||||
|
}
|
||||||
|
|
||||||
|
addPath<
|
||||||
|
NamedPath extends string,
|
||||||
|
VolumeName extends string,
|
||||||
|
PathNamed extends string,
|
||||||
|
M extends SDKManifest,
|
||||||
|
>(
|
||||||
|
newPath: ValidIfNotInNested<
|
||||||
|
Building,
|
||||||
|
{
|
||||||
|
name: NamedPath
|
||||||
|
volume: VolumeName
|
||||||
|
path: PathNamed
|
||||||
|
manifest: M
|
||||||
|
readonly: boolean
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
const building = deepMerge(this.building, {
|
||||||
|
[newPath.manifest.id]: {
|
||||||
|
[newPath.volume]: {
|
||||||
|
[newPath.name]: newPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Building & BuildPath<typeof newPath>
|
||||||
|
return new SetupDependencyMounts(building)
|
||||||
|
}
|
||||||
|
build() {
|
||||||
|
return this.building
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupDependencyMounts() {
|
||||||
|
return SetupDependencyMounts.of()
|
||||||
|
}
|
||||||
@@ -14,64 +14,68 @@ export interface Container {
|
|||||||
|
|
||||||
export type ManifestVersion = ValidEmVer
|
export type ManifestVersion = ValidEmVer
|
||||||
|
|
||||||
export interface SDKManifest {
|
export type SDKManifest = {
|
||||||
/** The package identifier used by the OS. This must be unique amongst all other known packages */
|
/** The package identifier used by the OS. This must be unique amongst all other known packages */
|
||||||
id: string
|
readonly id: string
|
||||||
/** A human readable service title */
|
/** A human readable service title */
|
||||||
title: string
|
readonly title: string
|
||||||
/** Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOs
|
/** Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOs
|
||||||
* - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of
|
* - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of
|
||||||
* the service
|
* the service
|
||||||
*/
|
*/
|
||||||
version: ManifestVersion
|
readonly version: ManifestVersion
|
||||||
/** Release notes for the update - can be a string, paragraph or URL */
|
/** Release notes for the update - can be a string, paragraph or URL */
|
||||||
releaseNotes: string
|
readonly releaseNotes: string
|
||||||
/** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/
|
/** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/
|
||||||
license: string // name of license
|
readonly license: string // name of license
|
||||||
/** A list of normie (hosted, SaaS, custodial, etc) services this services intends to replace */
|
/** A list of normie (hosted, SaaS, custodial, etc) services this services intends to replace */
|
||||||
replaces: string[]
|
readonly replaces: Readonly<string[]>
|
||||||
/** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this),
|
/** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this),
|
||||||
* any scripts necessary for configuration, backups, actions, or health checks (more below). This key
|
* any scripts necessary for configuration, backups, actions, or health checks (more below). This key
|
||||||
* must exist. But could be embedded into the source repository
|
* must exist. But could be embedded into the source repository
|
||||||
*/
|
*/
|
||||||
wrapperRepo: string
|
readonly wrapperRepo: string
|
||||||
/** The original project repository URL. There is no upstream repo in this example */
|
/** The original project repository URL. There is no upstream repo in this example */
|
||||||
upstreamRepo: string
|
readonly upstreamRepo: string
|
||||||
/** URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues */
|
/** URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues */
|
||||||
supportSite: string
|
readonly supportSite: string
|
||||||
/** URL to the marketing site for the project. If there is no marketing site, it can link to the original project repository */
|
/** URL to the marketing site for the project. If there is no marketing site, it can link to the original project repository */
|
||||||
marketingSite: string
|
readonly marketingSite: string
|
||||||
/** URL where users can donate to the upstream project */
|
/** URL where users can donate to the upstream project */
|
||||||
donationUrl: string | null
|
readonly donationUrl: string | null
|
||||||
/**Human readable descriptions for the service. These are used throughout the StartOS user interface, primarily in the marketplace. */
|
/**Human readable descriptions for the service. These are used throughout the StartOS user interface, primarily in the marketplace. */
|
||||||
description: {
|
readonly description: {
|
||||||
/**This is the first description visible to the user in the marketplace */
|
/**This is the first description visible to the user in the marketplace */
|
||||||
short: string
|
readonly short: string
|
||||||
/** This description will display with additional details in the service's individual marketplace page */
|
/** This description will display with additional details in the service's individual marketplace page */
|
||||||
long: string
|
readonly long: string
|
||||||
}
|
}
|
||||||
/** These assets are static files necessary for packaging the service for Start9 (into an s9pk).
|
/** These assets are static files necessary for packaging the service for Start9 (into an s9pk).
|
||||||
* Each value is a path to the specified asset. If an asset is missing from this list, or otherwise
|
* Each value is a path to the specified asset. If an asset is missing from this list, or otherwise
|
||||||
* denoted, it will be defaulted to the values denoted below.
|
* denoted, it will be defaulted to the values denoted below.
|
||||||
*/
|
*/
|
||||||
assets: {
|
readonly assets: {
|
||||||
icon: string // file path
|
/** This is the file path for the icon that will be this packages icon on the ui */
|
||||||
instructions: string // file path
|
readonly icon: string
|
||||||
license: string // file path
|
/** Instructions path to be seen in the ui section of the package */
|
||||||
|
readonly instructions: string
|
||||||
|
/** license path */
|
||||||
|
readonly license: string
|
||||||
}
|
}
|
||||||
/** Defines the containers needed to run the main and mounted volumes */
|
/** Defines the containers needed to run the main and mounted volumes */
|
||||||
containers: Record<string, Container>
|
readonly containers: Record<string, Container>
|
||||||
/** This denotes any data, asset, or pointer volumes that should be connected when the "docker run" command is invoked */
|
/** This denotes any data, asset, or pointer volumes that should be connected when the "docker run" command is invoked */
|
||||||
volumes: Record<string, "data" | "assets">
|
readonly volumes: Record<string, "data" | "assets">
|
||||||
alerts: {
|
|
||||||
install: string | null
|
readonly alerts: {
|
||||||
update: string | null
|
readonly install: string | null
|
||||||
uninstall: string | null
|
readonly update: string | null
|
||||||
restore: string | null
|
readonly uninstall: string | null
|
||||||
start: string | null
|
readonly restore: string | null
|
||||||
stop: string | null
|
readonly start: string | null
|
||||||
|
readonly stop: string | null
|
||||||
}
|
}
|
||||||
dependencies: Record<string, ManifestDependency>
|
readonly dependencies: Readonly<Record<string, ManifestDependency>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ManifestDependency {
|
export interface ManifestDependency {
|
||||||
|
|||||||
139
lib/test/mountDependencies.test.ts
Normal file
139
lib/test/mountDependencies.test.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { setupManifest } from "../manifest/setupManifest"
|
||||||
|
import { mountDependencies } from "../dependency/mountDependencies"
|
||||||
|
import {
|
||||||
|
BuildPath,
|
||||||
|
setupDependencyMounts,
|
||||||
|
} from "../dependency/setupDependencyMounts"
|
||||||
|
|
||||||
|
describe("mountDependencies", () => {
|
||||||
|
const clnManifest = setupManifest({
|
||||||
|
id: "cln",
|
||||||
|
title: "",
|
||||||
|
version: "1",
|
||||||
|
releaseNotes: "",
|
||||||
|
license: "",
|
||||||
|
replaces: [],
|
||||||
|
wrapperRepo: "",
|
||||||
|
upstreamRepo: "",
|
||||||
|
supportSite: "",
|
||||||
|
marketingSite: "",
|
||||||
|
donationUrl: null,
|
||||||
|
description: {
|
||||||
|
short: "",
|
||||||
|
long: "",
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
icon: "",
|
||||||
|
instructions: "",
|
||||||
|
license: "",
|
||||||
|
},
|
||||||
|
containers: {},
|
||||||
|
volumes: { main: "data" },
|
||||||
|
alerts: {
|
||||||
|
install: null,
|
||||||
|
update: null,
|
||||||
|
uninstall: null,
|
||||||
|
restore: null,
|
||||||
|
start: null,
|
||||||
|
stop: null,
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
})
|
||||||
|
const lndManifest = setupManifest({
|
||||||
|
id: "lnd",
|
||||||
|
title: "",
|
||||||
|
version: "1",
|
||||||
|
releaseNotes: "",
|
||||||
|
license: "",
|
||||||
|
replaces: [],
|
||||||
|
wrapperRepo: "",
|
||||||
|
upstreamRepo: "",
|
||||||
|
supportSite: "",
|
||||||
|
marketingSite: "",
|
||||||
|
donationUrl: null,
|
||||||
|
description: {
|
||||||
|
short: "",
|
||||||
|
long: "",
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
icon: "",
|
||||||
|
instructions: "",
|
||||||
|
license: "",
|
||||||
|
},
|
||||||
|
containers: {},
|
||||||
|
volumes: {},
|
||||||
|
alerts: {
|
||||||
|
install: null,
|
||||||
|
update: null,
|
||||||
|
uninstall: null,
|
||||||
|
restore: null,
|
||||||
|
start: null,
|
||||||
|
stop: null,
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
})
|
||||||
|
clnManifest.id
|
||||||
|
type test = BuildPath<{
|
||||||
|
name: "root"
|
||||||
|
manifest: typeof clnManifest
|
||||||
|
volume: "main"
|
||||||
|
path: "/"
|
||||||
|
readonly: true
|
||||||
|
}> extends BuildPath<{
|
||||||
|
name: "root"
|
||||||
|
manifest: typeof clnManifest
|
||||||
|
volume: "main2"
|
||||||
|
path: "/"
|
||||||
|
readonly: true
|
||||||
|
}>
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
|
||||||
|
test("Types work", () => {
|
||||||
|
const dependencyMounts = setupDependencyMounts()
|
||||||
|
.addPath({
|
||||||
|
name: "root",
|
||||||
|
volume: "main",
|
||||||
|
path: "/",
|
||||||
|
manifest: clnManifest,
|
||||||
|
readonly: true,
|
||||||
|
})
|
||||||
|
.addPath({
|
||||||
|
name: "root",
|
||||||
|
manifest: lndManifest,
|
||||||
|
volume: "main",
|
||||||
|
path: "/",
|
||||||
|
readonly: true,
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
;() => {
|
||||||
|
const test = mountDependencies(
|
||||||
|
null as any,
|
||||||
|
dependencyMounts,
|
||||||
|
) satisfies Promise<{
|
||||||
|
cln: {
|
||||||
|
main: {
|
||||||
|
root: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lnd: {
|
||||||
|
main: {
|
||||||
|
root: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
const test2 = mountDependencies(
|
||||||
|
null as any,
|
||||||
|
dependencyMounts.cln,
|
||||||
|
) satisfies Promise<{
|
||||||
|
main: { root: string }
|
||||||
|
}>
|
||||||
|
const test3 = mountDependencies(
|
||||||
|
null as any,
|
||||||
|
dependencyMounts.cln.main,
|
||||||
|
) satisfies Promise<{
|
||||||
|
root: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -383,7 +383,8 @@ export type Effects = {
|
|||||||
|
|
||||||
mount(options: {
|
mount(options: {
|
||||||
location: {
|
location: {
|
||||||
volumeId: string
|
/** If there is no volumeId then we mount to runMedia a special mounting location */
|
||||||
|
volumeId?: string
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
target: {
|
target: {
|
||||||
@@ -392,7 +393,7 @@ export type Effects = {
|
|||||||
path: string
|
path: string
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
}
|
}
|
||||||
}): Promise<void>
|
}): Promise<string>
|
||||||
|
|
||||||
stopped(packageId?: string): Promise<boolean>
|
stopped(packageId?: string): Promise<boolean>
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,22 @@ import { DefaultString } from "../config/configTypes"
|
|||||||
import { getDefaultString } from "./getDefaultString"
|
import { getDefaultString } from "./getDefaultString"
|
||||||
import { GetStore, getStore } from "../store/getStore"
|
import { GetStore, getStore } from "../store/getStore"
|
||||||
import { GetVault, getVault } from "./getVault"
|
import { GetVault, getVault } from "./getVault"
|
||||||
|
import {
|
||||||
|
MountDependenciesOut,
|
||||||
|
mountDependencies,
|
||||||
|
} from "../dependency/mountDependencies"
|
||||||
|
import {
|
||||||
|
ManifestId,
|
||||||
|
VolumeName,
|
||||||
|
NamedPath,
|
||||||
|
Path,
|
||||||
|
} from "../dependency/setupDependencyMounts"
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
type skipFirstParam<A> =
|
||||||
|
A extends [any, ...infer B] ? B :
|
||||||
|
A extends [] ? [] :
|
||||||
|
never
|
||||||
export type Utils<Store, Vault, WrapperOverWrite = { const: never }> = {
|
export type Utils<Store, Vault, WrapperOverWrite = { const: never }> = {
|
||||||
createOrUpdateVault: (opts: {
|
createOrUpdateVault: (opts: {
|
||||||
key: string
|
key: string
|
||||||
@@ -67,6 +82,15 @@ export type Utils<Store, Vault, WrapperOverWrite = { const: never }> = {
|
|||||||
networkBuilder: () => NetworkBuilder
|
networkBuilder: () => NetworkBuilder
|
||||||
torHostName: (id: string) => TorHostname
|
torHostName: (id: string) => TorHostname
|
||||||
nullIfEmpty: typeof nullIfEmpty
|
nullIfEmpty: typeof nullIfEmpty
|
||||||
|
mountDependencies: <
|
||||||
|
In extends
|
||||||
|
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||||
|
| Record<VolumeName, Record<NamedPath, Path>>
|
||||||
|
| Record<NamedPath, Path>
|
||||||
|
| Path,
|
||||||
|
>(
|
||||||
|
value: In,
|
||||||
|
) => Promise<MountDependenciesOut<In>>
|
||||||
}
|
}
|
||||||
export const utils = <
|
export const utils = <
|
||||||
Store = never,
|
Store = never,
|
||||||
@@ -128,5 +152,14 @@ export const utils = <
|
|||||||
set: (key: keyof Vault & string, value: string) =>
|
set: (key: keyof Vault & string, value: string) =>
|
||||||
effects.vault.set({ key, value }),
|
effects.vault.set({ key, value }),
|
||||||
},
|
},
|
||||||
|
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 {}
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -20,6 +20,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsc-multi": "^0.6.1",
|
"tsc-multi": "^0.6.1",
|
||||||
"tsconfig-paths": "^3.14.2",
|
"tsconfig-paths": "^3.14.2",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4878,17 +4879,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.9.5",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=12.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ufo": {
|
"node_modules/ufo": {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsc-multi": "^0.6.1",
|
"tsc-multi": "^0.6.1",
|
||||||
"tsconfig-paths": "^3.14.2",
|
"tsconfig-paths": "^3.14.2",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
},
|
},
|
||||||
"declaration": true
|
"declaration": true
|
||||||
|
|||||||
Reference in New Issue
Block a user