mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
chore: Add changes
This commit is contained in:
@@ -63,10 +63,16 @@ export class Config<A extends InputSpec> extends IBuilder<A> {
|
|||||||
static empty() {
|
static empty() {
|
||||||
return new Config({});
|
return new Config({});
|
||||||
}
|
}
|
||||||
static withValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
static withValue<K extends string, B extends ValueSpec>(
|
||||||
|
key: K,
|
||||||
|
value: Value<B>
|
||||||
|
) {
|
||||||
return Config.empty().withValue(key, value);
|
return Config.empty().withValue(key, value);
|
||||||
}
|
}
|
||||||
static addValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
static addValue<K extends string, B extends ValueSpec>(
|
||||||
|
key: K,
|
||||||
|
value: Value<B>
|
||||||
|
) {
|
||||||
return Config.empty().withValue(key, value);
|
return Config.empty().withValue(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { BuilderExtract, IBuilder } from "./builder";
|
import { BuilderExtract, IBuilder } from "./builder";
|
||||||
import { Config } from "./config";
|
import { Config } from "./config";
|
||||||
import { InputSpec, ListValueSpecNumber, ListValueSpecString, UniqueBy, ValueSpecList } from "../configTypes";
|
import {
|
||||||
|
InputSpec,
|
||||||
|
ListValueSpecNumber,
|
||||||
|
ListValueSpecString,
|
||||||
|
UniqueBy,
|
||||||
|
ValueSpecList,
|
||||||
|
} from "../configTypes";
|
||||||
import { guardAll } from "../../util";
|
import { guardAll } from "../../util";
|
||||||
/**
|
/**
|
||||||
* Used as a subtype of Value.list
|
* Used as a subtype of Value.list
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ const username = Value.string({
|
|||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Value<A extends ValueSpec> extends IBuilder<A> {
|
export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||||
static boolean(a: { name: string; description?: string | null; warning?: string | null; default?: boolean | null }) {
|
static boolean(a: {
|
||||||
|
name: string;
|
||||||
|
description?: string | null;
|
||||||
|
warning?: string | null;
|
||||||
|
default?: boolean | null;
|
||||||
|
}) {
|
||||||
return new Value({
|
return new Value({
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
@@ -161,7 +166,9 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
spec,
|
spec,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static union<V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>>(
|
static union<
|
||||||
|
V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>
|
||||||
|
>(
|
||||||
a: {
|
a: {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
|
|||||||
@@ -80,7 +80,10 @@ export class Variants<
|
|||||||
static empty() {
|
static empty() {
|
||||||
return Variants.of({});
|
return Variants.of({});
|
||||||
}
|
}
|
||||||
static withVariant<K extends string, B extends InputSpec>(key: K, value: Config<B>) {
|
static withVariant<K extends string, B extends InputSpec>(
|
||||||
|
key: K,
|
||||||
|
value: Config<B>
|
||||||
|
) {
|
||||||
return Variants.empty().withVariant(key, value);
|
return Variants.empty().withVariant(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ export type InputSpec = Record<string, WithOutOptionals<C.ValueSpec>>;
|
|||||||
export type ValueSpec = WithOutOptionals<C.ValueSpec>;
|
export type ValueSpec = WithOutOptionals<C.ValueSpec>;
|
||||||
|
|
||||||
/** core spec types. These types provide the metadata for performing validations */
|
/** core spec types. These types provide the metadata for performing validations */
|
||||||
export type ValueSpecOf<T extends ValueType> = WithOutOptionals<C.ValueSpecOf<T>>;
|
export type ValueSpecOf<T extends ValueType> = WithOutOptionals<
|
||||||
|
C.ValueSpecOf<T>
|
||||||
|
>;
|
||||||
|
|
||||||
export type ValueSpecString = WithOutOptionals<C.ValueSpecString>;
|
export type ValueSpecString = WithOutOptionals<C.ValueSpecString>;
|
||||||
export type ValueSpecTextarea = WithOutOptionals<C.ValueSpecTextarea>;
|
export type ValueSpecTextarea = WithOutOptionals<C.ValueSpecTextarea>;
|
||||||
@@ -27,11 +29,15 @@ export type SelectBase = WithOutOptionals<C.SelectBase>;
|
|||||||
export type ListValueSpecType = WithOutOptionals<C.ListValueSpecType>;
|
export type ListValueSpecType = WithOutOptionals<C.ListValueSpecType>;
|
||||||
|
|
||||||
/** represents a spec for the values of a list */
|
/** represents a spec for the values of a list */
|
||||||
export type ListValueSpecOf<T extends ListValueSpecType> = WithOutOptionals<C.ListValueSpecOf<T>>;
|
export type ListValueSpecOf<T extends ListValueSpecType> = WithOutOptionals<
|
||||||
|
C.ListValueSpecOf<T>
|
||||||
|
>;
|
||||||
|
|
||||||
/** represents a spec for a list */
|
/** represents a spec for a list */
|
||||||
export type ValueSpecList = WithOutOptionals<C.ValueSpecList>;
|
export type ValueSpecList = WithOutOptionals<C.ValueSpecList>;
|
||||||
export type ValueSpecListOf<T extends ListValueSpecType> = WithOutOptionals<C.ValueSpecListOf<T>>;
|
export type ValueSpecListOf<T extends ListValueSpecType> = WithOutOptionals<
|
||||||
|
C.ValueSpecListOf<T>
|
||||||
|
>;
|
||||||
|
|
||||||
// sometimes the type checker needs just a little bit of help
|
// sometimes the type checker needs just a little bit of help
|
||||||
export function isValueSpecListOf<S extends ListValueSpecType>(
|
export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ export type ListValueSpecOf<T extends ListValueSpecType> = T extends "string"
|
|||||||
: never;
|
: never;
|
||||||
/** represents a spec for a list */
|
/** represents a spec for a list */
|
||||||
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
|
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
|
||||||
export interface ValueSpecListOf<T extends ListValueSpecType> extends WithStandalone {
|
export interface ValueSpecListOf<T extends ListValueSpecType>
|
||||||
|
extends WithStandalone {
|
||||||
type: "list";
|
type: "list";
|
||||||
spec: ListValueSpecOf<T>;
|
spec: ListValueSpecOf<T>;
|
||||||
range?: string;
|
range?: string;
|
||||||
|
|||||||
@@ -37,18 +37,15 @@ async function timeoutHealth(
|
|||||||
* The stop function is used to stop the health check.
|
* The stop function is used to stop the health check.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function healthRunner(
|
export default function healthRunner(name: string, fn: HealthCheck) {
|
||||||
name: string,
|
|
||||||
fn: HealthCheck,
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* All values in seconds. Defaults):
|
* All values in seconds. Defaults):
|
||||||
*
|
*
|
||||||
* interval: 60s
|
* interval: 60s
|
||||||
*
|
*
|
||||||
* timeout: 10s
|
* timeout: 10s
|
||||||
*
|
*
|
||||||
* delay: 10s
|
* delay: 10s
|
||||||
*/
|
*/
|
||||||
create(
|
create(
|
||||||
|
|||||||
@@ -141,7 +141,10 @@ describe("values", () => {
|
|||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
|
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
|
||||||
type Test = typeof validator._TYPE;
|
type Test = typeof validator._TYPE;
|
||||||
testOutput<Test, { unionSelectKey: "a" } & { unionValueKey: { b: boolean } }>()(null);
|
testOutput<
|
||||||
|
Test,
|
||||||
|
{ unionSelectKey: "a" } & { unionValueKey: { b: boolean } }
|
||||||
|
>()(null);
|
||||||
});
|
});
|
||||||
test("list", () => {
|
test("list", () => {
|
||||||
const value = Value.list(
|
const value = Value.list(
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { ListValueSpecOf, ValueSpecList, isValueSpecListOf } from "../config/configTypes";
|
import {
|
||||||
|
ListValueSpecOf,
|
||||||
|
ValueSpecList,
|
||||||
|
isValueSpecListOf,
|
||||||
|
} from "../config/configTypes";
|
||||||
import { Config } from "../config/builder/config";
|
import { Config } from "../config/builder/config";
|
||||||
import { List } from "../config/builder/list";
|
import { List } from "../config/builder/list";
|
||||||
import { Value } from "../config/builder/value";
|
import { Value } from "../config/builder/value";
|
||||||
@@ -16,7 +20,9 @@ describe("Config Types", () => {
|
|||||||
} else if (isValueSpecListOf(someList, "object")) {
|
} else if (isValueSpecListOf(someList, "object")) {
|
||||||
someList.spec satisfies ListValueSpecOf<"object">;
|
someList.spec satisfies ListValueSpecOf<"object">;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Failed to figure out the type: " + JSON.stringify(someList));
|
throw new Error(
|
||||||
|
"Failed to figure out the type: " + JSON.stringify(someList)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ writeConvertedFile(
|
|||||||
tag: {
|
tag: {
|
||||||
id: "type",
|
id: "type",
|
||||||
name: "Type",
|
name: "Type",
|
||||||
description: "- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n",
|
description:
|
||||||
|
"- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n",
|
||||||
"variant-names": {
|
"variant-names": {
|
||||||
lnd: "Lightning Network Daemon (LND)",
|
lnd: "Lightning Network Daemon (LND)",
|
||||||
"c-lightning": "Core Lightning (CLN)",
|
"c-lightning": "Core Lightning (CLN)",
|
||||||
@@ -57,7 +58,8 @@ writeConvertedFile(
|
|||||||
default: "bitcoin",
|
default: "bitcoin",
|
||||||
masked: true,
|
masked: true,
|
||||||
pattern: "^[a-zA-Z0-9_]+$",
|
pattern: "^[a-zA-Z0-9_]+$",
|
||||||
"pattern-description": "Must be alphanumeric (can contain underscore).",
|
"pattern-description":
|
||||||
|
"Must be alphanumeric (can contain underscore).",
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -69,7 +71,8 @@ writeConvertedFile(
|
|||||||
len: 20,
|
len: 20,
|
||||||
},
|
},
|
||||||
pattern: '^[^\\n"]*$',
|
pattern: '^[^\\n"]*$',
|
||||||
"pattern-description": "Must not contain newline or quote characters.",
|
"pattern-description":
|
||||||
|
"Must not contain newline or quote characters.",
|
||||||
copyable: true,
|
copyable: true,
|
||||||
masked: true,
|
masked: true,
|
||||||
},
|
},
|
||||||
@@ -81,7 +84,8 @@ writeConvertedFile(
|
|||||||
default: "bitcoin",
|
default: "bitcoin",
|
||||||
masked: true,
|
masked: true,
|
||||||
pattern: "^[a-zA-Z0-9_]+$",
|
pattern: "^[a-zA-Z0-9_]+$",
|
||||||
"pattern-description": "Must be alphanumeric (can contain underscore).",
|
"pattern-description":
|
||||||
|
"Must be alphanumeric (can contain underscore).",
|
||||||
textarea: true,
|
textarea: true,
|
||||||
},
|
},
|
||||||
advanced: {
|
advanced: {
|
||||||
@@ -97,15 +101,18 @@ writeConvertedFile(
|
|||||||
subtype: "string",
|
subtype: "string",
|
||||||
default: [],
|
default: [],
|
||||||
spec: {
|
spec: {
|
||||||
pattern: "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
|
pattern:
|
||||||
"pattern-description": 'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
|
"^[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,
|
masked: false,
|
||||||
},
|
},
|
||||||
range: "[0,*)",
|
range: "[0,*)",
|
||||||
},
|
},
|
||||||
serialversion: {
|
serialversion: {
|
||||||
name: "Serialization Version",
|
name: "Serialization Version",
|
||||||
description: "Return raw transaction or block hex with Segwit or non-SegWit serialization.",
|
description:
|
||||||
|
"Return raw transaction or block hex with Segwit or non-SegWit serialization.",
|
||||||
type: "enum",
|
type: "enum",
|
||||||
values: ["non-segwit", "segwit"],
|
values: ["non-segwit", "segwit"],
|
||||||
"value-names": {},
|
"value-names": {},
|
||||||
@@ -113,7 +120,8 @@ writeConvertedFile(
|
|||||||
},
|
},
|
||||||
servertimeout: {
|
servertimeout: {
|
||||||
name: "Rpc Server Timeout",
|
name: "Rpc Server Timeout",
|
||||||
description: "Number of seconds after which an uncompleted RPC call will time out.",
|
description:
|
||||||
|
"Number of seconds after which an uncompleted RPC call will time out.",
|
||||||
type: "number",
|
type: "number",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
range: "[5,300]",
|
range: "[5,300]",
|
||||||
@@ -216,7 +224,8 @@ writeConvertedFile(
|
|||||||
type: "number",
|
type: "number",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
name: "Max Mempool Size",
|
name: "Max Mempool Size",
|
||||||
description: "Keep the transaction memory pool below <n> megabytes.",
|
description:
|
||||||
|
"Keep the transaction memory pool below <n> megabytes.",
|
||||||
range: "[1,*)",
|
range: "[1,*)",
|
||||||
integral: true,
|
integral: true,
|
||||||
units: "MiB",
|
units: "MiB",
|
||||||
@@ -226,7 +235,8 @@ writeConvertedFile(
|
|||||||
type: "number",
|
type: "number",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
name: "Mempool Expiration",
|
name: "Mempool Expiration",
|
||||||
description: "Do not keep transactions in the mempool longer than <n> hours.",
|
description:
|
||||||
|
"Do not keep transactions in the mempool longer than <n> hours.",
|
||||||
range: "[1,*)",
|
range: "[1,*)",
|
||||||
integral: true,
|
integral: true,
|
||||||
units: "Hr",
|
units: "Hr",
|
||||||
@@ -242,7 +252,8 @@ writeConvertedFile(
|
|||||||
listen: {
|
listen: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
name: "Make Public",
|
name: "Make Public",
|
||||||
description: "Allow other nodes to find your server on the network.",
|
description:
|
||||||
|
"Allow other nodes to find your server on the network.",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
onlyconnect: {
|
onlyconnect: {
|
||||||
@@ -282,7 +293,8 @@ writeConvertedFile(
|
|||||||
type: "number",
|
type: "number",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
name: "Port",
|
name: "Port",
|
||||||
description: "Port that peer is listening on for inbound p2p connections",
|
description:
|
||||||
|
"Port that peer is listening on for inbound p2p connections",
|
||||||
range: "[0,65535]",
|
range: "[0,65535]",
|
||||||
integral: true,
|
integral: true,
|
||||||
},
|
},
|
||||||
@@ -306,7 +318,8 @@ writeConvertedFile(
|
|||||||
pruning: {
|
pruning: {
|
||||||
type: "union",
|
type: "union",
|
||||||
name: "Pruning Settings",
|
name: "Pruning Settings",
|
||||||
description: "Blockchain Pruning Options\nReduce the blockchain size on disk\n",
|
description:
|
||||||
|
"Blockchain Pruning Options\nReduce the blockchain size on disk\n",
|
||||||
warning:
|
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",
|
"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: {
|
tag: {
|
||||||
@@ -328,7 +341,8 @@ writeConvertedFile(
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
name: "Max Chain Size",
|
name: "Max Chain Size",
|
||||||
description: "Limit of blockchain size on disk.",
|
description: "Limit of blockchain size on disk.",
|
||||||
warning: "Increasing this value will require re-syncing your node.",
|
warning:
|
||||||
|
"Increasing this value will require re-syncing your node.",
|
||||||
default: 550,
|
default: 550,
|
||||||
range: "[550,1000000)",
|
range: "[550,1000000)",
|
||||||
integral: true,
|
integral: true,
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { Parser } from "ts-matches";
|
import { Parser } from "ts-matches";
|
||||||
import { UnionSelectKey, unionSelectKey, UnionValueKey, unionValueKey } from "../config/configTypes";
|
import {
|
||||||
|
UnionSelectKey,
|
||||||
|
unionSelectKey,
|
||||||
|
UnionValueKey,
|
||||||
|
unionValueKey,
|
||||||
|
} from "../config/configTypes";
|
||||||
import { InputSpec, matchInputSpec, testListUnion } from "./output";
|
import { InputSpec, matchInputSpec, testListUnion } from "./output";
|
||||||
|
|
||||||
export type IfEquals<T, U, Y = unknown, N = never> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2
|
export type IfEquals<T, U, Y = unknown, N = never> = (<G>() => G extends T
|
||||||
|
? 1
|
||||||
|
: 2) extends <G>() => G extends U ? 1 : 2
|
||||||
? Y
|
? Y
|
||||||
: N;
|
: N;
|
||||||
export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
|
export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
|
||||||
@@ -12,7 +19,11 @@ export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
|
|||||||
function isObject(item: unknown): item is object {
|
function isObject(item: unknown): item is object {
|
||||||
return !!(item && typeof item === "object" && !Array.isArray(item));
|
return !!(item && typeof item === "object" && !Array.isArray(item));
|
||||||
}
|
}
|
||||||
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;
|
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (
|
||||||
|
x: infer R
|
||||||
|
) => any
|
||||||
|
? R
|
||||||
|
: never;
|
||||||
export function mergeDeep<A extends unknown[]>(...sources: A) {
|
export function mergeDeep<A extends unknown[]>(...sources: A) {
|
||||||
return _mergeDeep({}, ...sources);
|
return _mergeDeep({}, ...sources);
|
||||||
}
|
}
|
||||||
@@ -44,11 +55,21 @@ testOutput<InputSpec["rpc"]["enable"], boolean>()(null);
|
|||||||
testOutput<InputSpec["rpc"]["username"], string>()(null);
|
testOutput<InputSpec["rpc"]["username"], string>()(null);
|
||||||
|
|
||||||
testOutput<InputSpec["rpc"]["advanced"]["auth"], string[]>()(null);
|
testOutput<InputSpec["rpc"]["advanced"]["auth"], string[]>()(null);
|
||||||
testOutput<InputSpec["rpc"]["advanced"]["serialversion"], "segwit" | "non-segwit">()(null);
|
testOutput<
|
||||||
|
InputSpec["rpc"]["advanced"]["serialversion"],
|
||||||
|
"segwit" | "non-segwit"
|
||||||
|
>()(null);
|
||||||
testOutput<InputSpec["rpc"]["advanced"]["servertimeout"], number>()(null);
|
testOutput<InputSpec["rpc"]["advanced"]["servertimeout"], number>()(null);
|
||||||
testOutput<InputSpec["advanced"]["peers"]["addnode"][0]["hostname"], string>()(null);
|
testOutput<InputSpec["advanced"]["peers"]["addnode"][0]["hostname"], string>()(
|
||||||
testOutput<InputSpec["testListUnion"][0]["union"][UnionValueKey]["name"], string>()(null);
|
null
|
||||||
testOutput<InputSpec["testListUnion"][0]["union"][UnionSelectKey], "lnd">()(null);
|
);
|
||||||
|
testOutput<
|
||||||
|
InputSpec["testListUnion"][0]["union"][UnionValueKey]["name"],
|
||||||
|
string
|
||||||
|
>()(null);
|
||||||
|
testOutput<InputSpec["testListUnion"][0]["union"][UnionSelectKey], "lnd">()(
|
||||||
|
null
|
||||||
|
);
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
// @ts-expect-error Expect that the string is the one above
|
// @ts-expect-error Expect that the string is the one above
|
||||||
testOutput<InputSpec["testListUnion"][0][UnionSelectKey][UnionSelectKey], "unionSelectKey">()(null);
|
testOutput<InputSpec["testListUnion"][0][UnionSelectKey][UnionSelectKey], "unionSelectKey">()(null);
|
||||||
@@ -117,9 +138,13 @@ describe("Inputs", () => {
|
|||||||
});
|
});
|
||||||
test("test errors", () => {
|
test("test errors", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { advanced: { threads: 0 } } }))
|
matchInputSpec.unsafeCast(
|
||||||
|
mergeDeep(validInput, { rpc: { advanced: { threads: 0 } } })
|
||||||
|
)
|
||||||
|
).toThrowError();
|
||||||
|
expect(() =>
|
||||||
|
matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { enable: 2 } }))
|
||||||
).toThrowError();
|
).toThrowError();
|
||||||
expect(() => matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { enable: 2 } }))).toThrowError();
|
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
matchInputSpec.unsafeCast(
|
matchInputSpec.unsafeCast(
|
||||||
|
|||||||
111
lib/types.ts
111
lib/types.ts
@@ -4,24 +4,39 @@ import { InputSpec } from "./config/configTypes";
|
|||||||
export namespace ExpectedExports {
|
export namespace ExpectedExports {
|
||||||
version: 1;
|
version: 1;
|
||||||
/** Set configuration is called after we have modified and saved the configuration in the embassy ui. Use this to make a file for the docker to read from for configuration. */
|
/** Set configuration is called after we have modified and saved the configuration in the embassy ui. Use this to make a file for the docker to read from for configuration. */
|
||||||
export type setConfig = (options: { effects: Effects; input: Record<string, unknown> }) => Promise<unknown>;
|
export type setConfig = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
input: Record<string, unknown>;
|
||||||
|
}) => Promise<unknown>;
|
||||||
/** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */
|
/** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */
|
||||||
export type getConfig = (options: { effects: Effects; config: unknown }) => Promise<ConfigRes>;
|
export type getConfig = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
config: unknown;
|
||||||
|
}) => Promise<ConfigRes>;
|
||||||
/** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
/** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
||||||
export type dependencies = Dependencies;
|
export type dependencies = Dependencies;
|
||||||
/** For backing up service data though the embassyOS UI */
|
/** For backing up service data though the embassyOS UI */
|
||||||
export type createBackup = (options: { effects: Effects }) => Promise<unknown>;
|
export type createBackup = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
}) => Promise<unknown>;
|
||||||
/** For restoring service data that was previously backed up using the embassyOS UI create backup flow. Backup restores are also triggered via the embassyOS UI, or doing a system restore flow during setup. */
|
/** For restoring service data that was previously backed up using the embassyOS UI create backup flow. Backup restores are also triggered via the embassyOS UI, or doing a system restore flow during setup. */
|
||||||
export type restoreBackup = (options: { effects: Effects }) => Promise<unknown>;
|
export type restoreBackup = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
}) => Promise<unknown>;
|
||||||
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
|
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
|
||||||
export type properties = (options: { effects: Effects }) => Promise<Properties>;
|
export type properties = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
}) => Promise<Properties>;
|
||||||
|
|
||||||
/** Health checks are used to determine if the service is working properly after starting
|
/** Health checks are used to determine if the service is working properly after starting
|
||||||
* A good use case is if we are using a web server, seeing if we can get to the web server.
|
* A good use case is if we are using a web server, seeing if we can get to the web server.
|
||||||
*/
|
*/
|
||||||
export type health = {
|
export type health = {
|
||||||
/** Should be the health check id */
|
/** Should be the health check id */
|
||||||
[id: string]: (options: { effects: Effects; input: TimeMs }) => Promise<unknown>;
|
[id: string]: (options: {
|
||||||
|
effects: Effects;
|
||||||
|
input: TimeMs;
|
||||||
|
}) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,24 +45,36 @@ export namespace ExpectedExports {
|
|||||||
* service starting, and that file would indicate that it would rescan all the data.
|
* service starting, and that file would indicate that it would rescan all the data.
|
||||||
*/
|
*/
|
||||||
export type action = {
|
export type action = {
|
||||||
[id: string]: (options: { effects: Effects; input?: Record<string, unknown> }) => Promise<ActionResult>;
|
[id: string]: (options: {
|
||||||
|
effects: Effects;
|
||||||
|
input?: Record<string, unknown>;
|
||||||
|
}) => Promise<ActionResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the entrypoint for the main container. Used to start up something like the service that the
|
* This is the entrypoint for the main container. Used to start up something like the service that the
|
||||||
* package represents, like running a bitcoind in a bitcoind-wrapper.
|
* package represents, like running a bitcoind in a bitcoind-wrapper.
|
||||||
*/
|
*/
|
||||||
export type main = (options: { effects: Effects; started(onTerm: () => void): null }) => Promise<unknown>;
|
export type main = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
started(onTerm: () => void): null;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every time a package completes an install, this function is called before the main.
|
* Every time a package completes an install, this function is called before the main.
|
||||||
* Can be used to do migration like things.
|
* Can be used to do migration like things.
|
||||||
*/
|
*/
|
||||||
export type init = (options: { effects: Effects; previousVersion: null | string }) => Promise<unknown>;
|
export type init = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
previousVersion: null | string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
/** This will be ran during any time a package is uninstalled, for example during a update
|
/** This will be ran during any time a package is uninstalled, for example during a update
|
||||||
* this will be called.
|
* this will be called.
|
||||||
*/
|
*/
|
||||||
export type uninit = (options: { effects: Effects; nextVersion: null | string }) => Promise<unknown>;
|
export type uninit = (options: {
|
||||||
|
effects: Effects;
|
||||||
|
nextVersion: null | string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
export type TimeMs = number;
|
export type TimeMs = number;
|
||||||
export type VersionString = string;
|
export type VersionString = string;
|
||||||
@@ -61,7 +88,11 @@ export type ConfigRes = {
|
|||||||
/** Used to reach out from the pure js runtime */
|
/** Used to reach out from the pure js runtime */
|
||||||
export type Effects = {
|
export type Effects = {
|
||||||
/** Usable when not sandboxed */
|
/** Usable when not sandboxed */
|
||||||
writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise<void>;
|
writeFile(input: {
|
||||||
|
path: string;
|
||||||
|
volumeId: string;
|
||||||
|
toWrite: string;
|
||||||
|
}): Promise<void>;
|
||||||
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
||||||
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
||||||
/** Create a directory. Usable when not sandboxed */
|
/** Create a directory. Usable when not sandboxed */
|
||||||
@@ -73,13 +104,27 @@ export type Effects = {
|
|||||||
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
||||||
|
|
||||||
/** Write a json file into an object. Usable when not sandboxed */
|
/** Write a json file into an object. Usable when not sandboxed */
|
||||||
writeJsonFile(input: { volumeId: string; path: string; toWrite: Record<string, unknown> }): Promise<void>;
|
writeJsonFile(input: {
|
||||||
|
volumeId: string;
|
||||||
|
path: string;
|
||||||
|
toWrite: Record<string, unknown>;
|
||||||
|
}): Promise<void>;
|
||||||
|
|
||||||
/** Read a json file into an object */
|
/** Read a json file into an object */
|
||||||
readJsonFile(input: { volumeId: string; path: string }): Promise<Record<string, unknown>>;
|
readJsonFile(input: {
|
||||||
|
volumeId: string;
|
||||||
|
path: string;
|
||||||
|
}): Promise<Record<string, unknown>>;
|
||||||
|
|
||||||
shell(command: string, options?: { timeoutMillis?: number | null }): Promise<string>;
|
shell(
|
||||||
runCommand(input: { command: string; args?: string[]; timeoutMillis?: number }): Promise<string>;
|
command: string,
|
||||||
|
options?: { timeoutMillis?: number | null }
|
||||||
|
): Promise<string>;
|
||||||
|
runCommand(input: {
|
||||||
|
command: string;
|
||||||
|
args?: string[];
|
||||||
|
timeoutMillis?: number;
|
||||||
|
}): Promise<string>;
|
||||||
runDaemon(input: { command: string; args?: string[] }): {
|
runDaemon(input: { command: string; args?: string[] }): {
|
||||||
wait(): Promise<string>;
|
wait(): Promise<string>;
|
||||||
term(): Promise<void>;
|
term(): Promise<void>;
|
||||||
@@ -109,9 +154,17 @@ export type Effects = {
|
|||||||
/** Check that a file exists or not */
|
/** Check that a file exists or not */
|
||||||
exists(input: { volumeId: string; path: string }): Promise<boolean>;
|
exists(input: { volumeId: string; path: string }): Promise<boolean>;
|
||||||
/** Declaring that we are opening a interface on some protocal for local network */
|
/** Declaring that we are opening a interface on some protocal for local network */
|
||||||
bindLocal(options: { internalPort: number; name: string; externalPort: number }): Promise<string>;
|
bindLocal(options: {
|
||||||
|
internalPort: number;
|
||||||
|
name: string;
|
||||||
|
externalPort: number;
|
||||||
|
}): Promise<string>;
|
||||||
/** Declaring that we are opening a interface on some protocal for tor network */
|
/** Declaring that we are opening a interface on some protocal for tor network */
|
||||||
bindTor(options: { internalPort: number; name: string; externalPort: number }): Promise<string>;
|
bindTor(options: {
|
||||||
|
internalPort: number;
|
||||||
|
name: string;
|
||||||
|
externalPort: number;
|
||||||
|
}): Promise<string>;
|
||||||
|
|
||||||
/** Similar to the fetch api via the mdn, this is simplified but the point is
|
/** Similar to the fetch api via the mdn, this is simplified but the point is
|
||||||
* to get something from some website, and return the response.
|
* to get something from some website, and return the response.
|
||||||
@@ -159,13 +212,22 @@ export type Effects = {
|
|||||||
}): Promise<unknown>;
|
}): Promise<unknown>;
|
||||||
|
|
||||||
/** Get the address for another service for local internet*/
|
/** Get the address for another service for local internet*/
|
||||||
getServiceLocalAddress(options: { packageId: string; interfaceName: string }): Promise<string>;
|
getServiceLocalAddress(options: {
|
||||||
|
packageId: string;
|
||||||
|
interfaceName: string;
|
||||||
|
}): Promise<string>;
|
||||||
/** Get the address for another service for tor interfaces */
|
/** Get the address for another service for tor interfaces */
|
||||||
getServiceTorAddress(options: { packageId: string; interfaceName: string }): Promise<string>;
|
getServiceTorAddress(options: {
|
||||||
|
packageId: string;
|
||||||
|
interfaceName: string;
|
||||||
|
}): Promise<string>;
|
||||||
/**
|
/**
|
||||||
* Get the port address for another service
|
* Get the port address for another service
|
||||||
*/
|
*/
|
||||||
getServicePortForward(options: { packageId: string; internalPort: number }): Promise<string>;
|
getServicePortForward(options: {
|
||||||
|
packageId: string;
|
||||||
|
internalPort: number;
|
||||||
|
}): Promise<string>;
|
||||||
|
|
||||||
/** When we want to create a link in the front end interfaces, and example is
|
/** When we want to create a link in the front end interfaces, and example is
|
||||||
* exposing a url to view a web service
|
* exposing a url to view a web service
|
||||||
@@ -221,7 +283,10 @@ export type Effects = {
|
|||||||
*
|
*
|
||||||
* @returns PEM encoded fullchain (ecdsa)
|
* @returns PEM encoded fullchain (ecdsa)
|
||||||
*/
|
*/
|
||||||
getSslCertificate: (packageId: string, algorithm?: "ecdsa" | "ed25519") => [string, string, string];
|
getSslCertificate: (
|
||||||
|
packageId: string,
|
||||||
|
algorithm?: "ecdsa" | "ed25519"
|
||||||
|
) => [string, string, string];
|
||||||
/**
|
/**
|
||||||
* @returns PEM encoded ssl key (ecdsa)
|
* @returns PEM encoded ssl key (ecdsa)
|
||||||
*/
|
*/
|
||||||
@@ -349,4 +414,6 @@ export type Dependencies = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends {} ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
|
export type DeepPartial<T> = T extends {}
|
||||||
|
? { [P in keyof T]?: DeepPartial<T[P]> }
|
||||||
|
: T;
|
||||||
|
|||||||
@@ -10,7 +10,17 @@ import {
|
|||||||
ValueSpec as ValueSpecAny,
|
ValueSpec as ValueSpecAny,
|
||||||
} from "../config/configTypes";
|
} from "../config/configTypes";
|
||||||
|
|
||||||
const { string, some, arrayOf, object, dictionary, unknown, number, literals, boolean } = matches;
|
const {
|
||||||
|
string,
|
||||||
|
some,
|
||||||
|
arrayOf,
|
||||||
|
object,
|
||||||
|
dictionary,
|
||||||
|
unknown,
|
||||||
|
number,
|
||||||
|
literals,
|
||||||
|
boolean,
|
||||||
|
} = matches;
|
||||||
|
|
||||||
type TypeBoolean = "boolean";
|
type TypeBoolean = "boolean";
|
||||||
type TypeString = "string";
|
type TypeString = "string";
|
||||||
@@ -132,18 +142,28 @@ function charRange(value = "") {
|
|||||||
* @param param1
|
* @param param1
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function generateDefault(generate: { charset: string; len: number }, { random = () => Math.random() } = {}) {
|
export function generateDefault(
|
||||||
const validCharSets: number[][] = generate.charset.split(",").map(charRange).filter(Array.isArray);
|
generate: { charset: string; len: number },
|
||||||
|
{ random = () => Math.random() } = {}
|
||||||
|
) {
|
||||||
|
const validCharSets: number[][] = generate.charset
|
||||||
|
.split(",")
|
||||||
|
.map(charRange)
|
||||||
|
.filter(Array.isArray);
|
||||||
if (validCharSets.length === 0) {
|
if (validCharSets.length === 0) {
|
||||||
throw new Error("Expecing that we have a valid charset");
|
throw new Error("Expecing that we have a valid charset");
|
||||||
}
|
}
|
||||||
const max = validCharSets.reduce((acc, x) => x.reduce((x, y) => Math.max(x, y), acc), 0);
|
const max = validCharSets.reduce(
|
||||||
|
(acc, x) => x.reduce((x, y) => Math.max(x, y), acc),
|
||||||
|
0
|
||||||
|
);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const answer: string[] = Array(generate.len);
|
const answer: string[] = Array(generate.len);
|
||||||
while (i < generate.len) {
|
while (i < generate.len) {
|
||||||
const nextValue = Math.round(random() * max);
|
const nextValue = Math.round(random() * max);
|
||||||
const inRange = validCharSets.reduce(
|
const inRange = validCharSets.reduce(
|
||||||
(acc, [lower, upper]) => acc || (nextValue >= lower && nextValue <= upper),
|
(acc, [lower, upper]) =>
|
||||||
|
acc || (nextValue >= lower && nextValue <= upper),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
if (!inRange) continue;
|
if (!inRange) continue;
|
||||||
@@ -160,8 +180,16 @@ export function matchNumberWithRange(range: string) {
|
|||||||
|
|
||||||
return number
|
return number
|
||||||
.validate(
|
.validate(
|
||||||
leftValue === "*" ? (_) => true : left === "[" ? (x) => x >= Number(leftValue) : (x) => x > Number(leftValue),
|
leftValue === "*"
|
||||||
leftValue === "*" ? "any" : left === "[" ? `greaterThanOrEqualTo${leftValue}` : `greaterThan${leftValue}`
|
? (_) => true
|
||||||
|
: left === "["
|
||||||
|
? (x) => x >= Number(leftValue)
|
||||||
|
: (x) => x > Number(leftValue),
|
||||||
|
leftValue === "*"
|
||||||
|
? "any"
|
||||||
|
: left === "["
|
||||||
|
? `greaterThanOrEqualTo${leftValue}`
|
||||||
|
: `greaterThan${leftValue}`
|
||||||
)
|
)
|
||||||
.validate(
|
.validate(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -193,7 +221,9 @@ const isGenerator = object({
|
|||||||
function defaultRequired<A>(parser: Parser<unknown, A>, value: unknown) {
|
function defaultRequired<A>(parser: Parser<unknown, A>, value: unknown) {
|
||||||
if (matchDefault.test(value)) {
|
if (matchDefault.test(value)) {
|
||||||
if (isGenerator(value.default)) {
|
if (isGenerator(value.default)) {
|
||||||
return parser.defaultTo(parser.unsafeCast(generateDefault(value.default)));
|
return parser.defaultTo(
|
||||||
|
parser.unsafeCast(generateDefault(value.default))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return parser.defaultTo(value.default);
|
return parser.defaultTo(value.default);
|
||||||
}
|
}
|
||||||
@@ -209,7 +239,9 @@ function defaultRequired<A>(parser: Parser<unknown, A>, value: unknown) {
|
|||||||
* @param value
|
* @param value
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function guardAll<A extends ValueSpecAny>(value: A): Parser<unknown, GuardAll<A>> {
|
export function guardAll<A extends ValueSpecAny>(
|
||||||
|
value: A
|
||||||
|
): Parser<unknown, GuardAll<A>> {
|
||||||
if (!isType.test(value)) {
|
if (!isType.test(value)) {
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
}
|
}
|
||||||
@@ -224,7 +256,10 @@ export function guardAll<A extends ValueSpecAny>(value: A): Parser<unknown, Guar
|
|||||||
return defaultRequired(string, value) as any;
|
return defaultRequired(string, value) as any;
|
||||||
|
|
||||||
case "number":
|
case "number":
|
||||||
return defaultRequired(withIntegral(withRange(value), value), value) as any;
|
return defaultRequired(
|
||||||
|
withIntegral(withRange(value), value),
|
||||||
|
value
|
||||||
|
) as any;
|
||||||
|
|
||||||
case "object":
|
case "object":
|
||||||
if (matchSpec.test(value)) {
|
if (matchSpec.test(value)) {
|
||||||
@@ -234,29 +269,44 @@ export function guardAll<A extends ValueSpecAny>(value: A): Parser<unknown, Guar
|
|||||||
|
|
||||||
case "list": {
|
case "list": {
|
||||||
const spec = (matchSpec.test(value) && value.spec) || {};
|
const spec = (matchSpec.test(value) && value.spec) || {};
|
||||||
const rangeValidate = (matchRange.test(value) && matchNumberWithRange(value.range).test) || (() => true);
|
const rangeValidate =
|
||||||
|
(matchRange.test(value) && matchNumberWithRange(value.range).test) ||
|
||||||
|
(() => true);
|
||||||
|
|
||||||
return defaultRequired(
|
return defaultRequired(
|
||||||
matches.arrayOf(guardAll(spec as any)).validate((x) => rangeValidate(x.length), "valid length"),
|
matches
|
||||||
|
.arrayOf(guardAll(spec as any))
|
||||||
|
.validate((x) => rangeValidate(x.length), "valid length"),
|
||||||
value
|
value
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
case "select":
|
case "select":
|
||||||
if (matchValues.test(value)) {
|
if (matchValues.test(value)) {
|
||||||
const valueKeys = Object.keys(value.values);
|
const valueKeys = Object.keys(value.values);
|
||||||
return defaultRequired(literals(valueKeys[0], ...valueKeys), value) as any;
|
return defaultRequired(
|
||||||
|
literals(valueKeys[0], ...valueKeys),
|
||||||
|
value
|
||||||
|
) as any;
|
||||||
}
|
}
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
|
|
||||||
case "multiselect":
|
case "multiselect":
|
||||||
if (matchValues.test(value)) {
|
if (matchValues.test(value)) {
|
||||||
const maybeAddRangeValidate = <X extends Validator<unknown, B[]>, B>(x: X) => {
|
const maybeAddRangeValidate = <X extends Validator<unknown, B[]>, B>(
|
||||||
|
x: X
|
||||||
|
) => {
|
||||||
if (!matchRange.test(value)) return x;
|
if (!matchRange.test(value)) return x;
|
||||||
return x.validate((x) => matchNumberWithRange(value.range).test(x.length), "validLength");
|
return x.validate(
|
||||||
|
(x) => matchNumberWithRange(value.range).test(x.length),
|
||||||
|
"validLength"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const valueKeys = Object.keys(value.values);
|
const valueKeys = Object.keys(value.values);
|
||||||
return defaultRequired(maybeAddRangeValidate(arrayOf(literals(valueKeys[0], ...valueKeys))), value) as any;
|
return defaultRequired(
|
||||||
|
maybeAddRangeValidate(arrayOf(literals(valueKeys[0], ...valueKeys))),
|
||||||
|
value
|
||||||
|
) as any;
|
||||||
}
|
}
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
|
|
||||||
@@ -286,9 +336,16 @@ export function guardAll<A extends ValueSpecAny>(value: A): Parser<unknown, Guar
|
|||||||
* @param valueDictionary
|
* @param valueDictionary
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function typeFromProps<A extends InputSpec>(valueDictionary: A): Parser<unknown, TypeFromProps<A>> {
|
export function typeFromProps<A extends InputSpec>(
|
||||||
|
valueDictionary: A
|
||||||
|
): Parser<unknown, TypeFromProps<A>> {
|
||||||
if (!recordString.test(valueDictionary)) return unknown as any;
|
if (!recordString.test(valueDictionary)) return unknown as any;
|
||||||
return object(
|
return object(
|
||||||
Object.fromEntries(Object.entries(valueDictionary).map(([key, value]) => [key, guardAll(value)]))
|
Object.fromEntries(
|
||||||
|
Object.entries(valueDictionary).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
guardAll(value),
|
||||||
|
])
|
||||||
|
)
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "start-sdk",
|
"name": "start-sdk",
|
||||||
"version": "0.4.0-lib0.beta9",
|
"version": "0.4.0-lib0.charlie1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "start-sdk",
|
"name": "start-sdk",
|
||||||
"version": "0.4.0-lib0.beta9",
|
"version": "0.4.0-lib0.charlie1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ export async function writeConvertedFile(
|
|||||||
inputData: Promise<any> | any,
|
inputData: Promise<any> | any,
|
||||||
options: Parameters<typeof makeFileContent>[1]
|
options: Parameters<typeof makeFileContent>[1]
|
||||||
) {
|
) {
|
||||||
await fs.writeFile(file, await makeFileContent(inputData, options), (err) => console.error(err));
|
await fs.writeFile(file, await makeFileContent(inputData, options), (err) =>
|
||||||
|
console.error(err)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function makeFileContent(inputData: Promise<any> | any, { startSdk = "start-sdk" } = {}) {
|
export default async function makeFileContent(
|
||||||
|
inputData: Promise<any> | any,
|
||||||
|
{ startSdk = "start-sdk" } = {}
|
||||||
|
) {
|
||||||
const outputLines: string[] = [];
|
const outputLines: string[] = [];
|
||||||
outputLines.push(`
|
outputLines.push(`
|
||||||
import {Config, Value, List, Variants} from '${startSdk}/config/builder';
|
import {Config, Value, List, Variants} from '${startSdk}/config/builder';
|
||||||
@@ -20,8 +25,13 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
|
|
||||||
const namedConsts = new Set(["Config", "Value", "List"]);
|
const namedConsts = new Set(["Config", "Value", "List"]);
|
||||||
const configName = newConst("InputSpec", convertInputSpec(data));
|
const configName = newConst("InputSpec", convertInputSpec(data));
|
||||||
const configMatcherName = newConst("matchInputSpec", `${configName}.validator()`);
|
const configMatcherName = newConst(
|
||||||
outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`);
|
"matchInputSpec",
|
||||||
|
`${configName}.validator()`
|
||||||
|
);
|
||||||
|
outputLines.push(
|
||||||
|
`export type InputSpec = typeof ${configMatcherName}._TYPE;`
|
||||||
|
);
|
||||||
|
|
||||||
return outputLines.join("\n");
|
return outputLines.join("\n");
|
||||||
|
|
||||||
@@ -101,7 +111,10 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
case "enum": {
|
case "enum": {
|
||||||
const allValueNames = new Set([...(value?.["values"] || []), ...Object.keys(value?.["value-names"] || {})]);
|
const allValueNames = new Set([
|
||||||
|
...(value?.["values"] || []),
|
||||||
|
...Object.keys(value?.["value-names"] || {}),
|
||||||
|
]);
|
||||||
const values = Object.fromEntries(
|
const values = Object.fromEntries(
|
||||||
Array.from(allValueNames)
|
Array.from(allValueNames)
|
||||||
.filter(string.test)
|
.filter(string.test)
|
||||||
@@ -121,7 +134,10 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
)} as const)`;
|
)} as const)`;
|
||||||
}
|
}
|
||||||
case "object": {
|
case "object": {
|
||||||
const specName = newConst(value.name + "_spec", convertInputSpec(value.spec));
|
const specName = newConst(
|
||||||
|
value.name + "_spec",
|
||||||
|
convertInputSpec(value.spec)
|
||||||
|
);
|
||||||
return `Value.object({
|
return `Value.object({
|
||||||
name: ${JSON.stringify(value.name || null)},
|
name: ${JSON.stringify(value.name || null)},
|
||||||
description: ${JSON.stringify(value.description || null)},
|
description: ${JSON.stringify(value.description || null)},
|
||||||
@@ -158,7 +174,9 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
function convertList(value: any) {
|
function convertList(value: any) {
|
||||||
switch (value.subtype) {
|
switch (value.subtype) {
|
||||||
case "string": {
|
case "string": {
|
||||||
return `List.${value?.spec?.textarea === true ? "textarea" : "string"}(${JSON.stringify(
|
return `List.${
|
||||||
|
value?.spec?.textarea === true ? "textarea" : "string"
|
||||||
|
}(${JSON.stringify(
|
||||||
{
|
{
|
||||||
name: value.name || null,
|
name: value.name || null,
|
||||||
range: value.range || null,
|
range: value.range || null,
|
||||||
@@ -217,7 +235,10 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
case "object": {
|
case "object": {
|
||||||
const specName = newConst(value.name + "_spec", convertInputSpec(value.spec.spec));
|
const specName = newConst(
|
||||||
|
value.name + "_spec",
|
||||||
|
convertInputSpec(value.spec.spec)
|
||||||
|
);
|
||||||
return `List.obj({
|
return `List.obj({
|
||||||
name: ${JSON.stringify(value.name || null)},
|
name: ${JSON.stringify(value.name || null)},
|
||||||
range: ${JSON.stringify(value.range || null)},
|
range: ${JSON.stringify(value.range || null)},
|
||||||
@@ -233,14 +254,19 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
case "union": {
|
case "union": {
|
||||||
const variants = newConst(
|
const variants = newConst(
|
||||||
value.name + "_variants",
|
value.name + "_variants",
|
||||||
convertVariants(value.spec.variants, value.spec["variant-names"] || {})
|
convertVariants(
|
||||||
|
value.spec.variants,
|
||||||
|
value.spec["variant-names"] || {}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const unionValueName = newConst(
|
const unionValueName = newConst(
|
||||||
value.name + "_union",
|
value.name + "_union",
|
||||||
`
|
`
|
||||||
Value.union({
|
Value.union({
|
||||||
name: ${JSON.stringify(value?.spec?.tag?.name || null)},
|
name: ${JSON.stringify(value?.spec?.tag?.name || null)},
|
||||||
description: ${JSON.stringify(value?.spec?.tag?.description || null)},
|
description: ${JSON.stringify(
|
||||||
|
value?.spec?.tag?.description || null
|
||||||
|
)},
|
||||||
warning: ${JSON.stringify(value?.spec?.tag?.warning || null)},
|
warning: ${JSON.stringify(value?.spec?.tag?.warning || null)},
|
||||||
required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))},
|
required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))},
|
||||||
default: ${JSON.stringify(value?.spec?.default || null)},
|
default: ${JSON.stringify(value?.spec?.default || null)},
|
||||||
@@ -271,11 +297,16 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
|||||||
throw new Error(`Unknown subtype "${value.subtype}"`);
|
throw new Error(`Unknown subtype "${value.subtype}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertVariants(variants: Record<string, unknown>, variantNames: Record<string, string>): string {
|
function convertVariants(
|
||||||
|
variants: Record<string, unknown>,
|
||||||
|
variantNames: Record<string, string>
|
||||||
|
): string {
|
||||||
let answer = "Variants.of({";
|
let answer = "Variants.of({";
|
||||||
for (const [key, value] of Object.entries(variants)) {
|
for (const [key, value] of Object.entries(variants)) {
|
||||||
const variantSpec = newConst(key, convertInputSpec(value));
|
const variantSpec = newConst(key, convertInputSpec(value));
|
||||||
answer += `"${key}": {name: "${variantNames[key] || key}", spec: ${variantSpec}},`;
|
answer += `"${key}": {name: "${
|
||||||
|
variantNames[key] || key
|
||||||
|
}", spec: ${variantSpec}},`;
|
||||||
}
|
}
|
||||||
return `${answer}})`;
|
return `${answer}})`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user