mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
chore: migrate from ts-matches to zod across all TypeScript packages
This commit is contained in:
13
container-runtime/package-lock.json
generated
13
container-runtime/package-lock.json
generated
@@ -19,7 +19,6 @@
|
|||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"ts-matches": "^6.3.2",
|
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.50",
|
"version": "0.4.0-beta.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
@@ -49,8 +48,8 @@
|
|||||||
"ini": "^5.0.0",
|
"ini": "^5.0.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"yaml": "^2.7.1",
|
||||||
"yaml": "^2.7.1"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
@@ -6494,12 +6493,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-matches": {
|
|
||||||
"version": "6.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.3.2.tgz",
|
|
||||||
"integrity": "sha512-UhSgJymF8cLd4y0vV29qlKVCkQpUtekAaujXbQVc729FezS8HwqzepqvtjzQ3HboatIqN/Idor85O2RMwT7lIQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"ts-matches": "^6.3.2",
|
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
|
|||||||
@@ -3,33 +3,39 @@ import {
|
|||||||
types as T,
|
types as T,
|
||||||
utils,
|
utils,
|
||||||
VersionRange,
|
VersionRange,
|
||||||
|
z,
|
||||||
} from "@start9labs/start-sdk"
|
} from "@start9labs/start-sdk"
|
||||||
import * as net from "net"
|
import * as net from "net"
|
||||||
import { object, string, number, literals, some, unknown } from "ts-matches"
|
|
||||||
import { Effects } from "../Models/Effects"
|
import { Effects } from "../Models/Effects"
|
||||||
|
|
||||||
import { CallbackHolder } from "../Models/CallbackHolder"
|
import { CallbackHolder } from "../Models/CallbackHolder"
|
||||||
import { asError } from "@start9labs/start-sdk/base/lib/util"
|
import { asError } from "@start9labs/start-sdk/base/lib/util"
|
||||||
const matchRpcError = object({
|
const matchRpcError = z.object({
|
||||||
error: object({
|
error: z.object({
|
||||||
code: number,
|
code: z.number(),
|
||||||
message: string,
|
message: z.string(),
|
||||||
data: some(
|
data: z
|
||||||
string,
|
.union([
|
||||||
object({
|
z.string(),
|
||||||
details: string,
|
z.object({
|
||||||
debug: string.nullable().optional(),
|
details: z.string(),
|
||||||
}),
|
debug: z.string().nullable().optional(),
|
||||||
)
|
}),
|
||||||
|
])
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const testRpcError = matchRpcError.test
|
function testRpcError(v: unknown): v is RpcError {
|
||||||
const testRpcResult = object({
|
return matchRpcError.safeParse(v).success
|
||||||
result: unknown,
|
}
|
||||||
}).test
|
const matchRpcResult = z.object({
|
||||||
type RpcError = typeof matchRpcError._TYPE
|
result: z.unknown(),
|
||||||
|
})
|
||||||
|
function testRpcResult(v: unknown): v is z.infer<typeof matchRpcResult> {
|
||||||
|
return matchRpcResult.safeParse(v).success
|
||||||
|
}
|
||||||
|
type RpcError = z.infer<typeof matchRpcError>
|
||||||
|
|
||||||
const SOCKET_PATH = "/media/startos/rpc/host.sock"
|
const SOCKET_PATH = "/media/startos/rpc/host.sock"
|
||||||
let hostSystemId = 0
|
let hostSystemId = 0
|
||||||
@@ -71,7 +77,7 @@ const rpcRoundFor =
|
|||||||
"Error in host RPC:",
|
"Error in host RPC:",
|
||||||
utils.asError({ method, params, error: res.error }),
|
utils.asError({ method, params, error: res.error }),
|
||||||
)
|
)
|
||||||
if (string.test(res.error.data)) {
|
if (typeof res.error.data === "string") {
|
||||||
message += ": " + res.error.data
|
message += ": " + res.error.data
|
||||||
console.error(`Details: ${res.error.data}`)
|
console.error(`Details: ${res.error.data}`)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import * as net from "net"
|
import * as net from "net"
|
||||||
import {
|
|
||||||
object,
|
|
||||||
some,
|
|
||||||
string,
|
|
||||||
literal,
|
|
||||||
array,
|
|
||||||
number,
|
|
||||||
matches,
|
|
||||||
any,
|
|
||||||
shape,
|
|
||||||
anyOf,
|
|
||||||
literals,
|
|
||||||
} from "ts-matches"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExtendedVersion,
|
ExtendedVersion,
|
||||||
types as T,
|
types as T,
|
||||||
utils,
|
utils,
|
||||||
VersionRange,
|
VersionRange,
|
||||||
|
z,
|
||||||
} from "@start9labs/start-sdk"
|
} from "@start9labs/start-sdk"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
|
|
||||||
@@ -29,89 +17,92 @@ import { jsonPath, unNestPath } from "../Models/JsonPath"
|
|||||||
import { System } from "../Interfaces/System"
|
import { System } from "../Interfaces/System"
|
||||||
import { makeEffects } from "./EffectCreator"
|
import { makeEffects } from "./EffectCreator"
|
||||||
type MaybePromise<T> = T | Promise<T>
|
type MaybePromise<T> = T | Promise<T>
|
||||||
export const matchRpcResult = anyOf(
|
export const matchRpcResult = z.union([
|
||||||
object({ result: any }),
|
z.object({ result: z.any() }),
|
||||||
object({
|
z.object({
|
||||||
error: object({
|
error: z.object({
|
||||||
code: number,
|
code: z.number(),
|
||||||
message: string,
|
message: z.string(),
|
||||||
data: object({
|
data: z
|
||||||
details: string.optional(),
|
.object({
|
||||||
debug: any.optional(),
|
details: z.string().optional(),
|
||||||
})
|
debug: z.any().optional(),
|
||||||
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
)
|
])
|
||||||
|
|
||||||
export type RpcResult = typeof matchRpcResult._TYPE
|
export type RpcResult = z.infer<typeof matchRpcResult>
|
||||||
type SocketResponse = ({ jsonrpc: "2.0"; id: IdType } & RpcResult) | null
|
type SocketResponse = ({ jsonrpc: "2.0"; id: IdType } & RpcResult) | null
|
||||||
|
|
||||||
const SOCKET_PARENT = "/media/startos/rpc"
|
const SOCKET_PARENT = "/media/startos/rpc"
|
||||||
const SOCKET_PATH = "/media/startos/rpc/service.sock"
|
const SOCKET_PATH = "/media/startos/rpc/service.sock"
|
||||||
const jsonrpc = "2.0" as const
|
const jsonrpc = "2.0" as const
|
||||||
|
|
||||||
const isResult = object({ result: any }).test
|
const isResultSchema = z.object({ result: z.any() })
|
||||||
|
const isResult = (v: unknown): v is z.infer<typeof isResultSchema> =>
|
||||||
|
isResultSchema.safeParse(v).success
|
||||||
|
|
||||||
const idType = some(string, number, literal(null))
|
const idType = z.union([z.string(), z.number(), z.literal(null)])
|
||||||
type IdType = null | string | number | undefined
|
type IdType = null | string | number | undefined
|
||||||
const runType = object({
|
const runType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("execute"),
|
method: z.literal("execute"),
|
||||||
params: object({
|
params: z.object({
|
||||||
id: string,
|
id: z.string(),
|
||||||
procedure: string,
|
procedure: z.string(),
|
||||||
input: any,
|
input: z.any(),
|
||||||
timeout: number.nullable().optional(),
|
timeout: z.number().nullable().optional(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const sandboxRunType = object({
|
const sandboxRunType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("sandbox"),
|
method: z.literal("sandbox"),
|
||||||
params: object({
|
params: z.object({
|
||||||
id: string,
|
id: z.string(),
|
||||||
procedure: string,
|
procedure: z.string(),
|
||||||
input: any,
|
input: z.any(),
|
||||||
timeout: number.nullable().optional(),
|
timeout: z.number().nullable().optional(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const callbackType = object({
|
const callbackType = z.object({
|
||||||
method: literal("callback"),
|
method: z.literal("callback"),
|
||||||
params: object({
|
params: z.object({
|
||||||
id: number,
|
id: z.number(),
|
||||||
args: array,
|
args: z.array(z.unknown()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const initType = object({
|
const initType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("init"),
|
method: z.literal("init"),
|
||||||
params: object({
|
params: z.object({
|
||||||
id: string,
|
id: z.string(),
|
||||||
kind: literals("install", "update", "restore").nullable(),
|
kind: z.enum(["install", "update", "restore"]).nullable(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const startType = object({
|
const startType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("start"),
|
method: z.literal("start"),
|
||||||
})
|
})
|
||||||
const stopType = object({
|
const stopType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("stop"),
|
method: z.literal("stop"),
|
||||||
})
|
})
|
||||||
const exitType = object({
|
const exitType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("exit"),
|
method: z.literal("exit"),
|
||||||
params: object({
|
params: z.object({
|
||||||
id: string,
|
id: z.string(),
|
||||||
target: string.nullable(),
|
target: z.string().nullable(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const evalType = object({
|
const evalType = z.object({
|
||||||
id: idType.optional(),
|
id: idType.optional(),
|
||||||
method: literal("eval"),
|
method: z.literal("eval"),
|
||||||
params: object({
|
params: z.object({
|
||||||
script: string,
|
script: z.string(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -144,7 +135,9 @@ const handleRpc = (id: IdType, result: Promise<RpcResult>) =>
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const hasId = object({ id: idType }).test
|
const hasIdSchema = z.object({ id: idType })
|
||||||
|
const hasId = (v: unknown): v is z.infer<typeof hasIdSchema> =>
|
||||||
|
hasIdSchema.safeParse(v).success
|
||||||
export class RpcListener {
|
export class RpcListener {
|
||||||
shouldExit = false
|
shouldExit = false
|
||||||
unixSocketServer = net.createServer(async (server) => {})
|
unixSocketServer = net.createServer(async (server) => {})
|
||||||
@@ -246,40 +239,52 @@ export class RpcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private dealWithInput(input: unknown): MaybePromise<SocketResponse> {
|
private dealWithInput(input: unknown): MaybePromise<SocketResponse> {
|
||||||
return matches(input)
|
const parsed = z.object({ method: z.string() }).safeParse(input)
|
||||||
.when(runType, async ({ id, params }) => {
|
if (!parsed.success) {
|
||||||
|
console.warn(
|
||||||
|
`Couldn't parse the following input ${JSON.stringify(input)}`,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
jsonrpc,
|
||||||
|
id: (input as any)?.id,
|
||||||
|
error: {
|
||||||
|
code: -32602,
|
||||||
|
message: "invalid params",
|
||||||
|
data: {
|
||||||
|
details: JSON.stringify(input),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parsed.data.method) {
|
||||||
|
case "execute": {
|
||||||
|
const { id, params } = runType.parse(input)
|
||||||
const system = this.system
|
const system = this.system
|
||||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
const procedure = jsonPath.parse(params.procedure)
|
||||||
const { input, timeout, id: eventId } = params
|
const { input: inp, timeout, id: eventId } = params
|
||||||
const result = this.getResult(
|
const result = this.getResult(procedure, system, eventId, timeout, inp)
|
||||||
procedure,
|
|
||||||
system,
|
|
||||||
eventId,
|
|
||||||
timeout,
|
|
||||||
input,
|
|
||||||
)
|
|
||||||
|
|
||||||
return handleRpc(id, result)
|
return handleRpc(id, result)
|
||||||
})
|
}
|
||||||
.when(sandboxRunType, async ({ id, params }) => {
|
case "sandbox": {
|
||||||
|
const { id, params } = sandboxRunType.parse(input)
|
||||||
const system = this.system
|
const system = this.system
|
||||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
const procedure = jsonPath.parse(params.procedure)
|
||||||
const { input, timeout, id: eventId } = params
|
const { input: inp, timeout, id: eventId } = params
|
||||||
const result = this.getResult(
|
const result = this.getResult(procedure, system, eventId, timeout, inp)
|
||||||
procedure,
|
|
||||||
system,
|
|
||||||
eventId,
|
|
||||||
timeout,
|
|
||||||
input,
|
|
||||||
)
|
|
||||||
|
|
||||||
return handleRpc(id, result)
|
return handleRpc(id, result)
|
||||||
})
|
}
|
||||||
.when(callbackType, async ({ params: { id, args } }) => {
|
case "callback": {
|
||||||
|
const {
|
||||||
|
params: { id, args },
|
||||||
|
} = callbackType.parse(input)
|
||||||
this.callCallback(id, args)
|
this.callCallback(id, args)
|
||||||
return null
|
return null
|
||||||
})
|
}
|
||||||
.when(startType, async ({ id }) => {
|
case "start": {
|
||||||
|
const { id } = startType.parse(input)
|
||||||
const callbacks =
|
const callbacks =
|
||||||
this.callbacks?.getChild("main") || this.callbacks?.child("main")
|
this.callbacks?.getChild("main") || this.callbacks?.child("main")
|
||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
@@ -290,8 +295,9 @@ export class RpcListener {
|
|||||||
id,
|
id,
|
||||||
this.system.start(effects).then((result) => ({ result })),
|
this.system.start(effects).then((result) => ({ result })),
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
.when(stopType, async ({ id }) => {
|
case "stop": {
|
||||||
|
const { id } = stopType.parse(input)
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
this.system.stop().then((result) => {
|
this.system.stop().then((result) => {
|
||||||
@@ -300,8 +306,9 @@ export class RpcListener {
|
|||||||
return { result }
|
return { result }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
.when(exitType, async ({ id, params }) => {
|
case "exit": {
|
||||||
|
const { id, params } = exitType.parse(input)
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -323,8 +330,9 @@ export class RpcListener {
|
|||||||
}
|
}
|
||||||
})().then((result) => ({ result })),
|
})().then((result) => ({ result })),
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
.when(initType, async ({ id, params }) => {
|
case "init": {
|
||||||
|
const { id, params } = initType.parse(input)
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -349,8 +357,9 @@ export class RpcListener {
|
|||||||
}
|
}
|
||||||
})().then((result) => ({ result })),
|
})().then((result) => ({ result })),
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
.when(evalType, async ({ id, params }) => {
|
case "eval": {
|
||||||
|
const { id, params } = evalType.parse(input)
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
id,
|
id,
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -375,41 +384,28 @@ export class RpcListener {
|
|||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
.when(
|
default: {
|
||||||
shape({ id: idType.optional(), method: string }),
|
const { id, method } = z
|
||||||
({ id, method }) => ({
|
.object({ id: idType.optional(), method: z.string() })
|
||||||
|
.passthrough()
|
||||||
|
.parse(input)
|
||||||
|
return {
|
||||||
jsonrpc,
|
jsonrpc,
|
||||||
id,
|
id,
|
||||||
error: {
|
error: {
|
||||||
code: -32601,
|
code: -32601,
|
||||||
message: `Method not found`,
|
message: "Method not found",
|
||||||
data: {
|
data: {
|
||||||
details: method,
|
details: method,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
.defaultToLazy(() => {
|
|
||||||
console.warn(
|
|
||||||
`Couldn't parse the following input ${JSON.stringify(input)}`,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
jsonrpc,
|
|
||||||
id: (input as any)?.id,
|
|
||||||
error: {
|
|
||||||
code: -32602,
|
|
||||||
message: "invalid params",
|
|
||||||
data: {
|
|
||||||
details: JSON.stringify(input),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private getResult(
|
private getResult(
|
||||||
procedure: typeof jsonPath._TYPE,
|
procedure: z.infer<typeof jsonPath>,
|
||||||
system: System,
|
system: System,
|
||||||
eventId: string,
|
eventId: string,
|
||||||
timeout: number | null | undefined,
|
timeout: number | null | undefined,
|
||||||
@@ -449,26 +445,18 @@ export class RpcListener {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})().then(ensureResultTypeShape, (error) =>
|
})().then(ensureResultTypeShape, (error) => {
|
||||||
matches(error)
|
const errorSchema = z.object({
|
||||||
.when(
|
error: z.string(),
|
||||||
object({
|
code: z.number().default(0),
|
||||||
error: string,
|
})
|
||||||
code: number.defaultTo(0),
|
const parsed = errorSchema.safeParse(error)
|
||||||
}),
|
if (parsed.success) {
|
||||||
(error) => ({
|
return {
|
||||||
error: {
|
error: { code: parsed.data.code, message: parsed.data.error },
|
||||||
code: error.code,
|
}
|
||||||
message: error.error,
|
}
|
||||||
},
|
return { error: { code: 0, message: String(error) } }
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
.defaultToLazy(() => ({
|
|
||||||
error: {
|
|
||||||
code: 0,
|
|
||||||
message: String(error),
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as fs from "fs/promises"
|
|||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import { SubContainer, types as T } from "@start9labs/start-sdk"
|
import { SubContainer, types as T } from "@start9labs/start-sdk"
|
||||||
import { promisify } from "util"
|
import { promisify } from "util"
|
||||||
import { DockerProcedure, VolumeId } from "../../../Models/DockerProcedure"
|
import { DockerProcedure } from "../../../Models/DockerProcedure"
|
||||||
import { Volume } from "./matchVolume"
|
import { Volume } from "./matchVolume"
|
||||||
import {
|
import {
|
||||||
CommandOptions,
|
CommandOptions,
|
||||||
@@ -28,7 +28,7 @@ export class DockerProcedureContainer extends Drop {
|
|||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
packageId: string,
|
packageId: string,
|
||||||
data: DockerProcedure,
|
data: DockerProcedure,
|
||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: string]: Volume },
|
||||||
name: string,
|
name: string,
|
||||||
options: { subcontainer?: SubContainer<SDKManifest> } = {},
|
options: { subcontainer?: SubContainer<SDKManifest> } = {},
|
||||||
) {
|
) {
|
||||||
@@ -47,7 +47,7 @@ export class DockerProcedureContainer extends Drop {
|
|||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
packageId: string,
|
packageId: string,
|
||||||
data: DockerProcedure,
|
data: DockerProcedure,
|
||||||
volumes: { [id: VolumeId]: Volume },
|
volumes: { [id: string]: Volume },
|
||||||
name: string,
|
name: string,
|
||||||
) {
|
) {
|
||||||
const subcontainer = await SubContainerOwned.of(
|
const subcontainer = await SubContainerOwned.of(
|
||||||
@@ -64,7 +64,7 @@ export class DockerProcedureContainer extends Drop {
|
|||||||
? `${subcontainer.rootfs}${mounts[mount]}`
|
? `${subcontainer.rootfs}${mounts[mount]}`
|
||||||
: `${subcontainer.rootfs}/${mounts[mount]}`
|
: `${subcontainer.rootfs}/${mounts[mount]}`
|
||||||
await fs.mkdir(path, { recursive: true })
|
await fs.mkdir(path, { recursive: true })
|
||||||
const volumeMount = volumes[mount]
|
const volumeMount: Volume = volumes[mount]
|
||||||
if (volumeMount.type === "data") {
|
if (volumeMount.type === "data") {
|
||||||
await subcontainer.mount(
|
await subcontainer.mount(
|
||||||
Mounts.of().mountVolume({
|
Mounts.of().mountVolume({
|
||||||
|
|||||||
@@ -15,26 +15,11 @@ import { System } from "../../../Interfaces/System"
|
|||||||
import { matchManifest, Manifest } from "./matchManifest"
|
import { matchManifest, Manifest } from "./matchManifest"
|
||||||
import * as childProcess from "node:child_process"
|
import * as childProcess from "node:child_process"
|
||||||
import { DockerProcedureContainer } from "./DockerProcedureContainer"
|
import { DockerProcedureContainer } from "./DockerProcedureContainer"
|
||||||
|
import { DockerProcedure } from "../../../Models/DockerProcedure"
|
||||||
import { promisify } from "node:util"
|
import { promisify } from "node:util"
|
||||||
import * as U from "./oldEmbassyTypes"
|
import * as U from "./oldEmbassyTypes"
|
||||||
import { MainLoop } from "./MainLoop"
|
import { MainLoop } from "./MainLoop"
|
||||||
import {
|
import { z } from "@start9labs/start-sdk"
|
||||||
matches,
|
|
||||||
boolean,
|
|
||||||
dictionary,
|
|
||||||
literal,
|
|
||||||
literals,
|
|
||||||
object,
|
|
||||||
string,
|
|
||||||
unknown,
|
|
||||||
any,
|
|
||||||
tuple,
|
|
||||||
number,
|
|
||||||
anyOf,
|
|
||||||
deferred,
|
|
||||||
Parser,
|
|
||||||
array,
|
|
||||||
} from "ts-matches"
|
|
||||||
import { AddSslOptions } from "@start9labs/start-sdk/base/lib/osBindings"
|
import { AddSslOptions } from "@start9labs/start-sdk/base/lib/osBindings"
|
||||||
import {
|
import {
|
||||||
BindOptionsByProtocol,
|
BindOptionsByProtocol,
|
||||||
@@ -57,6 +42,15 @@ function todo(): never {
|
|||||||
throw new Error("Not implemented")
|
throw new Error("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local type for procedure values from the manifest.
|
||||||
|
* The manifest's zod schemas use ZodTypeAny casts that produce `unknown` in zod v4.
|
||||||
|
* This type restores the expected shape for type-safe property access.
|
||||||
|
*/
|
||||||
|
type Procedure =
|
||||||
|
| (DockerProcedure & { type: "docker" })
|
||||||
|
| { type: "script"; args: unknown[] | null }
|
||||||
|
|
||||||
const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
|
const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
|
||||||
export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
||||||
|
|
||||||
@@ -65,26 +59,24 @@ const configFile = FileHelper.json(
|
|||||||
base: new Volume("embassy"),
|
base: new Volume("embassy"),
|
||||||
subpath: "config.json",
|
subpath: "config.json",
|
||||||
},
|
},
|
||||||
matches.any,
|
z.any(),
|
||||||
)
|
)
|
||||||
const dependsOnFile = FileHelper.json(
|
const dependsOnFile = FileHelper.json(
|
||||||
{
|
{
|
||||||
base: new Volume("embassy"),
|
base: new Volume("embassy"),
|
||||||
subpath: "dependsOn.json",
|
subpath: "dependsOn.json",
|
||||||
},
|
},
|
||||||
dictionary([string, array(string)]),
|
z.record(z.string(), z.array(z.string())),
|
||||||
)
|
)
|
||||||
|
|
||||||
const matchResult = object({
|
const matchResult = z.object({
|
||||||
result: any,
|
result: z.any(),
|
||||||
})
|
})
|
||||||
const matchError = object({
|
const matchError = z.object({
|
||||||
error: string,
|
error: z.string(),
|
||||||
})
|
})
|
||||||
const matchErrorCode = object<{
|
const matchErrorCode = z.object({
|
||||||
"error-code": [number, string] | readonly [number, string]
|
"error-code": z.tuple([z.number(), z.string()]),
|
||||||
}>({
|
|
||||||
"error-code": tuple(number, string),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const assertNever = (
|
const assertNever = (
|
||||||
@@ -96,29 +88,34 @@ const assertNever = (
|
|||||||
/**
|
/**
|
||||||
Should be changing the type for specific properties, and this is mostly a transformation for the old return types to the newer one.
|
Should be changing the type for specific properties, and this is mostly a transformation for the old return types to the newer one.
|
||||||
*/
|
*/
|
||||||
|
function isMatchResult(a: unknown): a is z.infer<typeof matchResult> {
|
||||||
|
return matchResult.safeParse(a).success
|
||||||
|
}
|
||||||
|
function isMatchError(a: unknown): a is z.infer<typeof matchError> {
|
||||||
|
return matchError.safeParse(a).success
|
||||||
|
}
|
||||||
|
function isMatchErrorCode(a: unknown): a is z.infer<typeof matchErrorCode> {
|
||||||
|
return matchErrorCode.safeParse(a).success
|
||||||
|
}
|
||||||
const fromReturnType = <A>(a: U.ResultType<A>): A => {
|
const fromReturnType = <A>(a: U.ResultType<A>): A => {
|
||||||
if (matchResult.test(a)) {
|
if (isMatchResult(a)) {
|
||||||
return a.result
|
return a.result
|
||||||
}
|
}
|
||||||
if (matchError.test(a)) {
|
if (isMatchError(a)) {
|
||||||
console.info({ passedErrorStack: new Error().stack, error: a.error })
|
console.info({ passedErrorStack: new Error().stack, error: a.error })
|
||||||
throw { error: a.error }
|
throw { error: a.error }
|
||||||
}
|
}
|
||||||
if (matchErrorCode.test(a)) {
|
if (isMatchErrorCode(a)) {
|
||||||
const [code, message] = a["error-code"]
|
const [code, message] = a["error-code"]
|
||||||
throw { error: message, code }
|
throw { error: message, code }
|
||||||
}
|
}
|
||||||
return assertNever(a)
|
return assertNever(a as never)
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchSetResult = object({
|
const matchSetResult = z.object({
|
||||||
"depends-on": dictionary([string, array(string)])
|
"depends-on": z.record(z.string(), z.array(z.string())).nullable().optional(),
|
||||||
.nullable()
|
dependsOn: z.record(z.string(), z.array(z.string())).nullable().optional(),
|
||||||
.optional(),
|
signal: z.enum([
|
||||||
dependsOn: dictionary([string, array(string)])
|
|
||||||
.nullable()
|
|
||||||
.optional(),
|
|
||||||
signal: literals(
|
|
||||||
"SIGTERM",
|
"SIGTERM",
|
||||||
"SIGHUP",
|
"SIGHUP",
|
||||||
"SIGINT",
|
"SIGINT",
|
||||||
@@ -151,7 +148,7 @@ const matchSetResult = object({
|
|||||||
"SIGPWR",
|
"SIGPWR",
|
||||||
"SIGSYS",
|
"SIGSYS",
|
||||||
"SIGINFO",
|
"SIGINFO",
|
||||||
),
|
]),
|
||||||
})
|
})
|
||||||
|
|
||||||
type OldGetConfigRes = {
|
type OldGetConfigRes = {
|
||||||
@@ -233,33 +230,29 @@ const asProperty = (x: PackagePropertiesV2): PropertiesReturn =>
|
|||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(x).map(([key, value]) => [key, asProperty_(value)]),
|
Object.entries(x).map(([key, value]) => [key, asProperty_(value)]),
|
||||||
)
|
)
|
||||||
const [matchPackageProperties, setMatchPackageProperties] =
|
const matchPackagePropertyObject: z.ZodType<PackagePropertyObject> = z.object({
|
||||||
deferred<PackagePropertiesV2>()
|
value: z.lazy(() => matchPackageProperties),
|
||||||
const matchPackagePropertyObject: Parser<unknown, PackagePropertyObject> =
|
type: z.literal("object"),
|
||||||
object({
|
description: z.string(),
|
||||||
value: matchPackageProperties,
|
})
|
||||||
type: literal("object"),
|
|
||||||
description: string,
|
|
||||||
})
|
|
||||||
|
|
||||||
const matchPackagePropertyString: Parser<unknown, PackagePropertyString> =
|
const matchPackagePropertyString: z.ZodType<PackagePropertyString> = z.object({
|
||||||
object({
|
type: z.literal("string"),
|
||||||
type: literal("string"),
|
description: z.string().nullable().optional(),
|
||||||
description: string.nullable().optional(),
|
value: z.string(),
|
||||||
value: string,
|
copyable: z.boolean().nullable().optional(),
|
||||||
copyable: boolean.nullable().optional(),
|
qr: z.boolean().nullable().optional(),
|
||||||
qr: boolean.nullable().optional(),
|
masked: z.boolean().nullable().optional(),
|
||||||
masked: boolean.nullable().optional(),
|
})
|
||||||
})
|
const matchPackageProperties: z.ZodType<PackagePropertiesV2> = z.lazy(() =>
|
||||||
setMatchPackageProperties(
|
z.record(
|
||||||
dictionary([
|
z.string(),
|
||||||
string,
|
z.union([matchPackagePropertyObject, matchPackagePropertyString]),
|
||||||
anyOf(matchPackagePropertyObject, matchPackagePropertyString),
|
),
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const matchProperties = object({
|
const matchProperties = z.object({
|
||||||
version: literal(2),
|
version: z.literal(2),
|
||||||
data: matchPackageProperties,
|
data: matchPackageProperties,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -303,7 +296,7 @@ export class SystemForEmbassy implements System {
|
|||||||
})
|
})
|
||||||
const manifestData = await fs.readFile(manifestLocation, "utf-8")
|
const manifestData = await fs.readFile(manifestLocation, "utf-8")
|
||||||
return new SystemForEmbassy(
|
return new SystemForEmbassy(
|
||||||
matchManifest.unsafeCast(JSON.parse(manifestData)),
|
matchManifest.parse(JSON.parse(manifestData)),
|
||||||
moduleCode,
|
moduleCode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -389,7 +382,9 @@ export class SystemForEmbassy implements System {
|
|||||||
delete this.currentRunning
|
delete this.currentRunning
|
||||||
if (currentRunning) {
|
if (currentRunning) {
|
||||||
await currentRunning.clean({
|
await currentRunning.clean({
|
||||||
timeout: fromDuration(this.manifest.main["sigterm-timeout"] || "30s"),
|
timeout: fromDuration(
|
||||||
|
(this.manifest.main["sigterm-timeout"] as any) || "30s",
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -623,7 +618,7 @@ export class SystemForEmbassy implements System {
|
|||||||
effects: Effects,
|
effects: Effects,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const backup = this.manifest.backup.create
|
const backup = this.manifest.backup.create as Procedure
|
||||||
if (backup.type === "docker") {
|
if (backup.type === "docker") {
|
||||||
const commands = [backup.entrypoint, ...backup.args]
|
const commands = [backup.entrypoint, ...backup.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
@@ -656,7 +651,7 @@ export class SystemForEmbassy implements System {
|
|||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
})
|
})
|
||||||
.catch((_) => null)
|
.catch((_) => null)
|
||||||
const restoreBackup = this.manifest.backup.restore
|
const restoreBackup = this.manifest.backup.restore as Procedure
|
||||||
if (restoreBackup.type === "docker") {
|
if (restoreBackup.type === "docker") {
|
||||||
const commands = [restoreBackup.entrypoint, ...restoreBackup.args]
|
const commands = [restoreBackup.entrypoint, ...restoreBackup.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
@@ -689,7 +684,7 @@ export class SystemForEmbassy implements System {
|
|||||||
effects: Effects,
|
effects: Effects,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<OldGetConfigRes> {
|
): Promise<OldGetConfigRes> {
|
||||||
const config = this.manifest.config?.get
|
const config = this.manifest.config?.get as Procedure | undefined
|
||||||
if (!config) return { spec: {} }
|
if (!config) return { spec: {} }
|
||||||
if (config.type === "docker") {
|
if (config.type === "docker") {
|
||||||
const commands = [config.entrypoint, ...config.args]
|
const commands = [config.entrypoint, ...config.args]
|
||||||
@@ -731,7 +726,7 @@ export class SystemForEmbassy implements System {
|
|||||||
)
|
)
|
||||||
await updateConfig(effects, this.manifest, spec, newConfig)
|
await updateConfig(effects, this.manifest, spec, newConfig)
|
||||||
await configFile.write(effects, newConfig)
|
await configFile.write(effects, newConfig)
|
||||||
const setConfigValue = this.manifest.config?.set
|
const setConfigValue = this.manifest.config?.set as Procedure | undefined
|
||||||
if (!setConfigValue) return
|
if (!setConfigValue) return
|
||||||
if (setConfigValue.type === "docker") {
|
if (setConfigValue.type === "docker") {
|
||||||
const commands = [
|
const commands = [
|
||||||
@@ -746,7 +741,7 @@ export class SystemForEmbassy implements System {
|
|||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
`Set Config - ${commands.join(" ")}`,
|
`Set Config - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
const answer = matchSetResult.unsafeCast(
|
const answer = matchSetResult.parse(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
),
|
),
|
||||||
@@ -759,7 +754,7 @@ export class SystemForEmbassy implements System {
|
|||||||
const method = moduleCode.setConfig
|
const method = moduleCode.setConfig
|
||||||
if (!method) throw new Error("Expecting that the method setConfig exists")
|
if (!method) throw new Error("Expecting that the method setConfig exists")
|
||||||
|
|
||||||
const answer = matchSetResult.unsafeCast(
|
const answer = matchSetResult.parse(
|
||||||
await method(
|
await method(
|
||||||
polyfillEffects(effects, this.manifest),
|
polyfillEffects(effects, this.manifest),
|
||||||
newConfig as U.Config,
|
newConfig as U.Config,
|
||||||
@@ -788,7 +783,11 @@ export class SystemForEmbassy implements System {
|
|||||||
const requiredDeps = {
|
const requiredDeps = {
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(this.manifest.dependencies ?? {})
|
Object.entries(this.manifest.dependencies ?? {})
|
||||||
.filter(([k, v]) => v?.requirement.type === "required")
|
.filter(
|
||||||
|
([k, v]) =>
|
||||||
|
(v?.requirement as { type: string } | undefined)?.type ===
|
||||||
|
"required",
|
||||||
|
)
|
||||||
.map((x) => [x[0], []]) || [],
|
.map((x) => [x[0], []]) || [],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -856,7 +855,7 @@ export class SystemForEmbassy implements System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (migration) {
|
if (migration) {
|
||||||
const [_, procedure] = migration
|
const [_, procedure] = migration as readonly [unknown, Procedure]
|
||||||
if (procedure.type === "docker") {
|
if (procedure.type === "docker") {
|
||||||
const commands = [procedure.entrypoint, ...procedure.args]
|
const commands = [procedure.entrypoint, ...procedure.args]
|
||||||
const container = await DockerProcedureContainer.of(
|
const container = await DockerProcedureContainer.of(
|
||||||
@@ -894,7 +893,10 @@ export class SystemForEmbassy implements System {
|
|||||||
effects: Effects,
|
effects: Effects,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<PropertiesReturn> {
|
): Promise<PropertiesReturn> {
|
||||||
const setConfigValue = this.manifest.properties
|
const setConfigValue = this.manifest.properties as
|
||||||
|
| Procedure
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
if (!setConfigValue) throw new Error("There is no properties")
|
if (!setConfigValue) throw new Error("There is no properties")
|
||||||
if (setConfigValue.type === "docker") {
|
if (setConfigValue.type === "docker") {
|
||||||
const commands = [setConfigValue.entrypoint, ...setConfigValue.args]
|
const commands = [setConfigValue.entrypoint, ...setConfigValue.args]
|
||||||
@@ -905,7 +907,7 @@ export class SystemForEmbassy implements System {
|
|||||||
this.manifest.volumes,
|
this.manifest.volumes,
|
||||||
`Properties - ${commands.join(" ")}`,
|
`Properties - ${commands.join(" ")}`,
|
||||||
)
|
)
|
||||||
const properties = matchProperties.unsafeCast(
|
const properties = matchProperties.parse(
|
||||||
JSON.parse(
|
JSON.parse(
|
||||||
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
(await container.execFail(commands, timeoutMs)).stdout.toString(),
|
||||||
),
|
),
|
||||||
@@ -916,7 +918,7 @@ export class SystemForEmbassy implements System {
|
|||||||
const method = moduleCode.properties
|
const method = moduleCode.properties
|
||||||
if (!method)
|
if (!method)
|
||||||
throw new Error("Expecting that the method properties exists")
|
throw new Error("Expecting that the method properties exists")
|
||||||
const properties = matchProperties.unsafeCast(
|
const properties = matchProperties.parse(
|
||||||
await method(polyfillEffects(effects, this.manifest)).then(
|
await method(polyfillEffects(effects, this.manifest)).then(
|
||||||
fromReturnType,
|
fromReturnType,
|
||||||
),
|
),
|
||||||
@@ -931,7 +933,8 @@ export class SystemForEmbassy implements System {
|
|||||||
formData: unknown,
|
formData: unknown,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<T.ActionResult> {
|
): Promise<T.ActionResult> {
|
||||||
const actionProcedure = this.manifest.actions?.[actionId]?.implementation
|
const actionProcedure = this.manifest.actions?.[actionId]
|
||||||
|
?.implementation as Procedure | undefined
|
||||||
const toActionResult = ({
|
const toActionResult = ({
|
||||||
message,
|
message,
|
||||||
value,
|
value,
|
||||||
@@ -998,7 +1001,9 @@ export class SystemForEmbassy implements System {
|
|||||||
oldConfig: unknown,
|
oldConfig: unknown,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<object> {
|
): Promise<object> {
|
||||||
const actionProcedure = this.manifest.dependencies?.[id]?.config?.check
|
const actionProcedure = this.manifest.dependencies?.[id]?.config?.check as
|
||||||
|
| Procedure
|
||||||
|
| undefined
|
||||||
if (!actionProcedure) return { message: "Action not found", value: null }
|
if (!actionProcedure) return { message: "Action not found", value: null }
|
||||||
if (actionProcedure.type === "docker") {
|
if (actionProcedure.type === "docker") {
|
||||||
const commands = [
|
const commands = [
|
||||||
@@ -1090,40 +1095,50 @@ export class SystemForEmbassy implements System {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchPointer = object({
|
const matchPointer = z.object({
|
||||||
type: literal("pointer"),
|
type: z.literal("pointer"),
|
||||||
})
|
})
|
||||||
|
|
||||||
const matchPointerPackage = object({
|
const matchPointerPackage = z.object({
|
||||||
subtype: literal("package"),
|
subtype: z.literal("package"),
|
||||||
target: literals("tor-key", "tor-address", "lan-address"),
|
target: z.enum(["tor-key", "tor-address", "lan-address"]),
|
||||||
"package-id": string,
|
"package-id": z.string(),
|
||||||
interface: string,
|
interface: z.string(),
|
||||||
})
|
})
|
||||||
const matchPointerConfig = object({
|
const matchPointerConfig = z.object({
|
||||||
subtype: literal("package"),
|
subtype: z.literal("package"),
|
||||||
target: literals("config"),
|
target: z.enum(["config"]),
|
||||||
"package-id": string,
|
"package-id": z.string(),
|
||||||
selector: string,
|
selector: z.string(),
|
||||||
multi: boolean,
|
multi: z.boolean(),
|
||||||
})
|
})
|
||||||
const matchSpec = object({
|
const matchSpec = z.object({
|
||||||
spec: object,
|
spec: z.record(z.string(), z.unknown()),
|
||||||
})
|
})
|
||||||
const matchVariants = object({ variants: dictionary([string, unknown]) })
|
const matchVariants = z.object({ variants: z.record(z.string(), z.unknown()) })
|
||||||
|
function isMatchPointer(v: unknown): v is z.infer<typeof matchPointer> {
|
||||||
|
return matchPointer.safeParse(v).success
|
||||||
|
}
|
||||||
|
function isMatchSpec(v: unknown): v is z.infer<typeof matchSpec> {
|
||||||
|
return matchSpec.safeParse(v).success
|
||||||
|
}
|
||||||
|
function isMatchVariants(v: unknown): v is z.infer<typeof matchVariants> {
|
||||||
|
return matchVariants.safeParse(v).success
|
||||||
|
}
|
||||||
function cleanSpecOfPointers<T>(mutSpec: T): T {
|
function cleanSpecOfPointers<T>(mutSpec: T): T {
|
||||||
if (!object.test(mutSpec)) return mutSpec
|
if (typeof mutSpec !== "object" || mutSpec === null) return mutSpec
|
||||||
for (const key in mutSpec) {
|
for (const key in mutSpec) {
|
||||||
const value = mutSpec[key]
|
const value = mutSpec[key]
|
||||||
if (matchSpec.test(value)) value.spec = cleanSpecOfPointers(value.spec)
|
if (isMatchSpec(value))
|
||||||
if (matchVariants.test(value))
|
value.spec = cleanSpecOfPointers(value.spec) as Record<string, unknown>
|
||||||
|
if (isMatchVariants(value))
|
||||||
value.variants = Object.fromEntries(
|
value.variants = Object.fromEntries(
|
||||||
Object.entries(value.variants).map(([key, value]) => [
|
Object.entries(value.variants).map(([key, value]) => [
|
||||||
key,
|
key,
|
||||||
cleanSpecOfPointers(value),
|
cleanSpecOfPointers(value),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
if (!matchPointer.test(value)) continue
|
if (!isMatchPointer(value)) continue
|
||||||
delete mutSpec[key]
|
delete mutSpec[key]
|
||||||
// // if (value.target === )
|
// // if (value.target === )
|
||||||
}
|
}
|
||||||
@@ -1269,7 +1284,7 @@ function extractServiceInterfaceId(manifest: Manifest, specInterface: string) {
|
|||||||
}
|
}
|
||||||
async function convertToNewConfig(value: OldGetConfigRes) {
|
async function convertToNewConfig(value: OldGetConfigRes) {
|
||||||
try {
|
try {
|
||||||
const valueSpec: OldConfigSpec = matchOldConfigSpec.unsafeCast(value.spec)
|
const valueSpec: OldConfigSpec = matchOldConfigSpec.parse(value.spec)
|
||||||
const spec = transformConfigSpec(valueSpec)
|
const spec = transformConfigSpec(valueSpec)
|
||||||
if (!value.config) return { spec, config: null }
|
if (!value.config) return { spec, config: null }
|
||||||
const config = transformOldConfigToNew(valueSpec, value.config) ?? null
|
const config = transformOldConfigToNew(valueSpec, value.config) ?? null
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import synapseManifest from "./__fixtures__/synapseManifest"
|
|||||||
|
|
||||||
describe("matchManifest", () => {
|
describe("matchManifest", () => {
|
||||||
test("gittea", () => {
|
test("gittea", () => {
|
||||||
matchManifest.unsafeCast(giteaManifest)
|
matchManifest.parse(giteaManifest)
|
||||||
})
|
})
|
||||||
test("synapse", () => {
|
test("synapse", () => {
|
||||||
matchManifest.unsafeCast(synapseManifest)
|
matchManifest.parse(synapseManifest)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,126 +1,121 @@
|
|||||||
import {
|
import { z } from "@start9labs/start-sdk"
|
||||||
object,
|
|
||||||
literal,
|
|
||||||
string,
|
|
||||||
array,
|
|
||||||
boolean,
|
|
||||||
dictionary,
|
|
||||||
literals,
|
|
||||||
number,
|
|
||||||
unknown,
|
|
||||||
some,
|
|
||||||
every,
|
|
||||||
} from "ts-matches"
|
|
||||||
import { matchVolume } from "./matchVolume"
|
import { matchVolume } from "./matchVolume"
|
||||||
import { matchDockerProcedure } from "../../../Models/DockerProcedure"
|
import { matchDockerProcedure } from "../../../Models/DockerProcedure"
|
||||||
|
|
||||||
const matchJsProcedure = object({
|
const matchJsProcedure = z.object({
|
||||||
type: literal("script"),
|
type: z.literal("script"),
|
||||||
args: array(unknown).nullable().optional().defaultTo([]),
|
args: z.array(z.unknown()).nullable().optional().default([]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const matchProcedure = some(matchDockerProcedure, matchJsProcedure)
|
const matchProcedure = z.union([matchDockerProcedure, matchJsProcedure])
|
||||||
export type Procedure = typeof matchProcedure._TYPE
|
export type Procedure = z.infer<typeof matchProcedure>
|
||||||
|
|
||||||
const matchAction = object({
|
const matchAction = z.object({
|
||||||
name: string,
|
name: z.string(),
|
||||||
description: string,
|
description: z.string(),
|
||||||
warning: string.nullable().optional(),
|
warning: z.string().nullable().optional(),
|
||||||
implementation: matchProcedure,
|
implementation: matchProcedure,
|
||||||
"allowed-statuses": array(literals("running", "stopped")),
|
"allowed-statuses": z.array(z.enum(["running", "stopped"])),
|
||||||
"input-spec": unknown.nullable().optional(),
|
"input-spec": z.unknown().nullable().optional(),
|
||||||
})
|
})
|
||||||
export const matchManifest = object({
|
export const matchManifest = z.object({
|
||||||
id: string,
|
id: z.string(),
|
||||||
title: string,
|
title: z.string(),
|
||||||
version: string,
|
version: z.string(),
|
||||||
main: matchDockerProcedure,
|
main: matchDockerProcedure,
|
||||||
assets: object({
|
assets: z
|
||||||
assets: string.nullable().optional(),
|
.object({
|
||||||
scripts: string.nullable().optional(),
|
assets: z.string().nullable().optional(),
|
||||||
})
|
scripts: z.string().nullable().optional(),
|
||||||
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
"health-checks": dictionary([
|
"health-checks": z.record(
|
||||||
string,
|
z.string(),
|
||||||
every(
|
z.intersection(
|
||||||
matchProcedure,
|
matchProcedure,
|
||||||
object({
|
z.object({
|
||||||
name: string,
|
name: z.string(),
|
||||||
["success-message"]: string.nullable().optional(),
|
"success-message": z.string().nullable().optional(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
]),
|
),
|
||||||
config: object({
|
config: z
|
||||||
get: matchProcedure,
|
.object({
|
||||||
set: matchProcedure,
|
get: matchProcedure,
|
||||||
})
|
set: matchProcedure,
|
||||||
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
properties: matchProcedure.nullable().optional(),
|
properties: matchProcedure.nullable().optional(),
|
||||||
volumes: dictionary([string, matchVolume]),
|
volumes: z.record(z.string(), matchVolume),
|
||||||
interfaces: dictionary([
|
interfaces: z.record(
|
||||||
string,
|
z.string(),
|
||||||
object({
|
z.object({
|
||||||
name: string,
|
name: z.string(),
|
||||||
description: string,
|
description: z.string(),
|
||||||
"tor-config": object({
|
"tor-config": z
|
||||||
"port-mapping": dictionary([string, string]),
|
.object({
|
||||||
})
|
"port-mapping": z.record(z.string(), z.string()),
|
||||||
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
"lan-config": dictionary([
|
"lan-config": z
|
||||||
string,
|
.record(
|
||||||
object({
|
z.string(),
|
||||||
ssl: boolean,
|
z.object({
|
||||||
internal: number,
|
ssl: z.boolean(),
|
||||||
}),
|
internal: z.number(),
|
||||||
])
|
}),
|
||||||
|
)
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
ui: boolean,
|
ui: z.boolean(),
|
||||||
protocols: array(string),
|
protocols: z.array(z.string()),
|
||||||
}),
|
}),
|
||||||
]),
|
),
|
||||||
backup: object({
|
backup: z.object({
|
||||||
create: matchProcedure,
|
create: matchProcedure,
|
||||||
restore: matchProcedure,
|
restore: matchProcedure,
|
||||||
}),
|
}),
|
||||||
migrations: object({
|
migrations: z
|
||||||
to: dictionary([string, matchProcedure]),
|
.object({
|
||||||
from: dictionary([string, matchProcedure]),
|
to: z.record(z.string(), matchProcedure),
|
||||||
})
|
from: z.record(z.string(), matchProcedure),
|
||||||
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
dependencies: dictionary([
|
dependencies: z.record(
|
||||||
string,
|
z.string(),
|
||||||
object({
|
z
|
||||||
version: string,
|
.object({
|
||||||
requirement: some(
|
version: z.string(),
|
||||||
object({
|
requirement: z.union([
|
||||||
type: literal("opt-in"),
|
z.object({
|
||||||
how: string,
|
type: z.literal("opt-in"),
|
||||||
}),
|
how: z.string(),
|
||||||
object({
|
}),
|
||||||
type: literal("opt-out"),
|
z.object({
|
||||||
how: string,
|
type: z.literal("opt-out"),
|
||||||
}),
|
how: z.string(),
|
||||||
object({
|
}),
|
||||||
type: literal("required"),
|
z.object({
|
||||||
}),
|
type: z.literal("required"),
|
||||||
),
|
}),
|
||||||
description: string.nullable().optional(),
|
]),
|
||||||
config: object({
|
description: z.string().nullable().optional(),
|
||||||
check: matchProcedure,
|
config: z
|
||||||
"auto-configure": matchProcedure,
|
.object({
|
||||||
|
check: matchProcedure,
|
||||||
|
"auto-configure": matchProcedure,
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
.nullable()
|
|
||||||
.optional(),
|
|
||||||
})
|
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
]),
|
),
|
||||||
|
|
||||||
actions: dictionary([string, matchAction]),
|
actions: z.record(z.string(), matchAction),
|
||||||
})
|
})
|
||||||
export type Manifest = typeof matchManifest._TYPE
|
export type Manifest = z.infer<typeof matchManifest>
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
import { object, literal, string, boolean, some } from "ts-matches"
|
import { z } from "@start9labs/start-sdk"
|
||||||
|
|
||||||
const matchDataVolume = object({
|
const matchDataVolume = z.object({
|
||||||
type: literal("data"),
|
type: z.literal("data"),
|
||||||
readonly: boolean.optional(),
|
readonly: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
const matchAssetVolume = object({
|
const matchAssetVolume = z.object({
|
||||||
type: literal("assets"),
|
type: z.literal("assets"),
|
||||||
})
|
})
|
||||||
const matchPointerVolume = object({
|
const matchPointerVolume = z.object({
|
||||||
type: literal("pointer"),
|
type: z.literal("pointer"),
|
||||||
"package-id": string,
|
"package-id": z.string(),
|
||||||
"volume-id": string,
|
"volume-id": z.string(),
|
||||||
path: string,
|
path: z.string(),
|
||||||
readonly: boolean,
|
readonly: z.boolean(),
|
||||||
})
|
})
|
||||||
const matchCertificateVolume = object({
|
const matchCertificateVolume = z.object({
|
||||||
type: literal("certificate"),
|
type: z.literal("certificate"),
|
||||||
"interface-id": string,
|
"interface-id": z.string(),
|
||||||
})
|
})
|
||||||
const matchBackupVolume = object({
|
const matchBackupVolume = z.object({
|
||||||
type: literal("backup"),
|
type: z.literal("backup"),
|
||||||
readonly: boolean,
|
readonly: z.boolean(),
|
||||||
})
|
})
|
||||||
export const matchVolume = some(
|
export const matchVolume = z.union([
|
||||||
matchDataVolume,
|
matchDataVolume,
|
||||||
matchAssetVolume,
|
matchAssetVolume,
|
||||||
matchPointerVolume,
|
matchPointerVolume,
|
||||||
matchCertificateVolume,
|
matchCertificateVolume,
|
||||||
matchBackupVolume,
|
matchBackupVolume,
|
||||||
)
|
])
|
||||||
export type Volume = typeof matchVolume._TYPE
|
export type Volume = z.infer<typeof matchVolume>
|
||||||
|
|||||||
@@ -12,43 +12,43 @@ import nostrConfig2 from "./__fixtures__/nostrConfig2"
|
|||||||
|
|
||||||
describe("transformConfigSpec", () => {
|
describe("transformConfigSpec", () => {
|
||||||
test("matchOldConfigSpec(embassyPages.homepage.variants[web-page])", () => {
|
test("matchOldConfigSpec(embassyPages.homepage.variants[web-page])", () => {
|
||||||
matchOldConfigSpec.unsafeCast(
|
matchOldConfigSpec.parse(
|
||||||
fixtureEmbassyPagesConfig.homepage.variants["web-page"],
|
fixtureEmbassyPagesConfig.homepage.variants["web-page"],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test("matchOldConfigSpec(embassyPages)", () => {
|
test("matchOldConfigSpec(embassyPages)", () => {
|
||||||
matchOldConfigSpec.unsafeCast(fixtureEmbassyPagesConfig)
|
matchOldConfigSpec.parse(fixtureEmbassyPagesConfig)
|
||||||
})
|
})
|
||||||
test("transformConfigSpec(embassyPages)", () => {
|
test("transformConfigSpec(embassyPages)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(fixtureEmbassyPagesConfig)
|
const spec = matchOldConfigSpec.parse(fixtureEmbassyPagesConfig)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("matchOldConfigSpec(RTL.nodes)", () => {
|
test("matchOldConfigSpec(RTL.nodes)", () => {
|
||||||
matchOldValueSpecList.unsafeCast(fixtureRTLConfig.nodes)
|
matchOldValueSpecList.parse(fixtureRTLConfig.nodes)
|
||||||
})
|
})
|
||||||
test("matchOldConfigSpec(RTL)", () => {
|
test("matchOldConfigSpec(RTL)", () => {
|
||||||
matchOldConfigSpec.unsafeCast(fixtureRTLConfig)
|
matchOldConfigSpec.parse(fixtureRTLConfig)
|
||||||
})
|
})
|
||||||
test("transformConfigSpec(RTL)", () => {
|
test("transformConfigSpec(RTL)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(fixtureRTLConfig)
|
const spec = matchOldConfigSpec.parse(fixtureRTLConfig)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("transformConfigSpec(searNXG)", () => {
|
test("transformConfigSpec(searNXG)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(searNXG)
|
const spec = matchOldConfigSpec.parse(searNXG)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test("transformConfigSpec(bitcoind)", () => {
|
test("transformConfigSpec(bitcoind)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(bitcoind)
|
const spec = matchOldConfigSpec.parse(bitcoind)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test("transformConfigSpec(nostr)", () => {
|
test("transformConfigSpec(nostr)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(nostr)
|
const spec = matchOldConfigSpec.parse(nostr)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test("transformConfigSpec(nostr2)", () => {
|
test("transformConfigSpec(nostr2)", () => {
|
||||||
const spec = matchOldConfigSpec.unsafeCast(nostrConfig2)
|
const spec = matchOldConfigSpec.parse(nostrConfig2)
|
||||||
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
expect(transformConfigSpec(spec)).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,19 +1,4 @@
|
|||||||
import { IST } from "@start9labs/start-sdk"
|
import { IST, z } from "@start9labs/start-sdk"
|
||||||
import {
|
|
||||||
dictionary,
|
|
||||||
object,
|
|
||||||
anyOf,
|
|
||||||
string,
|
|
||||||
literals,
|
|
||||||
array,
|
|
||||||
number,
|
|
||||||
boolean,
|
|
||||||
Parser,
|
|
||||||
deferred,
|
|
||||||
every,
|
|
||||||
nill,
|
|
||||||
literal,
|
|
||||||
} from "ts-matches"
|
|
||||||
|
|
||||||
export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec {
|
export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec {
|
||||||
return Object.entries(oldSpec).reduce((inputSpec, [key, oldVal]) => {
|
return Object.entries(oldSpec).reduce((inputSpec, [key, oldVal]) => {
|
||||||
@@ -82,7 +67,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec {
|
|||||||
name: oldVal.name,
|
name: oldVal.name,
|
||||||
description: oldVal.description || null,
|
description: oldVal.description || null,
|
||||||
warning: oldVal.warning || null,
|
warning: oldVal.warning || null,
|
||||||
spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(oldVal.spec)),
|
spec: transformConfigSpec(matchOldConfigSpec.parse(oldVal.spec)),
|
||||||
}
|
}
|
||||||
} else if (oldVal.type === "string") {
|
} else if (oldVal.type === "string") {
|
||||||
newVal = {
|
newVal = {
|
||||||
@@ -121,7 +106,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec {
|
|||||||
...obj,
|
...obj,
|
||||||
[id]: {
|
[id]: {
|
||||||
name: oldVal.tag["variant-names"][id] || id,
|
name: oldVal.tag["variant-names"][id] || id,
|
||||||
spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(spec)),
|
spec: transformConfigSpec(matchOldConfigSpec.parse(spec)),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{} as Record<string, { name: string; spec: IST.InputSpec }>,
|
{} as Record<string, { name: string; spec: IST.InputSpec }>,
|
||||||
@@ -153,7 +138,7 @@ export function transformOldConfigToNew(
|
|||||||
|
|
||||||
if (isObject(val)) {
|
if (isObject(val)) {
|
||||||
newVal = transformOldConfigToNew(
|
newVal = transformOldConfigToNew(
|
||||||
matchOldConfigSpec.unsafeCast(val.spec),
|
matchOldConfigSpec.parse(val.spec),
|
||||||
config[key],
|
config[key],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -172,7 +157,7 @@ export function transformOldConfigToNew(
|
|||||||
newVal = {
|
newVal = {
|
||||||
selection,
|
selection,
|
||||||
value: transformOldConfigToNew(
|
value: transformOldConfigToNew(
|
||||||
matchOldConfigSpec.unsafeCast(val.variants[selection]),
|
matchOldConfigSpec.parse(val.variants[selection]),
|
||||||
config[key],
|
config[key],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -183,10 +168,7 @@ export function transformOldConfigToNew(
|
|||||||
|
|
||||||
if (isObjectList(val)) {
|
if (isObjectList(val)) {
|
||||||
newVal = (config[key] as object[]).map((obj) =>
|
newVal = (config[key] as object[]).map((obj) =>
|
||||||
transformOldConfigToNew(
|
transformOldConfigToNew(matchOldConfigSpec.parse(val.spec.spec), obj),
|
||||||
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
|
||||||
obj,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} else if (isUnionList(val)) return obj
|
} else if (isUnionList(val)) return obj
|
||||||
}
|
}
|
||||||
@@ -212,7 +194,7 @@ export function transformNewConfigToOld(
|
|||||||
|
|
||||||
if (isObject(val)) {
|
if (isObject(val)) {
|
||||||
newVal = transformNewConfigToOld(
|
newVal = transformNewConfigToOld(
|
||||||
matchOldConfigSpec.unsafeCast(val.spec),
|
matchOldConfigSpec.parse(val.spec),
|
||||||
config[key],
|
config[key],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -221,7 +203,7 @@ export function transformNewConfigToOld(
|
|||||||
newVal = {
|
newVal = {
|
||||||
[val.tag.id]: config[key].selection,
|
[val.tag.id]: config[key].selection,
|
||||||
...transformNewConfigToOld(
|
...transformNewConfigToOld(
|
||||||
matchOldConfigSpec.unsafeCast(val.variants[config[key].selection]),
|
matchOldConfigSpec.parse(val.variants[config[key].selection]),
|
||||||
config[key].value,
|
config[key].value,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -230,10 +212,7 @@ export function transformNewConfigToOld(
|
|||||||
if (isList(val)) {
|
if (isList(val)) {
|
||||||
if (isObjectList(val)) {
|
if (isObjectList(val)) {
|
||||||
newVal = (config[key] as object[]).map((obj) =>
|
newVal = (config[key] as object[]).map((obj) =>
|
||||||
transformNewConfigToOld(
|
transformNewConfigToOld(matchOldConfigSpec.parse(val.spec.spec), obj),
|
||||||
matchOldConfigSpec.unsafeCast(val.spec.spec),
|
|
||||||
obj,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} else if (isUnionList(val)) return obj
|
} else if (isUnionList(val)) return obj
|
||||||
}
|
}
|
||||||
@@ -337,9 +316,7 @@ function getListSpec(
|
|||||||
default: oldVal.default as Record<string, unknown>[],
|
default: oldVal.default as Record<string, unknown>[],
|
||||||
spec: {
|
spec: {
|
||||||
type: "object",
|
type: "object",
|
||||||
spec: transformConfigSpec(
|
spec: transformConfigSpec(matchOldConfigSpec.parse(oldVal.spec.spec)),
|
||||||
matchOldConfigSpec.unsafeCast(oldVal.spec.spec),
|
|
||||||
),
|
|
||||||
uniqueBy: oldVal.spec["unique-by"] || null,
|
uniqueBy: oldVal.spec["unique-by"] || null,
|
||||||
displayAs: oldVal.spec["display-as"] || null,
|
displayAs: oldVal.spec["display-as"] || null,
|
||||||
},
|
},
|
||||||
@@ -393,211 +370,281 @@ function isUnionList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type OldConfigSpec = Record<string, OldValueSpec>
|
export type OldConfigSpec = Record<string, OldValueSpec>
|
||||||
const [_matchOldConfigSpec, setMatchOldConfigSpec] = deferred<unknown>()
|
export const matchOldConfigSpec: z.ZodType<OldConfigSpec> = z.lazy(() =>
|
||||||
export const matchOldConfigSpec = _matchOldConfigSpec as Parser<
|
z.record(z.string(), matchOldValueSpec),
|
||||||
unknown,
|
|
||||||
OldConfigSpec
|
|
||||||
>
|
|
||||||
export const matchOldDefaultString = anyOf(
|
|
||||||
string,
|
|
||||||
object({ charset: string, len: number }),
|
|
||||||
)
|
)
|
||||||
type OldDefaultString = typeof matchOldDefaultString._TYPE
|
export const matchOldDefaultString = z.union([
|
||||||
|
z.string(),
|
||||||
|
z.object({ charset: z.string(), len: z.number() }),
|
||||||
|
])
|
||||||
|
type OldDefaultString = z.infer<typeof matchOldDefaultString>
|
||||||
|
|
||||||
export const matchOldValueSpecString = object({
|
export const matchOldValueSpecString = z.object({
|
||||||
type: literals("string"),
|
type: z.enum(["string"]),
|
||||||
name: string,
|
name: z.string(),
|
||||||
masked: boolean.nullable().optional(),
|
masked: z.boolean().nullable().optional(),
|
||||||
copyable: boolean.nullable().optional(),
|
copyable: z.boolean().nullable().optional(),
|
||||||
nullable: boolean.nullable().optional(),
|
nullable: z.boolean().nullable().optional(),
|
||||||
placeholder: string.nullable().optional(),
|
placeholder: z.string().nullable().optional(),
|
||||||
pattern: string.nullable().optional(),
|
pattern: z.string().nullable().optional(),
|
||||||
"pattern-description": string.nullable().optional(),
|
"pattern-description": z.string().nullable().optional(),
|
||||||
default: matchOldDefaultString.nullable().optional(),
|
default: matchOldDefaultString.nullable().optional(),
|
||||||
textarea: boolean.nullable().optional(),
|
textarea: z.boolean().nullable().optional(),
|
||||||
description: string.nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
warning: string.nullable().optional(),
|
warning: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const matchOldValueSpecNumber = object({
|
export const matchOldValueSpecNumber = z.object({
|
||||||
type: literals("number"),
|
type: z.enum(["number"]),
|
||||||
nullable: boolean,
|
nullable: z.boolean(),
|
||||||
name: string,
|
name: z.string(),
|
||||||
range: string,
|
range: z.string(),
|
||||||
integral: boolean,
|
integral: z.boolean(),
|
||||||
default: number.nullable().optional(),
|
default: z.number().nullable().optional(),
|
||||||
description: string.nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
warning: string.nullable().optional(),
|
warning: z.string().nullable().optional(),
|
||||||
units: string.nullable().optional(),
|
units: z.string().nullable().optional(),
|
||||||
placeholder: anyOf(number, string).nullable().optional(),
|
placeholder: z.union([z.number(), z.string()]).nullable().optional(),
|
||||||
})
|
})
|
||||||
type OldValueSpecNumber = typeof matchOldValueSpecNumber._TYPE
|
type OldValueSpecNumber = z.infer<typeof matchOldValueSpecNumber>
|
||||||
|
|
||||||
export const matchOldValueSpecBoolean = object({
|
export const matchOldValueSpecBoolean = z.object({
|
||||||
type: literals("boolean"),
|
type: z.enum(["boolean"]),
|
||||||
default: boolean,
|
default: z.boolean(),
|
||||||
name: string,
|
name: z.string(),
|
||||||
description: string.nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
warning: string.nullable().optional(),
|
warning: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
type OldValueSpecBoolean = typeof matchOldValueSpecBoolean._TYPE
|
type OldValueSpecBoolean = z.infer<typeof matchOldValueSpecBoolean>
|
||||||
|
|
||||||
const matchOldValueSpecObject = object({
|
type OldValueSpecObject = {
|
||||||
type: literals("object"),
|
type: "object"
|
||||||
spec: _matchOldConfigSpec,
|
spec: OldConfigSpec
|
||||||
name: string,
|
name: string
|
||||||
description: string.nullable().optional(),
|
description?: string | null
|
||||||
warning: string.nullable().optional(),
|
warning?: string | null
|
||||||
|
}
|
||||||
|
const matchOldValueSpecObject: z.ZodType<OldValueSpecObject> = z.object({
|
||||||
|
type: z.enum(["object"]),
|
||||||
|
spec: z.lazy(() => matchOldConfigSpec),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
warning: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
type OldValueSpecObject = typeof matchOldValueSpecObject._TYPE
|
|
||||||
|
|
||||||
const matchOldValueSpecEnum = object({
|
const matchOldValueSpecEnum = z.object({
|
||||||
values: array(string),
|
values: z.array(z.string()),
|
||||||
"value-names": dictionary([string, string]),
|
"value-names": z.record(z.string(), z.string()),
|
||||||
type: literals("enum"),
|
type: z.enum(["enum"]),
|
||||||
default: string,
|
default: z.string(),
|
||||||
name: string,
|
name: z.string(),
|
||||||
description: string.nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
warning: string.nullable().optional(),
|
warning: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
type OldValueSpecEnum = typeof matchOldValueSpecEnum._TYPE
|
type OldValueSpecEnum = z.infer<typeof matchOldValueSpecEnum>
|
||||||
|
|
||||||
const matchOldUnionTagSpec = object({
|
const matchOldUnionTagSpec = z.object({
|
||||||
id: string, // The name of the field containing one of the union variants
|
id: z.string(), // The name of the field containing one of the union variants
|
||||||
"variant-names": dictionary([string, string]), // The name of each variant
|
"variant-names": z.record(z.string(), z.string()), // The name of each variant
|
||||||
name: string,
|
name: z.string(),
|
||||||
description: string.nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
warning: string.nullable().optional(),
|
warning: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
const matchOldValueSpecUnion = object({
|
type OldValueSpecUnion = {
|
||||||
type: literals("union"),
|
type: "union"
|
||||||
|
tag: z.infer<typeof matchOldUnionTagSpec>
|
||||||
|
variants: Record<string, OldConfigSpec>
|
||||||
|
default: string
|
||||||
|
}
|
||||||
|
const matchOldValueSpecUnion: z.ZodType<OldValueSpecUnion> = z.object({
|
||||||
|
type: z.enum(["union"]),
|
||||||
tag: matchOldUnionTagSpec,
|
tag: matchOldUnionTagSpec,
|
||||||
variants: dictionary([string, _matchOldConfigSpec]),
|
variants: z.record(
|
||||||
default: string,
|
z.string(),
|
||||||
|
z.lazy(() => matchOldConfigSpec),
|
||||||
|
),
|
||||||
|
default: z.string(),
|
||||||
})
|
})
|
||||||
type OldValueSpecUnion = typeof matchOldValueSpecUnion._TYPE
|
|
||||||
|
|
||||||
const [matchOldUniqueBy, setOldUniqueBy] = deferred<OldUniqueBy>()
|
|
||||||
type OldUniqueBy =
|
type OldUniqueBy =
|
||||||
| null
|
| null
|
||||||
| string
|
| string
|
||||||
| { any: OldUniqueBy[] }
|
| { any: OldUniqueBy[] }
|
||||||
| { all: OldUniqueBy[] }
|
| { all: OldUniqueBy[] }
|
||||||
|
|
||||||
setOldUniqueBy(
|
const matchOldUniqueBy: z.ZodType<OldUniqueBy> = z.lazy(() =>
|
||||||
anyOf(
|
z.union([
|
||||||
nill,
|
z.null(),
|
||||||
string,
|
z.string(),
|
||||||
object({ any: array(matchOldUniqueBy) }),
|
z.object({ any: z.array(matchOldUniqueBy) }),
|
||||||
object({ all: array(matchOldUniqueBy) }),
|
z.object({ all: z.array(matchOldUniqueBy) }),
|
||||||
),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
const matchOldListValueSpecObject = object({
|
type OldListValueSpecObject = {
|
||||||
spec: _matchOldConfigSpec, // this is a mapped type of the config object at this level, replacing the object's values with specs on those values
|
spec: OldConfigSpec
|
||||||
"unique-by": matchOldUniqueBy.nullable().optional(), // indicates whether duplicates can be permitted in the list
|
"unique-by"?: OldUniqueBy | null
|
||||||
"display-as": string.nullable().optional(), // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
|
"display-as"?: string | null
|
||||||
})
|
}
|
||||||
const matchOldListValueSpecUnion = object({
|
const matchOldListValueSpecObject: z.ZodType<OldListValueSpecObject> = z.object(
|
||||||
|
{
|
||||||
|
spec: z.lazy(() => matchOldConfigSpec), // this is a mapped type of the config object at this level, replacing the object's values with specs on those values
|
||||||
|
"unique-by": matchOldUniqueBy.nullable().optional(), // indicates whether duplicates can be permitted in the list
|
||||||
|
"display-as": z.string().nullable().optional(), // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
|
||||||
|
},
|
||||||
|
)
|
||||||
|
type OldListValueSpecUnion = {
|
||||||
|
"unique-by"?: OldUniqueBy | null
|
||||||
|
"display-as"?: string | null
|
||||||
|
tag: z.infer<typeof matchOldUnionTagSpec>
|
||||||
|
variants: Record<string, OldConfigSpec>
|
||||||
|
}
|
||||||
|
const matchOldListValueSpecUnion: z.ZodType<OldListValueSpecUnion> = z.object({
|
||||||
"unique-by": matchOldUniqueBy.nullable().optional(),
|
"unique-by": matchOldUniqueBy.nullable().optional(),
|
||||||
"display-as": string.nullable().optional(),
|
"display-as": z.string().nullable().optional(),
|
||||||
tag: matchOldUnionTagSpec,
|
tag: matchOldUnionTagSpec,
|
||||||
variants: dictionary([string, _matchOldConfigSpec]),
|
variants: z.record(
|
||||||
|
z.string(),
|
||||||
|
z.lazy(() => matchOldConfigSpec),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
const matchOldListValueSpecString = object({
|
const matchOldListValueSpecString = z.object({
|
||||||
masked: boolean.nullable().optional(),
|
masked: z.boolean().nullable().optional(),
|
||||||
copyable: boolean.nullable().optional(),
|
copyable: z.boolean().nullable().optional(),
|
||||||
pattern: string.nullable().optional(),
|
pattern: z.string().nullable().optional(),
|
||||||
"pattern-description": string.nullable().optional(),
|
"pattern-description": z.string().nullable().optional(),
|
||||||
placeholder: string.nullable().optional(),
|
placeholder: z.string().nullable().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const matchOldListValueSpecEnum = object({
|
const matchOldListValueSpecEnum = z.object({
|
||||||
values: array(string),
|
values: z.array(z.string()),
|
||||||
"value-names": dictionary([string, string]),
|
"value-names": z.record(z.string(), z.string()),
|
||||||
})
|
})
|
||||||
const matchOldListValueSpecNumber = object({
|
const matchOldListValueSpecNumber = z.object({
|
||||||
range: string,
|
range: z.string(),
|
||||||
integral: boolean,
|
integral: z.boolean(),
|
||||||
units: string.nullable().optional(),
|
units: z.string().nullable().optional(),
|
||||||
placeholder: anyOf(number, string).nullable().optional(),
|
placeholder: z.union([z.number(), z.string()]).nullable().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
type OldValueSpecListBase = {
|
||||||
|
type: "list"
|
||||||
|
range: string
|
||||||
|
default: string[] | number[] | OldDefaultString[] | Record<string, unknown>[]
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type OldValueSpecList = OldValueSpecListBase &
|
||||||
|
(
|
||||||
|
| { subtype: "string"; spec: z.infer<typeof matchOldListValueSpecString> }
|
||||||
|
| { subtype: "enum"; spec: z.infer<typeof matchOldListValueSpecEnum> }
|
||||||
|
| { subtype: "object"; spec: OldListValueSpecObject }
|
||||||
|
| { subtype: "number"; spec: z.infer<typeof matchOldListValueSpecNumber> }
|
||||||
|
| { subtype: "union"; spec: OldListValueSpecUnion }
|
||||||
|
)
|
||||||
|
|
||||||
// represents a spec for a list
|
// represents a spec for a list
|
||||||
export const matchOldValueSpecList = every(
|
export const matchOldValueSpecList: z.ZodType<OldValueSpecList> =
|
||||||
object({
|
z.intersection(
|
||||||
type: literals("list"),
|
z.object({
|
||||||
range: string, // '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules
|
type: z.enum(["list"]),
|
||||||
default: anyOf(
|
range: z.string(), // '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules
|
||||||
array(string),
|
default: z.union([
|
||||||
array(number),
|
z.array(z.string()),
|
||||||
array(matchOldDefaultString),
|
z.array(z.number()),
|
||||||
array(object),
|
z.array(matchOldDefaultString),
|
||||||
),
|
z.array(z.object({}).passthrough()),
|
||||||
name: string,
|
]),
|
||||||
description: string.nullable().optional(),
|
name: z.string(),
|
||||||
warning: string.nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
}),
|
warning: z.string().nullable().optional(),
|
||||||
anyOf(
|
|
||||||
object({
|
|
||||||
subtype: literals("string"),
|
|
||||||
spec: matchOldListValueSpecString,
|
|
||||||
}),
|
}),
|
||||||
object({
|
z.union([
|
||||||
subtype: literals("enum"),
|
z.object({
|
||||||
spec: matchOldListValueSpecEnum,
|
subtype: z.enum(["string"]),
|
||||||
}),
|
spec: matchOldListValueSpecString,
|
||||||
object({
|
}),
|
||||||
subtype: literals("object"),
|
z.object({
|
||||||
spec: matchOldListValueSpecObject,
|
subtype: z.enum(["enum"]),
|
||||||
}),
|
spec: matchOldListValueSpecEnum,
|
||||||
object({
|
}),
|
||||||
subtype: literals("number"),
|
z.object({
|
||||||
spec: matchOldListValueSpecNumber,
|
subtype: z.enum(["object"]),
|
||||||
}),
|
spec: matchOldListValueSpecObject,
|
||||||
object({
|
}),
|
||||||
subtype: literals("union"),
|
z.object({
|
||||||
spec: matchOldListValueSpecUnion,
|
subtype: z.enum(["number"]),
|
||||||
}),
|
spec: matchOldListValueSpecNumber,
|
||||||
),
|
}),
|
||||||
)
|
z.object({
|
||||||
type OldValueSpecList = typeof matchOldValueSpecList._TYPE
|
subtype: z.enum(["union"]),
|
||||||
|
spec: matchOldListValueSpecUnion,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
) as unknown as z.ZodType<OldValueSpecList>
|
||||||
|
|
||||||
const matchOldValueSpecPointer = every(
|
type OldValueSpecPointer = {
|
||||||
object({
|
type: "pointer"
|
||||||
type: literal("pointer"),
|
} & (
|
||||||
}),
|
| {
|
||||||
anyOf(
|
subtype: "package"
|
||||||
object({
|
target: "tor-key" | "tor-address" | "lan-address"
|
||||||
subtype: literal("package"),
|
"package-id": string
|
||||||
target: literals("tor-key", "tor-address", "lan-address"),
|
interface: string
|
||||||
"package-id": string,
|
}
|
||||||
interface: string,
|
| {
|
||||||
}),
|
subtype: "package"
|
||||||
object({
|
target: "config"
|
||||||
subtype: literal("package"),
|
"package-id": string
|
||||||
target: literals("config"),
|
selector: string
|
||||||
"package-id": string,
|
multi: boolean
|
||||||
selector: string,
|
}
|
||||||
multi: boolean,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
type OldValueSpecPointer = typeof matchOldValueSpecPointer._TYPE
|
const matchOldValueSpecPointer: z.ZodType<OldValueSpecPointer> = z.intersection(
|
||||||
|
z.object({
|
||||||
|
type: z.literal("pointer"),
|
||||||
|
}),
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
subtype: z.literal("package"),
|
||||||
|
target: z.enum(["tor-key", "tor-address", "lan-address"]),
|
||||||
|
"package-id": z.string(),
|
||||||
|
interface: z.string(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subtype: z.literal("package"),
|
||||||
|
target: z.enum(["config"]),
|
||||||
|
"package-id": z.string(),
|
||||||
|
selector: z.string(),
|
||||||
|
multi: z.boolean(),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
) as unknown as z.ZodType<OldValueSpecPointer>
|
||||||
|
|
||||||
export const matchOldValueSpec = anyOf(
|
type OldValueSpecString = z.infer<typeof matchOldValueSpecString>
|
||||||
|
|
||||||
|
type OldValueSpec =
|
||||||
|
| OldValueSpecString
|
||||||
|
| OldValueSpecNumber
|
||||||
|
| OldValueSpecBoolean
|
||||||
|
| OldValueSpecObject
|
||||||
|
| OldValueSpecEnum
|
||||||
|
| OldValueSpecList
|
||||||
|
| OldValueSpecUnion
|
||||||
|
| OldValueSpecPointer
|
||||||
|
|
||||||
|
export const matchOldValueSpec: z.ZodType<OldValueSpec> = z.union([
|
||||||
matchOldValueSpecString,
|
matchOldValueSpecString,
|
||||||
matchOldValueSpecNumber,
|
matchOldValueSpecNumber,
|
||||||
matchOldValueSpecBoolean,
|
matchOldValueSpecBoolean,
|
||||||
matchOldValueSpecObject,
|
matchOldValueSpecObject as z.ZodType<OldValueSpecObject>,
|
||||||
matchOldValueSpecEnum,
|
matchOldValueSpecEnum,
|
||||||
matchOldValueSpecList,
|
matchOldValueSpecList as z.ZodType<OldValueSpecList>,
|
||||||
matchOldValueSpecUnion,
|
matchOldValueSpecUnion as z.ZodType<OldValueSpecUnion>,
|
||||||
matchOldValueSpecPointer,
|
matchOldValueSpecPointer as z.ZodType<OldValueSpecPointer>,
|
||||||
)
|
])
|
||||||
type OldValueSpec = typeof matchOldValueSpec._TYPE
|
|
||||||
|
|
||||||
setMatchOldConfigSpec(dictionary([string, matchOldValueSpec]))
|
|
||||||
|
|
||||||
export class Range {
|
export class Range {
|
||||||
min?: number
|
min?: number
|
||||||
|
|||||||
@@ -1,41 +1,19 @@
|
|||||||
import {
|
import { z } from "@start9labs/start-sdk"
|
||||||
object,
|
|
||||||
literal,
|
|
||||||
string,
|
|
||||||
boolean,
|
|
||||||
array,
|
|
||||||
dictionary,
|
|
||||||
literals,
|
|
||||||
number,
|
|
||||||
Parser,
|
|
||||||
some,
|
|
||||||
} from "ts-matches"
|
|
||||||
import { matchDuration } from "./Duration"
|
import { matchDuration } from "./Duration"
|
||||||
|
|
||||||
const VolumeId = string
|
export const matchDockerProcedure = z.object({
|
||||||
const Path = string
|
type: z.literal("docker"),
|
||||||
|
image: z.string(),
|
||||||
export type VolumeId = string
|
system: z.boolean().optional(),
|
||||||
export type Path = string
|
entrypoint: z.string(),
|
||||||
export const matchDockerProcedure = object({
|
args: z.array(z.string()).default([]),
|
||||||
type: literal("docker"),
|
mounts: z.record(z.string(), z.string()).optional(),
|
||||||
image: string,
|
"io-format": z
|
||||||
system: boolean.optional(),
|
.enum(["json", "json-pretty", "yaml", "cbor", "toml", "toml-pretty"])
|
||||||
entrypoint: string,
|
|
||||||
args: array(string).defaultTo([]),
|
|
||||||
mounts: dictionary([VolumeId, Path]).optional(),
|
|
||||||
"io-format": literals(
|
|
||||||
"json",
|
|
||||||
"json-pretty",
|
|
||||||
"yaml",
|
|
||||||
"cbor",
|
|
||||||
"toml",
|
|
||||||
"toml-pretty",
|
|
||||||
)
|
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
"sigterm-timeout": some(number, matchDuration).onMismatch(30),
|
"sigterm-timeout": z.union([z.number(), matchDuration]).catch(30),
|
||||||
inject: boolean.defaultTo(false),
|
inject: z.boolean().default(false),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type DockerProcedure = typeof matchDockerProcedure._TYPE
|
export type DockerProcedure = z.infer<typeof matchDockerProcedure>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { string } from "ts-matches"
|
import { z } from "@start9labs/start-sdk"
|
||||||
|
|
||||||
export type TimeUnit = "d" | "h" | "s" | "ms" | "m" | "µs" | "ns"
|
export type TimeUnit = "d" | "h" | "s" | "ms" | "m" | "µs" | "ns"
|
||||||
export type Duration = `${number}${TimeUnit}`
|
export type Duration = `${number}${TimeUnit}`
|
||||||
|
|
||||||
const durationRegex = /^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/
|
const durationRegex = /^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/
|
||||||
|
|
||||||
export const matchDuration = string.refine(isDuration)
|
export const matchDuration = z.string().refine(isDuration)
|
||||||
export function isDuration(value: string): value is Duration {
|
export function isDuration(value: string): value is Duration {
|
||||||
return durationRegex.test(value)
|
return durationRegex.test(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { literals, some, string } from "ts-matches"
|
import { z } from "@start9labs/start-sdk"
|
||||||
|
|
||||||
type NestedPath<A extends string, B extends string> = `/${A}/${string}/${B}`
|
type NestedPath<A extends string, B extends string> = `/${A}/${string}/${B}`
|
||||||
type NestedPaths = NestedPath<"actions", "run" | "getInput">
|
type NestedPaths = NestedPath<"actions", "run" | "getInput">
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type UnNestPaths<A> =
|
type UnNestPaths<A> =
|
||||||
A extends `${infer A}/${infer B}` ? [...UnNestPaths<A>, ... UnNestPaths<B>] :
|
A extends `${infer A}/${infer B}` ? [...UnNestPaths<A>, ... UnNestPaths<B>] :
|
||||||
[A]
|
[A]
|
||||||
|
|
||||||
export function unNestPath<A extends string>(a: A): UnNestPaths<A> {
|
export function unNestPath<A extends string>(a: A): UnNestPaths<A> {
|
||||||
@@ -17,14 +17,14 @@ function isNestedPath(path: string): path is NestedPaths {
|
|||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
export const jsonPath = some(
|
export const jsonPath = z.union([
|
||||||
literals(
|
z.enum([
|
||||||
"/packageInit",
|
"/packageInit",
|
||||||
"/packageUninit",
|
"/packageUninit",
|
||||||
"/backup/create",
|
"/backup/create",
|
||||||
"/backup/restore",
|
"/backup/restore",
|
||||||
),
|
]),
|
||||||
string.refine(isNestedPath, "isNestedPath"),
|
z.string().refine(isNestedPath),
|
||||||
)
|
])
|
||||||
|
|
||||||
export type JsonPath = typeof jsonPath._TYPE
|
export type JsonPath = z.infer<typeof jsonPath>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
PACKAGE_TS_FILES := $(shell git ls-files package/lib) package/lib/test/output.ts
|
PACKAGE_TS_FILES := $(shell git ls-files package/lib)
|
||||||
BASE_TS_FILES := $(shell git ls-files base/lib) package/lib/test/output.ts
|
BASE_TS_FILES := $(shell git ls-files base/lib)
|
||||||
version = $(shell git tag --sort=committerdate | tail -1)
|
version = $(shell git tag --sort=committerdate | tail -1)
|
||||||
|
|
||||||
.PHONY: test base/test package/test clean bundle fmt buildOutput check
|
.PHONY: test base/test package/test clean bundle fmt buildOutput check
|
||||||
|
|
||||||
all: bundle
|
all: bundle
|
||||||
|
|
||||||
package/test: $(PACKAGE_TS_FILES) package/lib/test/output.ts package/node_modules base/node_modules
|
package/test: $(PACKAGE_TS_FILES) package/node_modules base/node_modules
|
||||||
cd package && npm test
|
cd package && npm test
|
||||||
|
|
||||||
base/test: $(BASE_TS_FILES) base/node_modules
|
base/test: $(BASE_TS_FILES) base/node_modules
|
||||||
@@ -21,9 +21,6 @@ clean:
|
|||||||
rm -f package/lib/test/output.ts
|
rm -f package/lib/test/output.ts
|
||||||
rm -rf package/node_modules
|
rm -rf package/node_modules
|
||||||
|
|
||||||
package/lib/test/output.ts: package/node_modules package/lib/test/makeOutput.ts package/scripts/oldSpecToBuilder.ts
|
|
||||||
cd package && npm run buildOutput
|
|
||||||
|
|
||||||
bundle: baseDist dist | test fmt
|
bundle: baseDist dist | test fmt
|
||||||
touch dist
|
touch dist
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ValueSpec } from '../inputSpecTypes'
|
|||||||
import { Value } from './value'
|
import { Value } from './value'
|
||||||
import { _ } from '../../../util'
|
import { _ } from '../../../util'
|
||||||
import { Effects } from '../../../Effects'
|
import { Effects } from '../../../Effects'
|
||||||
import { Parser, object } from 'ts-matches'
|
import { z } from 'zod'
|
||||||
import { DeepPartial } from '../../../types'
|
import { DeepPartial } from '../../../types'
|
||||||
import { InputSpecTools, createInputSpecTools } from './inputSpecTools'
|
import { InputSpecTools, createInputSpecTools } from './inputSpecTools'
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export class InputSpec<
|
|||||||
private readonly spec: {
|
private readonly spec: {
|
||||||
[K in keyof Type]: Value<Type[K]>
|
[K in keyof Type]: Value<Type[K]>
|
||||||
},
|
},
|
||||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
public readonly validator: z.ZodType<StaticValidatedAs>,
|
||||||
) {}
|
) {}
|
||||||
public _TYPE: Type = null as any as Type
|
public _TYPE: Type = null as any as Type
|
||||||
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
||||||
@@ -104,13 +104,13 @@ export class InputSpec<
|
|||||||
spec: {
|
spec: {
|
||||||
[K in keyof Type]: ValueSpec
|
[K in keyof Type]: ValueSpec
|
||||||
}
|
}
|
||||||
validator: Parser<unknown, Type>
|
validator: z.ZodType<Type>
|
||||||
}> {
|
}> {
|
||||||
const answer = {} as {
|
const answer = {} as {
|
||||||
[K in keyof Type]: ValueSpec
|
[K in keyof Type]: ValueSpec
|
||||||
}
|
}
|
||||||
const validator = {} as {
|
const validator = {} as {
|
||||||
[K in keyof Type]: Parser<unknown, any>
|
[K in keyof Type]: z.ZodType<any>
|
||||||
}
|
}
|
||||||
for (const k in this.spec) {
|
for (const k in this.spec) {
|
||||||
const built = await this.spec[k].build(options as any)
|
const built = await this.spec[k].build(options as any)
|
||||||
@@ -119,7 +119,7 @@ export class InputSpec<
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
spec: answer,
|
spec: answer,
|
||||||
validator: object(validator) as any,
|
validator: z.object(validator) as any,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export class InputSpec<
|
|||||||
const value =
|
const value =
|
||||||
build instanceof Function ? build(createInputSpecTools<Type>()) : build
|
build instanceof Function ? build(createInputSpecTools<Type>()) : build
|
||||||
const newSpec = { ...this.spec, [key]: value } as any
|
const newSpec = { ...this.spec, [key]: value } as any
|
||||||
const newValidator = object(
|
const newValidator = z.object(
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(newSpec).map(([k, v]) => [
|
Object.entries(newSpec).map(([k, v]) => [
|
||||||
k,
|
k,
|
||||||
@@ -163,7 +163,7 @@ export class InputSpec<
|
|||||||
const addedValues =
|
const addedValues =
|
||||||
build instanceof Function ? build(createInputSpecTools<Type>()) : build
|
build instanceof Function ? build(createInputSpecTools<Type>()) : build
|
||||||
const newSpec = { ...this.spec, ...addedValues } as any
|
const newSpec = { ...this.spec, ...addedValues } as any
|
||||||
const newValidator = object(
|
const newValidator = z.object(
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(newSpec).map(([k, v]) => [
|
Object.entries(newSpec).map(([k, v]) => [
|
||||||
k,
|
k,
|
||||||
@@ -175,7 +175,7 @@ export class InputSpec<
|
|||||||
}
|
}
|
||||||
|
|
||||||
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec) {
|
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec) {
|
||||||
const validator = object(
|
const validator = z.object(
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(spec).map(([k, v]) => [k, v.validator]),
|
Object.entries(spec).map(([k, v]) => [k, v.validator]),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
ValueSpecText,
|
ValueSpecText,
|
||||||
} from '../inputSpecTypes'
|
} from '../inputSpecTypes'
|
||||||
import { DefaultString } from '../inputSpecTypes'
|
import { DefaultString } from '../inputSpecTypes'
|
||||||
import { Parser } from 'ts-matches'
|
import { z } from 'zod'
|
||||||
import { ListValueSpecText } from '../inputSpecTypes'
|
import { ListValueSpecText } from '../inputSpecTypes'
|
||||||
|
|
||||||
export interface InputSpecTools<OuterType> {
|
export interface InputSpecTools<OuterType> {
|
||||||
@@ -224,7 +224,7 @@ export interface BoundValue<OuterType> {
|
|||||||
},
|
},
|
||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
|
validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>,
|
||||||
): Value<
|
): Value<
|
||||||
UnionRes<VariantValues>,
|
UnionRes<VariantValues>,
|
||||||
UnionResStaticValidatedAs<StaticVariantValues>,
|
UnionResStaticValidatedAs<StaticVariantValues>,
|
||||||
@@ -232,7 +232,7 @@ export interface BoundValue<OuterType> {
|
|||||||
>
|
>
|
||||||
|
|
||||||
dynamicHidden<T>(
|
dynamicHidden<T>(
|
||||||
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
|
getParser: LazyBuild<z.ZodType<T>, OuterType>,
|
||||||
): Value<T, T, OuterType>
|
): Value<T, T, OuterType>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
ValueSpecList,
|
ValueSpecList,
|
||||||
ValueSpecListOf,
|
ValueSpecListOf,
|
||||||
} from '../inputSpecTypes'
|
} from '../inputSpecTypes'
|
||||||
import { Parser, arrayOf, string } from 'ts-matches'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export class List<
|
export class List<
|
||||||
Type extends StaticValidatedAs,
|
Type extends StaticValidatedAs,
|
||||||
@@ -18,11 +18,11 @@ export class List<
|
|||||||
public build: LazyBuild<
|
public build: LazyBuild<
|
||||||
{
|
{
|
||||||
spec: ValueSpecList
|
spec: ValueSpecList
|
||||||
validator: Parser<unknown, Type>
|
validator: z.ZodType<Type>
|
||||||
},
|
},
|
||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
public readonly validator: z.ZodType<StaticValidatedAs>,
|
||||||
) {}
|
) {}
|
||||||
readonly _TYPE: Type = null as any
|
readonly _TYPE: Type = null as any
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export class List<
|
|||||||
generate?: null | RandomString
|
generate?: null | RandomString
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const validator = arrayOf(string)
|
const validator = z.array(z.string())
|
||||||
return new List<string[]>(() => {
|
return new List<string[]>(() => {
|
||||||
const spec = {
|
const spec = {
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
@@ -120,7 +120,7 @@ export class List<
|
|||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
const validator = arrayOf(string)
|
const validator = z.array(z.string())
|
||||||
return new List<string[], string[], OuterType>(async (options) => {
|
return new List<string[], string[], OuterType>(async (options) => {
|
||||||
const { spec: aSpec, ...a } = await getA(options)
|
const { spec: aSpec, ...a } = await getA(options)
|
||||||
const spec = {
|
const spec = {
|
||||||
@@ -193,8 +193,8 @@ export class List<
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
...value,
|
...value,
|
||||||
},
|
},
|
||||||
validator: arrayOf(built.validator),
|
validator: z.array(built.validator),
|
||||||
}
|
}
|
||||||
}, arrayOf(aSpec.spec.validator))
|
}, z.array(aSpec.spec.validator))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,37 +12,27 @@ import {
|
|||||||
} from '../inputSpecTypes'
|
} from '../inputSpecTypes'
|
||||||
import { DefaultString } from '../inputSpecTypes'
|
import { DefaultString } from '../inputSpecTypes'
|
||||||
import { _, once } from '../../../util'
|
import { _, once } from '../../../util'
|
||||||
import {
|
import { z } from 'zod'
|
||||||
Parser,
|
|
||||||
any,
|
|
||||||
anyOf,
|
|
||||||
arrayOf,
|
|
||||||
boolean,
|
|
||||||
literal,
|
|
||||||
literals,
|
|
||||||
number,
|
|
||||||
object,
|
|
||||||
string,
|
|
||||||
} from 'ts-matches'
|
|
||||||
import { DeepPartial } from '../../../types'
|
import { DeepPartial } from '../../../types'
|
||||||
|
|
||||||
export const fileInfoParser = object({
|
export const fileInfoParser = z.object({
|
||||||
path: string,
|
path: z.string(),
|
||||||
commitment: object({ hash: string, size: number }),
|
commitment: z.object({ hash: z.string(), size: z.number() }),
|
||||||
})
|
})
|
||||||
export type FileInfo = typeof fileInfoParser._TYPE
|
export type FileInfo = z.infer<typeof fileInfoParser>
|
||||||
|
|
||||||
export type AsRequired<T, Required extends boolean> = Required extends true
|
export type AsRequired<T, Required extends boolean> = Required extends true
|
||||||
? T
|
? T
|
||||||
: T | null
|
: T | null
|
||||||
|
|
||||||
const testForAsRequiredParser = once(
|
const testForAsRequiredParser = once(
|
||||||
() => object({ required: literal(true) }).test,
|
() => (v: unknown) =>
|
||||||
|
z.object({ required: z.literal(true) }).safeParse(v).success,
|
||||||
)
|
)
|
||||||
function asRequiredParser<Type, Input extends { required: boolean }>(
|
function asRequiredParser<Type, Input extends { required: boolean }>(
|
||||||
parser: Parser<unknown, Type>,
|
parser: z.ZodType<Type>,
|
||||||
input: Input,
|
input: Input,
|
||||||
): Parser<unknown, AsRequired<Type, Input['required']>> {
|
): z.ZodType<AsRequired<Type, Input['required']>> {
|
||||||
if (testForAsRequiredParser()(input)) return parser as any
|
if (testForAsRequiredParser()(input)) return parser as any
|
||||||
return parser.nullable() as any
|
return parser.nullable() as any
|
||||||
}
|
}
|
||||||
@@ -56,11 +46,11 @@ export class Value<
|
|||||||
public build: LazyBuild<
|
public build: LazyBuild<
|
||||||
{
|
{
|
||||||
spec: ValueSpec
|
spec: ValueSpec
|
||||||
validator: Parser<unknown, Type>
|
validator: z.ZodType<Type>
|
||||||
},
|
},
|
||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
public readonly validator: z.ZodType<StaticValidatedAs>,
|
||||||
) {}
|
) {}
|
||||||
public _TYPE: Type = null as any as Type
|
public _TYPE: Type = null as any as Type
|
||||||
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
||||||
@@ -93,7 +83,7 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = boolean
|
const validator = z.boolean()
|
||||||
return new Value<boolean>(
|
return new Value<boolean>(
|
||||||
async () => ({
|
async () => ({
|
||||||
spec: {
|
spec: {
|
||||||
@@ -121,7 +111,7 @@ export class Value<
|
|||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
const validator = boolean
|
const validator = z.boolean()
|
||||||
return new Value<boolean, boolean, OuterType>(
|
return new Value<boolean, boolean, OuterType>(
|
||||||
async (options) => ({
|
async (options) => ({
|
||||||
spec: {
|
spec: {
|
||||||
@@ -212,7 +202,7 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
generate?: RandomString | null
|
generate?: RandomString | null
|
||||||
}) {
|
}) {
|
||||||
const validator = asRequiredParser(string, a)
|
const validator = asRequiredParser(z.string(), a)
|
||||||
return new Value<AsRequired<string, Required>>(
|
return new Value<AsRequired<string, Required>>(
|
||||||
async () => ({
|
async () => ({
|
||||||
spec: {
|
spec: {
|
||||||
@@ -274,10 +264,10 @@ export class Value<
|
|||||||
generate: a.generate ?? null,
|
generate: a.generate ?? null,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: asRequiredParser(string, a),
|
validator: asRequiredParser(z.string(), a),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
string.nullable(),
|
z.string().nullable(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -336,7 +326,7 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = asRequiredParser(string, a)
|
const validator = asRequiredParser(z.string(), a)
|
||||||
return new Value<AsRequired<string, Required>>(async () => {
|
return new Value<AsRequired<string, Required>>(async () => {
|
||||||
const built: ValueSpecTextarea = {
|
const built: ValueSpecTextarea = {
|
||||||
description: null,
|
description: null,
|
||||||
@@ -392,10 +382,10 @@ export class Value<
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: asRequiredParser(string, a),
|
validator: asRequiredParser(z.string(), a),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
string.nullable(),
|
z.string().nullable(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -456,7 +446,7 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = asRequiredParser(number, a)
|
const validator = asRequiredParser(z.number(), a)
|
||||||
return new Value<AsRequired<number, Required>>(
|
return new Value<AsRequired<number, Required>>(
|
||||||
() => ({
|
() => ({
|
||||||
spec: {
|
spec: {
|
||||||
@@ -513,10 +503,10 @@ export class Value<
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: asRequiredParser(number, a),
|
validator: asRequiredParser(z.number(), a),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
number.nullable(),
|
z.number().nullable(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -555,7 +545,7 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = asRequiredParser(string, a)
|
const validator = asRequiredParser(z.string(), a)
|
||||||
return new Value<AsRequired<string, Required>>(
|
return new Value<AsRequired<string, Required>>(
|
||||||
() => ({
|
() => ({
|
||||||
spec: {
|
spec: {
|
||||||
@@ -597,10 +587,10 @@ export class Value<
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: asRequiredParser(string, a),
|
validator: asRequiredParser(z.string(), a),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
string.nullable(),
|
z.string().nullable(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -649,7 +639,7 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = asRequiredParser(string, a)
|
const validator = asRequiredParser(z.string(), a)
|
||||||
return new Value<AsRequired<string, Required>>(
|
return new Value<AsRequired<string, Required>>(
|
||||||
() => ({
|
() => ({
|
||||||
spec: {
|
spec: {
|
||||||
@@ -700,10 +690,10 @@ export class Value<
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: asRequiredParser(string, a),
|
validator: asRequiredParser(z.string(), a),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
string.nullable(),
|
z.string().nullable(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -757,8 +747,12 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = anyOf(
|
const validator = z.union(
|
||||||
...Object.keys(a.values).map((x: keyof Values & string) => literal(x)),
|
Object.keys(a.values).map((x: keyof Values & string) => z.literal(x)) as [
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
...z.ZodLiteral<string>[],
|
||||||
|
],
|
||||||
)
|
)
|
||||||
return new Value<keyof Values & string>(
|
return new Value<keyof Values & string>(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -803,14 +797,18 @@ export class Value<
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: anyOf(
|
validator: z.union(
|
||||||
...Object.keys(a.values).map((x: keyof Values & string) =>
|
Object.keys(a.values).map((x: keyof Values & string) =>
|
||||||
literal(x),
|
z.literal(x),
|
||||||
),
|
) as [
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
...z.ZodLiteral<string>[],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
string,
|
z.string(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -865,8 +863,14 @@ export class Value<
|
|||||||
*/
|
*/
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
const validator = arrayOf(
|
const validator = z.array(
|
||||||
literals(...(Object.keys(a.values) as any as [keyof Values & string])),
|
z.union(
|
||||||
|
Object.keys(a.values).map((x) => z.literal(x)) as [
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
...z.ZodLiteral<string>[],
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return new Value<(keyof Values & string)[]>(
|
return new Value<(keyof Values & string)[]>(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -920,13 +924,17 @@ export class Value<
|
|||||||
immutable: false,
|
immutable: false,
|
||||||
...a,
|
...a,
|
||||||
},
|
},
|
||||||
validator: arrayOf(
|
validator: z.array(
|
||||||
literals(
|
z.union(
|
||||||
...(Object.keys(a.values) as any as [keyof Values & string]),
|
Object.keys(a.values).map((x) => z.literal(x)) as [
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
z.ZodLiteral<string>,
|
||||||
|
...z.ZodLiteral<string>[],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}, arrayOf(string))
|
}, z.array(z.string()))
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
|
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
|
||||||
@@ -1077,7 +1085,7 @@ export class Value<
|
|||||||
}) {
|
}) {
|
||||||
return new Value<
|
return new Value<
|
||||||
typeof a.variants._TYPE,
|
typeof a.variants._TYPE,
|
||||||
typeof a.variants.validator._TYPE
|
typeof a.variants.validator._output
|
||||||
>(async (options) => {
|
>(async (options) => {
|
||||||
const built = await a.variants.build(options as any)
|
const built = await a.variants.build(options as any)
|
||||||
return {
|
return {
|
||||||
@@ -1136,7 +1144,7 @@ export class Value<
|
|||||||
},
|
},
|
||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
|
validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>,
|
||||||
): Value<
|
): Value<
|
||||||
UnionRes<VariantValues>,
|
UnionRes<VariantValues>,
|
||||||
UnionResStaticValidatedAs<StaticVariantValues>,
|
UnionResStaticValidatedAs<StaticVariantValues>,
|
||||||
@@ -1162,11 +1170,11 @@ export class Value<
|
|||||||
},
|
},
|
||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
validator: Parser<unknown, unknown> = any,
|
validator: z.ZodType<unknown> = z.any(),
|
||||||
) {
|
) {
|
||||||
return new Value<
|
return new Value<
|
||||||
UnionRes<VariantValues>,
|
UnionRes<VariantValues>,
|
||||||
typeof validator._TYPE,
|
z.infer<typeof validator>,
|
||||||
OuterType
|
OuterType
|
||||||
>(async (options) => {
|
>(async (options) => {
|
||||||
const newValues = await getA(options)
|
const newValues = await getA(options)
|
||||||
@@ -1259,9 +1267,9 @@ export class Value<
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
static hidden<T>(): Value<T>
|
static hidden<T>(): Value<T>
|
||||||
static hidden<T>(parser: Parser<unknown, T>): Value<T>
|
static hidden<T>(parser: z.ZodType<T>): Value<T>
|
||||||
static hidden<T>(parser: Parser<unknown, T> = any) {
|
static hidden<T>(parser: z.ZodType<T> = z.any()) {
|
||||||
return new Value<T, typeof parser._TYPE>(async () => {
|
return new Value<T, z.infer<typeof parser>>(async () => {
|
||||||
return {
|
return {
|
||||||
spec: {
|
spec: {
|
||||||
type: 'hidden' as const,
|
type: 'hidden' as const,
|
||||||
@@ -1279,7 +1287,7 @@ export class Value<
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
static dynamicHidden<T, OuterType = unknown>(
|
static dynamicHidden<T, OuterType = unknown>(
|
||||||
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
|
getParser: LazyBuild<z.ZodType<T>, OuterType>,
|
||||||
) {
|
) {
|
||||||
return new Value<T, T, OuterType>(async (options) => {
|
return new Value<T, T, OuterType>(async (options) => {
|
||||||
const validator = await getParser(options)
|
const validator = await getParser(options)
|
||||||
@@ -1289,7 +1297,7 @@ export class Value<
|
|||||||
} as ValueSpecHidden,
|
} as ValueSpecHidden,
|
||||||
validator,
|
validator,
|
||||||
}
|
}
|
||||||
}, any)
|
}, z.any())
|
||||||
}
|
}
|
||||||
|
|
||||||
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType> {
|
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType> {
|
||||||
@@ -1297,8 +1305,8 @@ export class Value<
|
|||||||
const built = await this.build(options)
|
const built = await this.build(options)
|
||||||
return {
|
return {
|
||||||
spec: built.spec,
|
spec: built.spec,
|
||||||
validator: built.validator.map(fn),
|
validator: built.validator.transform(fn),
|
||||||
}
|
}
|
||||||
}, this.validator.map(fn))
|
}, this.validator.transform(fn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
ExtractInputSpecType,
|
ExtractInputSpecType,
|
||||||
ExtractInputSpecStaticValidatedAs,
|
ExtractInputSpecStaticValidatedAs,
|
||||||
} from './inputSpec'
|
} from './inputSpec'
|
||||||
import { Parser, any, anyOf, literal, object } from 'ts-matches'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export type UnionRes<
|
export type UnionRes<
|
||||||
VariantValues extends {
|
VariantValues extends {
|
||||||
@@ -109,12 +109,11 @@ export class Variants<
|
|||||||
public build: LazyBuild<
|
public build: LazyBuild<
|
||||||
{
|
{
|
||||||
spec: ValueSpecUnion['variants']
|
spec: ValueSpecUnion['variants']
|
||||||
validator: Parser<unknown, UnionRes<VariantValues>>
|
validator: z.ZodType<UnionRes<VariantValues>>
|
||||||
},
|
},
|
||||||
OuterType
|
OuterType
|
||||||
>,
|
>,
|
||||||
public readonly validator: Parser<
|
public readonly validator: z.ZodType<
|
||||||
unknown,
|
|
||||||
UnionResStaticValidatedAs<VariantValues>
|
UnionResStaticValidatedAs<VariantValues>
|
||||||
>,
|
>,
|
||||||
) {}
|
) {}
|
||||||
@@ -128,8 +127,7 @@ export class Variants<
|
|||||||
},
|
},
|
||||||
>(a: VariantValues) {
|
>(a: VariantValues) {
|
||||||
const staticValidators = {} as {
|
const staticValidators = {} as {
|
||||||
[K in keyof VariantValues]: Parser<
|
[K in keyof VariantValues]: z.ZodType<
|
||||||
unknown,
|
|
||||||
ExtractInputSpecStaticValidatedAs<VariantValues[K]['spec']>
|
ExtractInputSpecStaticValidatedAs<VariantValues[K]['spec']>
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
@@ -137,16 +135,20 @@ export class Variants<
|
|||||||
const value = a[key]
|
const value = a[key]
|
||||||
staticValidators[key] = value.spec.validator
|
staticValidators[key] = value.spec.validator
|
||||||
}
|
}
|
||||||
const other = object(
|
const other = z
|
||||||
Object.fromEntries(
|
.object(
|
||||||
Object.entries(staticValidators).map(([k, v]) => [k, any.optional()]),
|
Object.fromEntries(
|
||||||
),
|
Object.entries(staticValidators).map(([k, v]) => [
|
||||||
).optional()
|
k,
|
||||||
|
z.any().optional(),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
return new Variants<VariantValues>(
|
return new Variants<VariantValues>(
|
||||||
async (options) => {
|
async (options) => {
|
||||||
const validators = {} as {
|
const validators = {} as {
|
||||||
[K in keyof VariantValues]: Parser<
|
[K in keyof VariantValues]: z.ZodType<
|
||||||
unknown,
|
|
||||||
ExtractInputSpecType<VariantValues[K]['spec']>
|
ExtractInputSpecType<VariantValues[K]['spec']>
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
@@ -165,32 +167,37 @@ export class Variants<
|
|||||||
}
|
}
|
||||||
validators[key] = built.validator
|
validators[key] = built.validator
|
||||||
}
|
}
|
||||||
const other = object(
|
const other = z
|
||||||
Object.fromEntries(
|
.object(
|
||||||
Object.entries(validators).map(([k, v]) => [k, any.optional()]),
|
Object.fromEntries(
|
||||||
),
|
Object.entries(validators).map(([k, v]) => [
|
||||||
).optional()
|
k,
|
||||||
|
z.any().optional(),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
return {
|
return {
|
||||||
spec: variants,
|
spec: variants,
|
||||||
validator: anyOf(
|
validator: z.union(
|
||||||
...Object.entries(validators).map(([k, v]) =>
|
Object.entries(validators).map(([k, v]) =>
|
||||||
object({
|
z.object({
|
||||||
selection: literal(k),
|
selection: z.literal(k),
|
||||||
value: v,
|
value: v,
|
||||||
other,
|
other,
|
||||||
}),
|
}),
|
||||||
),
|
) as [z.ZodObject<any>, z.ZodObject<any>, ...z.ZodObject<any>[]],
|
||||||
) as any,
|
) as any,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
anyOf(
|
z.union(
|
||||||
...Object.entries(staticValidators).map(([k, v]) =>
|
Object.entries(staticValidators).map(([k, v]) =>
|
||||||
object({
|
z.object({
|
||||||
selection: literal(k),
|
selection: z.literal(k),
|
||||||
value: v,
|
value: v,
|
||||||
other,
|
other,
|
||||||
}),
|
}),
|
||||||
),
|
) as [z.ZodObject<any>, z.ZodObject<any>, ...z.ZodObject<any>[]],
|
||||||
) as any,
|
) as any,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ExtractInputSpecType } from './input/builder/inputSpec'
|
|||||||
import * as T from '../types'
|
import * as T from '../types'
|
||||||
import { once } from '../util'
|
import { once } from '../util'
|
||||||
import { InitScript } from '../inits'
|
import { InitScript } from '../inits'
|
||||||
import { Parser } from 'ts-matches'
|
import { z } from 'zod'
|
||||||
|
|
||||||
type MaybeInputSpec<Type> = {} extends Type ? null : InputSpec<Type>
|
type MaybeInputSpec<Type> = {} extends Type ? null : InputSpec<Type>
|
||||||
export type Run<A extends Record<string, any>> = (options: {
|
export type Run<A extends Record<string, any>> = (options: {
|
||||||
@@ -52,7 +52,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
|
|||||||
readonly _INPUT: Type = null as any as Type
|
readonly _INPUT: Type = null as any as Type
|
||||||
private prevInputSpec: Record<
|
private prevInputSpec: Record<
|
||||||
string,
|
string,
|
||||||
{ spec: T.inputSpecTypes.InputSpec; validator: Parser<unknown, Type> }
|
{ spec: T.inputSpecTypes.InputSpec; validator: z.ZodType<Type> }
|
||||||
> = {}
|
> = {}
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly id: Id,
|
readonly id: Id,
|
||||||
@@ -137,7 +137,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
|
|||||||
`getActionInput has not been called for EventID ${options.effects.eventId}`,
|
`getActionInput has not been called for EventID ${options.effects.eventId}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
options.input = prevInputSpec.validator.unsafeCast(options.input)
|
options.input = prevInputSpec.validator.parse(options.input)
|
||||||
spec = prevInputSpec.spec
|
spec = prevInputSpec.spec
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export * as types from './types'
|
|||||||
export * as T from './types'
|
export * as T from './types'
|
||||||
export * as yaml from 'yaml'
|
export * as yaml from 'yaml'
|
||||||
export * as inits from './inits'
|
export * as inits from './inits'
|
||||||
export * as matches from 'ts-matches'
|
export { z } from 'zod'
|
||||||
|
|
||||||
export * as utils from './util'
|
export * as utils from './util'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { object, string } from 'ts-matches'
|
import { z } from 'zod'
|
||||||
import { Effects } from '../Effects'
|
import { Effects } from '../Effects'
|
||||||
import { Origin } from './Origin'
|
import { Origin } from './Origin'
|
||||||
import { AddSslOptions, BindParams } from '../osBindings'
|
import { AddSslOptions, BindParams } from '../osBindings'
|
||||||
@@ -69,9 +69,8 @@ export type BindOptionsByProtocol =
|
|||||||
| BindOptionsByKnownProtocol
|
| BindOptionsByKnownProtocol
|
||||||
| (BindOptions & { protocol: null })
|
| (BindOptions & { protocol: null })
|
||||||
|
|
||||||
const hasStringProtocol = object({
|
const hasStringProtocol = (v: unknown): v is { protocol: string } =>
|
||||||
protocol: string,
|
z.object({ protocol: z.string() }).safeParse(v).success
|
||||||
}).test
|
|
||||||
|
|
||||||
export class MultiHost {
|
export class MultiHost {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { object } from 'ts-matches'
|
|
||||||
|
|
||||||
export function deepEqual(...args: unknown[]) {
|
export function deepEqual(...args: unknown[]) {
|
||||||
const objects = args.filter(object.test)
|
const objects = args.filter(
|
||||||
|
(x): x is object => typeof x === 'object' && x !== null,
|
||||||
|
)
|
||||||
if (objects.length === 0) {
|
if (objects.length === 0) {
|
||||||
for (const x of args) if (x !== args[0]) return false
|
for (const x of args) if (x !== args[0]) return false
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { boolean } from 'ts-matches'
|
|
||||||
import { ExtendedVersion } from '../exver'
|
import { ExtendedVersion } from '../exver'
|
||||||
|
|
||||||
export type Vertex<VMetadata = null, EMetadata = null> = {
|
export type Vertex<VMetadata = null, EMetadata = null> = {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { arrayOf, string } from 'ts-matches'
|
|
||||||
|
|
||||||
export const splitCommand = (
|
export const splitCommand = (
|
||||||
command: string | [string, ...string[]],
|
command: string | [string, ...string[]],
|
||||||
): string[] => {
|
): string[] => {
|
||||||
if (arrayOf(string).test(command)) return command
|
if (Array.isArray(command)) return command
|
||||||
return ['sh', '-c', command]
|
return ['sh', '-c', command]
|
||||||
}
|
}
|
||||||
|
|||||||
19
sdk/base/package-lock.json
generated
19
sdk/base/package-lock.json
generated
@@ -13,8 +13,8 @@
|
|||||||
"deep-equality-data-structures": "^1.5.0",
|
"deep-equality-data-structures": "^1.5.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"yaml": "^2.7.1",
|
||||||
"yaml": "^2.7.1"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
@@ -4626,12 +4626,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-matches": {
|
|
||||||
"version": "6.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.3.2.tgz",
|
|
||||||
"integrity": "sha512-UhSgJymF8cLd4y0vV29qlKVCkQpUtekAaujXbQVc729FezS8HwqzepqvtjzQ3HboatIqN/Idor85O2RMwT7lIQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ts-morph": {
|
"node_modules/ts-morph": {
|
||||||
"version": "18.0.0",
|
"version": "18.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
|
||||||
@@ -5006,6 +5000,15 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||||
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@
|
|||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
"@noble/curves": "^1.8.2",
|
"@noble/curves": "^1.8.2",
|
||||||
"@noble/hashes": "^1.7.2",
|
"@noble/hashes": "^1.7.2",
|
||||||
|
"deep-equality-data-structures": "^1.5.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
|
||||||
"yaml": "^2.7.1",
|
"yaml": "^2.7.1",
|
||||||
"deep-equality-data-structures": "^1.5.0"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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 { once, asError, Drop } from '../util'
|
import { once, asError, Drop } from '../util'
|
||||||
import { object, unknown } from 'ts-matches'
|
|
||||||
|
|
||||||
export type HealthCheckParams = {
|
export type HealthCheckParams = {
|
||||||
id: HealthCheckId
|
id: HealthCheckId
|
||||||
@@ -109,7 +108,8 @@ export class HealthCheck extends Drop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function asMessage(e: unknown) {
|
function asMessage(e: unknown) {
|
||||||
if (object({ message: unknown }).test(e)) return String(e.message)
|
if (typeof e === 'object' && e !== null && 'message' in e)
|
||||||
|
return String((e as any).message)
|
||||||
const value = String(e)
|
const value = String(e)
|
||||||
if (value.length == null) return null
|
if (value.length == null) return null
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
ISB,
|
ISB,
|
||||||
IST,
|
IST,
|
||||||
types,
|
types,
|
||||||
matches,
|
z,
|
||||||
utils,
|
utils,
|
||||||
} from '../../base/lib'
|
} from '../../base/lib'
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export {
|
|||||||
ISB,
|
ISB,
|
||||||
IST,
|
IST,
|
||||||
types,
|
types,
|
||||||
matches,
|
z,
|
||||||
utils,
|
utils,
|
||||||
}
|
}
|
||||||
export { setupI18n } from './i18n'
|
export { setupI18n } from './i18n'
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { testOutput } from './output.test'
|
|
||||||
import { InputSpec } from '../../../base/lib/actions/input/builder/inputSpec'
|
import { InputSpec } from '../../../base/lib/actions/input/builder/inputSpec'
|
||||||
import { List } from '../../../base/lib/actions/input/builder/list'
|
import { List } from '../../../base/lib/actions/input/builder/list'
|
||||||
import { Value } from '../../../base/lib/actions/input/builder/value'
|
import { Value } from '../../../base/lib/actions/input/builder/value'
|
||||||
@@ -7,6 +6,12 @@ import { ValueSpec } from '../../../base/lib/actions/input/inputSpecTypes'
|
|||||||
import { setupManifest } from '../manifest/setupManifest'
|
import { setupManifest } from '../manifest/setupManifest'
|
||||||
import { StartSdk } from '../StartSdk'
|
import { StartSdk } from '../StartSdk'
|
||||||
|
|
||||||
|
export type IfEquals<T, U, Y = unknown, N = never> =
|
||||||
|
(<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? Y : N
|
||||||
|
export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
|
||||||
|
return () => null
|
||||||
|
}
|
||||||
|
|
||||||
describe('builder tests', () => {
|
describe('builder tests', () => {
|
||||||
test('text', async () => {
|
test('text', async () => {
|
||||||
const bitcoinPropertiesBuilt: {
|
const bitcoinPropertiesBuilt: {
|
||||||
@@ -50,8 +55,8 @@ describe('values', () => {
|
|||||||
default: false,
|
default: false,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(false)
|
validator.parse(false)
|
||||||
testOutput<typeof validator._TYPE, boolean>()(null)
|
testOutput<typeof validator._output, boolean>()(null)
|
||||||
})
|
})
|
||||||
test('text', async () => {
|
test('text', async () => {
|
||||||
const value = await Value.text({
|
const value = await Value.text({
|
||||||
@@ -61,9 +66,9 @@ describe('values', () => {
|
|||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
const rawIs = value.spec
|
const rawIs = value.spec
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.parse(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._output, string>()(null)
|
||||||
})
|
})
|
||||||
test('text with default', async () => {
|
test('text with default', async () => {
|
||||||
const value = await Value.text({
|
const value = await Value.text({
|
||||||
@@ -73,9 +78,9 @@ describe('values', () => {
|
|||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
const rawIs = value.spec
|
const rawIs = value.spec
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.parse(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._output, string>()(null)
|
||||||
})
|
})
|
||||||
test('optional text', async () => {
|
test('optional text', async () => {
|
||||||
const value = await Value.text({
|
const value = await Value.text({
|
||||||
@@ -85,9 +90,9 @@ describe('values', () => {
|
|||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
const rawIs = value.spec
|
const rawIs = value.spec
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
})
|
})
|
||||||
test('color', async () => {
|
test('color', async () => {
|
||||||
const value = await Value.color({
|
const value = await Value.color({
|
||||||
@@ -98,8 +103,8 @@ describe('values', () => {
|
|||||||
warning: null,
|
warning: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('#000000')
|
validator.parse('#000000')
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
})
|
})
|
||||||
test('datetime', async () => {
|
test('datetime', async () => {
|
||||||
const value = await Value.datetime({
|
const value = await Value.datetime({
|
||||||
@@ -113,8 +118,8 @@ describe('values', () => {
|
|||||||
max: null,
|
max: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('2021-01-01')
|
validator.parse('2021-01-01')
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._output, string>()(null)
|
||||||
})
|
})
|
||||||
test('optional datetime', async () => {
|
test('optional datetime', async () => {
|
||||||
const value = await Value.datetime({
|
const value = await Value.datetime({
|
||||||
@@ -128,8 +133,8 @@ describe('values', () => {
|
|||||||
max: null,
|
max: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('2021-01-01')
|
validator.parse('2021-01-01')
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
})
|
})
|
||||||
test('textarea', async () => {
|
test('textarea', async () => {
|
||||||
const value = await Value.textarea({
|
const value = await Value.textarea({
|
||||||
@@ -145,8 +150,8 @@ describe('values', () => {
|
|||||||
placeholder: null,
|
placeholder: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
})
|
})
|
||||||
test('number', async () => {
|
test('number', async () => {
|
||||||
const value = await Value.number({
|
const value = await Value.number({
|
||||||
@@ -163,8 +168,8 @@ describe('values', () => {
|
|||||||
placeholder: null,
|
placeholder: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(2)
|
validator.parse(2)
|
||||||
testOutput<typeof validator._TYPE, number>()(null)
|
testOutput<typeof validator._output, number>()(null)
|
||||||
})
|
})
|
||||||
test('optional number', async () => {
|
test('optional number', async () => {
|
||||||
const value = await Value.number({
|
const value = await Value.number({
|
||||||
@@ -181,8 +186,8 @@ describe('values', () => {
|
|||||||
placeholder: null,
|
placeholder: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(2)
|
validator.parse(2)
|
||||||
testOutput<typeof validator._TYPE, number | null>()(null)
|
testOutput<typeof validator._output, number | null>()(null)
|
||||||
})
|
})
|
||||||
test('select', async () => {
|
test('select', async () => {
|
||||||
const value = await Value.select({
|
const value = await Value.select({
|
||||||
@@ -196,10 +201,10 @@ describe('values', () => {
|
|||||||
warning: null,
|
warning: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('a')
|
validator.parse('a')
|
||||||
validator.unsafeCast('b')
|
validator.parse('b')
|
||||||
expect(() => validator.unsafeCast('c')).toThrowError()
|
expect(() => validator.parse('c')).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, 'a' | 'b'>()(null)
|
testOutput<typeof validator._output, 'a' | 'b'>()(null)
|
||||||
})
|
})
|
||||||
test('nullable select', async () => {
|
test('nullable select', async () => {
|
||||||
const value = await Value.select({
|
const value = await Value.select({
|
||||||
@@ -213,9 +218,9 @@ describe('values', () => {
|
|||||||
warning: null,
|
warning: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('a')
|
validator.parse('a')
|
||||||
validator.unsafeCast('b')
|
validator.parse('b')
|
||||||
testOutput<typeof validator._TYPE, 'a' | 'b'>()(null)
|
testOutput<typeof validator._output, 'a' | 'b'>()(null)
|
||||||
})
|
})
|
||||||
test('multiselect', async () => {
|
test('multiselect', async () => {
|
||||||
const value = await Value.multiselect({
|
const value = await Value.multiselect({
|
||||||
@@ -231,12 +236,12 @@ describe('values', () => {
|
|||||||
maxLength: null,
|
maxLength: null,
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast([])
|
validator.parse([])
|
||||||
validator.unsafeCast(['a', 'b'])
|
validator.parse(['a', 'b'])
|
||||||
|
|
||||||
expect(() => validator.unsafeCast(['e'])).toThrowError()
|
expect(() => validator.parse(['e'])).toThrowError()
|
||||||
expect(() => validator.unsafeCast([4])).toThrowError()
|
expect(() => validator.parse([4])).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, Array<'a' | 'b'>>()(null)
|
testOutput<typeof validator._output, Array<'a' | 'b'>>()(null)
|
||||||
})
|
})
|
||||||
test('object', async () => {
|
test('object', async () => {
|
||||||
const value = await Value.object(
|
const value = await Value.object(
|
||||||
@@ -254,8 +259,8 @@ describe('values', () => {
|
|||||||
}),
|
}),
|
||||||
).build({} as any)
|
).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: true })
|
validator.parse({ a: true })
|
||||||
testOutput<typeof validator._TYPE, { a: boolean }>()(null)
|
testOutput<typeof validator._output, { a: boolean }>()(null)
|
||||||
})
|
})
|
||||||
test('union', async () => {
|
test('union', async () => {
|
||||||
const value = await Value.union({
|
const value = await Value.union({
|
||||||
@@ -278,8 +283,8 @@ describe('values', () => {
|
|||||||
}),
|
}),
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ selection: 'a', value: { b: false } })
|
validator.parse({ selection: 'a', value: { b: false } })
|
||||||
type Test = typeof validator._TYPE
|
type Test = typeof validator._output
|
||||||
testOutput<
|
testOutput<
|
||||||
Test,
|
Test,
|
||||||
{
|
{
|
||||||
@@ -306,9 +311,9 @@ describe('values', () => {
|
|||||||
default: false,
|
default: false,
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(false)
|
validator.parse(false)
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.parse(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, boolean>()(null)
|
testOutput<typeof validator._output, boolean>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
description: null,
|
description: null,
|
||||||
@@ -324,9 +329,9 @@ describe('values', () => {
|
|||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
const rawIs = value.spec
|
const rawIs = value.spec
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -340,9 +345,9 @@ describe('values', () => {
|
|||||||
default: 'this is a default value',
|
default: 'this is a default value',
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -357,9 +362,9 @@ describe('values', () => {
|
|||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
const rawIs = value.spec
|
const rawIs = value.spec
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -375,9 +380,9 @@ describe('values', () => {
|
|||||||
warning: null,
|
warning: null,
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('#000000')
|
validator.parse('#000000')
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -432,9 +437,9 @@ describe('values', () => {
|
|||||||
}
|
}
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('2021-01-01')
|
validator.parse('2021-01-01')
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -458,8 +463,8 @@ describe('values', () => {
|
|||||||
placeholder: null,
|
placeholder: null,
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('test text')
|
validator.parse('test text')
|
||||||
testOutput<typeof validator._TYPE, string | null>()(null)
|
testOutput<typeof validator._output, string | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -480,10 +485,10 @@ describe('values', () => {
|
|||||||
placeholder: null,
|
placeholder: null,
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(2)
|
validator.parse(2)
|
||||||
validator.unsafeCast(null)
|
validator.parse(null)
|
||||||
expect(() => validator.unsafeCast('null')).toThrowError()
|
expect(() => validator.parse('null')).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, number | null>()(null)
|
testOutput<typeof validator._output, number | null>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
required: false,
|
required: false,
|
||||||
@@ -501,9 +506,9 @@ describe('values', () => {
|
|||||||
warning: null,
|
warning: null,
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast('a')
|
validator.parse('a')
|
||||||
validator.unsafeCast('b')
|
validator.parse('b')
|
||||||
testOutput<typeof validator._TYPE, 'a' | 'b'>()(null)
|
testOutput<typeof validator._output, 'a' | 'b'>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
})
|
})
|
||||||
@@ -522,12 +527,12 @@ describe('values', () => {
|
|||||||
maxLength: null,
|
maxLength: null,
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast([])
|
validator.parse([])
|
||||||
validator.unsafeCast(['a', 'b'])
|
validator.parse(['a', 'b'])
|
||||||
|
|
||||||
expect(() => validator.unsafeCast([4])).toThrowError()
|
expect(() => validator.parse([4])).toThrowError()
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.parse(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, Array<'a' | 'b'>>()(null)
|
testOutput<typeof validator._output, Array<'a' | 'b'>>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'Testing',
|
name: 'Testing',
|
||||||
default: [],
|
default: [],
|
||||||
@@ -568,8 +573,8 @@ describe('values', () => {
|
|||||||
}),
|
}),
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ selection: 'a', value: { b: false } })
|
validator.parse({ selection: 'a', value: { b: false } })
|
||||||
type Test = typeof validator._TYPE
|
type Test = typeof validator._output
|
||||||
testOutput<
|
testOutput<
|
||||||
Test,
|
Test,
|
||||||
| {
|
| {
|
||||||
@@ -653,8 +658,8 @@ describe('values', () => {
|
|||||||
}),
|
}),
|
||||||
})).build({} as any)
|
})).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ selection: 'a', value: { b: false } })
|
validator.parse({ selection: 'a', value: { b: false } })
|
||||||
type Test = typeof validator._TYPE
|
type Test = typeof validator._output
|
||||||
testOutput<
|
testOutput<
|
||||||
Test,
|
Test,
|
||||||
| {
|
| {
|
||||||
@@ -726,8 +731,8 @@ describe('Builder List', () => {
|
|||||||
),
|
),
|
||||||
).build({} as any)
|
).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast([{ test: true }])
|
validator.parse([{ test: true }])
|
||||||
testOutput<typeof validator._TYPE, { test: boolean }[]>()(null)
|
testOutput<typeof validator._output, { test: boolean }[]>()(null)
|
||||||
})
|
})
|
||||||
test('text', async () => {
|
test('text', async () => {
|
||||||
const value = await Value.list(
|
const value = await Value.list(
|
||||||
@@ -741,8 +746,8 @@ describe('Builder List', () => {
|
|||||||
),
|
),
|
||||||
).build({} as any)
|
).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(['test', 'text'])
|
validator.parse(['test', 'text'])
|
||||||
testOutput<typeof validator._TYPE, string[]>()(null)
|
testOutput<typeof validator._output, string[]>()(null)
|
||||||
})
|
})
|
||||||
describe('dynamic', () => {
|
describe('dynamic', () => {
|
||||||
test('text', async () => {
|
test('text', async () => {
|
||||||
@@ -753,10 +758,10 @@ describe('Builder List', () => {
|
|||||||
})),
|
})),
|
||||||
).build({} as any)
|
).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast(['test', 'text'])
|
validator.parse(['test', 'text'])
|
||||||
expect(() => validator.unsafeCast([3, 4])).toThrowError()
|
expect(() => validator.parse([3, 4])).toThrowError()
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.parse(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, string[]>()(null)
|
testOutput<typeof validator._output, string[]>()(null)
|
||||||
expect(value.spec).toMatchObject({
|
expect(value.spec).toMatchObject({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
spec: { patterns: [] },
|
spec: { patterns: [] },
|
||||||
@@ -777,10 +782,10 @@ describe('Nested nullable values', () => {
|
|||||||
}),
|
}),
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.parse({ a: null })
|
||||||
validator.unsafeCast({ a: 'test' })
|
validator.parse({ a: 'test' })
|
||||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
expect(() => validator.parse({ a: 4 })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: string | null }>()(null)
|
testOutput<typeof validator._output, { a: string | null }>()(null)
|
||||||
})
|
})
|
||||||
test('Testing number', async () => {
|
test('Testing number', async () => {
|
||||||
const value = await InputSpec.of({
|
const value = await InputSpec.of({
|
||||||
@@ -800,10 +805,10 @@ describe('Nested nullable values', () => {
|
|||||||
}),
|
}),
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.parse({ a: null })
|
||||||
validator.unsafeCast({ a: 5 })
|
validator.parse({ a: 5 })
|
||||||
expect(() => validator.unsafeCast({ a: '4' })).toThrowError()
|
expect(() => validator.parse({ a: '4' })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: number | null }>()(null)
|
testOutput<typeof validator._output, { a: number | null }>()(null)
|
||||||
})
|
})
|
||||||
test('Testing color', async () => {
|
test('Testing color', async () => {
|
||||||
const value = await InputSpec.of({
|
const value = await InputSpec.of({
|
||||||
@@ -817,10 +822,10 @@ describe('Nested nullable values', () => {
|
|||||||
}),
|
}),
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.parse({ a: null })
|
||||||
validator.unsafeCast({ a: '5' })
|
validator.parse({ a: '5' })
|
||||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
expect(() => validator.parse({ a: 4 })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: string | null }>()(null)
|
testOutput<typeof validator._output, { a: string | null }>()(null)
|
||||||
})
|
})
|
||||||
test('Testing select', async () => {
|
test('Testing select', async () => {
|
||||||
const value = await InputSpec.of({
|
const value = await InputSpec.of({
|
||||||
@@ -847,9 +852,9 @@ describe('Nested nullable values', () => {
|
|||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
|
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: 'a' })
|
validator.parse({ a: 'a' })
|
||||||
expect(() => validator.unsafeCast({ a: '4' })).toThrowError()
|
expect(() => validator.parse({ a: '4' })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: 'a' }>()(null)
|
testOutput<typeof validator._output, { a: 'a' }>()(null)
|
||||||
})
|
})
|
||||||
test('Testing multiselect', async () => {
|
test('Testing multiselect', async () => {
|
||||||
const value = await InputSpec.of({
|
const value = await InputSpec.of({
|
||||||
@@ -868,10 +873,10 @@ describe('Nested nullable values', () => {
|
|||||||
}),
|
}),
|
||||||
}).build({} as any)
|
}).build({} as any)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: [] })
|
validator.parse({ a: [] })
|
||||||
validator.unsafeCast({ a: ['a'] })
|
validator.parse({ a: ['a'] })
|
||||||
expect(() => validator.unsafeCast({ a: ['4'] })).toThrowError()
|
expect(() => validator.parse({ a: ['4'] })).toThrowError()
|
||||||
expect(() => validator.unsafeCast({ a: '4' })).toThrowError()
|
expect(() => validator.parse({ a: '4' })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: 'a'[] }>()(null)
|
testOutput<typeof validator._output, { a: 'a'[] }>()(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,428 +0,0 @@
|
|||||||
import { oldSpecToBuilder } from '../../scripts/oldSpecToBuilder'
|
|
||||||
|
|
||||||
oldSpecToBuilder(
|
|
||||||
// Make the location
|
|
||||||
'./lib/test/output.ts',
|
|
||||||
// Put the inputSpec here
|
|
||||||
{
|
|
||||||
mediasources: {
|
|
||||||
type: 'list',
|
|
||||||
subtype: 'enum',
|
|
||||||
name: 'Media Sources',
|
|
||||||
description: 'List of Media Sources to use with Jellyfin',
|
|
||||||
range: '[1,*)',
|
|
||||||
default: ['nextcloud'],
|
|
||||||
spec: {
|
|
||||||
values: ['nextcloud', 'filebrowser'],
|
|
||||||
'value-names': {
|
|
||||||
nextcloud: 'NextCloud',
|
|
||||||
filebrowser: 'File Browser',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testListUnion: {
|
|
||||||
type: 'list',
|
|
||||||
subtype: 'union',
|
|
||||||
name: 'Lightning Nodes',
|
|
||||||
description: 'List of Lightning Network node instances to manage',
|
|
||||||
range: '[1,*)',
|
|
||||||
default: ['lnd'],
|
|
||||||
spec: {
|
|
||||||
type: 'string',
|
|
||||||
'display-as': '{{name}}',
|
|
||||||
'unique-by': 'name',
|
|
||||||
name: 'Node Implementation',
|
|
||||||
tag: {
|
|
||||||
id: 'type',
|
|
||||||
name: 'Type',
|
|
||||||
description:
|
|
||||||
'- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n',
|
|
||||||
'variant-names': {
|
|
||||||
lnd: 'Lightning Network Daemon (LND)',
|
|
||||||
'c-lightning': 'Core Lightning (CLN)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
default: 'lnd',
|
|
||||||
variants: {
|
|
||||||
lnd: {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Node Name',
|
|
||||||
description: 'Name of this node in the list',
|
|
||||||
default: 'LND Wrapper',
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rpc: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'RPC Settings',
|
|
||||||
description: 'RPC configuration options.',
|
|
||||||
spec: {
|
|
||||||
enable: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Enable',
|
|
||||||
description: 'Allow remote RPC requests.',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
type: 'string',
|
|
||||||
nullable: false,
|
|
||||||
name: 'Username',
|
|
||||||
description: 'The username for connecting to Bitcoin over RPC.',
|
|
||||||
default: 'bitcoin',
|
|
||||||
masked: true,
|
|
||||||
pattern: '^[a-zA-Z0-9_]+$',
|
|
||||||
'pattern-description':
|
|
||||||
'Must be alphanumeric (can contain underscore).',
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: 'string',
|
|
||||||
nullable: false,
|
|
||||||
name: 'RPC Password',
|
|
||||||
description: 'The password for connecting to Bitcoin over RPC.',
|
|
||||||
default: {
|
|
||||||
charset: 'a-z,2-7',
|
|
||||||
len: 20,
|
|
||||||
},
|
|
||||||
pattern: '^[^\\n"]*$',
|
|
||||||
'pattern-description':
|
|
||||||
'Must not contain newline or quote characters.',
|
|
||||||
copyable: true,
|
|
||||||
masked: true,
|
|
||||||
},
|
|
||||||
bio: {
|
|
||||||
type: 'string',
|
|
||||||
nullable: false,
|
|
||||||
name: 'Username',
|
|
||||||
description: 'The username for connecting to Bitcoin over RPC.',
|
|
||||||
default: 'bitcoin',
|
|
||||||
masked: true,
|
|
||||||
pattern: '^[a-zA-Z0-9_]+$',
|
|
||||||
'pattern-description':
|
|
||||||
'Must be alphanumeric (can contain underscore).',
|
|
||||||
textarea: true,
|
|
||||||
},
|
|
||||||
advanced: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Advanced',
|
|
||||||
description: 'Advanced RPC Settings',
|
|
||||||
spec: {
|
|
||||||
auth: {
|
|
||||||
name: 'Authorization',
|
|
||||||
description:
|
|
||||||
'Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.',
|
|
||||||
type: 'list',
|
|
||||||
subtype: 'string',
|
|
||||||
default: [],
|
|
||||||
spec: {
|
|
||||||
pattern:
|
|
||||||
'^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$',
|
|
||||||
'pattern-description':
|
|
||||||
'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
|
|
||||||
masked: false,
|
|
||||||
},
|
|
||||||
range: '[0,*)',
|
|
||||||
},
|
|
||||||
serialversion: {
|
|
||||||
name: 'Serialization Version',
|
|
||||||
description:
|
|
||||||
'Return raw transaction or block hex with Segwit or non-SegWit serialization.',
|
|
||||||
type: 'enum',
|
|
||||||
values: ['non-segwit', 'segwit'],
|
|
||||||
'value-names': {},
|
|
||||||
default: 'segwit',
|
|
||||||
},
|
|
||||||
servertimeout: {
|
|
||||||
name: 'Rpc Server Timeout',
|
|
||||||
description:
|
|
||||||
'Number of seconds after which an uncompleted RPC call will time out.',
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
range: '[5,300]',
|
|
||||||
integral: true,
|
|
||||||
units: 'seconds',
|
|
||||||
default: 30,
|
|
||||||
},
|
|
||||||
threads: {
|
|
||||||
name: 'Threads',
|
|
||||||
description:
|
|
||||||
'Set the number of threads for handling RPC calls. You may wish to increase this if you are making lots of calls via an integration.',
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
default: 16,
|
|
||||||
range: '[1,64]',
|
|
||||||
integral: true,
|
|
||||||
},
|
|
||||||
workqueue: {
|
|
||||||
name: 'Work Queue',
|
|
||||||
description:
|
|
||||||
'Set the depth of the work queue to service RPC calls. Determines how long the backlog of RPC requests can get before it just rejects new ones.',
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
default: 128,
|
|
||||||
range: '[8,256]',
|
|
||||||
integral: true,
|
|
||||||
units: 'requests',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'zmq-enabled': {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'ZeroMQ Enabled',
|
|
||||||
description: 'Enable the ZeroMQ interface',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
txindex: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Transaction Index',
|
|
||||||
description: 'Enable the Transaction Index (txindex)',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
wallet: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Wallet',
|
|
||||||
description: 'Wallet Settings',
|
|
||||||
spec: {
|
|
||||||
enable: {
|
|
||||||
name: 'Enable Wallet',
|
|
||||||
description: 'Load the wallet and enable wallet RPC calls.',
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
avoidpartialspends: {
|
|
||||||
name: 'Avoid Partial Spends',
|
|
||||||
description:
|
|
||||||
'Group outputs by address, selecting all or none, instead of selecting on a per-output basis. This improves privacy at the expense of higher transaction fees.',
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
discardfee: {
|
|
||||||
name: 'Discard Change Tolerance',
|
|
||||||
description:
|
|
||||||
'The fee rate (in BTC/kB) that indicates your tolerance for discarding change by adding it to the fee.',
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
default: 0.0001,
|
|
||||||
range: '[0,.01]',
|
|
||||||
integral: false,
|
|
||||||
units: 'BTC/kB',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
advanced: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Advanced',
|
|
||||||
description: 'Advanced Settings',
|
|
||||||
spec: {
|
|
||||||
mempool: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Mempool',
|
|
||||||
description: 'Mempool Settings',
|
|
||||||
spec: {
|
|
||||||
mempoolfullrbf: {
|
|
||||||
name: 'Enable Full RBF',
|
|
||||||
description:
|
|
||||||
'Policy for your node to use for relaying and mining unconfirmed transactions. For details, see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-24.0.md#notice-of-new-option-for-transaction-replacement-policies',
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
persistmempool: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Persist Mempool',
|
|
||||||
description: 'Save the mempool on shutdown and load on restart.',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
maxmempool: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
name: 'Max Mempool Size',
|
|
||||||
description:
|
|
||||||
'Keep the transaction memory pool below <n> megabytes.',
|
|
||||||
range: '[1,*)',
|
|
||||||
integral: true,
|
|
||||||
units: 'MiB',
|
|
||||||
default: 300,
|
|
||||||
},
|
|
||||||
mempoolexpiry: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
name: 'Mempool Expiration',
|
|
||||||
description:
|
|
||||||
'Do not keep transactions in the mempool longer than <n> hours.',
|
|
||||||
range: '[1,*)',
|
|
||||||
integral: true,
|
|
||||||
units: 'Hr',
|
|
||||||
default: 336,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
peers: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Peers',
|
|
||||||
description: 'Peer Connection Settings',
|
|
||||||
spec: {
|
|
||||||
listen: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Make Public',
|
|
||||||
description:
|
|
||||||
'Allow other nodes to find your server on the network.',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
onlyconnect: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Disable Peer Discovery',
|
|
||||||
description: 'Only connect to specified peers.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
onlyonion: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Disable Clearnet',
|
|
||||||
description: 'Only connect to peers over Tor.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
addnode: {
|
|
||||||
name: 'Add Nodes',
|
|
||||||
description: 'Add addresses of nodes to connect to.',
|
|
||||||
type: 'list',
|
|
||||||
subtype: 'object',
|
|
||||||
range: '[0,*)',
|
|
||||||
default: [],
|
|
||||||
spec: {
|
|
||||||
'unique-by': null,
|
|
||||||
spec: {
|
|
||||||
hostname: {
|
|
||||||
type: 'string',
|
|
||||||
nullable: true,
|
|
||||||
name: 'Hostname',
|
|
||||||
description: 'Domain or IP address of bitcoin peer',
|
|
||||||
pattern:
|
|
||||||
'(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))',
|
|
||||||
'pattern-description':
|
|
||||||
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
|
|
||||||
masked: false,
|
|
||||||
},
|
|
||||||
port: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: true,
|
|
||||||
name: 'Port',
|
|
||||||
description:
|
|
||||||
'Port that peer is listening on for inbound p2p connections',
|
|
||||||
range: '[0,65535]',
|
|
||||||
integral: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dbcache: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: true,
|
|
||||||
name: 'Database Cache',
|
|
||||||
description:
|
|
||||||
"How much RAM to allocate for caching the TXO set. Higher values improve syncing performance, but increase your chance of using up all your system's memory or corrupting your database in the event of an ungraceful shutdown. Set this high but comfortably below your system's total RAM during IBD, then turn down to 450 (or leave blank) once the sync completes.",
|
|
||||||
warning:
|
|
||||||
'WARNING: Increasing this value results in a higher chance of ungraceful shutdowns, which can leave your node unusable if it happens during the initial block download. Use this setting with caution. Be sure to set this back to the default (450 or leave blank) once your node is synced. DO NOT press the STOP button if your dbcache is large. Instead, set this number back to the default, hit save, and wait for bitcoind to restart on its own.',
|
|
||||||
range: '(0,*)',
|
|
||||||
integral: true,
|
|
||||||
units: 'MiB',
|
|
||||||
},
|
|
||||||
pruning: {
|
|
||||||
type: 'union',
|
|
||||||
name: 'Pruning Settings',
|
|
||||||
description:
|
|
||||||
'Blockchain Pruning Options\nReduce the blockchain size on disk\n',
|
|
||||||
warning:
|
|
||||||
'If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n',
|
|
||||||
tag: {
|
|
||||||
id: 'mode',
|
|
||||||
name: 'Pruning Mode',
|
|
||||||
description:
|
|
||||||
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
|
|
||||||
'variant-names': {
|
|
||||||
disabled: 'Disabled',
|
|
||||||
automatic: 'Automatic',
|
|
||||||
manual: 'Manual',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
disabled: {},
|
|
||||||
automatic: {
|
|
||||||
size: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
name: 'Max Chain Size',
|
|
||||||
description: 'Limit of blockchain size on disk.',
|
|
||||||
warning:
|
|
||||||
'Increasing this value will require re-syncing your node.',
|
|
||||||
default: 550,
|
|
||||||
range: '[550,1000000)',
|
|
||||||
integral: true,
|
|
||||||
units: 'MiB',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
manual: {
|
|
||||||
size: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: false,
|
|
||||||
name: 'Failsafe Chain Size',
|
|
||||||
description: 'Prune blockchain if size expands beyond this.',
|
|
||||||
default: 65536,
|
|
||||||
range: '[550,1000000)',
|
|
||||||
integral: true,
|
|
||||||
units: 'MiB',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
default: 'disabled',
|
|
||||||
},
|
|
||||||
blockfilters: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Block Filters',
|
|
||||||
description: 'Settings for storing and serving compact block filters',
|
|
||||||
spec: {
|
|
||||||
blockfilterindex: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Compute Compact Block Filters (BIP158)',
|
|
||||||
description:
|
|
||||||
"Generate Compact Block Filters during initial sync (IBD) to enable 'getblockfilter' RPC. This is useful if dependent services need block filters to efficiently scan for addresses/transactions etc.",
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
peerblockfilters: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Serve Compact Block Filters to Peers (BIP157)',
|
|
||||||
description:
|
|
||||||
"Serve Compact Block Filters as a peer service to other nodes on the network. This is useful if you wish to connect an SPV client to your node to make it efficient to scan transactions without having to download all block data. 'Compute Compact Block Filters (BIP158)' is required.",
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bloomfilters: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Bloom Filters (BIP37)',
|
|
||||||
description: 'Setting for serving Bloom Filters',
|
|
||||||
spec: {
|
|
||||||
peerbloomfilters: {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Serve Bloom Filters to Peers',
|
|
||||||
description:
|
|
||||||
'Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.',
|
|
||||||
warning:
|
|
||||||
'This is ONLY for use with Bisq integration, please use Block Filters for all other applications.',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// convert this to `start-sdk/lib` for conversions
|
|
||||||
StartSdk: './output.sdk',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import { inputSpecSpec, InputSpecSpec } from './output'
|
|
||||||
import * as _I from '../index'
|
|
||||||
import { camelCase } from '../../scripts/oldSpecToBuilder'
|
|
||||||
import { deepMerge } from '../../../base/lib/util'
|
|
||||||
|
|
||||||
export type IfEquals<T, U, Y = unknown, N = never> =
|
|
||||||
(<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? Y : N
|
|
||||||
export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
|
|
||||||
return () => null
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Testing the types of the input spec
|
|
||||||
testOutput<InputSpecSpec['rpc']['enable'], boolean>()(null)
|
|
||||||
testOutput<InputSpecSpec['rpc']['username'], string>()(null)
|
|
||||||
testOutput<InputSpecSpec['rpc']['username'], string>()(null)
|
|
||||||
|
|
||||||
testOutput<InputSpecSpec['rpc']['advanced']['auth'], string[]>()(null)
|
|
||||||
testOutput<
|
|
||||||
InputSpecSpec['rpc']['advanced']['serialversion'],
|
|
||||||
'segwit' | 'non-segwit'
|
|
||||||
>()(null)
|
|
||||||
testOutput<InputSpecSpec['rpc']['advanced']['servertimeout'], number>()(null)
|
|
||||||
testOutput<
|
|
||||||
InputSpecSpec['advanced']['peers']['addnode'][0]['hostname'],
|
|
||||||
string | null
|
|
||||||
>()(null)
|
|
||||||
testOutput<
|
|
||||||
InputSpecSpec['testListUnion'][0]['union']['value']['name'],
|
|
||||||
string
|
|
||||||
>()(null)
|
|
||||||
testOutput<InputSpecSpec['testListUnion'][0]['union']['selection'], 'lnd'>()(
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
testOutput<InputSpecSpec['mediasources'], Array<'filebrowser' | 'nextcloud'>>()(
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
|
|
||||||
// @ts-expect-error Because enable should be a boolean
|
|
||||||
testOutput<InputSpecSpec['rpc']['enable'], string>()(null)
|
|
||||||
// prettier-ignore
|
|
||||||
// @ts-expect-error Expect that the string is the one above
|
|
||||||
testOutput<InputSpecSpec["testListUnion"][0]['selection']['selection'], "selection">()(null);
|
|
||||||
|
|
||||||
/// Here we test the output of the matchInputSpecSpec function
|
|
||||||
describe('Inputs', () => {
|
|
||||||
const validInput: InputSpecSpec = {
|
|
||||||
mediasources: ['filebrowser'],
|
|
||||||
testListUnion: [
|
|
||||||
{
|
|
||||||
union: { selection: 'lnd', value: { name: 'string' } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rpc: {
|
|
||||||
enable: true,
|
|
||||||
bio: 'This is a bio',
|
|
||||||
username: 'test',
|
|
||||||
password: 'test',
|
|
||||||
advanced: {
|
|
||||||
auth: ['test'],
|
|
||||||
serialversion: 'segwit',
|
|
||||||
servertimeout: 6,
|
|
||||||
threads: 3,
|
|
||||||
workqueue: 9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'zmq-enabled': false,
|
|
||||||
txindex: false,
|
|
||||||
wallet: { enable: false, avoidpartialspends: false, discardfee: 0.0001 },
|
|
||||||
advanced: {
|
|
||||||
mempool: {
|
|
||||||
maxmempool: 1,
|
|
||||||
persistmempool: true,
|
|
||||||
mempoolexpiry: 23,
|
|
||||||
mempoolfullrbf: true,
|
|
||||||
},
|
|
||||||
peers: {
|
|
||||||
listen: true,
|
|
||||||
onlyconnect: true,
|
|
||||||
onlyonion: true,
|
|
||||||
addnode: [
|
|
||||||
{
|
|
||||||
hostname: 'test',
|
|
||||||
port: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
dbcache: 5,
|
|
||||||
pruning: {
|
|
||||||
selection: 'disabled',
|
|
||||||
value: { disabled: {} },
|
|
||||||
},
|
|
||||||
blockfilters: {
|
|
||||||
blockfilterindex: false,
|
|
||||||
peerblockfilters: false,
|
|
||||||
},
|
|
||||||
bloomfilters: { peerbloomfilters: false },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
test('test valid input', async () => {
|
|
||||||
const { validator } = await inputSpecSpec.build({} as any)
|
|
||||||
const output = validator.unsafeCast(validInput)
|
|
||||||
expect(output).toEqual(validInput)
|
|
||||||
})
|
|
||||||
test('test no longer care about the conversion of min/max and validating', async () => {
|
|
||||||
const { validator } = await inputSpecSpec.build({} as any)
|
|
||||||
validator.unsafeCast(
|
|
||||||
deepMerge({}, validInput, { rpc: { advanced: { threads: 0 } } }),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('test errors should throw for number in string', async () => {
|
|
||||||
const { validator } = await inputSpecSpec.build({} as any)
|
|
||||||
expect(() =>
|
|
||||||
validator.unsafeCast(deepMerge({}, validInput, { rpc: { enable: 2 } })),
|
|
||||||
).toThrowError()
|
|
||||||
})
|
|
||||||
test('Test that we set serialversion to something not segwit or non-segwit', async () => {
|
|
||||||
const { validator } = await inputSpecSpec.build({} as any)
|
|
||||||
expect(() =>
|
|
||||||
validator.unsafeCast(
|
|
||||||
deepMerge({}, validInput, {
|
|
||||||
rpc: { advanced: { serialversion: 'testing' } },
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).toThrowError()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('camelCase', () => {
|
|
||||||
test("'EquipmentClass name'", () => {
|
|
||||||
expect(camelCase('EquipmentClass name')).toEqual('equipmentClassName')
|
|
||||||
})
|
|
||||||
test("'Equipment className'", () => {
|
|
||||||
expect(camelCase('Equipment className')).toEqual('equipmentClassName')
|
|
||||||
})
|
|
||||||
test("'equipment class name'", () => {
|
|
||||||
expect(camelCase('equipment class name')).toEqual('equipmentClassName')
|
|
||||||
})
|
|
||||||
test("'Equipment Class Name'", () => {
|
|
||||||
expect(camelCase('Equipment Class Name')).toEqual('equipmentClassName')
|
|
||||||
})
|
|
||||||
test("'hyphen-name-format'", () => {
|
|
||||||
expect(camelCase('hyphen-name-format')).toEqual('hyphenNameFormat')
|
|
||||||
})
|
|
||||||
test("'underscore_name_format'", () => {
|
|
||||||
expect(camelCase('underscore_name_format')).toEqual('underscoreNameFormat')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as matches from 'ts-matches'
|
import { z } from 'zod'
|
||||||
import * as YAML from 'yaml'
|
import * as YAML from 'yaml'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
import * as INI from 'ini'
|
import * as INI from 'ini'
|
||||||
@@ -97,7 +97,7 @@ function toPath(path: ToPath): string {
|
|||||||
return path.base.subpath(path.subpath)
|
return path.base.subpath(path.subpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Validator<T, U> = matches.Validator<T, U> | matches.Validator<unknown, U>
|
type Validator<_T, U> = z.ZodType<U>
|
||||||
|
|
||||||
type ReadType<A> = {
|
type ReadType<A> = {
|
||||||
once: () => Promise<A | null>
|
once: () => Promise<A | null>
|
||||||
@@ -499,7 +499,7 @@ export class FileHelper<A> {
|
|||||||
(inData) => inData,
|
(inData) => inData,
|
||||||
(inString) => inString,
|
(inString) => inString,
|
||||||
(data) =>
|
(data) =>
|
||||||
(shape || (matches.string as Validator<Transformed, A>)).unsafeCast(
|
(shape || (z.string() as unknown as Validator<Transformed, A>)).parse(
|
||||||
data,
|
data,
|
||||||
),
|
),
|
||||||
transformers,
|
transformers,
|
||||||
@@ -518,7 +518,7 @@ export class FileHelper<A> {
|
|||||||
path,
|
path,
|
||||||
(inData) => JSON.stringify(inData, null, 2),
|
(inData) => JSON.stringify(inData, null, 2),
|
||||||
(inString) => JSON.parse(inString),
|
(inString) => JSON.parse(inString),
|
||||||
(data) => shape.unsafeCast(data),
|
(data) => shape.parse(data),
|
||||||
transformers,
|
transformers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -544,7 +544,7 @@ export class FileHelper<A> {
|
|||||||
path,
|
path,
|
||||||
(inData) => YAML.stringify(inData, null, 2),
|
(inData) => YAML.stringify(inData, null, 2),
|
||||||
(inString) => YAML.parse(inString),
|
(inString) => YAML.parse(inString),
|
||||||
(data) => shape.unsafeCast(data),
|
(data) => shape.parse(data),
|
||||||
transformers,
|
transformers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -570,7 +570,7 @@ export class FileHelper<A> {
|
|||||||
path,
|
path,
|
||||||
(inData) => TOML.stringify(inData as TOML.JsonMap),
|
(inData) => TOML.stringify(inData as TOML.JsonMap),
|
||||||
(inString) => TOML.parse(inString),
|
(inString) => TOML.parse(inString),
|
||||||
(data) => shape.unsafeCast(data),
|
(data) => shape.parse(data),
|
||||||
transformers,
|
transformers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -596,7 +596,7 @@ export class FileHelper<A> {
|
|||||||
path,
|
path,
|
||||||
(inData) => INI.stringify(filterUndefined(inData), options),
|
(inData) => INI.stringify(filterUndefined(inData), options),
|
||||||
(inString) => INI.parse(inString, options),
|
(inString) => INI.parse(inString, options),
|
||||||
(data) => shape.unsafeCast(data),
|
(data) => shape.parse(data),
|
||||||
transformers,
|
transformers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -632,7 +632,7 @@ export class FileHelper<A> {
|
|||||||
return [line.slice(0, pos), line.slice(pos + 1)]
|
return [line.slice(0, pos), line.slice(pos + 1)]
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
(data) => shape.unsafeCast(data),
|
(data) => shape.parse(data),
|
||||||
transformers,
|
transformers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
23
sdk/package/package-lock.json
generated
23
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.50",
|
"version": "0.4.0-beta.51",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.50",
|
"version": "0.4.0-beta.51",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
"ini": "^5.0.0",
|
"ini": "^5.0.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"yaml": "^2.7.1",
|
||||||
"yaml": "^2.7.1"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
@@ -4815,12 +4815,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-matches": {
|
|
||||||
"version": "6.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.3.2.tgz",
|
|
||||||
"integrity": "sha512-UhSgJymF8cLd4y0vV29qlKVCkQpUtekAaujXbQVc729FezS8HwqzepqvtjzQ3HboatIqN/Idor85O2RMwT7lIQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ts-morph": {
|
"node_modules/ts-morph": {
|
||||||
"version": "18.0.0",
|
"version": "18.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
|
||||||
@@ -5232,6 +5226,15 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||||
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.50",
|
"version": "0.4.0-beta.51",
|
||||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./package/lib/index.js",
|
"main": "./package/lib/index.js",
|
||||||
"types": "./package/lib/index.d.ts",
|
"types": "./package/lib/index.d.ts",
|
||||||
@@ -31,16 +31,16 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Start9Labs/start-os#readme",
|
"homepage": "https://github.com/Start9Labs/start-os#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isomorphic-fetch": "^3.0.0",
|
|
||||||
"mime": "^4.0.7",
|
|
||||||
"ts-matches": "^6.3.2",
|
|
||||||
"yaml": "^2.7.1",
|
|
||||||
"deep-equality-data-structures": "^2.0.0",
|
|
||||||
"ini": "^5.0.0",
|
|
||||||
"@types/ini": "^4.1.1",
|
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
"@noble/curves": "^1.8.2",
|
"@noble/curves": "^1.8.2",
|
||||||
"@noble/hashes": "^1.7.2"
|
"@noble/hashes": "^1.7.2",
|
||||||
|
"@types/ini": "^4.1.1",
|
||||||
|
"deep-equality-data-structures": "^2.0.0",
|
||||||
|
"ini": "^5.0.0",
|
||||||
|
"isomorphic-fetch": "^3.0.0",
|
||||||
|
"mime": "^4.0.7",
|
||||||
|
"yaml": "^2.7.1",
|
||||||
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
|
|||||||
@@ -1,384 +0,0 @@
|
|||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
|
|
||||||
export function camelCase(value: string) {
|
|
||||||
return value
|
|
||||||
.replace(/([\(\)\[\]])/g, '')
|
|
||||||
.replace(/^([A-Z])|[\s-_](\w)/g, function (match, p1, p2, offset) {
|
|
||||||
if (p2) return p2.toUpperCase()
|
|
||||||
return p1.toLowerCase()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function oldSpecToBuilder(
|
|
||||||
file: string,
|
|
||||||
inputData: Promise<any> | any,
|
|
||||||
options?: Parameters<typeof makeFileContentFromOld>[1],
|
|
||||||
) {
|
|
||||||
await fs.writeFile(
|
|
||||||
file,
|
|
||||||
await makeFileContentFromOld(inputData, options),
|
|
||||||
(err) => console.error(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isString(x: unknown): x is string {
|
|
||||||
return typeof x === 'string'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function makeFileContentFromOld(
|
|
||||||
inputData: Promise<any> | any,
|
|
||||||
{ StartSdk = 'start-sdk', nested = true } = {},
|
|
||||||
) {
|
|
||||||
const outputLines: string[] = []
|
|
||||||
outputLines.push(`
|
|
||||||
import { sdk } from "${StartSdk}"
|
|
||||||
const {InputSpec, List, Value, Variants} = sdk
|
|
||||||
`)
|
|
||||||
const data = await inputData
|
|
||||||
|
|
||||||
const namedConsts = new Set(['InputSpec', 'Value', 'List'])
|
|
||||||
const inputSpecName = newConst('inputSpecSpec', convertInputSpec(data))
|
|
||||||
outputLines.push(`export type InputSpecSpec = typeof ${inputSpecName}._TYPE;`)
|
|
||||||
|
|
||||||
return outputLines.join('\n')
|
|
||||||
|
|
||||||
function newConst(key: string, data: string, type?: string) {
|
|
||||||
const variableName = getNextConstName(camelCase(key))
|
|
||||||
outputLines.push(
|
|
||||||
`export const ${variableName}${!type ? '' : `: ${type}`} = ${data};`,
|
|
||||||
)
|
|
||||||
return variableName
|
|
||||||
}
|
|
||||||
function maybeNewConst(key: string, data: string) {
|
|
||||||
if (nested) return data
|
|
||||||
return newConst(key, data)
|
|
||||||
}
|
|
||||||
function convertInputSpecInner(data: any) {
|
|
||||||
let answer = '{'
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
const variableName = maybeNewConst(key, convertValueSpec(value))
|
|
||||||
|
|
||||||
answer += `${JSON.stringify(key)}: ${variableName},`
|
|
||||||
}
|
|
||||||
return `${answer}}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertInputSpec(data: any) {
|
|
||||||
return `InputSpec.of(${convertInputSpecInner(data)})`
|
|
||||||
}
|
|
||||||
function convertValueSpec(value: any): string {
|
|
||||||
switch (value.type) {
|
|
||||||
case 'string': {
|
|
||||||
if (value.textarea) {
|
|
||||||
return `${rangeToTodoComment(
|
|
||||||
value?.range,
|
|
||||||
)}Value.textarea(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
required: !(value.nullable || false),
|
|
||||||
default: value.default,
|
|
||||||
placeholder: value.placeholder || null,
|
|
||||||
minLength: null,
|
|
||||||
maxLength: null,
|
|
||||||
minRows: 3,
|
|
||||||
maxRows: 6,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)})`
|
|
||||||
}
|
|
||||||
return `${rangeToTodoComment(value?.range)}Value.text(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
default: value.default || null,
|
|
||||||
required: !value.nullable,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
masked: value.masked || false,
|
|
||||||
placeholder: value.placeholder || null,
|
|
||||||
inputmode: 'text',
|
|
||||||
patterns: value.pattern
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
regex: value.pattern,
|
|
||||||
description: value['pattern-description'],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
minLength: null,
|
|
||||||
maxLength: null,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)})`
|
|
||||||
}
|
|
||||||
case 'number': {
|
|
||||||
return `${rangeToTodoComment(
|
|
||||||
value?.range,
|
|
||||||
)}Value.number(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
default: value.default || null,
|
|
||||||
required: !value.nullable,
|
|
||||||
min: null,
|
|
||||||
max: null,
|
|
||||||
step: null,
|
|
||||||
integer: value.integral || false,
|
|
||||||
units: value.units || null,
|
|
||||||
placeholder: value.placeholder || null,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)})`
|
|
||||||
}
|
|
||||||
case 'boolean': {
|
|
||||||
return `Value.toggle(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
default: value.default || false,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)})`
|
|
||||||
}
|
|
||||||
case 'enum': {
|
|
||||||
const allValueNames = new Set([
|
|
||||||
...(value?.['values'] || []),
|
|
||||||
...Object.keys(value?.['value-names'] || {}),
|
|
||||||
])
|
|
||||||
const values = Object.fromEntries(
|
|
||||||
Array.from(allValueNames)
|
|
||||||
.filter(isString)
|
|
||||||
.map((key) => [key, value?.spec?.['value-names']?.[key] || key]),
|
|
||||||
)
|
|
||||||
return `Value.select(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
default: value.default,
|
|
||||||
values,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)} as const)`
|
|
||||||
}
|
|
||||||
case 'object': {
|
|
||||||
const specName = maybeNewConst(
|
|
||||||
value.name + '_spec',
|
|
||||||
convertInputSpec(value.spec),
|
|
||||||
)
|
|
||||||
return `Value.object({
|
|
||||||
name: ${JSON.stringify(value.name || null)},
|
|
||||||
description: ${JSON.stringify(value.description || null)},
|
|
||||||
}, ${specName})`
|
|
||||||
}
|
|
||||||
case 'union': {
|
|
||||||
const variants = maybeNewConst(
|
|
||||||
value.name + '_variants',
|
|
||||||
convertVariants(value.variants, value.tag['variant-names'] || {}),
|
|
||||||
)
|
|
||||||
|
|
||||||
return `Value.union({
|
|
||||||
name: ${JSON.stringify(value.name || null)},
|
|
||||||
description: ${JSON.stringify(value.tag.description || null)},
|
|
||||||
warning: ${JSON.stringify(value.tag.warning || null)},
|
|
||||||
default: ${JSON.stringify(value.default)},
|
|
||||||
variants: ${variants},
|
|
||||||
})`
|
|
||||||
}
|
|
||||||
case 'list': {
|
|
||||||
if (value.subtype === 'enum') {
|
|
||||||
const allValueNames = new Set([
|
|
||||||
...(value?.spec?.['values'] || []),
|
|
||||||
...Object.keys(value?.spec?.['value-names'] || {}),
|
|
||||||
])
|
|
||||||
const values = Object.fromEntries(
|
|
||||||
Array.from(allValueNames)
|
|
||||||
.filter(isString)
|
|
||||||
.map((key: string) => [
|
|
||||||
key,
|
|
||||||
value?.spec?.['value-names']?.[key] ?? key,
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
return `Value.multiselect(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
minLength: null,
|
|
||||||
maxLength: null,
|
|
||||||
default: value.default ?? null,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
values,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)})`
|
|
||||||
}
|
|
||||||
const list = maybeNewConst(value.name + '_list', convertList(value))
|
|
||||||
return `Value.list(${list})`
|
|
||||||
}
|
|
||||||
case 'pointer': {
|
|
||||||
return `/* TODO deal with point removed point "${value.name}" */null as any`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw Error(`Unknown type "${value.type}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertList(value: any) {
|
|
||||||
switch (value.subtype) {
|
|
||||||
case 'string': {
|
|
||||||
return `${rangeToTodoComment(value?.range)}List.text(${JSON.stringify(
|
|
||||||
{
|
|
||||||
name: value.name || null,
|
|
||||||
minLength: null,
|
|
||||||
maxLength: null,
|
|
||||||
default: value.default || null,
|
|
||||||
description: value.description || null,
|
|
||||||
warning: value.warning || null,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)}, ${JSON.stringify({
|
|
||||||
masked: value?.spec?.masked || false,
|
|
||||||
placeholder: value?.spec?.placeholder || null,
|
|
||||||
patterns: value?.spec?.pattern
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
regex: value.spec.pattern,
|
|
||||||
description: value?.spec?.['pattern-description'],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
minLength: null,
|
|
||||||
maxLength: null,
|
|
||||||
})})`
|
|
||||||
}
|
|
||||||
// case "number": {
|
|
||||||
// return `${rangeToTodoComment(value?.range)}List.number(${JSON.stringify(
|
|
||||||
// {
|
|
||||||
// name: value.name || null,
|
|
||||||
// minLength: null,
|
|
||||||
// maxLength: null,
|
|
||||||
// default: value.default || null,
|
|
||||||
// description: value.description || null,
|
|
||||||
// warning: value.warning || null,
|
|
||||||
// },
|
|
||||||
// null,
|
|
||||||
// 2,
|
|
||||||
// )}, ${JSON.stringify({
|
|
||||||
// integer: value?.spec?.integral || false,
|
|
||||||
// min: null,
|
|
||||||
// max: null,
|
|
||||||
// units: value?.spec?.units || null,
|
|
||||||
// placeholder: value?.spec?.placeholder || null,
|
|
||||||
// })})`
|
|
||||||
// }
|
|
||||||
case 'enum': {
|
|
||||||
return '/* error!! list.enum */'
|
|
||||||
}
|
|
||||||
case 'object': {
|
|
||||||
const specName = maybeNewConst(
|
|
||||||
value.name + '_spec',
|
|
||||||
convertInputSpec(value.spec.spec),
|
|
||||||
)
|
|
||||||
return `${rangeToTodoComment(value?.range)}List.obj({
|
|
||||||
name: ${JSON.stringify(value.name || null)},
|
|
||||||
minLength: ${JSON.stringify(null)},
|
|
||||||
maxLength: ${JSON.stringify(null)},
|
|
||||||
default: ${JSON.stringify(value.default || null)},
|
|
||||||
description: ${JSON.stringify(value.description || null)},
|
|
||||||
}, {
|
|
||||||
spec: ${specName},
|
|
||||||
displayAs: ${JSON.stringify(value?.spec?.['display-as'] || null)},
|
|
||||||
uniqueBy: ${JSON.stringify(value?.spec?.['unique-by'] || null)},
|
|
||||||
})`
|
|
||||||
}
|
|
||||||
case 'union': {
|
|
||||||
const variants = maybeNewConst(
|
|
||||||
value.name + '_variants',
|
|
||||||
convertVariants(
|
|
||||||
value.spec.variants,
|
|
||||||
value.spec['variant-names'] || {},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const unionValueName = maybeNewConst(
|
|
||||||
value.name + '_union',
|
|
||||||
`${rangeToTodoComment(value?.range)}
|
|
||||||
Value.union({
|
|
||||||
name: ${JSON.stringify(value?.spec?.tag?.name || null)},
|
|
||||||
description: ${JSON.stringify(
|
|
||||||
value?.spec?.tag?.description || null,
|
|
||||||
)},
|
|
||||||
warning: ${JSON.stringify(value?.spec?.tag?.warning || null)},
|
|
||||||
default: ${JSON.stringify(value?.spec?.default || null)},
|
|
||||||
variants: ${variants},
|
|
||||||
})
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
const listInputSpec = maybeNewConst(
|
|
||||||
value.name + '_list_inputSpec',
|
|
||||||
`
|
|
||||||
InputSpec.of({
|
|
||||||
"union": ${unionValueName}
|
|
||||||
})
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
return `${rangeToTodoComment(value?.range)}List.obj({
|
|
||||||
name:${JSON.stringify(value.name || null)},
|
|
||||||
minLength:${JSON.stringify(null)},
|
|
||||||
maxLength:${JSON.stringify(null)},
|
|
||||||
default: [],
|
|
||||||
description: ${JSON.stringify(value.description || null)},
|
|
||||||
warning: ${JSON.stringify(value.warning || null)},
|
|
||||||
}, {
|
|
||||||
spec: ${listInputSpec},
|
|
||||||
displayAs: ${JSON.stringify(value?.spec?.['display-as'] || null)},
|
|
||||||
uniqueBy: ${JSON.stringify(value?.spec?.['unique-by'] || null)},
|
|
||||||
})`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Unknown subtype "${value.subtype}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertVariants(
|
|
||||||
variants: Record<string, unknown>,
|
|
||||||
variantNames: Record<string, string>,
|
|
||||||
): string {
|
|
||||||
let answer = 'Variants.of({'
|
|
||||||
for (const [key, value] of Object.entries(variants)) {
|
|
||||||
const variantSpec = maybeNewConst(key, convertInputSpec(value))
|
|
||||||
answer += `"${key}": {name: "${
|
|
||||||
variantNames[key] || key
|
|
||||||
}", spec: ${variantSpec}},`
|
|
||||||
}
|
|
||||||
return `${answer}})`
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNextConstName(name: string, i = 0): string {
|
|
||||||
const newName = !i ? name : name + i
|
|
||||||
if (namedConsts.has(newName)) {
|
|
||||||
return getNextConstName(name, i + 1)
|
|
||||||
}
|
|
||||||
namedConsts.add(newName)
|
|
||||||
return newName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rangeToTodoComment(range: string | undefined) {
|
|
||||||
if (!range) return ''
|
|
||||||
return `/* TODO: Convert range for this value (${range})*/`
|
|
||||||
}
|
|
||||||
|
|
||||||
// oldSpecToBuilder(
|
|
||||||
// "./inputSpec.ts",
|
|
||||||
// // Put inputSpec here
|
|
||||||
// {},
|
|
||||||
// )
|
|
||||||
@@ -16,5 +16,5 @@
|
|||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["lib/**/*"],
|
"include": ["lib/**/*"],
|
||||||
"exclude": ["lib/**/*.spec.ts", "lib/**/*.gen.ts", "list", "node_modules"]
|
"exclude": ["lib/**/*.spec.ts", "lib/**/*.test.ts", "lib/**/*.gen.ts", "list", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
11
web/package-lock.json
generated
11
web/package-lock.json
generated
@@ -62,7 +62,6 @@
|
|||||||
"pbkdf2": "^3.1.2",
|
"pbkdf2": "^3.1.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"tldts": "^7.0.11",
|
"tldts": "^7.0.11",
|
||||||
"ts-matches": "^6.3.2",
|
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zone.js": "^0.15.0"
|
"zone.js": "^0.15.0"
|
||||||
@@ -126,8 +125,8 @@
|
|||||||
"deep-equality-data-structures": "^1.5.0",
|
"deep-equality-data-structures": "^1.5.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"yaml": "^2.7.1",
|
||||||
"yaml": "^2.7.1"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
@@ -11393,12 +11392,6 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-matches": {
|
|
||||||
"version": "6.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.5.0.tgz",
|
|
||||||
"integrity": "sha512-MhuobYhHYn6MlOTPAF/qk3tsRRioPac5ofYn68tc3rAJaGjsw1MsX1MOSep52DkvNJPgNV0F73zfgcQfYTVeyQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ts-morph": {
|
"node_modules/ts-morph": {
|
||||||
"version": "23.0.0",
|
"version": "23.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-23.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-23.0.0.tgz",
|
||||||
|
|||||||
@@ -86,7 +86,6 @@
|
|||||||
"pbkdf2": "^3.1.2",
|
"pbkdf2": "^3.1.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"tldts": "^7.0.11",
|
"tldts": "^7.0.11",
|
||||||
"ts-matches": "^6.3.2",
|
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zone.js": "^0.15.0"
|
"zone.js": "^0.15.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user