mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
feat: Utils to do bindLan and have ipv4 and ipv6 if need be
This commit is contained in:
@@ -1,42 +1,37 @@
|
||||
import { Parser } from "ts-matches";
|
||||
import { Config } from "../config/builder";
|
||||
import {
|
||||
ActionMetaData,
|
||||
ActionResult,
|
||||
Effects,
|
||||
ExportedAction,
|
||||
} from "../types";
|
||||
import { Utils, once, utils } from "../util";
|
||||
import { TypeFromProps } from "../util/propertiesMatcher";
|
||||
import { InputSpec } from "../config/configTypes";
|
||||
import { Parser } from "ts-matches"
|
||||
import { Config } from "../config/builder"
|
||||
import { ActionMetaData, ActionResult, Effects, ExportedAction } from "../types"
|
||||
import { Utils, once, utils } from "../util"
|
||||
import { TypeFromProps } from "../util/propertiesMatcher"
|
||||
import { InputSpec } from "../config/configTypes"
|
||||
|
||||
export class CreatedAction<WrapperData, Input extends Config<InputSpec>> {
|
||||
private constructor(
|
||||
private myMetaData: Omit<ActionMetaData, "input"> & { input: Input },
|
||||
readonly fn: (options: {
|
||||
effects: Effects;
|
||||
utils: Utils<WrapperData>;
|
||||
input: TypeFromProps<Input>;
|
||||
effects: Effects
|
||||
utils: Utils<WrapperData>
|
||||
input: TypeFromProps<Input>
|
||||
}) => Promise<ActionResult>,
|
||||
) {}
|
||||
private validator = this.myMetaData.input.validator() as Parser<
|
||||
unknown,
|
||||
TypeFromProps<Input>
|
||||
>;
|
||||
>
|
||||
metaData = {
|
||||
...this.myMetaData,
|
||||
input: this.myMetaData.input.build(),
|
||||
};
|
||||
}
|
||||
|
||||
static of<WrapperData, Input extends Config<InputSpec>>(
|
||||
metaData: Omit<ActionMetaData, "input"> & { input: Input },
|
||||
fn: (options: {
|
||||
effects: Effects;
|
||||
utils: Utils<WrapperData>;
|
||||
input: TypeFromProps<Input>;
|
||||
effects: Effects
|
||||
utils: Utils<WrapperData>
|
||||
input: TypeFromProps<Input>
|
||||
}) => Promise<ActionResult>,
|
||||
) {
|
||||
return new CreatedAction<WrapperData, Input>(metaData, fn);
|
||||
return new CreatedAction<WrapperData, Input>(metaData, fn)
|
||||
}
|
||||
|
||||
exportedAction: ExportedAction = ({ effects, input }) => {
|
||||
@@ -44,12 +39,12 @@ export class CreatedAction<WrapperData, Input extends Config<InputSpec>> {
|
||||
effects,
|
||||
utils: utils<WrapperData>(effects),
|
||||
input: this.validator.unsafeCast(input),
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
async exportAction(effects: Effects) {
|
||||
await effects.exportAction(this.metaData);
|
||||
await effects.exportAction(this.metaData)
|
||||
}
|
||||
}
|
||||
|
||||
export const createAction = CreatedAction.of;
|
||||
export const createAction = CreatedAction.of
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { CreatedAction, createAction } from "./createAction";
|
||||
export { CreatedAction, createAction } from "./createAction"
|
||||
|
||||
export { setupActions } from "./setupActions";
|
||||
export { setupActions } from "./setupActions"
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Effects, ExpectedExports, ExportedAction } from "../types";
|
||||
import { ActionMetaData } from "../types";
|
||||
import { CreatedAction } from "./createAction";
|
||||
import { Effects, ExpectedExports, ExportedAction } from "../types"
|
||||
import { ActionMetaData } from "../types"
|
||||
import { CreatedAction } from "./createAction"
|
||||
|
||||
export function setupActions(...createdActions: CreatedAction<any, any>[]) {
|
||||
const actions: Record<string, ExportedAction> = {};
|
||||
const actions: Record<string, ExportedAction> = {}
|
||||
for (const action of createdActions) {
|
||||
actions[action.metaData.id] = action.exportedAction;
|
||||
actions[action.metaData.id] = action.exportedAction
|
||||
}
|
||||
|
||||
const actionsMetadata = createdActions.map((x) => x.metaData);
|
||||
const actionsMetadata = createdActions.map((x) => x.metaData)
|
||||
return {
|
||||
actions,
|
||||
actionsMetadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { AutoConfigure, DeepPartial, Effects, ExpectedExports } from "../types";
|
||||
import { Utils, deepEqual, deepMerge, utils } from "../util";
|
||||
import { AutoConfigure, DeepPartial, Effects, ExpectedExports } from "../types"
|
||||
import { Utils, deepEqual, deepMerge, utils } from "../util"
|
||||
|
||||
export type AutoConfigFrom<WD, Input, NestedConfigs> = {
|
||||
[key in keyof NestedConfigs & string]: (options: {
|
||||
effects: Effects;
|
||||
localConfig: Input;
|
||||
remoteConfig: NestedConfigs[key];
|
||||
utils: Utils<WD>;
|
||||
}) => Promise<void | DeepPartial<NestedConfigs[key]>>;
|
||||
};
|
||||
effects: Effects
|
||||
localConfig: Input
|
||||
remoteConfig: NestedConfigs[key]
|
||||
utils: Utils<WD>
|
||||
}) => Promise<void | DeepPartial<NestedConfigs[key]>>
|
||||
}
|
||||
export class AutoConfig<WD, Input, NestedConfigs> {
|
||||
constructor(
|
||||
readonly configs: AutoConfigFrom<WD, Input, NestedConfigs>,
|
||||
@@ -18,13 +18,13 @@ export class AutoConfig<WD, Input, NestedConfigs> {
|
||||
async check(
|
||||
options: Parameters<AutoConfigure["check"]>[0],
|
||||
): ReturnType<AutoConfigure["check"]> {
|
||||
const origConfig = JSON.parse(JSON.stringify(options.localConfig));
|
||||
const origConfig = JSON.parse(JSON.stringify(options.localConfig))
|
||||
const newOptions = {
|
||||
...options,
|
||||
utils: utils<WD>(options.effects),
|
||||
localConfig: options.localConfig as Input,
|
||||
remoteConfig: options.remoteConfig as any,
|
||||
};
|
||||
}
|
||||
if (
|
||||
!deepEqual(
|
||||
origConfig,
|
||||
@@ -35,7 +35,7 @@ export class AutoConfig<WD, Input, NestedConfigs> {
|
||||
),
|
||||
)
|
||||
)
|
||||
throw new Error(`Check failed for ${this.path}`);
|
||||
throw new Error(`Check failed for ${this.path}`)
|
||||
}
|
||||
async autoConfigure(
|
||||
options: Parameters<AutoConfigure["autoConfigure"]>[0],
|
||||
@@ -45,11 +45,11 @@ export class AutoConfig<WD, Input, NestedConfigs> {
|
||||
utils: utils<WD>(options.effects),
|
||||
localConfig: options.localConfig as Input,
|
||||
remoteConfig: options.remoteConfig as any,
|
||||
};
|
||||
}
|
||||
return deepMerge(
|
||||
{},
|
||||
options.localConfig,
|
||||
await this.configs[this.path](newOptions),
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
export type ReadonlyDeep<A> =
|
||||
A extends Function ? A :
|
||||
A extends {} ? { readonly [K in keyof A]: ReadonlyDeep<A[K]> } : A;
|
||||
export type MaybePromise<A> = Promise<A> | A;
|
||||
export type Message = string;
|
||||
export type MaybePromise<A> = Promise<A> | A
|
||||
export type Message = string
|
||||
|
||||
export { AutoConfig } from "./AutoConfig";
|
||||
export { setupAutoConfig } from "./setupAutoConfig";
|
||||
export { AutoConfig } from "./AutoConfig"
|
||||
export { setupAutoConfig } from "./setupAutoConfig"
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { GenericManifest } from "../manifest/ManifestTypes";
|
||||
import { AutoConfig, AutoConfigFrom } from "./AutoConfig";
|
||||
import { GenericManifest } from "../manifest/ManifestTypes"
|
||||
import { AutoConfig, AutoConfigFrom } from "./AutoConfig"
|
||||
|
||||
export function setupAutoConfig<
|
||||
WD,
|
||||
Input,
|
||||
Manifest extends GenericManifest,
|
||||
NestedConfigs extends {
|
||||
[key in keyof Manifest["dependencies"]]: unknown;
|
||||
[key in keyof Manifest["dependencies"]]: unknown
|
||||
},
|
||||
>(configs: AutoConfigFrom<WD, Input, NestedConfigs>) {
|
||||
type C = typeof configs;
|
||||
type C = typeof configs
|
||||
const answer = { ...configs } as unknown as {
|
||||
[k in keyof C]: AutoConfig<WD, Input, NestedConfigs>;
|
||||
};
|
||||
[k in keyof C]: AutoConfig<WD, Input, NestedConfigs>
|
||||
}
|
||||
for (const key in configs) {
|
||||
answer[key as keyof typeof configs] = new AutoConfig<
|
||||
WD,
|
||||
Input,
|
||||
NestedConfigs
|
||||
>(configs, key as keyof typeof configs);
|
||||
>(configs, key as keyof typeof configs)
|
||||
}
|
||||
return answer;
|
||||
return answer
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { GenericManifest } from "../manifest/ManifestTypes";
|
||||
import * as T from "../types";
|
||||
import { GenericManifest } from "../manifest/ManifestTypes"
|
||||
import * as T from "../types"
|
||||
|
||||
export type BACKUP = "BACKUP";
|
||||
export type BACKUP = "BACKUP"
|
||||
export const DEFAULT_OPTIONS: T.BackupOptions = {
|
||||
delete: true,
|
||||
force: true,
|
||||
ignoreExisting: false,
|
||||
exclude: [],
|
||||
};
|
||||
}
|
||||
type BackupSet<Volumes extends string> = {
|
||||
srcPath: string;
|
||||
srcVolume: Volumes | BACKUP;
|
||||
dstPath: string;
|
||||
dstVolume: Volumes | BACKUP;
|
||||
options?: Partial<T.BackupOptions>;
|
||||
};
|
||||
srcPath: string
|
||||
srcVolume: Volumes | BACKUP
|
||||
dstPath: string
|
||||
dstVolume: Volumes | BACKUP
|
||||
options?: Partial<T.BackupOptions>
|
||||
}
|
||||
/**
|
||||
* This utility simplifies the volume backup process.
|
||||
* ```ts
|
||||
@@ -38,7 +38,7 @@ type BackupSet<Volumes extends string> = {
|
||||
* ```
|
||||
*/
|
||||
export class Backups<M extends GenericManifest> {
|
||||
static BACKUP: BACKUP = "BACKUP";
|
||||
static BACKUP: BACKUP = "BACKUP"
|
||||
|
||||
constructor(
|
||||
private options = DEFAULT_OPTIONS,
|
||||
@@ -54,24 +54,24 @@ export class Backups<M extends GenericManifest> {
|
||||
dstPath: `./${srcVolume}/`,
|
||||
dstVolume: Backups.BACKUP,
|
||||
})),
|
||||
);
|
||||
)
|
||||
}
|
||||
static addSets<M extends GenericManifest = never>(
|
||||
...options: BackupSet<keyof M["volumes"] & string>[]
|
||||
) {
|
||||
return new Backups().addSets(...options);
|
||||
return new Backups().addSets(...options)
|
||||
}
|
||||
static with_options<M extends GenericManifest = never>(
|
||||
options?: Partial<T.BackupOptions>,
|
||||
) {
|
||||
return new Backups({ ...DEFAULT_OPTIONS, ...options });
|
||||
return new Backups({ ...DEFAULT_OPTIONS, ...options })
|
||||
}
|
||||
set_options(options?: Partial<T.BackupOptions>) {
|
||||
this.options = {
|
||||
...this.options,
|
||||
...options,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
return this
|
||||
}
|
||||
volumes(...volumeNames: Array<keyof M["volumes"] & string>) {
|
||||
return this.addSets(
|
||||
@@ -81,13 +81,13 @@ export class Backups<M extends GenericManifest> {
|
||||
dstPath: `./${srcVolume}/`,
|
||||
dstVolume: Backups.BACKUP,
|
||||
})),
|
||||
);
|
||||
)
|
||||
}
|
||||
addSets(...options: BackupSet<keyof M["volumes"] & string>[]) {
|
||||
options.forEach((x) =>
|
||||
this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }),
|
||||
);
|
||||
return this;
|
||||
)
|
||||
return this
|
||||
}
|
||||
build() {
|
||||
const createBackup: T.ExpectedExports.createBackup = async ({
|
||||
@@ -100,16 +100,16 @@ export class Backups<M extends GenericManifest> {
|
||||
path: ".",
|
||||
})
|
||||
.catch(() => [])
|
||||
).map((x) => `${x}`);
|
||||
).map((x) => `${x}`)
|
||||
const backupPaths = this.backupSet
|
||||
.filter((x) => x.dstVolume === Backups.BACKUP)
|
||||
.map((x) => x.dstPath)
|
||||
.map((x) => x.replace(/\.\/([^]*)\//, "$1"));
|
||||
.map((x) => x.replace(/\.\/([^]*)\//, "$1"))
|
||||
const filteredItems = previousItems.filter(
|
||||
(x) => backupPaths.indexOf(x) === -1,
|
||||
);
|
||||
)
|
||||
for (const itemToRemove of filteredItems) {
|
||||
effects.error(`Trying to remove ${itemToRemove}`);
|
||||
effects.console.error(`Trying to remove ${itemToRemove}`)
|
||||
await effects
|
||||
.removeDir({
|
||||
volumeId: Backups.BACKUP,
|
||||
@@ -122,15 +122,17 @@ export class Backups<M extends GenericManifest> {
|
||||
}),
|
||||
)
|
||||
.catch(() => {
|
||||
effects.warn(`Failed to remove ${itemToRemove} from backup volume`);
|
||||
});
|
||||
effects.console.warn(
|
||||
`Failed to remove ${itemToRemove} from backup volume`,
|
||||
)
|
||||
})
|
||||
}
|
||||
for (const item of this.backupSet) {
|
||||
if (notEmptyPath(item.dstPath)) {
|
||||
await effects.createDir({
|
||||
volumeId: item.dstVolume,
|
||||
path: item.dstPath,
|
||||
});
|
||||
})
|
||||
}
|
||||
await effects
|
||||
.runRsync({
|
||||
@@ -140,10 +142,10 @@ export class Backups<M extends GenericManifest> {
|
||||
...item.options,
|
||||
},
|
||||
})
|
||||
.wait();
|
||||
.wait()
|
||||
}
|
||||
return;
|
||||
};
|
||||
return
|
||||
}
|
||||
const restoreBackup: T.ExpectedExports.restoreBackup = async ({
|
||||
effects,
|
||||
}) => {
|
||||
@@ -152,7 +154,7 @@ export class Backups<M extends GenericManifest> {
|
||||
await effects.createDir({
|
||||
volumeId: item.srcVolume,
|
||||
path: item.srcPath,
|
||||
});
|
||||
})
|
||||
}
|
||||
await effects
|
||||
.runRsync({
|
||||
@@ -165,13 +167,13 @@ export class Backups<M extends GenericManifest> {
|
||||
srcPath: item.dstPath,
|
||||
dstPath: item.srcPath,
|
||||
})
|
||||
.wait();
|
||||
.wait()
|
||||
}
|
||||
return;
|
||||
};
|
||||
return { createBackup, restoreBackup };
|
||||
return
|
||||
}
|
||||
return { createBackup, restoreBackup }
|
||||
}
|
||||
}
|
||||
function notEmptyPath(file: string) {
|
||||
return ["", ".", "./"].indexOf(file) === -1;
|
||||
return ["", ".", "./"].indexOf(file) === -1
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { Backups } from "./Backups";
|
||||
export { Backups } from "./Backups"
|
||||
|
||||
export { setupBackups } from "./setupBackups";
|
||||
export { setupBackups } from "./setupBackups"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { string } from "ts-matches";
|
||||
import { Backups } from ".";
|
||||
import { GenericManifest } from "../manifest/ManifestTypes";
|
||||
import { BackupOptions } from "../types";
|
||||
import { _ } from "../util";
|
||||
import { string } from "ts-matches"
|
||||
import { Backups } from "."
|
||||
import { GenericManifest } from "../manifest/ManifestTypes"
|
||||
import { BackupOptions } from "../types"
|
||||
import { _ } from "../util"
|
||||
|
||||
export type SetupBackupsParams<M extends GenericManifest> = Array<
|
||||
keyof M["volumes"] & string
|
||||
>;
|
||||
>
|
||||
|
||||
export function setupBackups<M extends GenericManifest>(
|
||||
...args: _<SetupBackupsParams<M>>
|
||||
) {
|
||||
return Backups.volumes(...args).build();
|
||||
return Backups.volumes(...args).build()
|
||||
}
|
||||
|
||||
export function setupBackupsOptions<M extends GenericManifest>(
|
||||
@@ -20,5 +20,5 @@ export function setupBackupsOptions<M extends GenericManifest>(
|
||||
) {
|
||||
return Backups.with_options(options)
|
||||
.volumes(...args)
|
||||
.build();
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { _ } from "../../util";
|
||||
import { _ } from "../../util"
|
||||
export class IBuilder<A> {
|
||||
protected constructor(readonly a: A) {}
|
||||
|
||||
public build(): A {
|
||||
return this.a;
|
||||
return this.a
|
||||
}
|
||||
}
|
||||
|
||||
export type BuilderExtract<A> = A extends IBuilder<infer B> ? B : never;
|
||||
export type BuilderExtract<A> = A extends IBuilder<infer B> ? B : never
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { InputSpec, ValueSpec } from "../configTypes";
|
||||
import { typeFromProps } from "../../util";
|
||||
import { BuilderExtract, IBuilder } from "./builder";
|
||||
import { Value } from "./value";
|
||||
import { _ } from "../../util";
|
||||
import { InputSpec, ValueSpec } from "../configTypes"
|
||||
import { typeFromProps } from "../../util"
|
||||
import { BuilderExtract, IBuilder } from "./builder"
|
||||
import { Value } from "./value"
|
||||
import { _ } from "../../util"
|
||||
|
||||
/**
|
||||
* Configs are the specs that are used by the os configuration form for this service.
|
||||
@@ -62,42 +62,42 @@ export const addNodesSpec = Config.of({ hostname: hostname, port: port });
|
||||
*/
|
||||
export class Config<A extends InputSpec> extends IBuilder<A> {
|
||||
static empty() {
|
||||
return new Config({});
|
||||
return new Config({})
|
||||
}
|
||||
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>,
|
||||
) {
|
||||
return Config.empty().withValue(key, value);
|
||||
return Config.empty().withValue(key, value)
|
||||
}
|
||||
|
||||
static of<B extends { [key: string]: Value<ValueSpec> }>(spec: B) {
|
||||
const answer: { [K in keyof B]: BuilderExtract<B[K]> } = {} as any;
|
||||
const answer: { [K in keyof B]: BuilderExtract<B[K]> } = {} as any
|
||||
for (const key in spec) {
|
||||
answer[key] = spec[key].build() as any;
|
||||
answer[key] = spec[key].build() as any
|
||||
}
|
||||
return new Config(answer);
|
||||
return new Config(answer)
|
||||
}
|
||||
withValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
||||
return new Config({
|
||||
...this.a,
|
||||
[key]: value.build(),
|
||||
} as A & { [key in K]: B });
|
||||
} as A & { [key in K]: B })
|
||||
}
|
||||
addValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
||||
return new Config({
|
||||
...this.a,
|
||||
[key]: value.build(),
|
||||
} as A & { [key in K]: B });
|
||||
} as A & { [key in K]: B })
|
||||
}
|
||||
|
||||
public validator() {
|
||||
return typeFromProps(this.a);
|
||||
return typeFromProps(this.a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Config } from "./config";
|
||||
import { List } from "./list";
|
||||
import { Value } from "./value";
|
||||
import { Variants } from "./variants";
|
||||
import { Config } from "./config"
|
||||
import { List } from "./list"
|
||||
import { Value } from "./value"
|
||||
import { Variants } from "./variants"
|
||||
|
||||
export {
|
||||
/** @typedef { import("./config").Config } Pet
|
||||
@@ -10,4 +10,4 @@ export {
|
||||
List,
|
||||
Value,
|
||||
Variants,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { BuilderExtract, IBuilder } from "./builder";
|
||||
import { Config } from "./config";
|
||||
import { BuilderExtract, IBuilder } from "./builder"
|
||||
import { Config } from "./config"
|
||||
import {
|
||||
InputSpec,
|
||||
ListValueSpecText,
|
||||
Pattern,
|
||||
UniqueBy,
|
||||
ValueSpecList,
|
||||
} from "../configTypes";
|
||||
import { guardAll } from "../../util";
|
||||
} from "../configTypes"
|
||||
import { guardAll } from "../../util"
|
||||
/**
|
||||
* Used as a subtype of Value.list
|
||||
```ts
|
||||
@@ -24,23 +24,23 @@ export const auth = Value.list(authorizationList);
|
||||
export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||
static text(
|
||||
a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
/** Default = [] */
|
||||
default?: string[];
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
},
|
||||
aSpec: {
|
||||
/** Default = false */
|
||||
masked?: boolean;
|
||||
placeholder?: string | null;
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
patterns: Pattern[];
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns: Pattern[]
|
||||
/** Default = "text" */
|
||||
inputmode?: ListValueSpecText["inputmode"];
|
||||
inputmode?: ListValueSpecText["inputmode"]
|
||||
},
|
||||
) {
|
||||
const spec = {
|
||||
@@ -51,7 +51,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||
masked: false,
|
||||
inputmode: "text" as const,
|
||||
...aSpec,
|
||||
};
|
||||
}
|
||||
return new List({
|
||||
description: null,
|
||||
warning: null,
|
||||
@@ -61,25 +61,25 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||
maxLength: null,
|
||||
...a,
|
||||
spec,
|
||||
});
|
||||
})
|
||||
}
|
||||
static number(
|
||||
a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
/** Default = [] */
|
||||
default?: string[];
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
},
|
||||
aSpec: {
|
||||
integer: boolean;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
step?: string | null;
|
||||
units?: string | null;
|
||||
placeholder?: string | null;
|
||||
integer: boolean
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: string | null
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
},
|
||||
) {
|
||||
const spec = {
|
||||
@@ -90,7 +90,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||
step: null,
|
||||
units: null,
|
||||
...aSpec,
|
||||
};
|
||||
}
|
||||
return new List({
|
||||
description: null,
|
||||
warning: null,
|
||||
@@ -100,38 +100,38 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||
type: "list" as const,
|
||||
...a,
|
||||
spec,
|
||||
});
|
||||
})
|
||||
}
|
||||
static obj<Spec extends Config<InputSpec>>(
|
||||
a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
/** Default [] */
|
||||
default?: [];
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
default?: []
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
},
|
||||
aSpec: {
|
||||
spec: Spec;
|
||||
displayAs?: null | string;
|
||||
uniqueBy?: null | UniqueBy;
|
||||
spec: Spec
|
||||
displayAs?: null | string
|
||||
uniqueBy?: null | UniqueBy
|
||||
},
|
||||
) {
|
||||
const { spec: previousSpecSpec, ...restSpec } = aSpec;
|
||||
const specSpec = previousSpecSpec.build() as BuilderExtract<Spec>;
|
||||
const { spec: previousSpecSpec, ...restSpec } = aSpec
|
||||
const specSpec = previousSpecSpec.build() as BuilderExtract<Spec>
|
||||
const spec = {
|
||||
type: "object" as const,
|
||||
displayAs: null,
|
||||
uniqueBy: null,
|
||||
...restSpec,
|
||||
spec: specSpec,
|
||||
};
|
||||
}
|
||||
const value = {
|
||||
spec,
|
||||
default: [],
|
||||
...a,
|
||||
};
|
||||
}
|
||||
return new List({
|
||||
description: null,
|
||||
warning: null,
|
||||
@@ -139,10 +139,10 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
||||
maxLength: null,
|
||||
type: "list" as const,
|
||||
...value,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
public validator() {
|
||||
return guardAll(this.a);
|
||||
return guardAll(this.a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BuilderExtract, IBuilder } from "./builder";
|
||||
import { Config } from "./config";
|
||||
import { List } from "./list";
|
||||
import { Variants } from "./variants";
|
||||
import { BuilderExtract, IBuilder } from "./builder"
|
||||
import { Config } from "./config"
|
||||
import { List } from "./list"
|
||||
import { Variants } from "./variants"
|
||||
import {
|
||||
InputSpec,
|
||||
Pattern,
|
||||
@@ -13,32 +13,31 @@ import {
|
||||
ValueSpecSelect,
|
||||
ValueSpecText,
|
||||
ValueSpecTextarea,
|
||||
} from "../configTypes";
|
||||
import { guardAll } from "../../util";
|
||||
import { DefaultString } from "../configTypes";
|
||||
import { _ } from "../../util";
|
||||
} from "../configTypes"
|
||||
import { guardAll } from "../../util"
|
||||
import { DefaultString } from "../configTypes"
|
||||
import { _ } from "../../util"
|
||||
|
||||
type RequiredLike<A> =
|
||||
type RequiredDefault<A> =
|
||||
| false
|
||||
| true
|
||||
| { default: A }
|
||||
| { defaultWithRequired: A };
|
||||
| {
|
||||
default: A | null
|
||||
}
|
||||
|
||||
function requiredLikeToAbove<Input extends RequiredLike<any>>(
|
||||
function requiredLikeToAbove<Input extends RequiredDefault<A>, A>(
|
||||
requiredLike: Input,
|
||||
) {
|
||||
// prettier-ignore
|
||||
return {
|
||||
default:
|
||||
(typeof requiredLike === "object" ? (
|
||||
'default' in requiredLike ? requiredLike.default : requiredLike.defaultWithRequired
|
||||
) :
|
||||
null) as Input extends { default: infer T } | {defaultWithRequired: infer T} ? T : null,
|
||||
required: (requiredLike === true ? true : false) as (
|
||||
required: (typeof requiredLike === 'object' ? true : requiredLike) as (
|
||||
Input extends { default: unknown} ? true:
|
||||
Input extends true ? true :
|
||||
Input extends { defaultWithRequired: unknown } ? true :
|
||||
false
|
||||
),
|
||||
default:(typeof requiredLike === 'object' ? requiredLike.default : null) as (
|
||||
Input extends { default: infer Default } ? Default :
|
||||
null
|
||||
)
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -65,10 +64,10 @@ const username = Value.string({
|
||||
*/
|
||||
export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
static toggle(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
default?: boolean | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: boolean | null
|
||||
}) {
|
||||
return new Value({
|
||||
description: null,
|
||||
@@ -76,21 +75,22 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
default: null,
|
||||
type: "toggle" as const,
|
||||
...a,
|
||||
});
|
||||
})
|
||||
}
|
||||
static text<Required extends RequiredLike<DefaultString>>(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: Required;
|
||||
static text<Required extends RequiredDefault<DefaultString>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
|
||||
/** Default = false */
|
||||
masked?: boolean;
|
||||
placeholder?: string | null;
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
patterns?: Pattern[];
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
/** Default = 'text' */
|
||||
inputmode?: ValueSpecText["inputmode"];
|
||||
inputmode?: ValueSpecText["inputmode"]
|
||||
}) {
|
||||
return new Value({
|
||||
type: "text" as const,
|
||||
@@ -104,16 +104,16 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
inputmode: "text",
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
});
|
||||
})
|
||||
}
|
||||
static textarea(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: boolean;
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
placeholder?: string | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: boolean
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
placeholder?: string | null
|
||||
}) {
|
||||
return new Value({
|
||||
description: null,
|
||||
@@ -123,20 +123,20 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
placeholder: null,
|
||||
type: "textarea" as const,
|
||||
...a,
|
||||
} as ValueSpecTextarea);
|
||||
} as ValueSpecTextarea)
|
||||
}
|
||||
static number<Required extends RequiredLike<number>>(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: Required;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
static number<Required extends RequiredDefault<number>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
/** Default = '1' */
|
||||
step?: string | null;
|
||||
integer: boolean;
|
||||
units?: string | null;
|
||||
placeholder?: string | null;
|
||||
step?: string | null
|
||||
integer: boolean
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
}) {
|
||||
return new Value({
|
||||
type: "number" as const,
|
||||
@@ -149,13 +149,13 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
placeholder: null,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
});
|
||||
})
|
||||
}
|
||||
static color<Required extends RequiredLike<string>>(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: Required;
|
||||
static color<Required extends RequiredDefault<string>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
}) {
|
||||
return new Value({
|
||||
type: "color" as const,
|
||||
@@ -163,18 +163,18 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
warning: null,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
});
|
||||
})
|
||||
}
|
||||
static datetime<Required extends RequiredLike<string>>(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: Required;
|
||||
static datetime<Required extends RequiredDefault<string>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
/** Default = 'datetime-local' */
|
||||
inputmode?: ValueSpecDatetime["inputmode"];
|
||||
min?: string | null;
|
||||
max?: string | null;
|
||||
step?: string | null;
|
||||
inputmode?: ValueSpecDatetime["inputmode"]
|
||||
min?: string | null
|
||||
max?: string | null
|
||||
step?: string | null
|
||||
}) {
|
||||
return new Value({
|
||||
type: "datetime" as const,
|
||||
@@ -186,17 +186,17 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
step: null,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
});
|
||||
})
|
||||
}
|
||||
static select<
|
||||
Required extends RequiredLike<string>,
|
||||
Required extends RequiredDefault<string>,
|
||||
B extends Record<string, string>,
|
||||
>(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: Required;
|
||||
values: B;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
values: B
|
||||
}) {
|
||||
return new Value({
|
||||
description: null,
|
||||
@@ -204,16 +204,16 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
type: "select" as const,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
});
|
||||
})
|
||||
}
|
||||
static multiselect<Values extends Record<string, string>>(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
default: string[];
|
||||
values: Values;
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string[]
|
||||
values: Values
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
}) {
|
||||
return new Value({
|
||||
type: "multiselect" as const,
|
||||
@@ -222,39 +222,39 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
warning: null,
|
||||
description: null,
|
||||
...a,
|
||||
});
|
||||
})
|
||||
}
|
||||
static object<Spec extends Config<InputSpec>>(
|
||||
a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
},
|
||||
previousSpec: Spec,
|
||||
) {
|
||||
const spec = previousSpec.build() as BuilderExtract<Spec>;
|
||||
const spec = previousSpec.build() as BuilderExtract<Spec>
|
||||
return new Value({
|
||||
type: "object" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...a,
|
||||
spec,
|
||||
});
|
||||
})
|
||||
}
|
||||
static union<
|
||||
Required extends RequiredLike<string>,
|
||||
Required extends RequiredDefault<string>,
|
||||
V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>,
|
||||
>(
|
||||
a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
required: Required;
|
||||
default?: string | null;
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
default?: string | null
|
||||
},
|
||||
aVariants: V,
|
||||
) {
|
||||
const variants = aVariants.build() as BuilderExtract<V>;
|
||||
const variants = aVariants.build() as BuilderExtract<V>
|
||||
return new Value({
|
||||
type: "union" as const,
|
||||
description: null,
|
||||
@@ -262,13 +262,13 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
...a,
|
||||
variants,
|
||||
...requiredLikeToAbove(a.required),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
static list<A extends ValueSpecList>(a: List<A>) {
|
||||
return new Value(a.build());
|
||||
return new Value(a.build())
|
||||
}
|
||||
public validator() {
|
||||
return guardAll(this.a);
|
||||
return guardAll(this.a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { InputSpec } from "../configTypes";
|
||||
import { BuilderExtract, IBuilder } from "./builder";
|
||||
import { Config } from ".";
|
||||
import { InputSpec } from "../configTypes"
|
||||
import { BuilderExtract, IBuilder } from "./builder"
|
||||
import { Config } from "."
|
||||
|
||||
/**
|
||||
* Used in the the Value.select { @link './value.ts' }
|
||||
@@ -54,43 +54,43 @@ export const pruning = Value.union(
|
||||
export class Variants<
|
||||
A extends {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
spec: InputSpec;
|
||||
};
|
||||
name: string
|
||||
spec: InputSpec
|
||||
}
|
||||
},
|
||||
> extends IBuilder<A> {
|
||||
static of<
|
||||
A extends {
|
||||
[key: string]: { name: string; spec: Config<InputSpec> };
|
||||
[key: string]: { name: string; spec: Config<InputSpec> }
|
||||
},
|
||||
>(a: A) {
|
||||
const variants: {
|
||||
[K in keyof A]: { name: string; spec: BuilderExtract<A[K]["spec"]> };
|
||||
} = {} as any;
|
||||
[K in keyof A]: { name: string; spec: BuilderExtract<A[K]["spec"]> }
|
||||
} = {} as any
|
||||
for (const key in a) {
|
||||
const value = a[key];
|
||||
const value = a[key]
|
||||
variants[key] = {
|
||||
name: value.name,
|
||||
spec: value.spec.build() as any,
|
||||
};
|
||||
}
|
||||
}
|
||||
return new Variants(variants);
|
||||
return new Variants(variants)
|
||||
}
|
||||
|
||||
static empty() {
|
||||
return Variants.of({});
|
||||
return Variants.of({})
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
withVariant<K extends string, B extends InputSpec>(key: K, value: Config<B>) {
|
||||
return new Variants({
|
||||
...this.a,
|
||||
[key]: value.build(),
|
||||
} as A & { [key in K]: B });
|
||||
} as A & { [key in K]: B })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type InputSpec = Record<string, ValueSpec>;
|
||||
export type InputSpec = Record<string, ValueSpec>
|
||||
export type ValueType =
|
||||
| "text"
|
||||
| "textarea"
|
||||
@@ -11,8 +11,8 @@ export type ValueType =
|
||||
| "list"
|
||||
| "object"
|
||||
| "file"
|
||||
| "union";
|
||||
export type ValueSpec = ValueSpecOf<ValueType>;
|
||||
| "union"
|
||||
export type ValueSpec = ValueSpecOf<ValueType>
|
||||
/** core spec types. These types provide the metadata for performing validations */
|
||||
export type ValueSpecOf<T extends ValueType> = T extends "text"
|
||||
? ValueSpecText
|
||||
@@ -38,82 +38,82 @@ export type ValueSpecOf<T extends ValueType> = T extends "text"
|
||||
? ValueSpecFile
|
||||
: T extends "union"
|
||||
? ValueSpecUnion
|
||||
: never;
|
||||
: never
|
||||
|
||||
export interface ValueSpecText extends ListValueSpecText, WithStandalone {
|
||||
required: boolean;
|
||||
default: DefaultString | null;
|
||||
required: boolean
|
||||
default: DefaultString | null
|
||||
}
|
||||
export interface ValueSpecTextarea extends WithStandalone {
|
||||
type: "textarea";
|
||||
placeholder: string | null;
|
||||
minLength: number | null;
|
||||
maxLength: number | null;
|
||||
required: boolean;
|
||||
type: "textarea"
|
||||
placeholder: string | null
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
required: boolean
|
||||
}
|
||||
export interface ValueSpecNumber extends ListValueSpecNumber, WithStandalone {
|
||||
required: boolean;
|
||||
default: number | null;
|
||||
required: boolean
|
||||
default: number | null
|
||||
}
|
||||
export interface ValueSpecColor extends WithStandalone {
|
||||
type: "color";
|
||||
required: boolean;
|
||||
default: string | null;
|
||||
type: "color"
|
||||
required: boolean
|
||||
default: string | null
|
||||
}
|
||||
export interface ValueSpecDatetime extends WithStandalone {
|
||||
type: "datetime";
|
||||
required: boolean;
|
||||
inputmode: "date" | "time" | "datetime-local";
|
||||
min: string | null;
|
||||
max: string | null;
|
||||
step: string | null;
|
||||
default: string | null;
|
||||
type: "datetime"
|
||||
required: boolean
|
||||
inputmode: "date" | "time" | "datetime-local"
|
||||
min: string | null
|
||||
max: string | null
|
||||
step: string | null
|
||||
default: string | null
|
||||
}
|
||||
export interface ValueSpecSelect extends SelectBase, WithStandalone {
|
||||
type: "select";
|
||||
required: boolean;
|
||||
default: string | null;
|
||||
type: "select"
|
||||
required: boolean
|
||||
default: string | null
|
||||
}
|
||||
export interface ValueSpecMultiselect extends SelectBase, WithStandalone {
|
||||
type: "multiselect";
|
||||
minLength: number | null;
|
||||
maxLength: number | null;
|
||||
default: string[];
|
||||
type: "multiselect"
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
default: string[]
|
||||
}
|
||||
export interface ValueSpecToggle extends WithStandalone {
|
||||
type: "toggle";
|
||||
default: boolean | null;
|
||||
type: "toggle"
|
||||
default: boolean | null
|
||||
}
|
||||
export interface ValueSpecUnion extends WithStandalone {
|
||||
type: "union";
|
||||
type: "union"
|
||||
variants: Record<
|
||||
string,
|
||||
{
|
||||
name: string;
|
||||
spec: InputSpec;
|
||||
name: string
|
||||
spec: InputSpec
|
||||
}
|
||||
>;
|
||||
required: boolean;
|
||||
default: string | null;
|
||||
>
|
||||
required: boolean
|
||||
default: string | null
|
||||
}
|
||||
export interface ValueSpecFile extends WithStandalone {
|
||||
type: "file";
|
||||
extensions: string[];
|
||||
required: boolean;
|
||||
type: "file"
|
||||
extensions: string[]
|
||||
required: boolean
|
||||
}
|
||||
export interface ValueSpecObject extends WithStandalone {
|
||||
type: "object";
|
||||
spec: InputSpec;
|
||||
type: "object"
|
||||
spec: InputSpec
|
||||
}
|
||||
export interface WithStandalone {
|
||||
name: string;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
}
|
||||
export interface SelectBase {
|
||||
values: Record<string, string>;
|
||||
values: Record<string, string>
|
||||
}
|
||||
export type ListValueSpecType = "text" | "number" | "object";
|
||||
export type ListValueSpecType = "text" | "number" | "object"
|
||||
/** represents a spec for the values of a list */
|
||||
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "text"
|
||||
? ListValueSpecText
|
||||
@@ -121,15 +121,15 @@ export type ListValueSpecOf<T extends ListValueSpecType> = T extends "text"
|
||||
? ListValueSpecNumber
|
||||
: T extends "object"
|
||||
? ListValueSpecObject
|
||||
: never;
|
||||
: never
|
||||
/** represents a spec for a list */
|
||||
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
|
||||
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>
|
||||
export interface ValueSpecListOf<T extends ListValueSpecType>
|
||||
extends WithStandalone {
|
||||
type: "list";
|
||||
spec: ListValueSpecOf<T>;
|
||||
minLength: number | null;
|
||||
maxLength: number | null;
|
||||
type: "list"
|
||||
spec: ListValueSpecOf<T>
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
default:
|
||||
| string[]
|
||||
| number[]
|
||||
@@ -138,64 +138,64 @@ export interface ValueSpecListOf<T extends ListValueSpecType>
|
||||
| readonly string[]
|
||||
| readonly number[]
|
||||
| readonly DefaultString[]
|
||||
| readonly Record<string, unknown>[];
|
||||
| readonly Record<string, unknown>[]
|
||||
}
|
||||
export interface Pattern {
|
||||
regex: string;
|
||||
description: string;
|
||||
regex: string
|
||||
description: string
|
||||
}
|
||||
export interface ListValueSpecText {
|
||||
type: "text";
|
||||
patterns: Pattern[];
|
||||
minLength: number | null;
|
||||
maxLength: number | null;
|
||||
masked: boolean;
|
||||
inputmode: "text" | "email" | "tel" | "url";
|
||||
placeholder: string | null;
|
||||
type: "text"
|
||||
patterns: Pattern[]
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
masked: boolean
|
||||
inputmode: "text" | "email" | "tel" | "url"
|
||||
placeholder: string | null
|
||||
}
|
||||
export interface ListValueSpecNumber {
|
||||
type: "number";
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
integer: boolean;
|
||||
step: string | null;
|
||||
units: string | null;
|
||||
placeholder: string | null;
|
||||
type: "number"
|
||||
min: number | null
|
||||
max: number | null
|
||||
integer: boolean
|
||||
step: string | null
|
||||
units: string | null
|
||||
placeholder: string | null
|
||||
}
|
||||
export interface ListValueSpecObject {
|
||||
type: "object";
|
||||
type: "object"
|
||||
/** this is a mapped type of the config object at this level, replacing the object's values with specs on those values */
|
||||
spec: InputSpec;
|
||||
spec: InputSpec
|
||||
/** indicates whether duplicates can be permitted in the list */
|
||||
uniqueBy: UniqueBy;
|
||||
uniqueBy: UniqueBy
|
||||
/** this should be a handlebars template which can make use of the entire config which corresponds to 'spec' */
|
||||
displayAs: string | null;
|
||||
displayAs: string | null
|
||||
}
|
||||
export type UniqueBy =
|
||||
| null
|
||||
| string
|
||||
| {
|
||||
any: readonly UniqueBy[] | UniqueBy[];
|
||||
any: readonly UniqueBy[] | UniqueBy[]
|
||||
}
|
||||
| {
|
||||
all: readonly UniqueBy[] | UniqueBy[];
|
||||
};
|
||||
all: readonly UniqueBy[] | UniqueBy[]
|
||||
}
|
||||
export type DefaultString =
|
||||
| string
|
||||
| {
|
||||
charset: string;
|
||||
len: number;
|
||||
};
|
||||
charset: string
|
||||
len: number
|
||||
}
|
||||
|
||||
// sometimes the type checker needs just a little bit of help
|
||||
export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||
t: ValueSpecListOf<ListValueSpecType>,
|
||||
s: S,
|
||||
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
||||
return t.spec.type === s;
|
||||
return t.spec.type === s
|
||||
}
|
||||
export const unionSelectKey = "unionSelectKey" as const;
|
||||
export type UnionSelectKey = typeof unionSelectKey;
|
||||
export const unionSelectKey = "unionSelectKey" as const
|
||||
export type UnionSelectKey = typeof unionSelectKey
|
||||
|
||||
export const unionValueKey = "unionValueKey" as const;
|
||||
export type UnionValueKey = typeof unionValueKey;
|
||||
export const unionValueKey = "unionValueKey" as const
|
||||
export type UnionValueKey = typeof unionValueKey
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { GenericManifest } from "../manifest/ManifestTypes";
|
||||
import { Dependency, PackageId } from "../types";
|
||||
import { GenericManifest } from "../manifest/ManifestTypes"
|
||||
import { Dependency, PackageId } from "../types"
|
||||
|
||||
export type Dependencies<T extends GenericManifest> = {
|
||||
exists(id: keyof T["dependencies"]): Dependency;
|
||||
running(id: keyof T["dependencies"]): Dependency;
|
||||
};
|
||||
exists(id: keyof T["dependencies"]): Dependency
|
||||
running(id: keyof T["dependencies"]): Dependency
|
||||
}
|
||||
|
||||
export const dependenciesSet = <
|
||||
T extends GenericManifest,
|
||||
@@ -13,13 +13,13 @@ export const dependenciesSet = <
|
||||
return {
|
||||
id,
|
||||
kind: "exists",
|
||||
} as Dependency;
|
||||
} as Dependency
|
||||
},
|
||||
|
||||
running(id: keyof T["dependencies"]) {
|
||||
return {
|
||||
id,
|
||||
kind: "running",
|
||||
} as Dependency;
|
||||
} as Dependency
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * as configBuilder from "./builder";
|
||||
export * as configBuilder from "./builder"
|
||||
|
||||
export { setupConfig } from "./setupConfig";
|
||||
export * as dependencies from "./dependencies";
|
||||
export { setupConfig } from "./setupConfig"
|
||||
export * as dependencies from "./dependencies"
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { Config } from "./builder";
|
||||
import { DeepPartial, Dependencies, Effects, ExpectedExports } from "../types";
|
||||
import { InputSpec } from "./configTypes";
|
||||
import { Utils, nullIfEmpty, once, utils } from "../util";
|
||||
import { TypeFromProps } from "../util/propertiesMatcher";
|
||||
import { GenericManifest } from "../manifest/ManifestTypes";
|
||||
import * as D from "./dependencies";
|
||||
import { Config } from "./builder"
|
||||
import { DeepPartial, Dependencies, Effects, ExpectedExports } from "../types"
|
||||
import { InputSpec } from "./configTypes"
|
||||
import { Utils, nullIfEmpty, once, utils } from "../util"
|
||||
import { TypeFromProps } from "../util/propertiesMatcher"
|
||||
import { GenericManifest } from "../manifest/ManifestTypes"
|
||||
import * as D from "./dependencies"
|
||||
|
||||
declare const dependencyProof: unique symbol;
|
||||
declare const dependencyProof: unique symbol
|
||||
export type DependenciesReceipt = void & {
|
||||
[dependencyProof]: never;
|
||||
};
|
||||
[dependencyProof]: never
|
||||
}
|
||||
|
||||
export type Save<WD, A, Manifest extends GenericManifest> = (options: {
|
||||
effects: Effects;
|
||||
input: A;
|
||||
utils: Utils<WD>;
|
||||
dependencies: D.Dependencies<Manifest>;
|
||||
}) => Promise<DependenciesReceipt>;
|
||||
effects: Effects
|
||||
input: A
|
||||
utils: Utils<WD>
|
||||
dependencies: D.Dependencies<Manifest>
|
||||
}) => Promise<DependenciesReceipt>
|
||||
export type Read<WD, A> = (options: {
|
||||
effects: Effects;
|
||||
utils: Utils<WD>;
|
||||
}) => Promise<void | DeepPartial<A>>;
|
||||
effects: Effects
|
||||
utils: Utils<WD>
|
||||
}) => Promise<void | DeepPartial<A>>
|
||||
/**
|
||||
* We want to setup a config export with a get and set, this
|
||||
* is going to be the default helper to setup config, because it will help
|
||||
@@ -37,19 +37,19 @@ export function setupConfig<
|
||||
write: Save<WD, TypeFromProps<A>, Manifest>,
|
||||
read: Read<WD, TypeFromProps<A>>,
|
||||
) {
|
||||
const validator = once(() => spec.validator());
|
||||
const validator = once(() => spec.validator())
|
||||
return {
|
||||
setConfig: (async ({ effects, input }) => {
|
||||
if (!validator().test(input)) {
|
||||
await effects.error(String(validator().errorMessage(input)));
|
||||
return { error: "Set config type error for config" };
|
||||
await effects.console.error(String(validator().errorMessage(input)))
|
||||
return { error: "Set config type error for config" }
|
||||
}
|
||||
await write({
|
||||
input: JSON.parse(JSON.stringify(input)),
|
||||
effects,
|
||||
utils: utils<WD>(effects),
|
||||
dependencies: D.dependenciesSet<Manifest>(),
|
||||
});
|
||||
})
|
||||
}) as ExpectedExports.setConfig,
|
||||
getConfig: (async ({ effects, config }) => {
|
||||
return {
|
||||
@@ -57,9 +57,9 @@ export function setupConfig<
|
||||
config: nullIfEmpty(
|
||||
(await read({ effects, utils: utils<WD>(effects) })) || null,
|
||||
),
|
||||
};
|
||||
}
|
||||
}) as ExpectedExports.getConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default setupConfig;
|
||||
export default setupConfig
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as matches from "ts-matches";
|
||||
import * as matches from "ts-matches"
|
||||
|
||||
const starSub = /((\d+\.)*\d+)\.\*/;
|
||||
const starSub = /((\d+\.)*\d+)\.\*/
|
||||
// prettier-ignore
|
||||
export type ValidEmVer = `${number | '*'}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`-${string}` | ""}`;
|
||||
|
||||
function incrementLastNumber(list: number[]) {
|
||||
const newList = [...list];
|
||||
newList[newList.length - 1]++;
|
||||
return newList;
|
||||
const newList = [...list]
|
||||
newList[newList.length - 1]++
|
||||
return newList
|
||||
}
|
||||
/**
|
||||
* Will take in a range, like `>1.2` or `<1.2.3.4` or `=1.2` or `1.*`
|
||||
@@ -16,7 +16,7 @@ function incrementLastNumber(list: number[]) {
|
||||
* @returns
|
||||
*/
|
||||
export function rangeOf(range: string | Checker): Checker {
|
||||
return Checker.parse(range);
|
||||
return Checker.parse(range)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,10 +26,10 @@ export function rangeOf(range: string | Checker): Checker {
|
||||
*/
|
||||
export function rangeAnd(...ranges: (string | Checker)[]): Checker {
|
||||
if (ranges.length === 0) {
|
||||
throw new Error("No ranges given");
|
||||
throw new Error("No ranges given")
|
||||
}
|
||||
const [firstCheck, ...rest] = ranges;
|
||||
return Checker.parse(firstCheck).and(...rest);
|
||||
const [firstCheck, ...rest] = ranges
|
||||
return Checker.parse(firstCheck).and(...rest)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,10 +39,10 @@ export function rangeAnd(...ranges: (string | Checker)[]): Checker {
|
||||
*/
|
||||
export function rangeOr(...ranges: (string | Checker)[]): Checker {
|
||||
if (ranges.length === 0) {
|
||||
throw new Error("No ranges given");
|
||||
throw new Error("No ranges given")
|
||||
}
|
||||
const [firstCheck, ...rest] = ranges;
|
||||
return Checker.parse(firstCheck).or(...rest);
|
||||
const [firstCheck, ...rest] = ranges
|
||||
return Checker.parse(firstCheck).or(...rest)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ export function rangeOr(...ranges: (string | Checker)[]): Checker {
|
||||
* @returns
|
||||
*/
|
||||
export function notRange(range: string | Checker): Checker {
|
||||
return rangeOf(range).not();
|
||||
return rangeOf(range).not()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,23 +65,23 @@ export class EmVer {
|
||||
*/
|
||||
static from(range: ValidEmVer | EmVer): EmVer {
|
||||
if (range instanceof EmVer) {
|
||||
return range;
|
||||
return range
|
||||
}
|
||||
return EmVer.parse(range);
|
||||
return EmVer.parse(range)
|
||||
}
|
||||
/**
|
||||
* Convert the range, should be 1.2.* or * into a emver
|
||||
* IsUnsafe
|
||||
*/
|
||||
static parse(rangeExtra: string): EmVer {
|
||||
const [range, extra] = rangeExtra.split("-");
|
||||
const values = range.split(".").map((x) => parseInt(x));
|
||||
const [range, extra] = rangeExtra.split("-")
|
||||
const values = range.split(".").map((x) => parseInt(x))
|
||||
for (const value of values) {
|
||||
if (isNaN(value)) {
|
||||
throw new Error(`Couldn't parse range: ${range}`);
|
||||
throw new Error(`Couldn't parse range: ${range}`)
|
||||
}
|
||||
}
|
||||
return new EmVer(values, extra);
|
||||
return new EmVer(values, extra)
|
||||
}
|
||||
private constructor(
|
||||
public readonly values: number[],
|
||||
@@ -92,44 +92,44 @@ export class EmVer {
|
||||
* Used when we need a new emver that has the last number incremented, used in the 1.* like things
|
||||
*/
|
||||
public withLastIncremented() {
|
||||
return new EmVer(incrementLastNumber(this.values), null);
|
||||
return new EmVer(incrementLastNumber(this.values), null)
|
||||
}
|
||||
|
||||
public greaterThan(other: EmVer): boolean {
|
||||
for (const i in this.values) {
|
||||
if (other.values[i] == null) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
if (this.values[i] > other.values[i]) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
if (this.values[i] < other.values[i]) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
public equals(other: EmVer): boolean {
|
||||
if (other.values.length !== this.values.length) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
for (const i in this.values) {
|
||||
if (this.values[i] !== other.values[i]) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
public greaterThanOrEqual(other: EmVer): boolean {
|
||||
return this.greaterThan(other) || this.equals(other);
|
||||
return this.greaterThan(other) || this.equals(other)
|
||||
}
|
||||
public lessThanOrEqual(other: EmVer): boolean {
|
||||
return !this.greaterThan(other);
|
||||
return !this.greaterThan(other)
|
||||
}
|
||||
public lessThan(other: EmVer): boolean {
|
||||
return !this.greaterThanOrEqual(other);
|
||||
return !this.greaterThanOrEqual(other)
|
||||
}
|
||||
/**
|
||||
* Return a enum string that describes (used for switching/iffs)
|
||||
@@ -139,11 +139,11 @@ export class EmVer {
|
||||
*/
|
||||
public compare(other: EmVer) {
|
||||
if (this.equals(other)) {
|
||||
return "equal" as const;
|
||||
return "equal" as const
|
||||
} else if (this.greaterThan(other)) {
|
||||
return "greater" as const;
|
||||
return "greater" as const
|
||||
} else {
|
||||
return "less" as const;
|
||||
return "less" as const
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -157,11 +157,11 @@ export class EmVer {
|
||||
.when("equal", () => 0 as const)
|
||||
.when("greater", () => 1 as const)
|
||||
.when("less", () => -1 as const)
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}`;
|
||||
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,80 +178,80 @@ export class Checker {
|
||||
*/
|
||||
static parse(range: string | Checker): Checker {
|
||||
if (range instanceof Checker) {
|
||||
return range;
|
||||
return range
|
||||
}
|
||||
range = range.trim();
|
||||
range = range.trim()
|
||||
if (range.indexOf("||") !== -1) {
|
||||
return rangeOr(...range.split("||").map((x) => Checker.parse(x)));
|
||||
return rangeOr(...range.split("||").map((x) => Checker.parse(x)))
|
||||
}
|
||||
if (range.indexOf("&&") !== -1) {
|
||||
return rangeAnd(...range.split("&&").map((x) => Checker.parse(x)));
|
||||
return rangeAnd(...range.split("&&").map((x) => Checker.parse(x)))
|
||||
}
|
||||
if (range === "*") {
|
||||
return new Checker((version) => {
|
||||
EmVer.from(version);
|
||||
return true;
|
||||
});
|
||||
EmVer.from(version)
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (range.startsWith("!")) {
|
||||
return Checker.parse(range.substring(1)).not();
|
||||
return Checker.parse(range.substring(1)).not()
|
||||
}
|
||||
const starSubMatches = starSub.exec(range);
|
||||
const starSubMatches = starSub.exec(range)
|
||||
if (starSubMatches != null) {
|
||||
const emVarLower = EmVer.parse(starSubMatches[1]);
|
||||
const emVarUpper = emVarLower.withLastIncremented();
|
||||
const emVarLower = EmVer.parse(starSubMatches[1])
|
||||
const emVarUpper = emVarLower.withLastIncremented()
|
||||
|
||||
return new Checker((version) => {
|
||||
const v = EmVer.from(version);
|
||||
const v = EmVer.from(version)
|
||||
return (
|
||||
(v.greaterThan(emVarLower) || v.equals(emVarLower)) &&
|
||||
!v.greaterThan(emVarUpper) &&
|
||||
!v.equals(emVarUpper)
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
switch (range.substring(0, 2)) {
|
||||
case ">=": {
|
||||
const emVar = EmVer.parse(range.substring(2));
|
||||
const emVar = EmVer.parse(range.substring(2))
|
||||
return new Checker((version) => {
|
||||
const v = EmVer.from(version);
|
||||
return v.greaterThanOrEqual(emVar);
|
||||
});
|
||||
const v = EmVer.from(version)
|
||||
return v.greaterThanOrEqual(emVar)
|
||||
})
|
||||
}
|
||||
case "<=": {
|
||||
const emVar = EmVer.parse(range.substring(2));
|
||||
const emVar = EmVer.parse(range.substring(2))
|
||||
return new Checker((version) => {
|
||||
const v = EmVer.from(version);
|
||||
return v.lessThanOrEqual(emVar);
|
||||
});
|
||||
const v = EmVer.from(version)
|
||||
return v.lessThanOrEqual(emVar)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
switch (range.substring(0, 1)) {
|
||||
case ">": {
|
||||
const emVar = EmVer.parse(range.substring(1));
|
||||
const emVar = EmVer.parse(range.substring(1))
|
||||
return new Checker((version) => {
|
||||
const v = EmVer.from(version);
|
||||
return v.greaterThan(emVar);
|
||||
});
|
||||
const v = EmVer.from(version)
|
||||
return v.greaterThan(emVar)
|
||||
})
|
||||
}
|
||||
case "<": {
|
||||
const emVar = EmVer.parse(range.substring(1));
|
||||
const emVar = EmVer.parse(range.substring(1))
|
||||
return new Checker((version) => {
|
||||
const v = EmVer.from(version);
|
||||
return v.lessThan(emVar);
|
||||
});
|
||||
const v = EmVer.from(version)
|
||||
return v.lessThan(emVar)
|
||||
})
|
||||
}
|
||||
case "=": {
|
||||
const emVar = EmVer.parse(range.substring(1));
|
||||
const emVar = EmVer.parse(range.substring(1))
|
||||
return new Checker((version) => {
|
||||
const v = EmVer.from(version);
|
||||
return v.equals(emVar);
|
||||
});
|
||||
const v = EmVer.from(version)
|
||||
return v.equals(emVar)
|
||||
})
|
||||
}
|
||||
}
|
||||
throw new Error("Couldn't parse range: " + range);
|
||||
throw new Error("Couldn't parse range: " + range)
|
||||
}
|
||||
constructor(
|
||||
/**
|
||||
@@ -267,15 +267,15 @@ export class Checker {
|
||||
public and(...others: (Checker | string)[]): Checker {
|
||||
return new Checker((value) => {
|
||||
if (!this.check(value)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
for (const other of others) {
|
||||
if (!Checker.parse(other).check(value)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,15 +284,15 @@ export class Checker {
|
||||
public or(...others: (Checker | string)[]): Checker {
|
||||
return new Checker((value) => {
|
||||
if (this.check(value)) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
for (const other of others) {
|
||||
if (Checker.parse(other).check(value)) {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,6 +300,6 @@ export class Checker {
|
||||
* @returns
|
||||
*/
|
||||
public not(): Checker {
|
||||
return new Checker((value) => !this.check(value));
|
||||
return new Checker((value) => !this.check(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import { InterfaceReceipt } from "../mainFn/interfaceReceipt";
|
||||
import { Daemon, Effects } from "../types";
|
||||
import { CheckResult } from "./checkFns/CheckResult";
|
||||
import { HealthReceipt } from "./HealthReceipt";
|
||||
import { Trigger } from "./trigger";
|
||||
import { TriggerInput } from "./trigger/TriggerInput";
|
||||
import { defaultTrigger } from "./trigger/defaultTrigger";
|
||||
import { InterfaceReceipt } from "../mainFn/interfaceReceipt"
|
||||
import { Daemon, Effects } from "../types"
|
||||
import { CheckResult } from "./checkFns/CheckResult"
|
||||
import { HealthReceipt } from "./HealthReceipt"
|
||||
import { Trigger } from "./trigger"
|
||||
import { TriggerInput } from "./trigger/TriggerInput"
|
||||
import { defaultTrigger } from "./trigger/defaultTrigger"
|
||||
|
||||
export function healthCheck(o: {
|
||||
effects: Effects;
|
||||
name: string;
|
||||
trigger?: Trigger;
|
||||
fn(): Promise<CheckResult> | CheckResult;
|
||||
onFirstSuccess?: () => () => Promise<unknown> | unknown;
|
||||
effects: Effects
|
||||
name: string
|
||||
trigger?: Trigger
|
||||
fn(): Promise<CheckResult> | CheckResult
|
||||
onFirstSuccess?: () => () => Promise<unknown> | unknown
|
||||
}) {
|
||||
new Promise(async () => {
|
||||
let currentValue: TriggerInput = {
|
||||
lastResult: null,
|
||||
hadSuccess: false,
|
||||
};
|
||||
const getCurrentValue = () => currentValue;
|
||||
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue);
|
||||
}
|
||||
const getCurrentValue = () => currentValue
|
||||
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
||||
for (
|
||||
let res = await trigger.next();
|
||||
!res.done;
|
||||
res = await trigger.next()
|
||||
) {
|
||||
try {
|
||||
const { status, message } = await o.fn();
|
||||
const { status, message } = await o.fn()
|
||||
await o.effects.setHealth({
|
||||
name: o.name,
|
||||
status,
|
||||
message,
|
||||
});
|
||||
currentValue.hadSuccess = true;
|
||||
currentValue.lastResult = "success";
|
||||
})
|
||||
currentValue.hadSuccess = true
|
||||
currentValue.lastResult = "success"
|
||||
} catch (_) {
|
||||
currentValue.lastResult = "failure";
|
||||
currentValue.lastResult = "failure"
|
||||
}
|
||||
}
|
||||
});
|
||||
return {} as HealthReceipt;
|
||||
})
|
||||
return {} as HealthReceipt
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
declare const HealthProof: unique symbol;
|
||||
declare const HealthProof: unique symbol
|
||||
export type HealthReceipt = {
|
||||
[HealthProof]: never;
|
||||
};
|
||||
[HealthProof]: never
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HealthStatus } from "../../types";
|
||||
import { HealthStatus } from "../../types"
|
||||
|
||||
export type CheckResult = {
|
||||
status: HealthStatus;
|
||||
message?: string;
|
||||
};
|
||||
status: HealthStatus
|
||||
message?: string
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Effects } from "../../types";
|
||||
import { CheckResult } from "./CheckResult";
|
||||
import { Effects } from "../../types"
|
||||
import { CheckResult } from "./CheckResult"
|
||||
export function containsAddress(x: string, port: number) {
|
||||
const readPorts = x
|
||||
.split("\n")
|
||||
@@ -8,8 +8,8 @@ export function containsAddress(x: string, port: number) {
|
||||
.map((x) => x.split(" ").filter(Boolean)[1]?.split(":")?.[1])
|
||||
.filter(Boolean)
|
||||
.map((x) => Number.parseInt(x, 16))
|
||||
.filter(Number.isFinite);
|
||||
return readPorts.indexOf(port) >= 0;
|
||||
.filter(Number.isFinite)
|
||||
return readPorts.indexOf(port) >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,12 +26,12 @@ export async function checkPortListening(
|
||||
): Promise<CheckResult> {
|
||||
const hasAddress =
|
||||
containsAddress(await effects.runCommand(`cat /proc/net/tcp`), port) ||
|
||||
containsAddress(await effects.runCommand("cat /proc/net/udp"), port);
|
||||
containsAddress(await effects.runCommand("cat /proc/net/udp"), port)
|
||||
if (hasAddress) {
|
||||
return { status: "passing", message };
|
||||
return { status: "passing", message }
|
||||
}
|
||||
return {
|
||||
status: "failing",
|
||||
message: error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Effects } from "../../types";
|
||||
import { CheckResult } from "./CheckResult";
|
||||
import { timeoutPromise } from "./index";
|
||||
import { Effects } from "../../types"
|
||||
import { CheckResult } from "./CheckResult"
|
||||
import { timeoutPromise } from "./index"
|
||||
|
||||
/**
|
||||
* This is a helper function to check if a web url is reachable.
|
||||
@@ -23,9 +23,9 @@ export const checkWebUrl = async (
|
||||
message: successMessage,
|
||||
}))
|
||||
.catch((e) => {
|
||||
effects.warn(`Error while fetching URL: ${url}`);
|
||||
effects.error(JSON.stringify(e));
|
||||
effects.error(e.toString());
|
||||
return { status: "failing" as const, message: errorMessage };
|
||||
});
|
||||
};
|
||||
effects.console.warn(`Error while fetching URL: ${url}`)
|
||||
effects.console.error(JSON.stringify(e))
|
||||
effects.console.error(e.toString())
|
||||
return { status: "failing" as const, message: errorMessage }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { runHealthScript } from "./runHealthScript";
|
||||
export { checkPortListening } from "./checkPortListening";
|
||||
export { CheckResult } from "./CheckResult";
|
||||
export { checkWebUrl } from "./checkWebUrl";
|
||||
import { runHealthScript } from "./runHealthScript"
|
||||
export { checkPortListening } from "./checkPortListening"
|
||||
export { CheckResult } from "./CheckResult"
|
||||
export { checkWebUrl } from "./checkWebUrl"
|
||||
|
||||
export function timeoutPromise(ms: number, { message = "Timed out" } = {}) {
|
||||
return new Promise<never>((resolve, reject) =>
|
||||
setTimeout(() => reject(new Error(message)), ms),
|
||||
);
|
||||
)
|
||||
}
|
||||
export { runHealthScript };
|
||||
export { runHealthScript }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandType, Effects } from "../../types";
|
||||
import { CheckResult } from "./CheckResult";
|
||||
import { timeoutPromise } from "./index";
|
||||
import { CommandType, Effects } from "../../types"
|
||||
import { CheckResult } from "./CheckResult"
|
||||
import { timeoutPromise } from "./index"
|
||||
|
||||
/**
|
||||
* Running a health script, is used when we want to have a simple
|
||||
@@ -23,13 +23,13 @@ export const runHealthScript = async <A extends string>(
|
||||
effects.runCommand(runCommand, { timeoutMillis: timeout }),
|
||||
timeoutPromise(timeout),
|
||||
]).catch((e) => {
|
||||
effects.warn(errorMessage);
|
||||
effects.warn(JSON.stringify(e));
|
||||
effects.warn(e.toString());
|
||||
throw { status: "failing", message: errorMessage } as CheckResult;
|
||||
});
|
||||
effects.console.warn(errorMessage)
|
||||
effects.console.warn(JSON.stringify(e))
|
||||
effects.console.warn(e.toString())
|
||||
throw { status: "failing", message: errorMessage } as CheckResult
|
||||
})
|
||||
return {
|
||||
status: "passing",
|
||||
message: message(res),
|
||||
} as CheckResult;
|
||||
};
|
||||
} as CheckResult
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export * as checkFns from "./checkFns";
|
||||
export * as trigger from "./trigger";
|
||||
export * as checkFns from "./checkFns"
|
||||
export * as trigger from "./trigger"
|
||||
|
||||
export { TriggerInput } from "./trigger/TriggerInput";
|
||||
export { HealthReceipt } from "./HealthReceipt";
|
||||
export { ReadyProof } from "../mainFn/ReadyProof";
|
||||
export { TriggerInput } from "./trigger/TriggerInput"
|
||||
export { HealthReceipt } from "./HealthReceipt"
|
||||
export { ReadyProof } from "../mainFn/ReadyProof"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type TriggerInput = {
|
||||
lastResult?: "success" | "failure" | null;
|
||||
hadSuccess?: boolean;
|
||||
};
|
||||
lastResult?: "success" | "failure" | null
|
||||
hadSuccess?: boolean
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { TriggerInput } from "./TriggerInput";
|
||||
import { Trigger } from "./index";
|
||||
import { TriggerInput } from "./TriggerInput"
|
||||
import { Trigger } from "./index"
|
||||
|
||||
export function changeOnFirstSuccess(o: {
|
||||
beforeFirstSuccess: Trigger;
|
||||
afterFirstSuccess: Trigger;
|
||||
beforeFirstSuccess: Trigger
|
||||
afterFirstSuccess: Trigger
|
||||
}): Trigger {
|
||||
return async function* (getInput) {
|
||||
const beforeFirstSuccess = o.beforeFirstSuccess(getInput);
|
||||
yield;
|
||||
let currentValue = getInput();
|
||||
beforeFirstSuccess.next();
|
||||
const beforeFirstSuccess = o.beforeFirstSuccess(getInput)
|
||||
yield
|
||||
let currentValue = getInput()
|
||||
beforeFirstSuccess.next()
|
||||
for (
|
||||
let res = await beforeFirstSuccess.next();
|
||||
currentValue?.lastResult !== "success" && !res.done;
|
||||
res = await beforeFirstSuccess.next()
|
||||
) {
|
||||
yield;
|
||||
currentValue = getInput();
|
||||
yield
|
||||
currentValue = getInput()
|
||||
}
|
||||
const afterFirstSuccess = o.afterFirstSuccess(getInput);
|
||||
const afterFirstSuccess = o.afterFirstSuccess(getInput)
|
||||
for (
|
||||
let res = await afterFirstSuccess.next();
|
||||
!res.done;
|
||||
res = await afterFirstSuccess.next()
|
||||
) {
|
||||
yield;
|
||||
currentValue = getInput();
|
||||
yield
|
||||
currentValue = getInput()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export function cooldownTrigger(timeMs: number) {
|
||||
return async function* () {
|
||||
while (true) {
|
||||
await new Promise((resolve) => setTimeout(resolve, timeMs));
|
||||
yield;
|
||||
await new Promise((resolve) => setTimeout(resolve, timeMs))
|
||||
yield
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cooldownTrigger } from "./cooldownTrigger";
|
||||
import { changeOnFirstSuccess } from "./changeOnFirstSuccess";
|
||||
import { cooldownTrigger } from "./cooldownTrigger"
|
||||
import { changeOnFirstSuccess } from "./changeOnFirstSuccess"
|
||||
|
||||
export const defaultTrigger = changeOnFirstSuccess({
|
||||
beforeFirstSuccess: cooldownTrigger(0),
|
||||
afterFirstSuccess: cooldownTrigger(30000),
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TriggerInput } from "./TriggerInput";
|
||||
export { changeOnFirstSuccess } from "./changeOnFirstSuccess";
|
||||
export { cooldownTrigger } from "./cooldownTrigger";
|
||||
import { TriggerInput } from "./TriggerInput"
|
||||
export { changeOnFirstSuccess } from "./changeOnFirstSuccess"
|
||||
export { cooldownTrigger } from "./cooldownTrigger"
|
||||
|
||||
export type Trigger = (
|
||||
getInput: () => TriggerInput,
|
||||
) => AsyncIterator<unknown, unknown, never>;
|
||||
) => AsyncIterator<unknown, unknown, never>
|
||||
|
||||
36
lib/index.ts
36
lib/index.ts
@@ -1,18 +1,18 @@
|
||||
export * as backup from "./backup";
|
||||
export * as config from "./config";
|
||||
export * as configBuilder from "./config/builder";
|
||||
export * as configTypes from "./config/configTypes";
|
||||
export * as health from "./health";
|
||||
export * as healthUtil from "./health/checkFns";
|
||||
export * as mainFn from "./mainFn";
|
||||
export * as matches from "ts-matches";
|
||||
export * as T from "./types";
|
||||
export * as TOML from "@iarna/toml";
|
||||
export * as Types from "./types";
|
||||
export * as util from "./util";
|
||||
export * as YAML from "yaml";
|
||||
export * as properties from "./properties";
|
||||
export * as autoconfig from "./autoconfig";
|
||||
export * as actions from "./actions";
|
||||
export * as manifest from "./manifest";
|
||||
export * as inits from "./inits";
|
||||
export * as backup from "./backup"
|
||||
export * as config from "./config"
|
||||
export * as configBuilder from "./config/builder"
|
||||
export * as configTypes from "./config/configTypes"
|
||||
export * as health from "./health"
|
||||
export * as healthUtil from "./health/checkFns"
|
||||
export * as mainFn from "./mainFn"
|
||||
export * as matches from "ts-matches"
|
||||
export * as T from "./types"
|
||||
export * as TOML from "@iarna/toml"
|
||||
export * as Types from "./types"
|
||||
export * as util from "./util"
|
||||
export * as YAML from "yaml"
|
||||
export * as properties from "./properties"
|
||||
export * as autoconfig from "./autoconfig"
|
||||
export * as actions from "./actions"
|
||||
export * as manifest from "./manifest"
|
||||
export * as inits from "./inits"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { setupInit } from "./setupInit";
|
||||
export { setupUninstall } from "./setupUninstall";
|
||||
export { setupInstall } from "./setupInstall";
|
||||
export { setupInit } from "./setupInit"
|
||||
export { setupUninstall } from "./setupUninstall"
|
||||
export { setupInstall } from "./setupInstall"
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { ManifestVersion } from "../../manifest/ManifestTypes";
|
||||
import { Effects } from "../../types";
|
||||
import { Utils } from "../../util";
|
||||
import { ManifestVersion } from "../../manifest/ManifestTypes"
|
||||
import { Effects } from "../../types"
|
||||
import { Utils } from "../../util"
|
||||
|
||||
export class Migration<Version extends ManifestVersion> {
|
||||
constructor(
|
||||
readonly options: {
|
||||
version: Version;
|
||||
up: (opts: { effects: Effects }) => Promise<void>;
|
||||
down: (opts: { effects: Effects }) => Promise<void>;
|
||||
version: Version
|
||||
up: (opts: { effects: Effects }) => Promise<void>
|
||||
down: (opts: { effects: Effects }) => Promise<void>
|
||||
},
|
||||
) {}
|
||||
static of<Version extends ManifestVersion>(options: {
|
||||
version: Version;
|
||||
up: (opts: { effects: Effects }) => Promise<void>;
|
||||
down: (opts: { effects: Effects }) => Promise<void>;
|
||||
version: Version
|
||||
up: (opts: { effects: Effects }) => Promise<void>
|
||||
down: (opts: { effects: Effects }) => Promise<void>
|
||||
}) {
|
||||
return new Migration(options);
|
||||
return new Migration(options)
|
||||
}
|
||||
|
||||
async up(opts: { effects: Effects }) {
|
||||
this.up(opts);
|
||||
this.up(opts)
|
||||
}
|
||||
|
||||
async down(opts: { effects: Effects }) {
|
||||
this.down(opts);
|
||||
this.down(opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { setupActions } from "../../actions/setupActions";
|
||||
import { EmVer } from "../../emverLite/mod";
|
||||
import { GenericManifest } from "../../manifest/ManifestTypes";
|
||||
import { ExpectedExports } from "../../types";
|
||||
import { once } from "../../util/once";
|
||||
import { Migration } from "./Migration";
|
||||
import { setupActions } from "../../actions/setupActions"
|
||||
import { EmVer } from "../../emverLite/mod"
|
||||
import { GenericManifest } from "../../manifest/ManifestTypes"
|
||||
import { ExpectedExports } from "../../types"
|
||||
import { once } from "../../util/once"
|
||||
import { Migration } from "./Migration"
|
||||
|
||||
export class Migrations {
|
||||
private constructor(
|
||||
@@ -13,27 +13,27 @@ export class Migrations {
|
||||
private sortedMigrations = once(() => {
|
||||
const migrationsAsVersions = (this.migrations as Array<Migration<any>>).map(
|
||||
(x) => [EmVer.parse(x.options.version), x] as const,
|
||||
);
|
||||
migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0]));
|
||||
return migrationsAsVersions;
|
||||
});
|
||||
private currentVersion = once(() => EmVer.parse(this.manifest.version));
|
||||
)
|
||||
migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0]))
|
||||
return migrationsAsVersions
|
||||
})
|
||||
private currentVersion = once(() => EmVer.parse(this.manifest.version))
|
||||
static of<Migrations extends Array<Migration<any>>>(
|
||||
manifest: GenericManifest,
|
||||
...migrations: EnsureUniqueId<Migrations>
|
||||
) {
|
||||
return new Migrations(manifest, migrations as Array<Migration<any>>);
|
||||
return new Migrations(manifest, migrations as Array<Migration<any>>)
|
||||
}
|
||||
async init({
|
||||
effects,
|
||||
previousVersion,
|
||||
}: Parameters<ExpectedExports.init>[0]) {
|
||||
if (!!previousVersion) {
|
||||
const previousVersionEmVer = EmVer.parse(previousVersion);
|
||||
const previousVersionEmVer = EmVer.parse(previousVersion)
|
||||
for (const [_, migration] of this.sortedMigrations()
|
||||
.filter((x) => x[0].greaterThan(previousVersionEmVer))
|
||||
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
||||
await migration.up({ effects });
|
||||
await migration.up({ effects })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,12 +42,12 @@ export class Migrations {
|
||||
nextVersion,
|
||||
}: Parameters<ExpectedExports.uninit>[0]) {
|
||||
if (!!nextVersion) {
|
||||
const nextVersionEmVer = EmVer.parse(nextVersion);
|
||||
const reversed = [...this.sortedMigrations()].reverse();
|
||||
const nextVersionEmVer = EmVer.parse(nextVersion)
|
||||
const reversed = [...this.sortedMigrations()].reverse()
|
||||
for (const [_, migration] of reversed
|
||||
.filter((x) => x[0].greaterThan(nextVersionEmVer))
|
||||
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
||||
await migration.down({ effects });
|
||||
await migration.down({ effects })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export function setupMigrations<Migrations extends Array<Migration<any>>>(
|
||||
manifest: GenericManifest,
|
||||
...migrations: EnsureUniqueId<Migrations>
|
||||
) {
|
||||
return Migrations.of(manifest, ...migrations);
|
||||
return Migrations.of(manifest, ...migrations)
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { ExpectedExports } from "../types";
|
||||
import { Migrations } from "./migrations/setupMigrations";
|
||||
import { Install } from "./setupInstall";
|
||||
import { Uninstall } from "./setupUninstall";
|
||||
import { ExpectedExports } from "../types"
|
||||
import { Migrations } from "./migrations/setupMigrations"
|
||||
import { Install } from "./setupInstall"
|
||||
import { Uninstall } from "./setupUninstall"
|
||||
|
||||
export function setupInit<WrapperData>(
|
||||
migrations: Migrations,
|
||||
install: Install<WrapperData>,
|
||||
uninstall: Uninstall<WrapperData>,
|
||||
): {
|
||||
init: ExpectedExports.init;
|
||||
uninit: ExpectedExports.uninit;
|
||||
init: ExpectedExports.init
|
||||
uninit: ExpectedExports.uninit
|
||||
} {
|
||||
return {
|
||||
init: async (opts) => {
|
||||
await migrations.init(opts);
|
||||
await install.init(opts);
|
||||
await migrations.init(opts)
|
||||
await install.init(opts)
|
||||
},
|
||||
uninit: async (opts) => {
|
||||
await migrations.uninit(opts);
|
||||
await uninstall.uninit(opts);
|
||||
await migrations.uninit(opts)
|
||||
await uninstall.uninit(opts)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Effects, ExpectedExports } from "../types";
|
||||
import { Utils, utils } from "../util";
|
||||
import { Effects, ExpectedExports } from "../types"
|
||||
import { Utils, utils } from "../util"
|
||||
|
||||
export type InstallFn<WrapperData> = (opts: {
|
||||
effects: Effects;
|
||||
utils: Utils<WrapperData>;
|
||||
}) => Promise<void>;
|
||||
effects: Effects
|
||||
utils: Utils<WrapperData>
|
||||
}) => Promise<void>
|
||||
export class Install<WrapperData> {
|
||||
private constructor(readonly fn: InstallFn<WrapperData>) {}
|
||||
static of<WrapperData>(fn: InstallFn<WrapperData>) {
|
||||
return new Install(fn);
|
||||
return new Install(fn)
|
||||
}
|
||||
|
||||
async init({
|
||||
effects,
|
||||
previousVersion,
|
||||
}: Parameters<ExpectedExports.init>[0]) {
|
||||
if (!previousVersion) await this.fn({ effects, utils: utils(effects) });
|
||||
if (!previousVersion) await this.fn({ effects, utils: utils(effects) })
|
||||
}
|
||||
}
|
||||
|
||||
export function setupInstall<WrapperData>(fn: InstallFn<WrapperData>) {
|
||||
return Install.of(fn);
|
||||
return Install.of(fn)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Effects, ExpectedExports } from "../types";
|
||||
import { Utils, utils } from "../util";
|
||||
import { Effects, ExpectedExports } from "../types"
|
||||
import { Utils, utils } from "../util"
|
||||
|
||||
export type UninstallFn<WrapperData> = (opts: {
|
||||
effects: Effects;
|
||||
utils: Utils<WrapperData>;
|
||||
}) => Promise<void>;
|
||||
effects: Effects
|
||||
utils: Utils<WrapperData>
|
||||
}) => Promise<void>
|
||||
export class Uninstall<WrapperData> {
|
||||
private constructor(readonly fn: UninstallFn<WrapperData>) {}
|
||||
static of<WrapperData>(fn: UninstallFn<WrapperData>) {
|
||||
return new Uninstall(fn);
|
||||
return new Uninstall(fn)
|
||||
}
|
||||
|
||||
async uninit({
|
||||
effects,
|
||||
nextVersion,
|
||||
}: Parameters<ExpectedExports.uninit>[0]) {
|
||||
if (!nextVersion) await this.fn({ effects, utils: utils(effects) });
|
||||
if (!nextVersion) await this.fn({ effects, utils: utils(effects) })
|
||||
}
|
||||
}
|
||||
|
||||
export function setupUninstall<WrapperData>(fn: UninstallFn<WrapperData>) {
|
||||
return Uninstall.of(fn);
|
||||
return Uninstall.of(fn)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
declare const AddressProof: unique symbol;
|
||||
declare const AddressProof: unique symbol
|
||||
export type AddressReceipt = {
|
||||
[AddressProof]: never;
|
||||
};
|
||||
[AddressProof]: never
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { HealthReceipt, ReadyProof } from "../health";
|
||||
import { CheckResult } from "../health/checkFns";
|
||||
import { Trigger } from "../health/trigger";
|
||||
import { defaultTrigger } from "../health/trigger/defaultTrigger";
|
||||
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types";
|
||||
import { InterfaceReceipt } from "./interfaceReceipt";
|
||||
import { HealthReceipt, ReadyProof } from "../health"
|
||||
import { CheckResult } from "../health/checkFns"
|
||||
import { Trigger } from "../health/trigger"
|
||||
import { defaultTrigger } from "../health/trigger/defaultTrigger"
|
||||
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
|
||||
import { InterfaceReceipt } from "./interfaceReceipt"
|
||||
type Daemon<
|
||||
Ids extends string | never,
|
||||
Command extends string,
|
||||
Id extends string,
|
||||
> = {
|
||||
id: Id;
|
||||
command: ValidIfNoStupidEscape<Command> | [string, ...string[]];
|
||||
id: Id
|
||||
command: ValidIfNoStupidEscape<Command> | [string, ...string[]]
|
||||
|
||||
ready: {
|
||||
display: null | {
|
||||
name: string;
|
||||
message: string;
|
||||
};
|
||||
fn: () => Promise<CheckResult> | CheckResult;
|
||||
trigger?: Trigger;
|
||||
};
|
||||
requires?: Exclude<Ids, Id>[];
|
||||
intervalTime?: number;
|
||||
};
|
||||
name: string
|
||||
message: string
|
||||
}
|
||||
fn: () => Promise<CheckResult> | CheckResult
|
||||
trigger?: Trigger
|
||||
}
|
||||
requires?: Exclude<Ids, Id>[]
|
||||
intervalTime?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Used during the main of a function, it allows us to describe and ensure a set of daemons are running.
|
||||
@@ -56,49 +56,49 @@ export class Daemons<Ids extends string | never> {
|
||||
) {}
|
||||
|
||||
static of(config: {
|
||||
effects: Effects;
|
||||
started: (onTerm: () => void) => null;
|
||||
interfaceReceipt: InterfaceReceipt;
|
||||
healthReceipts: HealthReceipt[];
|
||||
effects: Effects
|
||||
started: (onTerm: () => void) => null
|
||||
interfaceReceipt: InterfaceReceipt
|
||||
healthReceipts: HealthReceipt[]
|
||||
}) {
|
||||
return new Daemons<never>(config.effects, config.started);
|
||||
return new Daemons<never>(config.effects, config.started)
|
||||
}
|
||||
addDaemon<Id extends string, Command extends string>(
|
||||
newDaemon: Daemon<Ids, Command, Id>,
|
||||
) {
|
||||
const daemons = ((this?.daemons ?? []) as any[]).concat(newDaemon);
|
||||
return new Daemons<Ids | Id>(this.effects, this.started, daemons);
|
||||
const daemons = ((this?.daemons ?? []) as any[]).concat(newDaemon)
|
||||
return new Daemons<Ids | Id>(this.effects, this.started, daemons)
|
||||
}
|
||||
|
||||
async build() {
|
||||
const daemonsStarted = {} as Record<Ids, Promise<DaemonReturned>>;
|
||||
const { effects } = this;
|
||||
const daemons = this.daemons ?? [];
|
||||
const daemonsStarted = {} as Record<Ids, Promise<DaemonReturned>>
|
||||
const { effects } = this
|
||||
const daemons = this.daemons ?? []
|
||||
for (const daemon of daemons) {
|
||||
const requiredPromise = Promise.all(
|
||||
daemon.requires?.map((id) => daemonsStarted[id]) ?? [],
|
||||
);
|
||||
)
|
||||
daemonsStarted[daemon.id] = requiredPromise.then(async () => {
|
||||
const { command } = daemon;
|
||||
const { command } = daemon
|
||||
|
||||
const child = effects.runDaemon(command);
|
||||
let currentInput = {};
|
||||
const getCurrentInput = () => currentInput;
|
||||
const child = effects.runDaemon(command)
|
||||
let currentInput = {}
|
||||
const getCurrentInput = () => currentInput
|
||||
const trigger = (daemon.ready.trigger ?? defaultTrigger)(
|
||||
getCurrentInput,
|
||||
);
|
||||
)
|
||||
for (
|
||||
let res = await trigger.next();
|
||||
!res.done;
|
||||
res = await trigger.next()
|
||||
) {
|
||||
const response = await daemon.ready.fn();
|
||||
const response = await daemon.ready.fn()
|
||||
if (response.status === "passing") {
|
||||
return child;
|
||||
return child
|
||||
}
|
||||
}
|
||||
return child;
|
||||
});
|
||||
return child
|
||||
})
|
||||
}
|
||||
return {
|
||||
async term() {
|
||||
@@ -106,15 +106,15 @@ export class Daemons<Ids extends string | never> {
|
||||
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
||||
x.then((x) => x.term()),
|
||||
),
|
||||
);
|
||||
)
|
||||
},
|
||||
async wait() {
|
||||
await Promise.all(
|
||||
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
||||
x.then((x) => x.wait()),
|
||||
),
|
||||
);
|
||||
)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
import { Origin } from "./Origin";
|
||||
import { once } from "../util/once"
|
||||
import { Origin } from "./Origin"
|
||||
|
||||
/**
|
||||
* Pulled from https://www.oreilly.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
|
||||
* to test ipv4 addresses
|
||||
*/
|
||||
export const regexToTestIp4 = once(() => /(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)
|
||||
/**
|
||||
* Pulled from https://ihateregex.io/expr/ipv6/
|
||||
* to test ipv6 addresses
|
||||
*/
|
||||
export const ipv6 = once(
|
||||
() =>
|
||||
/(([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]))/,
|
||||
)
|
||||
export class LocalBinding {
|
||||
constructor(readonly localHost: string, readonly ipHost: string) {}
|
||||
constructor(readonly localHost: string, readonly ipHosts: string[]) {}
|
||||
createOrigins(protocol: string) {
|
||||
return {
|
||||
local: new Origin(protocol, this.localHost),
|
||||
ip: new Origin(protocol, this.ipHost),
|
||||
};
|
||||
ip: this.ipHosts.map((x) => new Origin(protocol, x)),
|
||||
ipv4: this.ipHosts
|
||||
.filter(regexToTestIp4().test)
|
||||
.map((x) => new Origin(protocol, x)),
|
||||
ipv6: this.ipHosts
|
||||
.filter(ipv6().test)
|
||||
.map((x) => new Origin(protocol, x)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Effects } from "../types";
|
||||
import { LocalBinding } from "./LocalBinding";
|
||||
import { Effects } from "../types"
|
||||
import { LocalBinding } from "./LocalBinding"
|
||||
|
||||
export class LocalPort {
|
||||
constructor(readonly effects: Effects, readonly id: string) {}
|
||||
async bindLan(internalPort: number) {
|
||||
const port = await this.effects.bindLan({
|
||||
constructor(readonly effects: Effects) {}
|
||||
static async bindLan(effects: Effects, internalPort: number) {
|
||||
const port = await effects.bindLan({
|
||||
internalPort,
|
||||
name: this.id,
|
||||
});
|
||||
const localAddress = `${await this.effects.getLocalHostname()}:${port}`;
|
||||
const ipAddress = `${await this.effects.getIPHostname()}:${port}`;
|
||||
return new LocalBinding(localAddress, ipAddress);
|
||||
})
|
||||
const localAddress = `${await effects.getLocalHostname()}:${port}`
|
||||
const ipAddress = await (
|
||||
await effects.getIPHostname()
|
||||
).map((x) => `${x}:${port}`)
|
||||
return new LocalBinding(localAddress, ipAddress)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { Effects } from "../types";
|
||||
import { LocalPort } from "./LocalPort";
|
||||
import { TorHostname } from "./TorHostname";
|
||||
import { Effects } from "../types"
|
||||
import { LocalPort } from "./LocalPort"
|
||||
import { TorHostname } from "./TorHostname"
|
||||
|
||||
export class NetworkBuilder {
|
||||
static of(effects: Effects) {
|
||||
return new NetworkBuilder(effects);
|
||||
return new NetworkBuilder(effects)
|
||||
}
|
||||
private constructor(private effects: Effects) {}
|
||||
|
||||
getTorHostName(id: string) {
|
||||
return new TorHostname(this.effects, id);
|
||||
}
|
||||
getPort(id: string) {
|
||||
return new LocalPort(this.effects, id);
|
||||
return new TorHostname(this.effects, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Effects } from "../types";
|
||||
import { AddressReceipt } from "./AddressReceipt";
|
||||
import { Origin } from "./Origin";
|
||||
import { Effects } from "../types"
|
||||
import { AddressReceipt } from "./AddressReceipt"
|
||||
import { Origin } from "./Origin"
|
||||
|
||||
export class NetworkInterfaceBuilder {
|
||||
constructor(
|
||||
readonly options: {
|
||||
effects: Effects;
|
||||
name: string;
|
||||
id: string;
|
||||
description: string;
|
||||
ui: boolean;
|
||||
basic?: null | { password: string; username: string };
|
||||
path?: string;
|
||||
search?: Record<string, string>;
|
||||
effects: Effects
|
||||
name: string
|
||||
id: string
|
||||
description: string
|
||||
ui: boolean
|
||||
basic?: null | { password: string; username: string }
|
||||
path?: string
|
||||
search?: Record<string, string>
|
||||
},
|
||||
) {}
|
||||
|
||||
async exportAddresses(addresses: Iterable<Origin>) {
|
||||
const { name, description, id, ui, path, search } = this.options;
|
||||
const { name, description, id, ui, path, search } = this.options
|
||||
for (const origin of addresses) {
|
||||
const address = origin.withAuth(this.options.basic);
|
||||
const address = origin.withAuth(this.options.basic)
|
||||
await this.options.effects.exportAddress({
|
||||
name,
|
||||
description,
|
||||
@@ -28,8 +28,8 @@ export class NetworkInterfaceBuilder {
|
||||
ui,
|
||||
path,
|
||||
search,
|
||||
});
|
||||
})
|
||||
}
|
||||
return {} as AddressReceipt;
|
||||
return {} as AddressReceipt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ export class Origin {
|
||||
withAuth(
|
||||
origin?:
|
||||
| {
|
||||
password: string;
|
||||
username: string;
|
||||
password: string
|
||||
username: string
|
||||
}
|
||||
| null
|
||||
| undefined,
|
||||
@@ -13,6 +13,6 @@ export class Origin {
|
||||
// prettier-ignore
|
||||
const urlAuth = !!(origin) ? `${origin.username}:${origin.password}@` :
|
||||
'';
|
||||
return `${this.protocol}://${urlAuth}${this.host}`;
|
||||
return `${this.protocol}://${urlAuth}${this.host}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export declare const ReadyProof: unique symbol;
|
||||
export declare const ReadyProof: unique symbol
|
||||
export type ReadyReceipt = {
|
||||
[ReadyProof]: never;
|
||||
};
|
||||
[ReadyProof]: never
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Daemon } from "../types";
|
||||
import { ReadyProof } from "./ReadyProof";
|
||||
import { Daemon } from "../types"
|
||||
import { ReadyProof } from "./ReadyProof"
|
||||
|
||||
export type RunningMainRet = {
|
||||
[ReadyProof]: never;
|
||||
daemon: Daemon;
|
||||
};
|
||||
[ReadyProof]: never
|
||||
daemon: Daemon
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Origin } from "./Origin";
|
||||
import { Origin } from "./Origin"
|
||||
|
||||
export class TorBinding {
|
||||
constructor(readonly host: string) {}
|
||||
createOrigin(protocol: string) {
|
||||
return new Origin(protocol, this.host);
|
||||
return new Origin(protocol, this.host)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Effects } from "../types";
|
||||
import { TorBinding } from "./TorBinding";
|
||||
import { Effects } from "../types"
|
||||
import { TorBinding } from "./TorBinding"
|
||||
|
||||
export class TorHostname {
|
||||
constructor(readonly effects: Effects, readonly id: string) {}
|
||||
static of(effects: Effects, id: string) {
|
||||
return new TorHostname(effects, id);
|
||||
return new TorHostname(effects, id)
|
||||
}
|
||||
async bindTor(internalPort: number, externalPort: number) {
|
||||
const address = await this.effects.bindTor({
|
||||
internalPort,
|
||||
name: this.id,
|
||||
externalPort,
|
||||
});
|
||||
return new TorBinding(address);
|
||||
})
|
||||
return new TorBinding(address)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AddressReceipt } from "./AddressReceipt";
|
||||
import { InterfaceReceipt } from "./interfaceReceipt";
|
||||
import { AddressReceipt } from "./AddressReceipt"
|
||||
import { InterfaceReceipt } from "./interfaceReceipt"
|
||||
|
||||
export const exportInterfaces = (
|
||||
_firstProof: AddressReceipt,
|
||||
..._rest: AddressReceipt[]
|
||||
) => ({} as InterfaceReceipt);
|
||||
export default exportInterfaces;
|
||||
) => ({} as InterfaceReceipt)
|
||||
export default exportInterfaces
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Effects, ExpectedExports } from "../types";
|
||||
import { Utils, utils } from "../util";
|
||||
import { Daemons } from "./Daemons";
|
||||
export * as network from "./exportInterfaces";
|
||||
export { LocalBinding } from "./LocalBinding";
|
||||
export { LocalPort } from "./LocalPort";
|
||||
export { NetworkBuilder } from "./NetworkBuilder";
|
||||
export { NetworkInterfaceBuilder } from "./NetworkInterfaceBuilder";
|
||||
export { Origin } from "./Origin";
|
||||
export { TorBinding } from "./TorBinding";
|
||||
export { TorHostname } from "./TorHostname";
|
||||
import { Effects, ExpectedExports } from "../types"
|
||||
import { Utils, utils } from "../util"
|
||||
import { Daemons } from "./Daemons"
|
||||
export * as network from "./exportInterfaces"
|
||||
export { LocalBinding } from "./LocalBinding"
|
||||
export { LocalPort } from "./LocalPort"
|
||||
export { NetworkBuilder } from "./NetworkBuilder"
|
||||
export { NetworkInterfaceBuilder } from "./NetworkInterfaceBuilder"
|
||||
export { Origin } from "./Origin"
|
||||
export { TorBinding } from "./TorBinding"
|
||||
export { TorHostname } from "./TorHostname"
|
||||
|
||||
export { Daemons } from "./Daemons";
|
||||
export { Daemons } from "./Daemons"
|
||||
|
||||
/**
|
||||
* Used to ensure that the main function is running with the valid proofs.
|
||||
@@ -24,16 +24,16 @@ export { Daemons } from "./Daemons";
|
||||
*/
|
||||
export const setupMain = <WrapperData>(
|
||||
fn: (o: {
|
||||
effects: Effects;
|
||||
started(onTerm: () => void): null;
|
||||
utils: Utils<WrapperData>;
|
||||
effects: Effects
|
||||
started(onTerm: () => void): null
|
||||
utils: Utils<WrapperData>
|
||||
}) => Promise<Daemons<any>>,
|
||||
): ExpectedExports.main => {
|
||||
return async (options) => {
|
||||
const result = await fn({
|
||||
...options,
|
||||
utils: utils<WrapperData>(options.effects),
|
||||
});
|
||||
await result.build().then((x) => x.wait());
|
||||
};
|
||||
};
|
||||
})
|
||||
await result.build().then((x) => x.wait())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
declare const InterfaceProof: unique symbol;
|
||||
declare const InterfaceProof: unique symbol
|
||||
export type InterfaceReceipt = {
|
||||
[InterfaceProof]: never;
|
||||
};
|
||||
[InterfaceProof]: never
|
||||
}
|
||||
|
||||
@@ -1,96 +1,96 @@
|
||||
import { ValidEmVer } from "../emverLite/mod";
|
||||
import { ActionMetaData } from "../types";
|
||||
import { ValidEmVer } from "../emverLite/mod"
|
||||
import { ActionMetaData } from "../types"
|
||||
|
||||
export interface Container {
|
||||
/** This should be pointing to a docker container name */
|
||||
image: string;
|
||||
image: string
|
||||
/** These should match the manifest data volumes */
|
||||
mounts: Record<string, string>;
|
||||
mounts: Record<string, string>
|
||||
/** Default is 64mb */
|
||||
shmSizeMb?: `${number}${"mb" | "gb" | "b" | "kb"}`;
|
||||
shmSizeMb?: `${number}${"mb" | "gb" | "b" | "kb"}`
|
||||
/** if more than 30s to shutdown */
|
||||
sigtermTimeout?: `${number}${"s" | "m" | "h"}`;
|
||||
sigtermTimeout?: `${number}${"s" | "m" | "h"}`
|
||||
}
|
||||
|
||||
export type ManifestVersion = ValidEmVer;
|
||||
export type ManifestVersion = ValidEmVer
|
||||
|
||||
export interface GenericManifest {
|
||||
/** The package identifier used by the OS. This must be unique amongst all other known packages */
|
||||
id: string;
|
||||
id: string
|
||||
/** A human readable service title */
|
||||
title: string;
|
||||
title: string
|
||||
/** Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOs - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of the service */
|
||||
version: ManifestVersion;
|
||||
version: ManifestVersion
|
||||
/** Release notes for the update - can be a string, paragraph or URL */
|
||||
releaseNotes: string;
|
||||
releaseNotes: string
|
||||
/** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/
|
||||
license: string; // name of license
|
||||
license: string // name of license
|
||||
/** A list of normie (hosted, SaaS, custodial, etc) services this services intends to replace */
|
||||
replaces: string[];
|
||||
replaces: string[]
|
||||
/** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this), any scripts necessary for configuration, backups, actions, or health checks (more below). This key must exist. But could be embedded into the source repository */
|
||||
wrapperRepo: string;
|
||||
wrapperRepo: string
|
||||
/** The original project repository URL. There is no upstream repo in this example */
|
||||
upstreamRepo: string;
|
||||
upstreamRepo: string
|
||||
/** URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues */
|
||||
supportSite: string;
|
||||
supportSite: string
|
||||
/** URL to the marketing site for the project. If there is no marketing site, it can link to the original project repository */
|
||||
marketingSite: string;
|
||||
marketingSite: string
|
||||
/** URL where users can donate to the upstream project */
|
||||
donationUrl: string | null;
|
||||
donationUrl: string | null
|
||||
/**Human readable descriptions for the service. These are used throughout the StartOS user interface, primarily in the marketplace. */
|
||||
description: {
|
||||
/**This is the first description visible to the user in the marketplace */
|
||||
short: string;
|
||||
short: string
|
||||
/** This description will display with additional details in the service's individual marketplace page */
|
||||
long: string;
|
||||
};
|
||||
long: string
|
||||
}
|
||||
/** These assets are static files necessary for packaging the service for Start9 (into an s9pk). Each value is a path to the specified asset. If an asset is missing from this list, or otherwise denoted, it will be defaulted to the values denoted below. */
|
||||
assets: {
|
||||
icon: string; // file path
|
||||
instructions: string; // file path
|
||||
license: string; // file path
|
||||
};
|
||||
icon: string // file path
|
||||
instructions: string // file path
|
||||
license: string // file path
|
||||
}
|
||||
/**Defines the containers needed to run the main and mounted volumes */
|
||||
containers: Record<string, Container>;
|
||||
containers: Record<string, Container>
|
||||
/**This denotes any data, asset, or pointer volumes that should be connected when the "docker run" command is invoked */
|
||||
volumes: Record<string, string>;
|
||||
actions: Array<ActionMetaData>;
|
||||
volumes: Record<string, string>
|
||||
actions: Array<ActionMetaData>
|
||||
alerts: {
|
||||
install: string | null;
|
||||
update: string | null;
|
||||
uninstall: string | null;
|
||||
restore: string | null;
|
||||
start: string | null;
|
||||
stop: string | null;
|
||||
};
|
||||
dependencies: Record<string, Dependency>;
|
||||
install: string | null
|
||||
update: string | null
|
||||
uninstall: string | null
|
||||
restore: string | null
|
||||
start: string | null
|
||||
stop: string | null
|
||||
}
|
||||
dependencies: Record<string, Dependency>
|
||||
}
|
||||
|
||||
export interface Dependency {
|
||||
/** The range of versions that would satisfy the dependency
|
||||
* ie: >=3.4.5 && <4.0.0
|
||||
*/
|
||||
version: string;
|
||||
version: string
|
||||
/**
|
||||
* A human readable explanation on what the dependency is used for
|
||||
*/
|
||||
description: string | null;
|
||||
description: string | null
|
||||
requirement:
|
||||
| {
|
||||
type: "opt-in";
|
||||
type: "opt-in"
|
||||
/**
|
||||
* The human readable explanation on how to opt-in to the dependency
|
||||
*/
|
||||
how: string;
|
||||
how: string
|
||||
}
|
||||
| {
|
||||
type: "opt-out";
|
||||
type: "opt-out"
|
||||
/**
|
||||
* The human readable explanation on how to opt-out to the dependency
|
||||
*/
|
||||
how: string;
|
||||
how: string
|
||||
}
|
||||
| {
|
||||
type: "required";
|
||||
};
|
||||
type: "required"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { setupManifest } from "./setupManifest";
|
||||
export * as ManifestTypes from "./ManifestTypes";
|
||||
export { setupManifest } from "./setupManifest"
|
||||
export * as ManifestTypes from "./ManifestTypes"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GenericManifest, ManifestVersion } from "./ManifestTypes";
|
||||
import { GenericManifest, ManifestVersion } from "./ManifestTypes"
|
||||
|
||||
export function setupManifest<
|
||||
Id extends string,
|
||||
@@ -6,11 +6,11 @@ export function setupManifest<
|
||||
Dependencies extends Record<string, unknown>,
|
||||
Volumes extends Record<string, unknown>,
|
||||
Manifest extends GenericManifest & {
|
||||
dependencies: Dependencies;
|
||||
id: Id;
|
||||
version: Version;
|
||||
volumes: Volumes;
|
||||
dependencies: Dependencies
|
||||
id: Id
|
||||
version: Version
|
||||
volumes: Volumes
|
||||
},
|
||||
>(manifest: Manifest): Manifest {
|
||||
return manifest;
|
||||
return manifest
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { PackagePropertyGroup } from "../types";
|
||||
import { PropertyString } from "./PropertyString";
|
||||
import { PackagePropertyGroup } from "../types"
|
||||
import { PropertyString } from "./PropertyString"
|
||||
|
||||
export class PropertyGroup {
|
||||
private constructor(readonly data: PackagePropertyGroup) {}
|
||||
static of(options: {
|
||||
description: string;
|
||||
value: (PropertyGroup | PropertyString)[];
|
||||
name: string;
|
||||
description: string
|
||||
value: (PropertyGroup | PropertyString)[]
|
||||
name: string
|
||||
}) {
|
||||
return new PropertyGroup({
|
||||
type: "object",
|
||||
name: options.name,
|
||||
description: options.description,
|
||||
value: options.value.map((x) => x.data),
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PackagePropertyString } from "../types";
|
||||
import { PackagePropertyString } from "../types"
|
||||
|
||||
export class PropertyString {
|
||||
private constructor(readonly data: PackagePropertyString) {}
|
||||
@@ -6,6 +6,6 @@ export class PropertyString {
|
||||
return new PropertyString({
|
||||
...value,
|
||||
type: "string",
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ExpectedExports, Properties } from "../types";
|
||||
import { ExpectedExports, Properties } from "../types"
|
||||
|
||||
import { PropertyGroup } from "./PropertyGroup";
|
||||
import { PropertyString } from "./PropertyString";
|
||||
export { PropertyGroup } from "./PropertyGroup";
|
||||
export { PropertyString } from "./PropertyString";
|
||||
import { PropertyGroup } from "./PropertyGroup"
|
||||
import { PropertyString } from "./PropertyString"
|
||||
export { PropertyGroup } from "./PropertyGroup"
|
||||
export { PropertyString } from "./PropertyString"
|
||||
|
||||
export const test = "";
|
||||
export const test = ""
|
||||
|
||||
export type UnionToIntersection<T> = ((x: T) => any) extends (x: infer R) => any
|
||||
? R
|
||||
: never;
|
||||
: never
|
||||
|
||||
/**
|
||||
* This is used during creating the type of properties fn in the service package.
|
||||
@@ -20,18 +20,18 @@ export type UnionToIntersection<T> = ((x: T) => any) extends (x: infer R) => any
|
||||
*/
|
||||
export function setupProperties<WrapperData>(
|
||||
fn: (args: {
|
||||
wrapperData: WrapperData;
|
||||
wrapperData: WrapperData
|
||||
}) => void | Promise<void> | Promise<(PropertyGroup | PropertyString)[]>,
|
||||
): ExpectedExports.properties {
|
||||
return (async (options) => {
|
||||
const result = await fn(
|
||||
options as {
|
||||
wrapperData: WrapperData & typeof options.wrapperData;
|
||||
wrapperData: WrapperData & typeof options.wrapperData
|
||||
},
|
||||
);
|
||||
)
|
||||
if (result) {
|
||||
const answer: Properties = result.map((x) => x.data);
|
||||
return answer;
|
||||
const answer: Properties = result.map((x) => x.data)
|
||||
return answer
|
||||
}
|
||||
}) as ExpectedExports.properties;
|
||||
}) as ExpectedExports.properties
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { testOutput } from "./output.test";
|
||||
import { Config } from "../config/builder/config";
|
||||
import { List } from "../config/builder/list";
|
||||
import { Value } from "../config/builder/value";
|
||||
import { Variants } from "../config/builder/variants";
|
||||
import { testOutput } from "./output.test"
|
||||
import { Config } from "../config/builder/config"
|
||||
import { List } from "../config/builder/list"
|
||||
import { Value } from "../config/builder/value"
|
||||
import { Variants } from "../config/builder/variants"
|
||||
|
||||
describe("builder tests", () => {
|
||||
test("text", () => {
|
||||
const bitcoinPropertiesBuilt: {
|
||||
"peer-tor-address": {
|
||||
name: string;
|
||||
description: string | null;
|
||||
type: "text";
|
||||
};
|
||||
name: string
|
||||
description: string | null
|
||||
type: "text"
|
||||
}
|
||||
} = Config.of({
|
||||
"peer-tor-address": Value.text({
|
||||
name: "Peer tor address",
|
||||
description: "The Tor address of the peer interface",
|
||||
required: true,
|
||||
required: { default: null },
|
||||
}),
|
||||
}).build();
|
||||
}).build()
|
||||
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
|
||||
/*json*/ `{
|
||||
"peer-tor-address": {
|
||||
@@ -38,9 +38,9 @@ describe("builder tests", () => {
|
||||
.replaceAll("\n", " ")
|
||||
.replaceAll(/\s{2,}/g, "")
|
||||
.replaceAll(": ", ":"),
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("values", () => {
|
||||
test("toggle", () => {
|
||||
@@ -49,71 +49,70 @@ describe("values", () => {
|
||||
description: null,
|
||||
warning: null,
|
||||
default: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast(false);
|
||||
testOutput<typeof validator._TYPE, boolean>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast(false)
|
||||
testOutput<typeof validator._TYPE, boolean>()(null)
|
||||
})
|
||||
test("text", () => {
|
||||
const value = Value.text({
|
||||
name: "Testing",
|
||||
required: { default: null },
|
||||
})
|
||||
const validator = value.validator()
|
||||
const rawIs = value.build()
|
||||
validator.unsafeCast("test text")
|
||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||
testOutput<typeof validator._TYPE, string>()(null)
|
||||
})
|
||||
test("text", () => {
|
||||
const value = Value.text({
|
||||
name: "Testing",
|
||||
required: { default: "null" },
|
||||
})
|
||||
const validator = value.validator()
|
||||
const rawIs = value.build()
|
||||
validator.unsafeCast("test text")
|
||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||
testOutput<typeof validator._TYPE, string>()(null)
|
||||
})
|
||||
test("optional text", () => {
|
||||
const value = Value.text({
|
||||
name: "Testing",
|
||||
required: false,
|
||||
description: null,
|
||||
warning: null,
|
||||
masked: false,
|
||||
placeholder: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [],
|
||||
inputmode: "text",
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("test text");
|
||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null);
|
||||
});
|
||||
test("text", () => {
|
||||
const value = Value.text({
|
||||
name: "Testing",
|
||||
required: true,
|
||||
description: null,
|
||||
warning: null,
|
||||
masked: false,
|
||||
placeholder: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [],
|
||||
inputmode: "text",
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("test text");
|
||||
testOutput<typeof validator._TYPE, string>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
const rawIs = value.build()
|
||||
validator.unsafeCast("test text")
|
||||
validator.unsafeCast(null)
|
||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||
})
|
||||
test("color", () => {
|
||||
const value = Value.color({
|
||||
name: "Testing",
|
||||
required: false,
|
||||
description: null,
|
||||
warning: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("#000000");
|
||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast("#000000")
|
||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||
})
|
||||
test("datetime", () => {
|
||||
const value = Value.datetime({
|
||||
name: "Testing",
|
||||
required: true,
|
||||
required: { default: null },
|
||||
description: null,
|
||||
warning: null,
|
||||
inputmode: "date",
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("2021-01-01");
|
||||
testOutput<typeof validator._TYPE, string>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast("2021-01-01")
|
||||
testOutput<typeof validator._TYPE, string>()(null)
|
||||
})
|
||||
test("optional datetime", () => {
|
||||
const value = Value.datetime({
|
||||
name: "Testing",
|
||||
@@ -124,11 +123,11 @@ describe("values", () => {
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("2021-01-01");
|
||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast("2021-01-01")
|
||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||
})
|
||||
test("textarea", () => {
|
||||
const value = Value.textarea({
|
||||
name: "Testing",
|
||||
@@ -138,15 +137,15 @@ describe("values", () => {
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
placeholder: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("test text");
|
||||
testOutput<typeof validator._TYPE, string>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast("test text")
|
||||
testOutput<typeof validator._TYPE, string>()(null)
|
||||
})
|
||||
test("number", () => {
|
||||
const value = Value.number({
|
||||
name: "Testing",
|
||||
required: true,
|
||||
required: { default: null },
|
||||
integer: false,
|
||||
description: null,
|
||||
warning: null,
|
||||
@@ -155,11 +154,11 @@ describe("values", () => {
|
||||
step: null,
|
||||
units: null,
|
||||
placeholder: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast(2);
|
||||
testOutput<typeof validator._TYPE, number>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast(2)
|
||||
testOutput<typeof validator._TYPE, number>()(null)
|
||||
})
|
||||
test("optional number", () => {
|
||||
const value = Value.number({
|
||||
name: "Testing",
|
||||
@@ -172,28 +171,28 @@ describe("values", () => {
|
||||
step: null,
|
||||
units: null,
|
||||
placeholder: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast(2);
|
||||
testOutput<typeof validator._TYPE, number | null | undefined>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast(2)
|
||||
testOutput<typeof validator._TYPE, number | null | undefined>()(null)
|
||||
})
|
||||
test("select", () => {
|
||||
const value = Value.select({
|
||||
name: "Testing",
|
||||
required: true,
|
||||
required: { default: null },
|
||||
values: {
|
||||
a: "A",
|
||||
b: "B",
|
||||
},
|
||||
description: null,
|
||||
warning: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("a");
|
||||
validator.unsafeCast("b");
|
||||
expect(() => validator.unsafeCast(null)).toThrowError();
|
||||
testOutput<typeof validator._TYPE, "a" | "b">()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast("a")
|
||||
validator.unsafeCast("b")
|
||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||
testOutput<typeof validator._TYPE, "a" | "b">()(null)
|
||||
})
|
||||
test("nullable select", () => {
|
||||
const value = Value.select({
|
||||
name: "Testing",
|
||||
@@ -204,13 +203,13 @@ describe("values", () => {
|
||||
},
|
||||
description: null,
|
||||
warning: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast("a");
|
||||
validator.unsafeCast("b");
|
||||
validator.unsafeCast(null);
|
||||
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast("a")
|
||||
validator.unsafeCast("b")
|
||||
validator.unsafeCast(null)
|
||||
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null)
|
||||
})
|
||||
test("multiselect", () => {
|
||||
const value = Value.multiselect({
|
||||
name: "Testing",
|
||||
@@ -223,12 +222,12 @@ describe("values", () => {
|
||||
warning: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast([]);
|
||||
validator.unsafeCast(["a", "b"]);
|
||||
testOutput<typeof validator._TYPE, Array<"a" | "b">>()(null);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast([])
|
||||
validator.unsafeCast(["a", "b"])
|
||||
testOutput<typeof validator._TYPE, Array<"a" | "b">>()(null)
|
||||
})
|
||||
test("object", () => {
|
||||
const value = Value.object(
|
||||
{
|
||||
@@ -244,16 +243,16 @@ describe("values", () => {
|
||||
default: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: true });
|
||||
testOutput<typeof validator._TYPE, { a: boolean }>()(null);
|
||||
});
|
||||
)
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ a: true })
|
||||
testOutput<typeof validator._TYPE, { a: boolean }>()(null)
|
||||
})
|
||||
test("union", () => {
|
||||
const value = Value.union(
|
||||
{
|
||||
name: "Testing",
|
||||
required: true,
|
||||
required: { default: null },
|
||||
description: null,
|
||||
warning: null,
|
||||
default: null,
|
||||
@@ -271,14 +270,14 @@ describe("values", () => {
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
|
||||
type Test = typeof validator._TYPE;
|
||||
)
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } })
|
||||
type Test = typeof validator._TYPE
|
||||
testOutput<Test, { unionSelectKey: "a"; unionValueKey: { b: boolean } }>()(
|
||||
null,
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
test("list", () => {
|
||||
const value = Value.list(
|
||||
List.number(
|
||||
@@ -289,12 +288,12 @@ describe("values", () => {
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast([1, 2, 3]);
|
||||
testOutput<typeof validator._TYPE, number[]>()(null);
|
||||
});
|
||||
});
|
||||
)
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast([1, 2, 3])
|
||||
testOutput<typeof validator._TYPE, number[]>()(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Builder List", () => {
|
||||
test("obj", () => {
|
||||
@@ -314,11 +313,11 @@ describe("Builder List", () => {
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast([{ test: true }]);
|
||||
testOutput<typeof validator._TYPE, { test: boolean }[]>()(null);
|
||||
});
|
||||
)
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast([{ test: true }])
|
||||
testOutput<typeof validator._TYPE, { test: boolean }[]>()(null)
|
||||
})
|
||||
test("text", () => {
|
||||
const value = Value.list(
|
||||
List.text(
|
||||
@@ -329,11 +328,11 @@ describe("Builder List", () => {
|
||||
patterns: [],
|
||||
},
|
||||
),
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast(["test", "text"]);
|
||||
testOutput<typeof validator._TYPE, string[]>()(null);
|
||||
});
|
||||
)
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast(["test", "text"])
|
||||
testOutput<typeof validator._TYPE, string[]>()(null)
|
||||
})
|
||||
test("number", () => {
|
||||
const value = Value.list(
|
||||
List.number(
|
||||
@@ -342,12 +341,12 @@ describe("Builder List", () => {
|
||||
},
|
||||
{ integer: true },
|
||||
),
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast([12, 45]);
|
||||
testOutput<typeof validator._TYPE, number[]>()(null);
|
||||
});
|
||||
});
|
||||
)
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast([12, 45])
|
||||
testOutput<typeof validator._TYPE, number[]>()(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Nested nullable values", () => {
|
||||
test("Testing text", () => {
|
||||
@@ -358,15 +357,13 @@ describe("Nested nullable values", () => {
|
||||
"If no name is provided, the name from config will be used",
|
||||
required: false,
|
||||
}),
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: null });
|
||||
validator.unsafeCast({ a: "test" });
|
||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError();
|
||||
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(
|
||||
null,
|
||||
);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ a: null })
|
||||
validator.unsafeCast({ a: "test" })
|
||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
||||
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
|
||||
})
|
||||
test("Testing number", () => {
|
||||
const value = Config.of({
|
||||
a: Value.number({
|
||||
@@ -382,15 +379,13 @@ describe("Nested nullable values", () => {
|
||||
step: null,
|
||||
units: null,
|
||||
}),
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: null });
|
||||
validator.unsafeCast({ a: 5 });
|
||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError();
|
||||
testOutput<typeof validator._TYPE, { a: number | null | undefined }>()(
|
||||
null,
|
||||
);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ a: null })
|
||||
validator.unsafeCast({ a: 5 })
|
||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
||||
testOutput<typeof validator._TYPE, { a: number | null | undefined }>()(null)
|
||||
})
|
||||
test("Testing color", () => {
|
||||
const value = Config.of({
|
||||
a: Value.color({
|
||||
@@ -400,15 +395,13 @@ describe("Nested nullable values", () => {
|
||||
required: false,
|
||||
warning: null,
|
||||
}),
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: null });
|
||||
validator.unsafeCast({ a: "5" });
|
||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError();
|
||||
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(
|
||||
null,
|
||||
);
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ a: null })
|
||||
validator.unsafeCast({ a: "5" })
|
||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
||||
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
|
||||
})
|
||||
test("Testing select", () => {
|
||||
const value = Config.of({
|
||||
a: Value.select({
|
||||
@@ -421,7 +414,7 @@ describe("Nested nullable values", () => {
|
||||
a: "A",
|
||||
},
|
||||
}),
|
||||
});
|
||||
})
|
||||
const higher = Value.select({
|
||||
name: "Temp Name",
|
||||
description: "If no name is provided, the name from config will be used",
|
||||
@@ -430,14 +423,14 @@ describe("Nested nullable values", () => {
|
||||
values: {
|
||||
a: "A",
|
||||
},
|
||||
}).build();
|
||||
}).build()
|
||||
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: null });
|
||||
validator.unsafeCast({ a: "a" });
|
||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError();
|
||||
testOutput<typeof validator._TYPE, { a: "a" | null | undefined }>()(null);
|
||||
});
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ a: null })
|
||||
validator.unsafeCast({ a: "a" })
|
||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
||||
testOutput<typeof validator._TYPE, { a: "a" | null | undefined }>()(null)
|
||||
})
|
||||
test("Testing multiselect", () => {
|
||||
const value = Config.of({
|
||||
a: Value.multiselect({
|
||||
@@ -453,11 +446,11 @@ describe("Nested nullable values", () => {
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
}),
|
||||
});
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: [] });
|
||||
validator.unsafeCast({ a: ["a"] });
|
||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError();
|
||||
testOutput<typeof validator._TYPE, { a: "a"[] }>()(null);
|
||||
});
|
||||
});
|
||||
})
|
||||
const validator = value.validator()
|
||||
validator.unsafeCast({ a: [] })
|
||||
validator.unsafeCast({ a: ["a"] })
|
||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
||||
testOutput<typeof validator._TYPE, { a: "a"[] }>()(null)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { ListValueSpecOf, isValueSpecListOf } from "../config/configTypes";
|
||||
import { Config } from "../config/builder/config";
|
||||
import { List } from "../config/builder/list";
|
||||
import { Value } from "../config/builder/value";
|
||||
import { ListValueSpecOf, isValueSpecListOf } from "../config/configTypes"
|
||||
import { Config } from "../config/builder/config"
|
||||
import { List } from "../config/builder/list"
|
||||
import { Value } from "../config/builder/value"
|
||||
|
||||
describe("Config Types", () => {
|
||||
test("isValueSpecListOf", () => {
|
||||
const options = [List.obj, List.text, List.number];
|
||||
const options = [List.obj, List.text, List.number]
|
||||
for (const option of options) {
|
||||
const test = option({} as any, { spec: Config.of({}) } as any) as any;
|
||||
const someList = Value.list(test).build();
|
||||
const test = option({} as any, { spec: Config.of({}) } as any) as any
|
||||
const someList = Value.list(test).build()
|
||||
if (isValueSpecListOf(someList, "text")) {
|
||||
someList.spec satisfies ListValueSpecOf<"text">;
|
||||
someList.spec satisfies ListValueSpecOf<"text">
|
||||
} else if (isValueSpecListOf(someList, "number")) {
|
||||
someList.spec satisfies ListValueSpecOf<"number">;
|
||||
someList.spec satisfies ListValueSpecOf<"number">
|
||||
} else if (isValueSpecListOf(someList, "object")) {
|
||||
someList.spec satisfies ListValueSpecOf<"object">;
|
||||
someList.spec satisfies ListValueSpecOf<"object">
|
||||
} else {
|
||||
throw new Error(
|
||||
"Failed to figure out the type: " + JSON.stringify(someList),
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,262 +1,262 @@
|
||||
import { EmVer, notRange, rangeAnd, rangeOf, rangeOr } from "../emverLite/mod";
|
||||
import { EmVer, notRange, rangeAnd, rangeOf, rangeOr } from "../emverLite/mod"
|
||||
describe("EmVer", () => {
|
||||
{
|
||||
{
|
||||
const checker = rangeOf("*");
|
||||
const checker = rangeOf("*")
|
||||
test("rangeOf('*')", () => {
|
||||
checker.check("1");
|
||||
checker.check("1.2");
|
||||
checker.check("1.2.3");
|
||||
checker.check("1.2.3.4");
|
||||
checker.check("1")
|
||||
checker.check("1.2")
|
||||
checker.check("1.2.3")
|
||||
checker.check("1.2.3.4")
|
||||
// @ts-expect-error
|
||||
checker.check("1.2.3.4.5");
|
||||
checker.check("1.2.3.4.5")
|
||||
// @ts-expect-error
|
||||
checker.check("1.2.3.4.5.6");
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
expect(checker.check("1.2")).toEqual(true);
|
||||
expect(checker.check("1.2.3.4")).toEqual(true);
|
||||
});
|
||||
checker.check("1.2.3.4.5.6")
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
expect(checker.check("1.2")).toEqual(true)
|
||||
expect(checker.check("1.2.3.4")).toEqual(true)
|
||||
})
|
||||
test("rangeOf('*') invalid", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => checker.check("a")).toThrow();
|
||||
expect(() => checker.check("a")).toThrow()
|
||||
// @ts-expect-error
|
||||
expect(() => checker.check("")).toThrow();
|
||||
expect(() => checker.check("1..3")).toThrow();
|
||||
});
|
||||
expect(() => checker.check("")).toThrow()
|
||||
expect(() => checker.check("1..3")).toThrow()
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const checker = rangeOf(">1.2.3.4");
|
||||
const checker = rangeOf(">1.2.3.4")
|
||||
test(`rangeOf(">1.2.3.4") valid`, () => {
|
||||
expect(checker.check("2-beta123")).toEqual(true);
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
expect(checker.check("1.2.3.5")).toEqual(true);
|
||||
expect(checker.check("2-beta123")).toEqual(true)
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
expect(checker.check("1.2.3.5")).toEqual(true)
|
||||
// @ts-expect-error
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(true)
|
||||
})
|
||||
|
||||
test(`rangeOf(">1.2.3.4") invalid`, () => {
|
||||
expect(checker.check("1.2.3.4")).toEqual(false);
|
||||
expect(checker.check("1.2.3")).toEqual(false);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2.3.4")).toEqual(false)
|
||||
expect(checker.check("1.2.3")).toEqual(false)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
})
|
||||
}
|
||||
{
|
||||
const checker = rangeOf("=1.2.3");
|
||||
const checker = rangeOf("=1.2.3")
|
||||
test(`rangeOf("=1.2.3") valid`, () => {
|
||||
expect(checker.check("1.2.3")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2.3")).toEqual(true)
|
||||
})
|
||||
|
||||
test(`rangeOf("=1.2.3") invalid`, () => {
|
||||
expect(checker.check("2")).toEqual(false);
|
||||
expect(checker.check("1.2.3.1")).toEqual(false);
|
||||
expect(checker.check("1.2")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("2")).toEqual(false)
|
||||
expect(checker.check("1.2.3.1")).toEqual(false)
|
||||
expect(checker.check("1.2")).toEqual(false)
|
||||
})
|
||||
}
|
||||
{
|
||||
const checker = rangeOf(">=1.2.3.4");
|
||||
const checker = rangeOf(">=1.2.3.4")
|
||||
test(`rangeOf(">=1.2.3.4") valid`, () => {
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
expect(checker.check("1.2.3.5")).toEqual(true);
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
expect(checker.check("1.2.3.5")).toEqual(true)
|
||||
// @ts-expect-error
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(true);
|
||||
expect(checker.check("1.2.3.4")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(true)
|
||||
expect(checker.check("1.2.3.4")).toEqual(true)
|
||||
})
|
||||
|
||||
test(`rangeOf(">=1.2.3.4") invalid`, () => {
|
||||
expect(checker.check("1.2.3")).toEqual(false);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2.3")).toEqual(false)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
})
|
||||
}
|
||||
{
|
||||
const checker = rangeOf("<1.2.3.4");
|
||||
const checker = rangeOf("<1.2.3.4")
|
||||
test(`rangeOf("<1.2.3.4") invalid`, () => {
|
||||
expect(checker.check("2")).toEqual(false);
|
||||
expect(checker.check("1.2.3.5")).toEqual(false);
|
||||
expect(checker.check("2")).toEqual(false)
|
||||
expect(checker.check("1.2.3.5")).toEqual(false)
|
||||
// @ts-expect-error
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(false);
|
||||
expect(checker.check("1.2.3.4")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(false)
|
||||
expect(checker.check("1.2.3.4")).toEqual(false)
|
||||
})
|
||||
|
||||
test(`rangeOf("<1.2.3.4") valid`, () => {
|
||||
expect(checker.check("1.2.3")).toEqual(true);
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2.3")).toEqual(true)
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
})
|
||||
}
|
||||
{
|
||||
const checker = rangeOf("<=1.2.3.4");
|
||||
const checker = rangeOf("<=1.2.3.4")
|
||||
test(`rangeOf("<=1.2.3.4") invalid`, () => {
|
||||
expect(checker.check("2")).toEqual(false);
|
||||
expect(checker.check("1.2.3.5")).toEqual(false);
|
||||
expect(checker.check("2")).toEqual(false)
|
||||
expect(checker.check("1.2.3.5")).toEqual(false)
|
||||
// @ts-expect-error
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(false)
|
||||
})
|
||||
|
||||
test(`rangeOf("<=1.2.3.4") valid`, () => {
|
||||
expect(checker.check("1.2.3")).toEqual(true);
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
expect(checker.check("1.2.3.4")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2.3")).toEqual(true)
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
expect(checker.check("1.2.3.4")).toEqual(true)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const checkA = rangeOf(">1");
|
||||
const checkB = rangeOf("<=2");
|
||||
const checkA = rangeOf(">1")
|
||||
const checkB = rangeOf("<=2")
|
||||
|
||||
const checker = rangeAnd(checkA, checkB);
|
||||
const checker = rangeAnd(checkA, checkB)
|
||||
test(`simple and(checkers) valid`, () => {
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
|
||||
expect(checker.check("1.1")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.1")).toEqual(true)
|
||||
})
|
||||
test(`simple and(checkers) invalid`, () => {
|
||||
expect(checker.check("2.1")).toEqual(false);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
expect(checker.check("0")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("2.1")).toEqual(false)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
expect(checker.check("0")).toEqual(false)
|
||||
})
|
||||
}
|
||||
{
|
||||
const checkA = rangeOf("<1");
|
||||
const checkB = rangeOf("=2");
|
||||
const checkA = rangeOf("<1")
|
||||
const checkB = rangeOf("=2")
|
||||
|
||||
const checker = rangeOr(checkA, checkB);
|
||||
const checker = rangeOr(checkA, checkB)
|
||||
test(`simple or(checkers) valid`, () => {
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
expect(checker.check("0.1")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
expect(checker.check("0.1")).toEqual(true)
|
||||
})
|
||||
test(`simple or(checkers) invalid`, () => {
|
||||
expect(checker.check("2.1")).toEqual(false);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
expect(checker.check("1.1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("2.1")).toEqual(false)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
expect(checker.check("1.1")).toEqual(false)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const checker = rangeOf("1.2.*");
|
||||
const checker = rangeOf("1.2.*")
|
||||
test(`rangeOf(1.2.*) valid`, () => {
|
||||
expect(checker.check("1.2")).toEqual(true);
|
||||
expect(checker.check("1.2.1")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2")).toEqual(true)
|
||||
expect(checker.check("1.2.1")).toEqual(true)
|
||||
})
|
||||
test(`rangeOf(1.2.*) invalid`, () => {
|
||||
expect(checker.check("1.3")).toEqual(false);
|
||||
expect(checker.check("1.3.1")).toEqual(false);
|
||||
expect(checker.check("1.3")).toEqual(false)
|
||||
expect(checker.check("1.3.1")).toEqual(false)
|
||||
|
||||
expect(checker.check("1.1.1")).toEqual(false);
|
||||
expect(checker.check("1.1")).toEqual(false);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
expect(checker.check("1.1.1")).toEqual(false)
|
||||
expect(checker.check("1.1")).toEqual(false)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
|
||||
expect(checker.check("2")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("2")).toEqual(false)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const checker = notRange(rangeOf("1.2.*"));
|
||||
const checker = notRange(rangeOf("1.2.*"))
|
||||
test(`notRange(rangeOf(1.2.*)) valid`, () => {
|
||||
expect(checker.check("1.3")).toEqual(true);
|
||||
expect(checker.check("1.3.1")).toEqual(true);
|
||||
expect(checker.check("1.3")).toEqual(true)
|
||||
expect(checker.check("1.3.1")).toEqual(true)
|
||||
|
||||
expect(checker.check("1.1.1")).toEqual(true);
|
||||
expect(checker.check("1.1")).toEqual(true);
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
expect(checker.check("1.1.1")).toEqual(true)
|
||||
expect(checker.check("1.1")).toEqual(true)
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
})
|
||||
test(`notRange(rangeOf(1.2.*)) invalid `, () => {
|
||||
expect(checker.check("1.2")).toEqual(false);
|
||||
expect(checker.check("1.2.1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2")).toEqual(false)
|
||||
expect(checker.check("1.2.1")).toEqual(false)
|
||||
})
|
||||
}
|
||||
{
|
||||
const checker = rangeOf("!1.2.*");
|
||||
const checker = rangeOf("!1.2.*")
|
||||
test(`!(rangeOf(1.2.*)) valid`, () => {
|
||||
expect(checker.check("1.3")).toEqual(true);
|
||||
expect(checker.check("1.3.1")).toEqual(true);
|
||||
expect(checker.check("1.3")).toEqual(true)
|
||||
expect(checker.check("1.3.1")).toEqual(true)
|
||||
|
||||
expect(checker.check("1.1.1")).toEqual(true);
|
||||
expect(checker.check("1.1")).toEqual(true);
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
expect(checker.check("1.1.1")).toEqual(true)
|
||||
expect(checker.check("1.1")).toEqual(true)
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
})
|
||||
test(`!(rangeOf(1.2.*)) invalid `, () => {
|
||||
expect(checker.check("1.2")).toEqual(false);
|
||||
expect(checker.check("1.2.1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2")).toEqual(false)
|
||||
expect(checker.check("1.2.1")).toEqual(false)
|
||||
})
|
||||
}
|
||||
{
|
||||
test(`no and ranges`, () => {
|
||||
expect(() => rangeAnd()).toThrow();
|
||||
});
|
||||
expect(() => rangeAnd()).toThrow()
|
||||
})
|
||||
test(`no or ranges`, () => {
|
||||
expect(() => rangeOr()).toThrow();
|
||||
});
|
||||
expect(() => rangeOr()).toThrow()
|
||||
})
|
||||
}
|
||||
{
|
||||
const checker = rangeOf("!>1.2.3.4");
|
||||
const checker = rangeOf("!>1.2.3.4")
|
||||
test(`rangeOf("!>1.2.3.4") invalid`, () => {
|
||||
expect(checker.check("2")).toEqual(false);
|
||||
expect(checker.check("1.2.3.5")).toEqual(false);
|
||||
expect(checker.check("2")).toEqual(false)
|
||||
expect(checker.check("1.2.3.5")).toEqual(false)
|
||||
// @ts-expect-error
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2.3.4.1")).toEqual(false)
|
||||
})
|
||||
|
||||
test(`rangeOf("!>1.2.3.4") valid`, () => {
|
||||
expect(checker.check("1.2.3.4")).toEqual(true);
|
||||
expect(checker.check("1.2.3")).toEqual(true);
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
});
|
||||
expect(checker.check("1.2.3.4")).toEqual(true)
|
||||
expect(checker.check("1.2.3")).toEqual(true)
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
test(">1 && =1.2", () => {
|
||||
const checker = rangeOf(">1 && =1.2");
|
||||
const checker = rangeOf(">1 && =1.2")
|
||||
|
||||
expect(checker.check("1.2")).toEqual(true);
|
||||
expect(checker.check("1.2.1")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2")).toEqual(true)
|
||||
expect(checker.check("1.2.1")).toEqual(false)
|
||||
})
|
||||
test("=1 || =2", () => {
|
||||
const checker = rangeOf("=1 || =2");
|
||||
const checker = rangeOf("=1 || =2")
|
||||
|
||||
expect(checker.check("1")).toEqual(true);
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
expect(checker.check("3")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1")).toEqual(true)
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
expect(checker.check("3")).toEqual(false)
|
||||
})
|
||||
|
||||
test(">1 && =1.2 || =2", () => {
|
||||
const checker = rangeOf(">1 && =1.2 || =2");
|
||||
const checker = rangeOf(">1 && =1.2 || =2")
|
||||
|
||||
expect(checker.check("1.2")).toEqual(true);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
expect(checker.check("3")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.2")).toEqual(true)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
expect(checker.check("3")).toEqual(false)
|
||||
})
|
||||
|
||||
test("&& before || order of operationns: <1.5 && >1 || >1.5 && <3", () => {
|
||||
const checker = rangeOf("<1.5 && >1 || >1.5 && <3");
|
||||
expect(checker.check("1.1")).toEqual(true);
|
||||
expect(checker.check("2")).toEqual(true);
|
||||
const checker = rangeOf("<1.5 && >1 || >1.5 && <3")
|
||||
expect(checker.check("1.1")).toEqual(true)
|
||||
expect(checker.check("2")).toEqual(true)
|
||||
|
||||
expect(checker.check("1.5")).toEqual(false);
|
||||
expect(checker.check("1")).toEqual(false);
|
||||
expect(checker.check("3")).toEqual(false);
|
||||
});
|
||||
expect(checker.check("1.5")).toEqual(false)
|
||||
expect(checker.check("1")).toEqual(false)
|
||||
expect(checker.check("3")).toEqual(false)
|
||||
})
|
||||
|
||||
test("Compare function on the emver", () => {
|
||||
const a = EmVer.from("1.2.3");
|
||||
const b = EmVer.from("1.2.4");
|
||||
const a = EmVer.from("1.2.3")
|
||||
const b = EmVer.from("1.2.4")
|
||||
|
||||
expect(a.compare(b)).toEqual("less");
|
||||
expect(b.compare(a)).toEqual("greater");
|
||||
expect(a.compare(a)).toEqual("equal");
|
||||
});
|
||||
expect(a.compare(b)).toEqual("less")
|
||||
expect(b.compare(a)).toEqual("greater")
|
||||
expect(a.compare(a)).toEqual("equal")
|
||||
})
|
||||
test("Compare for sort function on the emver", () => {
|
||||
const a = EmVer.from("1.2.3");
|
||||
const b = EmVer.from("1.2.4");
|
||||
const a = EmVer.from("1.2.3")
|
||||
const b = EmVer.from("1.2.4")
|
||||
|
||||
expect(a.compareForSort(b)).toEqual(-1);
|
||||
expect(b.compareForSort(a)).toEqual(1);
|
||||
expect(a.compareForSort(a)).toEqual(0);
|
||||
});
|
||||
expect(a.compareForSort(b)).toEqual(-1)
|
||||
expect(b.compareForSort(a)).toEqual(1)
|
||||
expect(a.compareForSort(a)).toEqual(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { containsAddress } from "../health/checkFns/checkPortListening";
|
||||
import { containsAddress } from "../health/checkFns/checkPortListening"
|
||||
|
||||
describe("Health ready check", () => {
|
||||
it("Should be able to parse an example information", () => {
|
||||
@@ -9,9 +9,9 @@ describe("Health ready check", () => {
|
||||
1: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634477 1 0000000000000000 100 0 0 10 0
|
||||
2: 0B00007F:9671 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21635458 1 0000000000000000 100 0 0 10 0
|
||||
3: 00000000:0D73 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21634479 1 0000000000000000 100 0 0 10 0
|
||||
`;
|
||||
`
|
||||
|
||||
expect(containsAddress(input, 80)).toBe(true);
|
||||
expect(containsAddress(input, 1234)).toBe(false);
|
||||
});
|
||||
});
|
||||
expect(containsAddress(input, 80)).toBe(true)
|
||||
expect(containsAddress(input, 1234)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { writeConvertedFileFromOld } from "../../scripts/oldSpecToBuilder";
|
||||
import { writeConvertedFileFromOld } from "../../scripts/oldSpecToBuilder"
|
||||
|
||||
writeConvertedFileFromOld(
|
||||
"./lib/test/output.ts", // Make the location
|
||||
@@ -408,4 +408,4 @@ writeConvertedFileFromOld(
|
||||
{
|
||||
startSdk: "../",
|
||||
},
|
||||
);
|
||||
)
|
||||
|
||||
@@ -3,44 +3,44 @@ import {
|
||||
unionSelectKey,
|
||||
UnionValueKey,
|
||||
unionValueKey,
|
||||
} from "../config/configTypes";
|
||||
import { deepMerge } from "../util";
|
||||
import { InputSpec, matchInputSpec } from "./output";
|
||||
} from "../config/configTypes"
|
||||
import { deepMerge } from "../util"
|
||||
import { InputSpec, matchInputSpec } from "./output"
|
||||
|
||||
export type IfEquals<T, U, Y = unknown, N = never> = (<G>() => G extends T
|
||||
? 1
|
||||
: 2) extends <G>() => G extends U ? 1 : 2
|
||||
? Y
|
||||
: N;
|
||||
: N
|
||||
export function testOutput<A, B>(): (c: IfEquals<A, B>) => null {
|
||||
return () => null;
|
||||
return () => null
|
||||
}
|
||||
|
||||
/// Testing the types of the input spec
|
||||
testOutput<InputSpec["rpc"]["enable"], boolean>()(null);
|
||||
testOutput<InputSpec["rpc"]["username"], string>()(null);
|
||||
testOutput<InputSpec["rpc"]["username"], string>()(null);
|
||||
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"]["servertimeout"], number>()(null);
|
||||
>()(null)
|
||||
testOutput<InputSpec["rpc"]["advanced"]["servertimeout"], number>()(null)
|
||||
testOutput<
|
||||
InputSpec["advanced"]["peers"]["addnode"][0]["hostname"],
|
||||
string | null | undefined
|
||||
>()(null);
|
||||
>()(null)
|
||||
testOutput<
|
||||
InputSpec["testListUnion"][0]["union"][UnionValueKey]["name"],
|
||||
string
|
||||
>()(null);
|
||||
>()(null)
|
||||
testOutput<InputSpec["testListUnion"][0]["union"][UnionSelectKey], "lnd">()(
|
||||
null,
|
||||
);
|
||||
)
|
||||
|
||||
// @ts-expect-error Because enable should be a boolean
|
||||
testOutput<InputSpec["rpc"]["enable"], string>()(null);
|
||||
testOutput<InputSpec["rpc"]["enable"], string>()(null)
|
||||
// prettier-ignore
|
||||
// @ts-expect-error Expect that the string is the one above
|
||||
testOutput<InputSpec["testListUnion"][0][UnionSelectKey][UnionSelectKey], "unionSelectKey">()(null);
|
||||
@@ -98,24 +98,24 @@ describe("Inputs", () => {
|
||||
},
|
||||
bloomfilters: { peerbloomfilters: false },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("test valid input", () => {
|
||||
const output = matchInputSpec.unsafeCast(validInput);
|
||||
expect(output).toEqual(validInput);
|
||||
});
|
||||
const output = matchInputSpec.unsafeCast(validInput)
|
||||
expect(output).toEqual(validInput)
|
||||
})
|
||||
test("test no longer care about the conversion of min/max and validating", () => {
|
||||
matchInputSpec.unsafeCast(
|
||||
deepMerge({}, validInput, { rpc: { advanced: { threads: 0 } } }),
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
test("test errors should throw for number in string", () => {
|
||||
expect(() =>
|
||||
matchInputSpec.unsafeCast(
|
||||
deepMerge({}, validInput, { rpc: { enable: 2 } }),
|
||||
),
|
||||
).toThrowError();
|
||||
});
|
||||
).toThrowError()
|
||||
})
|
||||
test("Test that we set serialversion to something not segwit or non-segwit", () => {
|
||||
expect(() =>
|
||||
matchInputSpec.unsafeCast(
|
||||
@@ -123,6 +123,6 @@ describe("Inputs", () => {
|
||||
rpc: { advanced: { serialversion: "testing" } },
|
||||
}),
|
||||
),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
).toThrowError()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { deepMerge } from "../util/deepMerge";
|
||||
import { deepMerge } from "../util/deepMerge"
|
||||
|
||||
describe("deepMerge", () => {
|
||||
test("deepMerge({}, {a: 1}, {b: 2}) should return {a: 1, b: 2}", () => {
|
||||
expect(deepMerge({}, { a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
expect(deepMerge({}, { a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 })
|
||||
})
|
||||
test("deepMerge(null, [1,2,3]) should equal [1,2,3]", () => {
|
||||
expect(deepMerge(null, [1, 2, 3])).toEqual([1, 2, 3]);
|
||||
});
|
||||
expect(deepMerge(null, [1, 2, 3])).toEqual([1, 2, 3])
|
||||
})
|
||||
test("deepMerge({a: {b: 1, c:2}}, {a: {b: 3}}) should equal {a: {b: 3, c: 2}}", () => {
|
||||
expect(deepMerge({ a: { b: 1, c: 2 } }, { a: { b: 3 } })).toEqual({
|
||||
a: { b: 3, c: 2 },
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
test("deepMerge([1,2,3], [2,3,4]) should equal [2,3,4]", () => {
|
||||
expect(deepMerge([1, 2, 3], [2, 3, 4])).toEqual([2, 3, 4]);
|
||||
});
|
||||
});
|
||||
expect(deepMerge([1, 2, 3], [2, 3, 4])).toEqual([2, 3, 4])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,81 +1,79 @@
|
||||
import { T } from "..";
|
||||
import { utils } from "../util";
|
||||
import { T } from ".."
|
||||
import { utils } from "../util"
|
||||
|
||||
type WrapperType = {
|
||||
config: {
|
||||
someValue: string;
|
||||
};
|
||||
};
|
||||
someValue: string
|
||||
}
|
||||
}
|
||||
const todo = <A>(): A => {
|
||||
throw new Error("not implemented");
|
||||
};
|
||||
const noop = () => {};
|
||||
throw new Error("not implemented")
|
||||
}
|
||||
const noop = () => {}
|
||||
describe("wrapperData", () => {
|
||||
test.skip("types", async () => {
|
||||
utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
|
||||
"/config/someValue",
|
||||
"someValue",
|
||||
);
|
||||
)
|
||||
utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
|
||||
"/config/someValue",
|
||||
|
||||
// @ts-expect-error Type is wrong for the setting value
|
||||
5,
|
||||
);
|
||||
)
|
||||
utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
|
||||
// @ts-expect-error Path is wrong
|
||||
"/config/someVae3lue",
|
||||
"someValue",
|
||||
);
|
||||
)
|
||||
|
||||
todo<T.Effects>().setWrapperData<WrapperType, "/config/someValue">({
|
||||
path: "/config/someValue",
|
||||
value: "someValueIn",
|
||||
});
|
||||
})
|
||||
todo<T.Effects>().setWrapperData<WrapperType, "/config/some2Value">({
|
||||
//@ts-expect-error Path is wrong
|
||||
path: "/config/someValue",
|
||||
//@ts-expect-error Path is wrong
|
||||
value: "someValueIn",
|
||||
});
|
||||
})
|
||||
todo<T.Effects>().setWrapperData<WrapperType, "/config/someValue">({
|
||||
//@ts-expect-error Path is wrong
|
||||
path: "/config/some2Value",
|
||||
value: "someValueIn",
|
||||
});
|
||||
|
||||
(await utils<WrapperType>(todo<T.Effects>())
|
||||
})
|
||||
;(await utils<WrapperType>(todo<T.Effects>())
|
||||
.getOwnWrapperData("/config/someValue")
|
||||
.const()) satisfies string;
|
||||
(await utils<WrapperType>(todo<T.Effects>())
|
||||
.const()) satisfies string
|
||||
;(await utils<WrapperType>(todo<T.Effects>())
|
||||
.getOwnWrapperData("/config")
|
||||
.const()) satisfies WrapperType["config"];
|
||||
.const()) satisfies WrapperType["config"]
|
||||
await utils<WrapperType>(todo<T.Effects>())
|
||||
// @ts-expect-error Path is wrong
|
||||
.getOwnWrapperData("/config/somdsfeValue")
|
||||
.const();
|
||||
(await utils<WrapperType>(todo<T.Effects>())
|
||||
.const()
|
||||
;(await utils<WrapperType>(todo<T.Effects>())
|
||||
.getOwnWrapperData("/config/someValue")
|
||||
// @ts-expect-error satisfies type is wrong
|
||||
.const()) satisfies number;
|
||||
(await utils<WrapperType>(todo<T.Effects>())
|
||||
.const()) satisfies number
|
||||
;(await utils<WrapperType>(todo<T.Effects>())
|
||||
// @ts-expect-error Path is wrong
|
||||
.getOwnWrapperData("/config/")
|
||||
.const()) satisfies WrapperType["config"];
|
||||
|
||||
(await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
||||
.const()) satisfies WrapperType["config"]
|
||||
;(await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
||||
path: "/config/someValue",
|
||||
callback: noop,
|
||||
})) satisfies string;
|
||||
})) satisfies string
|
||||
await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
||||
// @ts-expect-error Path is wrong as in it doesn't match above
|
||||
path: "/config/someV2alue",
|
||||
callback: noop,
|
||||
});
|
||||
})
|
||||
await todo<T.Effects>().getWrapperData<WrapperType, "/config/someV2alue">({
|
||||
// @ts-expect-error Path is wrong as in it doesn't exists in wrapper type
|
||||
path: "/config/someV2alue",
|
||||
callback: noop,
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
448
lib/types.ts
448
lib/types.ts
@@ -1,38 +1,36 @@
|
||||
export * as configTypes from "./config/configTypes";
|
||||
import { InputSpec } from "./config/configTypes";
|
||||
import { DependenciesReceipt } from "./config/setupConfig";
|
||||
export * as configTypes from "./config/configTypes"
|
||||
import { InputSpec } from "./config/configTypes"
|
||||
import { DependenciesReceipt } from "./config/setupConfig"
|
||||
|
||||
export type ExportedAction = (options: {
|
||||
effects: Effects;
|
||||
input?: Record<string, unknown>;
|
||||
}) => Promise<ActionResult>;
|
||||
effects: Effects
|
||||
input?: Record<string, unknown>
|
||||
}) => Promise<ActionResult>
|
||||
|
||||
export namespace ExpectedExports {
|
||||
version: 1;
|
||||
version: 1
|
||||
/** Set configuration is called after we have modified and saved the configuration in the start9 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<void>;
|
||||
effects: Effects
|
||||
input: Record<string, unknown>
|
||||
}) => Promise<void>
|
||||
/** Get configuration returns a shape that describes the format that the start9 ui will generate, and later send to the set config */
|
||||
export type getConfig = (options: {
|
||||
effects: Effects;
|
||||
config: unknown;
|
||||
}) => Promise<ConfigRes>;
|
||||
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. */
|
||||
// export type dependencies = Dependencies;
|
||||
/** For backing up service data though the startOS 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 startOS UI create backup flow. Backup restores are also triggered via the startOS UI, or doing a system restore flow during setup. */
|
||||
export type restoreBackup = (options: {
|
||||
effects: Effects;
|
||||
}) => Promise<unknown>;
|
||||
effects: Effects
|
||||
}) => Promise<unknown>
|
||||
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
|
||||
export type properties = <WrapperData>(options: {
|
||||
wrapperData: WrapperData;
|
||||
}) => Promise<Properties | null | undefined | void>;
|
||||
wrapperData: WrapperData
|
||||
}) => Promise<Properties | null | undefined | void>
|
||||
|
||||
// /** 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.
|
||||
@@ -48,49 +46,49 @@ export namespace ExpectedExports {
|
||||
* service starting, and that file would indicate that it would rescan all the data.
|
||||
*/
|
||||
export type actions = {
|
||||
[id: string]: ExportedAction;
|
||||
};
|
||||
[id: string]: ExportedAction
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export type main = (options: {
|
||||
effects: Effects;
|
||||
started(onTerm: () => void): null;
|
||||
}) => Promise<unknown>;
|
||||
effects: Effects
|
||||
started(onTerm: () => void): null
|
||||
}) => Promise<unknown>
|
||||
|
||||
/**
|
||||
* After a shutdown, if we wanted to do any operations to clean up things, like
|
||||
* set the action as unavailable or something.
|
||||
*/
|
||||
export type afterShutdown = (options: {
|
||||
effects: Effects;
|
||||
}) => Promise<unknown>;
|
||||
effects: Effects
|
||||
}) => Promise<unknown>
|
||||
|
||||
/**
|
||||
* Every time a package completes an install, this function is called before the main.
|
||||
* Can be used to do migration like things.
|
||||
*/
|
||||
export type init = (options: {
|
||||
effects: Effects;
|
||||
previousVersion: null | string;
|
||||
}) => Promise<unknown>;
|
||||
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 called.
|
||||
*/
|
||||
export type uninit = (options: {
|
||||
effects: Effects;
|
||||
nextVersion: null | string;
|
||||
}) => Promise<unknown>;
|
||||
effects: Effects
|
||||
nextVersion: null | string
|
||||
}) => Promise<unknown>
|
||||
|
||||
/** Auto configure is used to make sure that other dependencies have the values t
|
||||
* that this service could use.
|
||||
*/
|
||||
export type autoConfig = Record<PackageId, AutoConfigure>;
|
||||
export type autoConfig = Record<PackageId, AutoConfigure>
|
||||
}
|
||||
export type TimeMs = number;
|
||||
export type VersionString = string;
|
||||
export type TimeMs = number
|
||||
export type VersionString = string
|
||||
|
||||
/**
|
||||
* AutoConfigure is used as the value to the key of package id,
|
||||
@@ -99,17 +97,17 @@ export type VersionString = string;
|
||||
export type AutoConfigure = {
|
||||
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
||||
check(options: {
|
||||
effects: Effects;
|
||||
localConfig: unknown;
|
||||
remoteConfig: unknown;
|
||||
}): Promise<void>;
|
||||
effects: Effects
|
||||
localConfig: unknown
|
||||
remoteConfig: unknown
|
||||
}): Promise<void>
|
||||
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
||||
autoConfigure(options: {
|
||||
effects: Effects;
|
||||
localConfig: unknown;
|
||||
remoteConfig: unknown;
|
||||
}): Promise<unknown>;
|
||||
};
|
||||
effects: Effects
|
||||
localConfig: unknown
|
||||
remoteConfig: unknown
|
||||
}): Promise<unknown>
|
||||
}
|
||||
|
||||
export type ValidIfNoStupidEscape<A> = A extends
|
||||
| `${string}'"'"'${string}`
|
||||
@@ -117,126 +115,130 @@ export type ValidIfNoStupidEscape<A> = A extends
|
||||
? never
|
||||
: "" extends A & ""
|
||||
? never
|
||||
: A;
|
||||
: A
|
||||
|
||||
export type ConfigRes = {
|
||||
/** This should be the previous config, that way during set config we start with the previous */
|
||||
config?: null | Record<string, unknown>;
|
||||
config?: null | Record<string, unknown>
|
||||
/** Shape that is describing the form in the ui */
|
||||
spec: InputSpec;
|
||||
};
|
||||
spec: InputSpec
|
||||
}
|
||||
|
||||
declare const DaemonProof: unique symbol;
|
||||
declare const DaemonProof: unique symbol
|
||||
export type DaemonReceipt = {
|
||||
[DaemonProof]: never;
|
||||
};
|
||||
[DaemonProof]: never
|
||||
}
|
||||
export type Daemon = {
|
||||
wait(): Promise<string>;
|
||||
term(): Promise<void>;
|
||||
[DaemonProof]: never;
|
||||
};
|
||||
wait(): Promise<string>
|
||||
term(): Promise<void>
|
||||
[DaemonProof]: never
|
||||
}
|
||||
|
||||
export type HealthStatus = "passing" | "warning" | "failing" | "disabled";
|
||||
export type HealthStatus = "passing" | "warning" | "failing" | "disabled"
|
||||
|
||||
export type CommandType<A extends string> =
|
||||
| ValidIfNoStupidEscape<A>
|
||||
| [string, ...string[]];
|
||||
| [string, ...string[]]
|
||||
|
||||
export type DaemonReturned = {
|
||||
wait(): Promise<string>;
|
||||
term(): Promise<void>;
|
||||
};
|
||||
wait(): Promise<string>
|
||||
term(): Promise<void>
|
||||
}
|
||||
|
||||
export type ActionMetaData = {
|
||||
name: string;
|
||||
description: string;
|
||||
id: string;
|
||||
input: null | InputSpec;
|
||||
runningOnly: boolean;
|
||||
name: string
|
||||
description: string
|
||||
id: string
|
||||
input: null | InputSpec
|
||||
runningOnly: boolean
|
||||
/**
|
||||
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
||||
*/
|
||||
group?: string;
|
||||
};
|
||||
group?: string
|
||||
}
|
||||
|
||||
/** Used to reach out from the pure js runtime */
|
||||
export type Effects = {
|
||||
/** Usable when not sandboxed */
|
||||
writeFile(input: {
|
||||
path: string;
|
||||
volumeId: string;
|
||||
toWrite: string;
|
||||
}): Promise<void>;
|
||||
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
||||
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
||||
path: string
|
||||
volumeId: string
|
||||
toWrite: string
|
||||
}): Promise<void>
|
||||
readFile(input: { volumeId: string; path: string }): Promise<string>
|
||||
metadata(input: { volumeId: string; path: string }): Promise<Metadata>
|
||||
/** Create a directory. Usable when not sandboxed */
|
||||
createDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
createDir(input: { volumeId: string; path: string }): Promise<string>
|
||||
|
||||
readDir(input: { volumeId: string; path: string }): Promise<string[]>;
|
||||
readDir(input: { volumeId: string; path: string }): Promise<string[]>
|
||||
/** Remove a directory. Usable when not sandboxed */
|
||||
removeDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
||||
removeDir(input: { volumeId: string; path: string }): Promise<string>
|
||||
removeFile(input: { volumeId: string; path: string }): Promise<void>
|
||||
|
||||
/** Write a json file into an object. Usable when not sandboxed */
|
||||
writeJsonFile(input: {
|
||||
volumeId: string;
|
||||
path: string;
|
||||
toWrite: Record<string, unknown>;
|
||||
}): Promise<void>;
|
||||
volumeId: string
|
||||
path: string
|
||||
toWrite: Record<string, unknown>
|
||||
}): Promise<void>
|
||||
|
||||
/** Read a json file into an object */
|
||||
readJsonFile(input: {
|
||||
volumeId: string;
|
||||
path: string;
|
||||
}): Promise<Record<string, unknown>>;
|
||||
volumeId: string
|
||||
path: string
|
||||
}): Promise<Record<string, unknown>>
|
||||
|
||||
runCommand<A extends string>(
|
||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||
input?: {
|
||||
timeoutMillis?: number;
|
||||
timeoutMillis?: number
|
||||
},
|
||||
): Promise<string>;
|
||||
): Promise<string>
|
||||
runShellDaemon(command: string): {
|
||||
wait(): Promise<string>;
|
||||
term(): Promise<void>;
|
||||
};
|
||||
wait(): Promise<string>
|
||||
term(): Promise<void>
|
||||
}
|
||||
runDaemon<A extends string>(
|
||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||
): DaemonReturned;
|
||||
): DaemonReturned
|
||||
|
||||
/** Uses the chown on the system */
|
||||
chown(input: { volumeId: string; path: string; uid: string }): Promise<null>;
|
||||
chown(input: { volumeId: string; path: string; uid: string }): Promise<null>
|
||||
/** Uses the chmod on the system */
|
||||
chmod(input: { volumeId: string; path: string; mode: string }): Promise<null>;
|
||||
chmod(input: { volumeId: string; path: string; mode: string }): Promise<null>
|
||||
|
||||
sleep(timeMs: TimeMs): Promise<null>;
|
||||
sleep(timeMs: TimeMs): Promise<null>
|
||||
|
||||
/** Log at the trace level */
|
||||
trace(whatToPrint: string): void;
|
||||
/** Log at the warn level */
|
||||
warn(whatToPrint: string): void;
|
||||
/** Log at the error level */
|
||||
error(whatToPrint: string): void;
|
||||
/** Log at the debug level */
|
||||
debug(whatToPrint: string): void;
|
||||
/** Log at the info level */
|
||||
info(whatToPrint: string): void;
|
||||
console: {
|
||||
/** Log at the trace level */
|
||||
log(whatToPrint: string): void
|
||||
/** Log at the trace level */
|
||||
trace(whatToPrint: string): void
|
||||
/** Log at the warn level */
|
||||
warn(whatToPrint: string): void
|
||||
/** Log at the error level */
|
||||
error(whatToPrint: string): void
|
||||
/** Log at the debug level */
|
||||
debug(whatToPrint: string): void
|
||||
/** Log at the info level */
|
||||
info(whatToPrint: string): void
|
||||
}
|
||||
|
||||
/** Sandbox mode lets us read but not write */
|
||||
is_sandboxed(): boolean;
|
||||
is_sandboxed(): boolean
|
||||
|
||||
/** 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
|
||||
* Returns the port exposed
|
||||
*/
|
||||
bindLan(options: { internalPort: number; name: string }): Promise<number>;
|
||||
bindLan(options: { internalPort: number }): Promise<number>
|
||||
/** Declaring that we are opening a interface on some protocal for tor network */
|
||||
bindTor(options: {
|
||||
internalPort: number;
|
||||
name: string;
|
||||
externalPort: number;
|
||||
}): Promise<string>;
|
||||
internalPort: number
|
||||
name: string
|
||||
externalPort: number
|
||||
}): Promise<string>
|
||||
|
||||
/** 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.
|
||||
@@ -244,123 +246,123 @@ export type Effects = {
|
||||
fetch(
|
||||
url: string,
|
||||
options?: {
|
||||
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
|
||||
headers?: Record<string, string>;
|
||||
body?: string;
|
||||
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH"
|
||||
headers?: Record<string, string>
|
||||
body?: string
|
||||
},
|
||||
): Promise<{
|
||||
method: string;
|
||||
ok: boolean;
|
||||
status: number;
|
||||
headers: Record<string, string>;
|
||||
body?: string | null;
|
||||
method: string
|
||||
ok: boolean
|
||||
status: number
|
||||
headers: Record<string, string>
|
||||
body?: string | null
|
||||
/// Returns the body as a string
|
||||
text(): Promise<string>;
|
||||
text(): Promise<string>
|
||||
/// Returns the body as a json
|
||||
json(): Promise<unknown>;
|
||||
}>;
|
||||
json(): Promise<unknown>
|
||||
}>
|
||||
|
||||
/**
|
||||
* Run rsync between two volumes. This is used to backup data between volumes.
|
||||
* This is a long running process, and a structure that we can either wait for, or get the progress of.
|
||||
*/
|
||||
runRsync(options: {
|
||||
srcVolume: string;
|
||||
dstVolume: string;
|
||||
srcPath: string;
|
||||
dstPath: string;
|
||||
srcVolume: string
|
||||
dstVolume: string
|
||||
srcPath: string
|
||||
dstPath: string
|
||||
// rsync options: https://linux.die.net/man/1/rsync
|
||||
options: BackupOptions;
|
||||
options: BackupOptions
|
||||
}): {
|
||||
id: () => Promise<string>;
|
||||
wait: () => Promise<null>;
|
||||
progress: () => Promise<number>;
|
||||
};
|
||||
id: () => Promise<string>
|
||||
wait: () => Promise<null>
|
||||
progress: () => Promise<number>
|
||||
}
|
||||
|
||||
/** Get a value in a json like data, can be observed and subscribed */
|
||||
getWrapperData<WrapperData = never, Path extends string = never>(options: {
|
||||
/** If there is no packageId it is assumed the current package */
|
||||
packageId?: string;
|
||||
packageId?: string
|
||||
/** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */
|
||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>;
|
||||
callback: (config: unknown, previousConfig: unknown) => void;
|
||||
}): Promise<ExtractWrapperData<WrapperData, Path>>;
|
||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>
|
||||
callback: (config: unknown, previousConfig: unknown) => void
|
||||
}): Promise<ExtractWrapperData<WrapperData, Path>>
|
||||
|
||||
/** Used to store values that can be accessed and subscribed to */
|
||||
setWrapperData<WrapperData = never, Path extends string = never>(options: {
|
||||
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>;
|
||||
value: ExtractWrapperData<WrapperData, Path>;
|
||||
}): Promise<void>;
|
||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>
|
||||
value: ExtractWrapperData<WrapperData, Path>
|
||||
}): Promise<void>
|
||||
|
||||
getLocalHostname(): Promise<string>;
|
||||
getIPHostname(): Promise<string>;
|
||||
getLocalHostname(): Promise<string>
|
||||
getIPHostname(): Promise<string[]>
|
||||
/** Get the address for another service for tor interfaces */
|
||||
getServiceTorHostname(
|
||||
interfaceId: string,
|
||||
packageId?: string,
|
||||
): Promise<string>;
|
||||
): Promise<string>
|
||||
/**
|
||||
* Get the port address for another service
|
||||
*/
|
||||
getServicePortForward(
|
||||
internalPort: number,
|
||||
packageId?: string,
|
||||
): Promise<number>;
|
||||
): Promise<number>
|
||||
|
||||
/** When we want to create a link in the front end interfaces, and example is
|
||||
* exposing a url to view a web service
|
||||
*/
|
||||
exportAddress(options: {
|
||||
/** The title of this field to be dsimplayed */
|
||||
name: string;
|
||||
name: string
|
||||
/** Human readable description, used as tooltip usually */
|
||||
description: string;
|
||||
description: string
|
||||
/** URI location */
|
||||
address: string;
|
||||
id: string;
|
||||
address: string
|
||||
id: string
|
||||
/** Defaults to false, but describes if this address can be opened in a browser as an
|
||||
* ui interface
|
||||
*/
|
||||
ui?: boolean;
|
||||
ui?: boolean
|
||||
|
||||
/**
|
||||
* The id is that a path will create a link in the ui that can go to specific pages, like
|
||||
* admin, or settings, or something like that.
|
||||
* Default = ''
|
||||
*/
|
||||
path?: string;
|
||||
path?: string
|
||||
|
||||
/**
|
||||
* This is the query params in the url, and is a map of key value pairs
|
||||
* Default = {}
|
||||
* if empty then will not be added to the url
|
||||
*/
|
||||
search?: Record<string, string>;
|
||||
}): Promise<string>;
|
||||
search?: Record<string, string>
|
||||
}): Promise<string>
|
||||
|
||||
/**
|
||||
*Remove an address that was exported. Used problably during main or during setConfig.
|
||||
* @param options
|
||||
*/
|
||||
removeAddress(options: { id: string }): Promise<void>;
|
||||
removeAddress(options: { id: string }): Promise<void>
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
exportAction(options: ActionMetaData): Promise<void>;
|
||||
exportAction(options: ActionMetaData): Promise<void>
|
||||
/**
|
||||
* Remove an action that was exported. Used problably during main or during setConfig.
|
||||
*/
|
||||
removeAction(options: { id: string }): Promise<void>;
|
||||
removeAction(options: { id: string }): Promise<void>
|
||||
|
||||
getConfigured(): Promise<boolean>;
|
||||
getConfigured(): Promise<boolean>
|
||||
/**
|
||||
* This called after a valid set config as well as during init.
|
||||
* @param configured
|
||||
*/
|
||||
setConfigured(configured: boolean): Promise<void>;
|
||||
setConfigured(configured: boolean): Promise<void>
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -369,27 +371,27 @@ export type Effects = {
|
||||
getSslCertificate: (
|
||||
packageId: string,
|
||||
algorithm?: "ecdsa" | "ed25519",
|
||||
) => [string, string, string];
|
||||
) => [string, string, string]
|
||||
/**
|
||||
* @returns PEM encoded ssl key (ecdsa)
|
||||
*/
|
||||
getSslKey: (packageId: string, algorithm?: "ecdsa" | "ed25519") => string;
|
||||
getSslKey: (packageId: string, algorithm?: "ecdsa" | "ed25519") => string
|
||||
|
||||
setHealth(o: {
|
||||
name: string;
|
||||
status: HealthStatus;
|
||||
message?: string;
|
||||
}): Promise<void>;
|
||||
name: string
|
||||
status: HealthStatus
|
||||
message?: string
|
||||
}): Promise<void>
|
||||
|
||||
/** Set the dependencies of what the service needs, usually ran during the set config as a best practice */
|
||||
setDependencies(dependencies: Dependencies): Promise<DependenciesReceipt>;
|
||||
setDependencies(dependencies: Dependencies): Promise<DependenciesReceipt>
|
||||
/** Exists could be useful during the runtime to know if some service exists, option dep */
|
||||
exists(packageId: PackageId): Promise<boolean>;
|
||||
exists(packageId: PackageId): Promise<boolean>
|
||||
/** Exists could be useful during the runtime to know if some service is running, option dep */
|
||||
running(packageId: PackageId): Promise<boolean>;
|
||||
restart(): void;
|
||||
shutdown(): void;
|
||||
};
|
||||
running(packageId: PackageId): Promise<boolean>
|
||||
restart(): void
|
||||
shutdown(): void
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export type ExtractWrapperData<WrapperData, Path extends string> =
|
||||
@@ -408,41 +410,41 @@ export type EnsureWrapperDataPath<WrapperData, Path extends string> = _EnsureWra
|
||||
/* rsync options: https://linux.die.net/man/1/rsync
|
||||
*/
|
||||
export type BackupOptions = {
|
||||
delete: boolean;
|
||||
force: boolean;
|
||||
ignoreExisting: boolean;
|
||||
exclude: string[];
|
||||
};
|
||||
delete: boolean
|
||||
force: boolean
|
||||
ignoreExisting: boolean
|
||||
exclude: string[]
|
||||
}
|
||||
/**
|
||||
* This is the metadata that is returned from the metadata call.
|
||||
*/
|
||||
export type Metadata = {
|
||||
fileType: string;
|
||||
isDir: boolean;
|
||||
isFile: boolean;
|
||||
isSymlink: boolean;
|
||||
len: number;
|
||||
modified?: Date;
|
||||
accessed?: Date;
|
||||
created?: Date;
|
||||
readonly: boolean;
|
||||
uid: number;
|
||||
gid: number;
|
||||
mode: number;
|
||||
};
|
||||
fileType: string
|
||||
isDir: boolean
|
||||
isFile: boolean
|
||||
isSymlink: boolean
|
||||
len: number
|
||||
modified?: Date
|
||||
accessed?: Date
|
||||
created?: Date
|
||||
readonly: boolean
|
||||
uid: number
|
||||
gid: number
|
||||
mode: number
|
||||
}
|
||||
|
||||
export type MigrationRes = {
|
||||
configured: boolean;
|
||||
};
|
||||
configured: boolean
|
||||
}
|
||||
|
||||
export type ActionResult = {
|
||||
message: string;
|
||||
message: string
|
||||
value: null | {
|
||||
value: string;
|
||||
copyable: boolean;
|
||||
qr: boolean;
|
||||
};
|
||||
};
|
||||
value: string
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
}
|
||||
}
|
||||
export type SetResult = {
|
||||
/** These are the unix process signals */
|
||||
signal:
|
||||
@@ -478,52 +480,52 @@ export type SetResult = {
|
||||
| "SIGPWR"
|
||||
| "SIGSYS"
|
||||
| "SIGEMT"
|
||||
| "SIGINFO";
|
||||
"depends-on": DependsOn;
|
||||
};
|
||||
| "SIGINFO"
|
||||
"depends-on": DependsOn
|
||||
}
|
||||
|
||||
export type PackageId = string;
|
||||
export type Message = string;
|
||||
export type DependencyKind = "running" | "exists";
|
||||
export type PackageId = string
|
||||
export type Message = string
|
||||
export type DependencyKind = "running" | "exists"
|
||||
|
||||
export type DependsOn = {
|
||||
[packageId: string]: string[];
|
||||
};
|
||||
[packageId: string]: string[]
|
||||
}
|
||||
|
||||
export type KnownError =
|
||||
| { error: string }
|
||||
| {
|
||||
"error-code": [number, string] | readonly [number, string];
|
||||
};
|
||||
"error-code": [number, string] | readonly [number, string]
|
||||
}
|
||||
|
||||
export type PackageProperties = PackagePropertyGroup | PackagePropertyString;
|
||||
export type PackageProperties = PackagePropertyGroup | PackagePropertyString
|
||||
export type PackagePropertyString = {
|
||||
type: "string";
|
||||
name: string;
|
||||
description: string | null;
|
||||
value: string;
|
||||
type: "string"
|
||||
name: string
|
||||
description: string | null
|
||||
value: string
|
||||
/** Let's the ui make this copyable button */
|
||||
copyable: boolean;
|
||||
copyable: boolean
|
||||
/** Let the ui create a qr for this field */
|
||||
qr: boolean;
|
||||
qr: boolean
|
||||
/** Hiding the value unless toggled off for field */
|
||||
masked: boolean;
|
||||
};
|
||||
masked: boolean
|
||||
}
|
||||
export type PackagePropertyGroup = {
|
||||
value: PackageProperties[];
|
||||
type: "object";
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
value: PackageProperties[]
|
||||
type: "object"
|
||||
name: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export type Properties = PackageProperties[];
|
||||
export type Properties = PackageProperties[]
|
||||
|
||||
export type Dependency = {
|
||||
id: PackageId;
|
||||
kind: DependencyKind;
|
||||
};
|
||||
export type Dependencies = Array<Dependency>;
|
||||
id: PackageId
|
||||
kind: DependencyKind
|
||||
}
|
||||
export type Dependencies = Array<Dependency>
|
||||
|
||||
export type DeepPartial<T> = T extends {}
|
||||
? { [P in keyof T]?: DeepPartial<T[P]> }
|
||||
: T;
|
||||
: T
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { object } from "ts-matches";
|
||||
import { object } from "ts-matches"
|
||||
|
||||
export function deepEqual(...args: unknown[]) {
|
||||
if (!object.test(args[args.length - 1])) return args[args.length - 1];
|
||||
const objects = args.filter(object.test);
|
||||
if (!object.test(args[args.length - 1])) return args[args.length - 1]
|
||||
const objects = args.filter(object.test)
|
||||
if (objects.length === 0) {
|
||||
for (const x of args) if (x !== args[0]) return false;
|
||||
return true;
|
||||
for (const x of args) if (x !== args[0]) return false
|
||||
return true
|
||||
}
|
||||
if (objects.length !== args.length) return false;
|
||||
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)));
|
||||
if (objects.length !== args.length) return false
|
||||
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)))
|
||||
for (const key of allKeys) {
|
||||
for (const x of objects) {
|
||||
if (!(key in x)) return false;
|
||||
if (!deepEqual((objects[0] as any)[key], (x as any)[key])) return false;
|
||||
if (!(key in x)) return false
|
||||
if (!deepEqual((objects[0] as any)[key], (x as any)[key])) return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { object } from "ts-matches";
|
||||
import { object } from "ts-matches"
|
||||
|
||||
export function deepMerge(...args: unknown[]): unknown {
|
||||
const lastItem = (args as any)[args.length - 1];
|
||||
if (!object.test(lastItem)) return lastItem;
|
||||
const objects = args.filter(object.test).filter((x) => !Array.isArray(x));
|
||||
if (objects.length === 0) return lastItem as any;
|
||||
if (objects.length === 1) objects.unshift({});
|
||||
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)));
|
||||
const lastItem = (args as any)[args.length - 1]
|
||||
if (!object.test(lastItem)) return lastItem
|
||||
const objects = args.filter(object.test).filter((x) => !Array.isArray(x))
|
||||
if (objects.length === 0) return lastItem as any
|
||||
if (objects.length === 1) objects.unshift({})
|
||||
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)))
|
||||
for (const key of allKeys) {
|
||||
const filteredValues = objects.flatMap((x) =>
|
||||
key in x ? [(x as any)[key]] : [],
|
||||
);
|
||||
(objects as any)[0][key] = deepMerge(...filteredValues);
|
||||
)
|
||||
;(objects as any)[0][key] = deepMerge(...filteredValues)
|
||||
}
|
||||
return objects[0] as any;
|
||||
return objects[0] as any
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as matches from "ts-matches";
|
||||
import * as YAML from "yaml";
|
||||
import * as TOML from "@iarna/toml";
|
||||
import * as T from "../types";
|
||||
import { exists } from ".";
|
||||
import * as matches from "ts-matches"
|
||||
import * as YAML from "yaml"
|
||||
import * as TOML from "@iarna/toml"
|
||||
import * as T from "../types"
|
||||
import { exists } from "."
|
||||
|
||||
const previousPath = /(.+?)\/([^/]*)$/;
|
||||
const previousPath = /(.+?)\/([^/]*)$/
|
||||
|
||||
/**
|
||||
* Used in the get config and the set config exported functions.
|
||||
@@ -59,19 +59,19 @@ export class FileHelper<A> {
|
||||
readonly readData: (stringValue: string) => A,
|
||||
) {}
|
||||
async write(data: A, effects: T.Effects) {
|
||||
let matched;
|
||||
let matched
|
||||
if ((matched = previousPath.exec(this.path))) {
|
||||
await effects.createDir({
|
||||
volumeId: this.volume,
|
||||
path: matched[1],
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
await effects.writeFile({
|
||||
path: this.path,
|
||||
volumeId: this.volume,
|
||||
toWrite: this.writeData(data),
|
||||
});
|
||||
})
|
||||
}
|
||||
async read(effects: T.Effects) {
|
||||
if (
|
||||
@@ -80,14 +80,14 @@ export class FileHelper<A> {
|
||||
volumeId: this.volume,
|
||||
}))
|
||||
) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return this.readData(
|
||||
await effects.readFile({
|
||||
path: this.path,
|
||||
volumeId: this.volume,
|
||||
}),
|
||||
);
|
||||
)
|
||||
}
|
||||
static raw<A>(
|
||||
path: string,
|
||||
@@ -95,7 +95,7 @@ export class FileHelper<A> {
|
||||
toFile: (dataIn: A) => string,
|
||||
fromFile: (rawData: string) => A,
|
||||
) {
|
||||
return new FileHelper<A>(path, volume, toFile, fromFile);
|
||||
return new FileHelper<A>(path, volume, toFile, fromFile)
|
||||
}
|
||||
static json<A>(
|
||||
path: string,
|
||||
@@ -106,12 +106,12 @@ export class FileHelper<A> {
|
||||
path,
|
||||
volume,
|
||||
(inData) => {
|
||||
return JSON.stringify(inData, null, 2);
|
||||
return JSON.stringify(inData, null, 2)
|
||||
},
|
||||
(inString) => {
|
||||
return shape.unsafeCast(JSON.parse(inString));
|
||||
return shape.unsafeCast(JSON.parse(inString))
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
static toml<A extends Record<string, unknown>>(
|
||||
path: string,
|
||||
@@ -122,12 +122,12 @@ export class FileHelper<A> {
|
||||
path,
|
||||
volume,
|
||||
(inData) => {
|
||||
return JSON.stringify(inData, null, 2);
|
||||
return JSON.stringify(inData, null, 2)
|
||||
},
|
||||
(inString) => {
|
||||
return shape.unsafeCast(TOML.parse(inString));
|
||||
return shape.unsafeCast(TOML.parse(inString))
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
static yaml<A extends Record<string, unknown>>(
|
||||
path: string,
|
||||
@@ -138,13 +138,13 @@ export class FileHelper<A> {
|
||||
path,
|
||||
volume,
|
||||
(inData) => {
|
||||
return JSON.stringify(inData, null, 2);
|
||||
return JSON.stringify(inData, null, 2)
|
||||
},
|
||||
(inString) => {
|
||||
return shape.unsafeCast(YAML.parse(inString));
|
||||
return shape.unsafeCast(YAML.parse(inString))
|
||||
},
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FileHelper;
|
||||
export default FileHelper
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Parser } from "ts-matches";
|
||||
import { Effects, EnsureWrapperDataPath, ExtractWrapperData } from "../types";
|
||||
import { NoAny } from ".";
|
||||
import { Parser } from "ts-matches"
|
||||
import { Effects, EnsureWrapperDataPath, ExtractWrapperData } from "../types"
|
||||
import { NoAny } from "."
|
||||
|
||||
export class WrapperData<WrapperData, Path extends string> {
|
||||
constructor(
|
||||
@@ -8,7 +8,7 @@ export class WrapperData<WrapperData, Path extends string> {
|
||||
readonly path: Path & EnsureWrapperDataPath<WrapperData, Path>,
|
||||
readonly options: {
|
||||
/** Defaults to what ever the package currently in */
|
||||
packageId?: string | undefined;
|
||||
packageId?: string | undefined
|
||||
} = {},
|
||||
) {}
|
||||
|
||||
@@ -17,27 +17,27 @@ export class WrapperData<WrapperData, Path extends string> {
|
||||
...this.options,
|
||||
path: this.path as any,
|
||||
callback: this.effects.restart,
|
||||
});
|
||||
})
|
||||
}
|
||||
once() {
|
||||
return this.effects.getWrapperData<WrapperData, Path>({
|
||||
...this.options,
|
||||
path: this.path as any,
|
||||
callback: () => {},
|
||||
});
|
||||
})
|
||||
}
|
||||
async *watch() {
|
||||
while (true) {
|
||||
let callback: () => void;
|
||||
let callback: () => void
|
||||
const waitForNext = new Promise<void>((resolve) => {
|
||||
callback = resolve;
|
||||
});
|
||||
callback = resolve
|
||||
})
|
||||
yield await this.effects.getWrapperData<WrapperData, Path>({
|
||||
...this.options,
|
||||
path: this.path as any,
|
||||
callback: () => callback(),
|
||||
});
|
||||
await waitForNext;
|
||||
})
|
||||
await waitForNext
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,8 @@ export function getWrapperData<WrapperData, Path extends string>(
|
||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>,
|
||||
options: {
|
||||
/** Defaults to what ever the package currently in */
|
||||
packageId?: string | undefined;
|
||||
packageId?: string | undefined
|
||||
} = {},
|
||||
) {
|
||||
return new WrapperData<WrapperData, Path>(effects, path as any, options);
|
||||
return new WrapperData<WrapperData, Path>(effects, path as any, options)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { Parser } from "ts-matches";
|
||||
import * as T from "../types";
|
||||
import FileHelper from "./fileHelper";
|
||||
import nullIfEmpty from "./nullIfEmpty";
|
||||
import { WrapperData, getWrapperData } from "./getWrapperData";
|
||||
import { Parser } from "ts-matches"
|
||||
import * as T from "../types"
|
||||
import FileHelper from "./fileHelper"
|
||||
import nullIfEmpty from "./nullIfEmpty"
|
||||
import { WrapperData, getWrapperData } from "./getWrapperData"
|
||||
import {
|
||||
CheckResult,
|
||||
checkPortListening,
|
||||
checkWebUrl,
|
||||
} from "../health/checkFns";
|
||||
import { LocalPort, NetworkBuilder, TorHostname } from "../mainFn";
|
||||
import { ExtractWrapperData } from "../types";
|
||||
} from "../health/checkFns"
|
||||
import { LocalBinding, LocalPort, NetworkBuilder, TorHostname } from "../mainFn"
|
||||
import { ExtractWrapperData } from "../types"
|
||||
|
||||
export { guardAll, typeFromProps } from "./propertiesMatcher";
|
||||
export { default as nullIfEmpty } from "./nullIfEmpty";
|
||||
export { FileHelper } from "./fileHelper";
|
||||
export { getWrapperData } from "./getWrapperData";
|
||||
export { deepEqual } from "./deepEqual";
|
||||
export { deepMerge } from "./deepMerge";
|
||||
export { once } from "./once";
|
||||
export { guardAll, typeFromProps } from "./propertiesMatcher"
|
||||
export { default as nullIfEmpty } from "./nullIfEmpty"
|
||||
export { FileHelper } from "./fileHelper"
|
||||
export { getWrapperData } from "./getWrapperData"
|
||||
export { deepEqual } from "./deepEqual"
|
||||
export { deepMerge } from "./deepMerge"
|
||||
export { once } from "./once"
|
||||
|
||||
// prettier-ignore
|
||||
export type FlattenIntersection<T> =
|
||||
@@ -25,7 +25,7 @@ T extends ArrayLike<any> ? T :
|
||||
T extends object ? {} & {[P in keyof T]: T[P]} :
|
||||
T;
|
||||
|
||||
export type _<T> = FlattenIntersection<T>;
|
||||
export type _<T> = FlattenIntersection<T>
|
||||
|
||||
/** Used to check if the file exists before hand */
|
||||
export const exists = (
|
||||
@@ -35,63 +35,63 @@ export const exists = (
|
||||
effects.metadata(props).then(
|
||||
(_) => true,
|
||||
(_) => false,
|
||||
);
|
||||
)
|
||||
|
||||
export const isKnownError = (e: unknown): e is T.KnownError =>
|
||||
e instanceof Object && ("error" in e || "error-code" in e);
|
||||
e instanceof Object && ("error" in e || "error-code" in e)
|
||||
|
||||
type Cdr<A> = A extends [unknown, ...infer Cdr] ? Cdr : [];
|
||||
type Cdr<A> = A extends [unknown, ...infer Cdr] ? Cdr : []
|
||||
|
||||
declare const affine: unique symbol;
|
||||
declare const affine: unique symbol
|
||||
|
||||
function withAffine<B>() {
|
||||
return {} as { [affine]: B };
|
||||
return {} as { [affine]: B }
|
||||
}
|
||||
|
||||
export type WrapperDataOptionals<WrapperData, Path extends string> = {
|
||||
validator?: Parser<unknown, ExtractWrapperData<WrapperData, Path>>;
|
||||
validator?: Parser<unknown, ExtractWrapperData<WrapperData, Path>>
|
||||
/** Defaults to what ever the package currently in */
|
||||
packageId?: string | undefined;
|
||||
};
|
||||
packageId?: string | undefined
|
||||
}
|
||||
|
||||
export type Utils<WD> = {
|
||||
readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]>;
|
||||
readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]>
|
||||
writeFile: <A>(
|
||||
fileHelper: FileHelper<A>,
|
||||
data: A,
|
||||
) => ReturnType<FileHelper<A>["write"]>;
|
||||
) => ReturnType<FileHelper<A>["write"]>
|
||||
getWrapperData: <Path extends string>(
|
||||
packageId: string,
|
||||
path: T.EnsureWrapperDataPath<WD, Path>,
|
||||
) => WrapperData<WD, Path>;
|
||||
) => WrapperData<WD, Path>
|
||||
getOwnWrapperData: <Path extends string>(
|
||||
path: T.EnsureWrapperDataPath<WD, Path>,
|
||||
) => WrapperData<WD, Path>;
|
||||
) => WrapperData<WD, Path>
|
||||
setOwnWrapperData: <Path extends string | never>(
|
||||
path: T.EnsureWrapperDataPath<WD, Path>,
|
||||
value: ExtractWrapperData<WD, Path>,
|
||||
) => Promise<void>;
|
||||
) => Promise<void>
|
||||
checkPortListening(
|
||||
port: number,
|
||||
options?: {
|
||||
error?: string;
|
||||
message?: string;
|
||||
error?: string
|
||||
message?: string
|
||||
},
|
||||
): Promise<CheckResult>;
|
||||
): Promise<CheckResult>
|
||||
checkWebUrl(
|
||||
url: string,
|
||||
options?: {
|
||||
timeout?: number;
|
||||
successMessage?: string;
|
||||
errorMessage?: string;
|
||||
timeout?: number
|
||||
successMessage?: string
|
||||
errorMessage?: string
|
||||
},
|
||||
): Promise<CheckResult>;
|
||||
localPort: (id: string) => LocalPort;
|
||||
networkBuilder: () => NetworkBuilder;
|
||||
torHostName: (id: string) => TorHostname;
|
||||
exists: (props: { path: string; volumeId: string }) => Promise<boolean>;
|
||||
nullIfEmpty: typeof nullIfEmpty;
|
||||
};
|
||||
): Promise<CheckResult>
|
||||
bindLan: (port: number) => Promise<LocalBinding>
|
||||
networkBuilder: () => NetworkBuilder
|
||||
torHostName: (id: string) => TorHostname
|
||||
exists: (props: { path: string; volumeId: string }) => Promise<boolean>
|
||||
nullIfEmpty: typeof nullIfEmpty
|
||||
}
|
||||
export const utils = <WrapperData = never>(
|
||||
effects: T.Effects,
|
||||
): Utils<WrapperData> => ({
|
||||
@@ -113,14 +113,14 @@ export const utils = <WrapperData = never>(
|
||||
) => effects.setWrapperData<WrapperData, Path>({ value, path: path as any }),
|
||||
checkPortListening: checkPortListening.bind(null, effects),
|
||||
checkWebUrl: checkWebUrl.bind(null, effects),
|
||||
localPort: (id: string) => new LocalPort(effects, id),
|
||||
bindLan: async (port: number) => LocalPort.bindLan(effects, port),
|
||||
networkBuilder: () => NetworkBuilder.of(effects),
|
||||
torHostName: (id: string) => TorHostname.of(effects, id),
|
||||
});
|
||||
})
|
||||
|
||||
type NeverPossible = { [affine]: string };
|
||||
type NeverPossible = { [affine]: string }
|
||||
export type NoAny<A> = NeverPossible extends A
|
||||
? keyof NeverPossible extends keyof A
|
||||
? never
|
||||
: A
|
||||
: A;
|
||||
: A
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* @returns
|
||||
*/
|
||||
export default function nullIfEmpty(s: null | Record<string, unknown>) {
|
||||
if (s === null) return null;
|
||||
return Object.keys(s).length === 0 ? null : s;
|
||||
if (s === null) return null
|
||||
return Object.keys(s).length === 0 ? null : s
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export function once<B>(fn: () => B): () => B {
|
||||
let result: [B] | [] = [];
|
||||
let result: [B] | [] = []
|
||||
return () => {
|
||||
if (!result.length) {
|
||||
result = [fn()];
|
||||
result = [fn()]
|
||||
}
|
||||
return result[0];
|
||||
};
|
||||
return result[0]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as matches from "ts-matches";
|
||||
import { Parser, Validator } from "ts-matches";
|
||||
import * as matches from "ts-matches"
|
||||
import { Parser, Validator } from "ts-matches"
|
||||
import {
|
||||
UnionSelectKey,
|
||||
UnionValueKey,
|
||||
ValueSpec as ValueSpecAny,
|
||||
InputSpec,
|
||||
} from "../config/configTypes";
|
||||
import { Config } from "../config/builder/config";
|
||||
import { _ } from "../util";
|
||||
} from "../config/configTypes"
|
||||
import { Config } from "../config/builder/config"
|
||||
import { _ } from "../util"
|
||||
|
||||
const {
|
||||
string,
|
||||
@@ -20,19 +20,19 @@ const {
|
||||
literals,
|
||||
boolean,
|
||||
nill,
|
||||
} = matches;
|
||||
} = matches
|
||||
|
||||
type TypeToggle = "toggle";
|
||||
type TypeText = "text";
|
||||
type TypeTextarea = "textarea";
|
||||
type TypeNumber = "number";
|
||||
type TypeObject = "object";
|
||||
type TypeList = "list";
|
||||
type TypeSelect = "select";
|
||||
type TypeMultiselect = "multiselect";
|
||||
type TypeColor = "color";
|
||||
type TypeDatetime = "datetime";
|
||||
type TypeUnion = "union";
|
||||
type TypeToggle = "toggle"
|
||||
type TypeText = "text"
|
||||
type TypeTextarea = "textarea"
|
||||
type TypeNumber = "number"
|
||||
type TypeObject = "object"
|
||||
type TypeList = "list"
|
||||
type TypeSelect = "select"
|
||||
type TypeMultiselect = "multiselect"
|
||||
type TypeColor = "color"
|
||||
type TypeDatetime = "datetime"
|
||||
type TypeUnion = "union"
|
||||
|
||||
// prettier-ignore
|
||||
type GuardDefaultRequired<A, Type> =
|
||||
@@ -56,7 +56,7 @@ type GuardToggle<A> =
|
||||
A extends { type: TypeToggle } ? GuardDefaultRequired<A, boolean> :
|
||||
unknown
|
||||
|
||||
type TrueKeyOf<T> = _<T> extends Record<string, unknown> ? keyof T : never;
|
||||
type TrueKeyOf<T> = _<T> extends Record<string, unknown> ? keyof T : never
|
||||
// prettier-ignore
|
||||
type GuardObject<A> =
|
||||
A extends { type: TypeObject, spec: infer B } ? (
|
||||
@@ -94,7 +94,7 @@ type AsString<A> = A extends
|
||||
| null
|
||||
| undefined
|
||||
? `${A}`
|
||||
: "UnknownValue";
|
||||
: "UnknownValue"
|
||||
// prettier-ignore
|
||||
type VariantValue<A> =
|
||||
A extends { name: string, spec: infer B } ? TypeFromProps<_<B>> :
|
||||
@@ -116,44 +116,44 @@ export type GuardAll<A> = GuardNumber<A> &
|
||||
GuardSelect<A> &
|
||||
GuardMultiselect<A> &
|
||||
GuardColor<A> &
|
||||
GuardDatetime<A>;
|
||||
GuardDatetime<A>
|
||||
// prettier-ignore
|
||||
export type TypeFromProps<A> =
|
||||
A extends Config<infer B> ? TypeFromProps<B> :
|
||||
A extends Record<string, unknown> ? { [K in keyof A & string]: _<GuardAll<A[K]>> } :
|
||||
unknown;
|
||||
const isType = object({ type: string });
|
||||
const isType = object({ type: string })
|
||||
const matchVariant = object({
|
||||
name: string,
|
||||
spec: unknown,
|
||||
});
|
||||
const recordString = dictionary([string, unknown]);
|
||||
const matchDefault = object({ default: unknown });
|
||||
})
|
||||
const recordString = dictionary([string, unknown])
|
||||
const matchDefault = object({ default: unknown })
|
||||
const matchRequired = object(
|
||||
{
|
||||
required: literals(false),
|
||||
default: nill,
|
||||
},
|
||||
["default"],
|
||||
);
|
||||
const matchInteger = object({ integer: literals(true) });
|
||||
const matchSpec = object({ spec: recordString });
|
||||
)
|
||||
const matchInteger = object({ integer: literals(true) })
|
||||
const matchSpec = object({ spec: recordString })
|
||||
const matchUnion = object({
|
||||
variants: dictionary([string, matchVariant]),
|
||||
});
|
||||
})
|
||||
const matchValues = object({
|
||||
values: dictionary([string, string]),
|
||||
});
|
||||
})
|
||||
|
||||
function withInteger(parser: Parser<unknown, number>, value: unknown) {
|
||||
if (matchInteger.test(value)) {
|
||||
return parser.validate(Number.isInteger, "isIntegral");
|
||||
return parser.validate(Number.isInteger, "isIntegral")
|
||||
}
|
||||
return parser;
|
||||
return parser
|
||||
}
|
||||
function requiredParser<A>(parser: Parser<unknown, A>, value: unknown) {
|
||||
if (matchRequired.test(value)) return parser.optional();
|
||||
return parser;
|
||||
if (matchRequired.test(value)) return parser.optional()
|
||||
return parser
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,60 +168,60 @@ export function guardAll<A extends ValueSpecAny>(
|
||||
value: A,
|
||||
): Parser<unknown, GuardAll<A>> {
|
||||
if (!isType.test(value)) {
|
||||
return unknown as any;
|
||||
return unknown as any
|
||||
}
|
||||
switch (value.type) {
|
||||
case "toggle":
|
||||
return requiredParser(boolean, value) as any;
|
||||
return requiredParser(boolean, value) as any
|
||||
|
||||
case "text":
|
||||
return requiredParser(string, value) as any;
|
||||
return requiredParser(string, value) as any
|
||||
|
||||
case "textarea":
|
||||
return requiredParser(string, value) as any;
|
||||
return requiredParser(string, value) as any
|
||||
|
||||
case "color":
|
||||
return requiredParser(string, value) as any;
|
||||
return requiredParser(string, value) as any
|
||||
|
||||
case "datetime":
|
||||
return requiredParser(string, value) as any;
|
||||
return requiredParser(string, value) as any
|
||||
|
||||
case "number":
|
||||
return requiredParser(withInteger(number, value), value) as any;
|
||||
return requiredParser(withInteger(number, value), value) as any
|
||||
|
||||
case "object":
|
||||
if (matchSpec.test(value)) {
|
||||
return requiredParser(typeFromProps(value.spec), value) as any;
|
||||
return requiredParser(typeFromProps(value.spec), value) as any
|
||||
}
|
||||
return unknown as any;
|
||||
return unknown as any
|
||||
|
||||
case "list": {
|
||||
const spec = (matchSpec.test(value) && value.spec) || {};
|
||||
const spec = (matchSpec.test(value) && value.spec) || {}
|
||||
|
||||
return requiredParser(
|
||||
matches.arrayOf(guardAll(spec as any)),
|
||||
value,
|
||||
) as any;
|
||||
) as any
|
||||
}
|
||||
case "select":
|
||||
if (matchValues.test(value)) {
|
||||
const valueKeys = Object.keys(value.values);
|
||||
const valueKeys = Object.keys(value.values)
|
||||
return requiredParser(
|
||||
literals(valueKeys[0], ...valueKeys),
|
||||
value,
|
||||
) as any;
|
||||
) as any
|
||||
}
|
||||
return unknown as any;
|
||||
return unknown as any
|
||||
|
||||
case "multiselect":
|
||||
if (matchValues.test(value)) {
|
||||
const valueKeys = Object.keys(value.values);
|
||||
const valueKeys = Object.keys(value.values)
|
||||
return requiredParser(
|
||||
arrayOf(literals(valueKeys[0], ...valueKeys)),
|
||||
value,
|
||||
) as any;
|
||||
) as any
|
||||
}
|
||||
return unknown as any;
|
||||
return unknown as any
|
||||
|
||||
case "union":
|
||||
if (matchUnion.test(value)) {
|
||||
@@ -234,12 +234,12 @@ export function guardAll<A extends ValueSpecAny>(
|
||||
unionValueKey: typeFromProps(spec),
|
||||
}),
|
||||
),
|
||||
) as any;
|
||||
) as any
|
||||
}
|
||||
return unknown as any;
|
||||
return unknown as any
|
||||
}
|
||||
|
||||
return unknown as any;
|
||||
return unknown as any
|
||||
}
|
||||
/**
|
||||
* InputSpec: Tells the UI how to ask for information, verification, and will send the service a config in a shape via the spec.
|
||||
@@ -252,7 +252,7 @@ export function guardAll<A extends ValueSpecAny>(
|
||||
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(
|
||||
Object.fromEntries(
|
||||
Object.entries(valueDictionary).map(([key, value]) => [
|
||||
@@ -260,5 +260,5 @@ export function typeFromProps<A extends InputSpec>(
|
||||
guardAll(value),
|
||||
]),
|
||||
),
|
||||
) as any;
|
||||
) as any
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"prettier": {
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"semi": false,
|
||||
"singleQuote": false
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import camelCase from "lodash/camelCase";
|
||||
import * as fs from "fs";
|
||||
import { string } from "ts-matches";
|
||||
import camelCase from "lodash/camelCase"
|
||||
import * as fs from "fs"
|
||||
import { string } from "ts-matches"
|
||||
|
||||
export async function writeConvertedFileFromOld(
|
||||
file: string,
|
||||
@@ -11,48 +11,46 @@ export async function writeConvertedFileFromOld(
|
||||
file,
|
||||
await makeFileContentFromOld(inputData, options),
|
||||
(err) => console.error(err),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default async function makeFileContentFromOld(
|
||||
inputData: Promise<any> | any,
|
||||
{ startSdk = "start-sdk", nested = true } = {},
|
||||
) {
|
||||
const outputLines: string[] = [];
|
||||
const outputLines: string[] = []
|
||||
outputLines.push(`
|
||||
import {Config, Value, List, Variants} from '${startSdk}/config/builder'
|
||||
`);
|
||||
const data = await inputData;
|
||||
`)
|
||||
const data = await inputData
|
||||
|
||||
const namedConsts = new Set(["Config", "Value", "List"]);
|
||||
const configName = newConst("InputSpec", convertInputSpec(data));
|
||||
const namedConsts = new Set(["Config", "Value", "List"])
|
||||
const configName = newConst("InputSpec", convertInputSpec(data))
|
||||
const configMatcherName = newConst(
|
||||
"matchInputSpec",
|
||||
`${configName}.validator()`,
|
||||
);
|
||||
outputLines.push(
|
||||
`export type InputSpec = typeof ${configMatcherName}._TYPE;`,
|
||||
);
|
||||
)
|
||||
outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`)
|
||||
|
||||
return outputLines.join("\n");
|
||||
return outputLines.join("\n")
|
||||
|
||||
function newConst(key: string, data: string) {
|
||||
const variableName = getNextConstName(camelCase(key));
|
||||
outputLines.push(`export const ${variableName} = ${data};`);
|
||||
return variableName;
|
||||
const variableName = getNextConstName(camelCase(key))
|
||||
outputLines.push(`export const ${variableName} = ${data};`)
|
||||
return variableName
|
||||
}
|
||||
function maybeNewConst(key: string, data: string) {
|
||||
if (nested) return data;
|
||||
return newConst(key, data);
|
||||
if (nested) return data
|
||||
return newConst(key, data)
|
||||
}
|
||||
function convertInputSpec(data: any) {
|
||||
let answer = "Config.of({";
|
||||
let answer = "Config.of({"
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const variableName = maybeNewConst(key, convertValueSpec(value));
|
||||
const variableName = maybeNewConst(key, convertValueSpec(value))
|
||||
|
||||
answer += `${JSON.stringify(key)}: ${variableName},`;
|
||||
answer += `${JSON.stringify(key)}: ${variableName},`
|
||||
}
|
||||
return `${answer}})`;
|
||||
return `${answer}})`
|
||||
}
|
||||
function convertValueSpec(value: any): string {
|
||||
switch (value.type) {
|
||||
@@ -72,15 +70,15 @@ export default async function makeFileContentFromOld(
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)})`;
|
||||
)})`
|
||||
}
|
||||
return `${rangeToTodoComment(value?.range)}Value.text(${JSON.stringify(
|
||||
{
|
||||
name: value.name || null,
|
||||
// prettier-ignore
|
||||
required: (
|
||||
value.default != null && !value.nullable ? {default: value.default} :
|
||||
value.default != null && value.nullable ? {defaultWithRequired: value.default} :
|
||||
value.default != null ? {default: value.default} :
|
||||
value.nullable === false ? {default: null} :
|
||||
!value.nullable
|
||||
),
|
||||
description: value.description || null,
|
||||
@@ -101,7 +99,7 @@ export default async function makeFileContentFromOld(
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)})`;
|
||||
)})`
|
||||
}
|
||||
case "number": {
|
||||
return `${rangeToTodoComment(
|
||||
@@ -113,8 +111,8 @@ export default async function makeFileContentFromOld(
|
||||
warning: value.warning || null,
|
||||
// prettier-ignore
|
||||
required: (
|
||||
value.default != null && !value.nullable ? {default: value.default} :
|
||||
value.default != null && value.nullable ? {defaultWithRequired: value.default} :
|
||||
value.default != null ? {default: value.default} :
|
||||
value.nullable === false ? {default: null} :
|
||||
!value.nullable
|
||||
),
|
||||
min: null,
|
||||
@@ -126,7 +124,7 @@ export default async function makeFileContentFromOld(
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)})`;
|
||||
)})`
|
||||
}
|
||||
case "boolean": {
|
||||
return `Value.toggle(${JSON.stringify(
|
||||
@@ -138,18 +136,18 @@ export default async function makeFileContentFromOld(
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)})`;
|
||||
)})`
|
||||
}
|
||||
case "enum": {
|
||||
const allValueNames = new Set([
|
||||
...(value?.["values"] || []),
|
||||
...Object.keys(value?.["value-names"] || {}),
|
||||
]);
|
||||
])
|
||||
const values = Object.fromEntries(
|
||||
Array.from(allValueNames)
|
||||
.filter(string.test)
|
||||
.map((key) => [key, value?.spec?.["value-names"]?.[key] || key]),
|
||||
);
|
||||
)
|
||||
return `Value.select(${JSON.stringify(
|
||||
{
|
||||
name: value.name || null,
|
||||
@@ -157,33 +155,33 @@ export default async function makeFileContentFromOld(
|
||||
warning: value.warning || null,
|
||||
|
||||
// prettier-ignore
|
||||
required: (
|
||||
value.default != null && !value.nullable ? {default: value.default} :
|
||||
value.default != null && value.nullable ? {defaultWithRequired: value.default} :
|
||||
required:(
|
||||
value.default != null ? {default: value.default} :
|
||||
value.nullable === false ? {default: null} :
|
||||
!value.nullable
|
||||
),
|
||||
values,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)} as const)`;
|
||||
)} 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)},
|
||||
warning: ${JSON.stringify(value.warning || null)},
|
||||
}, ${specName})`;
|
||||
}, ${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)},
|
||||
@@ -193,21 +191,21 @@ export default async function makeFileContentFromOld(
|
||||
// prettier-ignore
|
||||
required: ${JSON.stringify(
|
||||
// prettier-ignore
|
||||
value.default != null && !value.nullable ? {default: value.default} :
|
||||
value.default != null && value.nullable ? {defaultWithRequired: value.default} :
|
||||
!value.nullable,
|
||||
value.default != null ? {default: value.default} :
|
||||
value.nullable === false ? {default: null} :
|
||||
!value.nullable,
|
||||
)},
|
||||
}, ${variants})`;
|
||||
}, ${variants})`
|
||||
}
|
||||
case "list": {
|
||||
const list = maybeNewConst(value.name + "_list", convertList(value));
|
||||
return `Value.list(${list})`;
|
||||
const list = maybeNewConst(value.name + "_list", convertList(value))
|
||||
return `Value.list(${list})`
|
||||
}
|
||||
case "pointer": {
|
||||
return `/* TODO deal with point removed ${JSON.stringify(value)} */`;
|
||||
return `/* TODO deal with point removed ${JSON.stringify(value)} */`
|
||||
}
|
||||
}
|
||||
throw Error(`Unknown type "${value.type}"`);
|
||||
throw Error(`Unknown type "${value.type}"`)
|
||||
}
|
||||
|
||||
function convertList(value: any) {
|
||||
@@ -237,7 +235,7 @@ export default async function makeFileContentFromOld(
|
||||
: [],
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
})})`;
|
||||
})})`
|
||||
}
|
||||
case "number": {
|
||||
return `${rangeToTodoComment(value?.range)}List.number(${JSON.stringify(
|
||||
@@ -257,13 +255,13 @@ export default async function makeFileContentFromOld(
|
||||
max: null,
|
||||
units: value?.spec?.units || null,
|
||||
placeholder: value?.spec?.placeholder || null,
|
||||
})})`;
|
||||
})})`
|
||||
}
|
||||
case "enum": {
|
||||
const allValueNames = new Set(
|
||||
...(value?.spec?.["values"] || []),
|
||||
...Object.keys(value?.spec?.["value-names"] || {}),
|
||||
);
|
||||
)
|
||||
const values = Object.fromEntries(
|
||||
Array.from(allValueNames)
|
||||
.filter(string.test)
|
||||
@@ -271,7 +269,7 @@ export default async function makeFileContentFromOld(
|
||||
key,
|
||||
value?.spec?.["value-names"]?.[key] || key,
|
||||
]),
|
||||
);
|
||||
)
|
||||
return `${rangeToTodoComment(
|
||||
value?.range,
|
||||
)}Value.multiselect(${JSON.stringify(
|
||||
@@ -286,13 +284,13 @@ export default async function makeFileContentFromOld(
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)})`;
|
||||
)})`
|
||||
}
|
||||
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)},
|
||||
@@ -304,7 +302,7 @@ export default async function makeFileContentFromOld(
|
||||
spec: ${specName},
|
||||
displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)},
|
||||
uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)},
|
||||
})`;
|
||||
})`
|
||||
}
|
||||
case "union": {
|
||||
const variants = maybeNewConst(
|
||||
@@ -313,7 +311,7 @@ export default async function makeFileContentFromOld(
|
||||
value.spec.variants,
|
||||
value.spec["variant-names"] || {},
|
||||
),
|
||||
);
|
||||
)
|
||||
const unionValueName = maybeNewConst(
|
||||
value.name + "_union",
|
||||
`${rangeToTodoComment(value?.range)}
|
||||
@@ -323,11 +321,15 @@ export default async function makeFileContentFromOld(
|
||||
value?.spec?.tag?.description || 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: null }
|
||||
: false,
|
||||
)},
|
||||
default: ${JSON.stringify(value?.spec?.default || null)},
|
||||
}, ${variants})
|
||||
`,
|
||||
);
|
||||
)
|
||||
const listConfig = maybeNewConst(
|
||||
value.name + "_list_config",
|
||||
`
|
||||
@@ -335,7 +337,7 @@ export default async function makeFileContentFromOld(
|
||||
"union": ${unionValueName}
|
||||
})
|
||||
`,
|
||||
);
|
||||
)
|
||||
return `${rangeToTodoComment(value?.range)}List.obj({
|
||||
name:${JSON.stringify(value.name || null)},
|
||||
minLength:${JSON.stringify(null)},
|
||||
@@ -347,37 +349,37 @@ export default async function makeFileContentFromOld(
|
||||
spec: ${listConfig},
|
||||
displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)},
|
||||
uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)},
|
||||
})`;
|
||||
})`
|
||||
}
|
||||
}
|
||||
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 {
|
||||
let answer = "Variants.of({";
|
||||
let answer = "Variants.of({"
|
||||
for (const [key, value] of Object.entries(variants)) {
|
||||
const variantSpec = maybeNewConst(key, convertInputSpec(value));
|
||||
const variantSpec = maybeNewConst(key, convertInputSpec(value))
|
||||
answer += `"${key}": {name: "${
|
||||
variantNames[key] || key
|
||||
}", spec: ${variantSpec}},`;
|
||||
}", spec: ${variantSpec}},`
|
||||
}
|
||||
return `${answer}})`;
|
||||
return `${answer}})`
|
||||
}
|
||||
|
||||
function getNextConstName(name: string, i = 0): string {
|
||||
const newName = !i ? name : name + i;
|
||||
const newName = !i ? name : name + i
|
||||
if (namedConsts.has(newName)) {
|
||||
return getNextConstName(name, i + 1);
|
||||
return getNextConstName(name, i + 1)
|
||||
}
|
||||
namedConsts.add(newName);
|
||||
return newName;
|
||||
namedConsts.add(newName)
|
||||
return newName
|
||||
}
|
||||
}
|
||||
|
||||
function rangeToTodoComment(range: string | undefined) {
|
||||
if (!range) return "";
|
||||
return `/* TODO: Convert range for this value (${range})*/`;
|
||||
if (!range) return ""
|
||||
return `/* TODO: Convert range for this value (${range})*/`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user