feat: Utils to do bindLan and have ipv4 and ipv6 if need be

This commit is contained in:
BluJ
2023-04-27 11:22:42 -06:00
parent 353692bf55
commit c7d38fc7ce
79 changed files with 1754 additions and 1744 deletions

View File

@@ -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

View File

@@ -1,3 +1,3 @@
export { CreatedAction, createAction } from "./createAction";
export { CreatedAction, createAction } from "./createAction"
export { setupActions } from "./setupActions";
export { setupActions } from "./setupActions"

View File

@@ -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,
};
}
}

View File

@@ -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),
);
)
}
}

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1,3 +1,3 @@
export { Backups } from "./Backups";
export { Backups } from "./Backups"
export { setupBackups } from "./setupBackups";
export { setupBackups } from "./setupBackups"

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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,
};
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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 })
}
}

View File

@@ -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

View File

@@ -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
},
});
})

View File

@@ -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"

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
declare const HealthProof: unique symbol;
declare const HealthProof: unique symbol
export type HealthReceipt = {
[HealthProof]: never;
};
[HealthProof]: never
}

View File

@@ -1,6 +1,6 @@
import { HealthStatus } from "../../types";
import { HealthStatus } from "../../types"
export type CheckResult = {
status: HealthStatus;
message?: string;
};
status: HealthStatus
message?: string
}

View File

@@ -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,
};
}
}

View File

@@ -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 }
})
}

View File

@@ -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 }

View File

@@ -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
}

View File

@@ -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"

View File

@@ -1,4 +1,4 @@
export type TriggerInput = {
lastResult?: "success" | "failure" | null;
hadSuccess?: boolean;
};
lastResult?: "success" | "failure" | null
hadSuccess?: boolean
}

View File

@@ -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()
}
};
}
}

View File

@@ -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
}
};
}
}

View File

@@ -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),
});
})

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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)
},
};
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -1,4 +1,4 @@
declare const AddressProof: unique symbol;
declare const AddressProof: unique symbol
export type AddressReceipt = {
[AddressProof]: never;
};
[AddressProof]: never
}

View File

@@ -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()),
),
);
)
},
};
}
}
}

View File

@@ -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)),
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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}`
}
}

View File

@@ -1,4 +1,4 @@
export declare const ReadyProof: unique symbol;
export declare const ReadyProof: unique symbol
export type ReadyReceipt = {
[ReadyProof]: never;
};
[ReadyProof]: never
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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())
}
}

View File

@@ -1,4 +1,4 @@
declare const InterfaceProof: unique symbol;
declare const InterfaceProof: unique symbol
export type InterfaceReceipt = {
[InterfaceProof]: never;
};
[InterfaceProof]: never
}

View File

@@ -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"
}
}

View File

@@ -1,2 +1,2 @@
export { setupManifest } from "./setupManifest";
export * as ManifestTypes from "./ManifestTypes";
export { setupManifest } from "./setupManifest"
export * as ManifestTypes from "./ManifestTypes"

View File

@@ -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
}

View File

@@ -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),
});
})
}
}

View File

@@ -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",
});
})
}
}

View File

@@ -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
}

View File

@@ -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)
})
})

View File

@@ -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),
);
)
}
}
});
});
})
})

View File

@@ -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)
})
}
}
});
})

View File

@@ -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)
})
})

View File

@@ -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: "../",
},
);
)

View File

@@ -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()
})
})

View File

@@ -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])
})
})

View File

@@ -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,
});
});
});
})
})
})

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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]
}
}

View File

@@ -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
}

View File

@@ -29,7 +29,7 @@
"prettier": {
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"semi": false,
"singleQuote": false
},
"devDependencies": {

View File

@@ -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})*/`
}