diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap b/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap index 01e2d07639..2c3d4b167c 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/__snapshots__/transformConfigSpec.test.ts.snap @@ -264,7 +264,6 @@ exports[`transformConfigSpec transformConfigSpec(bitcoind) 1`] = ` "disabled": false, "immutable": false, "name": "Pruning Mode", - "required": true, "type": "union", "variants": { "automatic": { @@ -524,7 +523,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Type", - "required": true, "type": "union", "variants": { "index": { @@ -589,7 +587,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Folder Location", - "required": false, "type": "select", "values": { "filebrowser": "filebrowser", @@ -644,7 +641,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Type", - "required": true, "type": "union", "variants": { "redirect": { @@ -705,7 +701,6 @@ exports[`transformConfigSpec transformConfigSpec(embassyPages) 1`] = ` "disabled": false, "immutable": false, "name": "Folder Location", - "required": false, "type": "select", "values": { "filebrowser": "filebrowser", @@ -758,7 +753,6 @@ exports[`transformConfigSpec transformConfigSpec(nostr2) 1`] = ` "disabled": false, "immutable": false, "name": "Relay Type", - "required": true, "type": "union", "variants": { "private": { diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts index 4c074a1bdb..1eb2ea508d 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/transformConfigSpec.ts @@ -43,7 +43,6 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec { }), {}, ), - required: false, disabled: false, immutable: false, } @@ -127,7 +126,6 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec { {} as Record, ), disabled: false, - required: true, default: oldVal.default, immutable: false, } diff --git a/sdk/base/lib/actions/index.ts b/sdk/base/lib/actions/index.ts index 2d052e0721..4bcbec8b19 100644 --- a/sdk/base/lib/actions/index.ts +++ b/sdk/base/lib/actions/index.ts @@ -1,6 +1,7 @@ import * as T from "../types" import * as IST from "../actions/input/inputSpecTypes" import { Action } from "./setupActions" +import { ExtractInputSpecType } from "./input/builder/inputSpec" export type RunActionInput = | Input @@ -44,36 +45,32 @@ export const runAction = async < }) } } -type GetActionInputType< - A extends Action>, -> = A extends Action ? I : never +type GetActionInputType> = + A extends Action ? ExtractInputSpecType : never type ActionRequestBase = { reason?: string replayId?: string } -type ActionRequestInput< - T extends Action>, -> = { +type ActionRequestInput> = { kind: "partial" value: Partial> } -export type ActionRequestOptions< - T extends Action>, -> = ActionRequestBase & - ( - | { - when?: Exclude< - T.ActionRequestTrigger, - { condition: "input-not-matches" } - > - input?: ActionRequestInput - } - | { - when: T.ActionRequestTrigger & { condition: "input-not-matches" } - input: ActionRequestInput - } - ) +export type ActionRequestOptions> = + ActionRequestBase & + ( + | { + when?: Exclude< + T.ActionRequestTrigger, + { condition: "input-not-matches" } + > + input?: ActionRequestInput + } + | { + when: T.ActionRequestTrigger & { condition: "input-not-matches" } + input: ActionRequestInput + } + ) const _validate: T.ActionRequest = {} as ActionRequestOptions & { actionId: string @@ -81,9 +78,7 @@ const _validate: T.ActionRequest = {} as ActionRequestOptions & { severity: T.ActionSeverity } -export const requestAction = < - T extends Action>, ->(options: { +export const requestAction = >(options: { effects: T.Effects packageId: T.PackageId action: T diff --git a/sdk/base/lib/actions/input/builder/inputSpec.ts b/sdk/base/lib/actions/input/builder/inputSpec.ts index 288ce8b3ea..611ad8a489 100644 --- a/sdk/base/lib/actions/input/builder/inputSpec.ts +++ b/sdk/base/lib/actions/input/builder/inputSpec.ts @@ -1,5 +1,5 @@ import { ValueSpec } from "../inputSpecTypes" -import { Value } from "./value" +import { PartialValue, Value } from "./value" import { _ } from "../../../util" import { Effects } from "../../../Effects" import { Parser, object } from "ts-matches" @@ -16,6 +16,15 @@ export type ExtractInputSpecType | InputSpec | InputSpec ? B : A +export type ExtractPartialInputSpecType< + A extends + | Record + | InputSpec, any> + | InputSpec, never>, +> = A extends InputSpec | InputSpec + ? PartialValue + : PartialValue + export type InputSpecOf, Store = never> = { [K in keyof A]: Value } @@ -84,6 +93,8 @@ export class InputSpec, Store = never> { }, public validator: Parser, ) {} + _TYPE: Type = null as any as Type + _PARTIAL: PartialValue = null as any as PartialValue async build(options: LazyBuildOptions) { const answer = {} as { [K in keyof Type]: ValueSpec diff --git a/sdk/base/lib/actions/input/builder/value.ts b/sdk/base/lib/actions/input/builder/value.ts index 78318f868a..053bd0596d 100644 --- a/sdk/base/lib/actions/input/builder/value.ts +++ b/sdk/base/lib/actions/input/builder/value.ts @@ -1,6 +1,6 @@ import { InputSpec, LazyBuild } from "./inputSpec" import { List } from "./list" -import { Variants } from "./variants" +import { PartialUnionRes, UnionRes, Variants } from "./variants" import { FilePath, Pattern, @@ -26,37 +26,14 @@ import { string, unknown, } from "ts-matches" +import { DeepPartial } from "../../../types" -export type RequiredDefault = - | false - | { - default: A | null - } - -function requiredLikeToAbove, A>( - requiredLike: Input, -) { - // prettier-ignore - return { - required: (typeof requiredLike === 'object' ? true : requiredLike) as ( - Input extends { default: unknown} ? true: - Input extends true ? true : - false - ), - default:(typeof requiredLike === 'object' ? requiredLike.default : null) as ( - Input extends { default: infer Default } ? Default : - null - ) - }; -} -type AsRequired = MaybeRequiredType extends - | { default: unknown } - | never - ? Type - : Type | null | undefined +type AsRequired = Required extends true + ? T + : T | null | undefined const testForAsRequiredParser = once( - () => object({ required: object({ default: unknown }) }).test, + () => object({ required: literal(true) }).test, ) function asRequiredParser< Type, @@ -69,6 +46,13 @@ function asRequiredParser< return parser.optional() as any } +export type PartialValue = + T extends UnionRes + ? PartialUnionRes + : T extends {} + ? { [P in keyof T]?: PartialValue } + : T + export class Value { protected constructor( public build: LazyBuild, @@ -122,19 +106,19 @@ export class Value { boolean, ) } - static text>(a: { + static text(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | RandomString | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'World' } - * @example required: { default: { charset: 'abcdefg', len: 16 } } + * provide a default value. + * @type { string | RandomString | null } + * @example default: null + * @example default: 'World' + * @example default: { charset: 'abcdefg', len: 16 } */ + default: string | RandomString | null required: Required /** * @description Mask (aka camouflage) text input with dots: ● ● ● @@ -188,7 +172,6 @@ export class Value { immutable: a.immutable ?? false, generate: a.generate ?? null, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(string, a), ) @@ -200,7 +183,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: DefaultString | null + required: boolean masked?: boolean placeholder?: string | null minLength?: number | null @@ -228,19 +212,16 @@ export class Value { immutable: false, generate: a.generate ?? null, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static textarea(a: { + static textarea(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - /** - * @description Unlike other "required" fields, for textarea this is a simple boolean. - */ - required: boolean + default: string | null + required: Required minLength?: number | null maxLength?: number | null placeholder?: string | null @@ -250,20 +231,23 @@ export class Value { */ immutable?: boolean }) { - return new Value(async () => { - const built: ValueSpecTextarea = { - description: null, - warning: null, - minLength: null, - maxLength: null, - placeholder: null, - type: "textarea" as const, - disabled: false, - immutable: a.immutable ?? false, - ...a, - } - return built - }, string) + return new Value, never>( + async () => { + const built: ValueSpecTextarea = { + description: null, + warning: null, + minLength: null, + maxLength: null, + placeholder: null, + type: "textarea" as const, + disabled: false, + immutable: a.immutable ?? false, + ...a, + } + return built + }, + asRequiredParser(string, a), + ) } static dynamicTextarea( getA: LazyBuild< @@ -272,6 +256,7 @@ export class Value { name: string description?: string | null warning?: string | null + default: string | null required: boolean minLength?: number | null maxLength?: number | null @@ -280,7 +265,7 @@ export class Value { } >, ) { - return new Value(async (options) => { + return new Value(async (options) => { const a = await getA(options) return { description: null, @@ -293,20 +278,20 @@ export class Value { immutable: false, ...a, } - }, string) + }, string.optional()) } - static number>(a: { + static number(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: number | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 7 } + * @description optionally provide a default value. + * @type { default: number | null } + * @example default: null + * @example default: 7 */ + default: number | null required: Required min?: number | null max?: number | null @@ -343,7 +328,6 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(number, a), ) @@ -355,7 +339,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: number | null + required: boolean min?: number | null max?: number | null step?: number | null @@ -380,22 +365,21 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, number.optional()) } - static color>(a: { + static color(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'ffffff' } + * @description optionally provide a default value. + * @type { default: string | null } + * @example default: null + * @example default: 'ffffff' */ + default: string | null required: Required /** * @description Once set, the value can never be changed. @@ -411,9 +395,7 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), - asRequiredParser(string, a), ) } @@ -425,7 +407,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string | null + required: boolean disabled?: false | string } >, @@ -439,22 +422,21 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static datetime>(a: { + static datetime(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: '1985-12-16 18:00:00.000' } + * @description optionally provide a default value. + * @type { default: string | null } + * @example default: null + * @example default: '1985-12-16 18:00:00.000' */ + default: string | null required: Required /** * @description Informs the browser how to behave and which date/time component to display. @@ -481,7 +463,6 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), asRequiredParser(string, a), ) @@ -493,7 +474,8 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string | null + required: boolean inputmode?: ValueSpecDatetime["inputmode"] min?: string | null max?: string | null @@ -513,26 +495,21 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } }, string.optional()) } - static select< - Required extends RequiredDefault, - Values extends Record, - >(a: { + static select>(a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** * @description Determines if the field is required. If so, optionally provide a default value from the list of values. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'radio1' } + * @type { (keyof Values & string) | null } + * @example default: null + * @example default: 'radio1' */ - required: Required + default: keyof Values & string /** * @description A mapping of unique radio options to their human readable display format. * @example @@ -551,7 +528,7 @@ export class Value { */ immutable?: boolean }) { - return new Value, never>( + return new Value( () => ({ description: null, warning: null, @@ -559,16 +536,10 @@ export class Value { disabled: false, immutable: a.immutable ?? false, ...a, - ...requiredLikeToAbove(a.required), }), - asRequiredParser( - anyOf( - ...Object.keys(a.values).map((x: keyof Values & string) => - literal(x), - ), - ), - a, - ) as any, + anyOf( + ...Object.keys(a.values).map((x: keyof Values & string) => literal(x)), + ), ) } static dynamicSelect( @@ -578,13 +549,13 @@ export class Value { name: string description?: string | null warning?: string | null - required: RequiredDefault + default: string values: Record disabled?: false | string | string[] } >, ) { - return new Value(async (options) => { + return new Value(async (options) => { const a = await getA(options) return { description: null, @@ -593,9 +564,8 @@ export class Value { disabled: false, immutable: false, ...a, - ...requiredLikeToAbove(a.required), } - }, string.optional()) + }, string) } static multiselect>(a: { name: string @@ -605,7 +575,7 @@ export class Value { /** * @description A simple list of which options should be checked by default. */ - default: string[] + default: (keyof Values & string)[] /** * @description A mapping of checkbox options to their human readable display format. * @example @@ -689,11 +659,11 @@ export class Value { } }, spec.validator) } - // static file(a: { + // static file(a: { // name: string // description?: string | null // extensions: string[] - // required: boolean + // required: Required // }) { // const buildValue = { // type: "file" as const, @@ -701,14 +671,14 @@ export class Value { // warning: null, // ...a, // } - // return new Value( + // return new Value, Store>( // () => ({ // ...buildValue, // }), // asRequiredParser(object({ filePath: string }), a), // ) // } - // static dynamicFile( + // static dynamicFile( // a: LazyBuild< // Store, // { @@ -716,43 +686,49 @@ export class Value { // description?: string | null // warning?: string | null // extensions: string[] - // required: Required + // required: boolean // } // >, // ) { - // return new Value( + // return new Value( // async (options) => ({ // type: "file" as const, // description: null, // warning: null, // ...(await a(options)), // }), - // string.optional(), + // object({ filePath: string }).optional(), // ) // } - static union, Type, Store>( + static union< + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, + >( a: { name: string description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value from the list of variants. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'variant1' } + * @description Provide a default value from the list of variants. + * @type { string } + * @example default: 'variant1' */ - required: Required + default: keyof VariantValues & string /** * @description Once set, the value can never be changed. * @default false */ immutable?: boolean }, - aVariants: Variants, + aVariants: Variants, ) { - return new Value, Store>( + return new Value( async (options) => ({ type: "union" as const, description: null, @@ -760,44 +736,50 @@ export class Value { disabled: false, ...a, variants: await aVariants.build(options as any), - ...requiredLikeToAbove(a.required), immutable: a.immutable ?? false, }), - asRequiredParser(aVariants.validator, a), + aVariants.validator, ) } static filteredUnion< - Required extends RequiredDefault, - Type extends Record, - Store = never, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, >( getDisabledFn: LazyBuild, a: { name: string description?: string | null warning?: string | null - required: Required + default: keyof VariantValues & string }, - aVariants: Variants | Variants, + aVariants: Variants | Variants, ) { - return new Value, Store>( + return new Value( async (options) => ({ type: "union" as const, description: null, warning: null, ...a, variants: await aVariants.build(options as any), - ...requiredLikeToAbove(a.required), disabled: (await getDisabledFn(options)) || false, immutable: false, }), - asRequiredParser(aVariants.validator, a), + aVariants.validator, ) } static dynamicUnion< - Required extends RequiredDefault, - Type extends Record, - Store = never, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, >( getA: LazyBuild< Store, @@ -805,24 +787,26 @@ export class Value { name: string description?: string | null warning?: string | null - required: Required + default: keyof VariantValues & string disabled: string[] | false | string } >, - aVariants: Variants | Variants, + aVariants: Variants | Variants, ) { - return new Value(async (options) => { - const newValues = await getA(options) - return { - type: "union" as const, - description: null, - warning: null, - ...newValues, - variants: await aVariants.build(options as any), - ...requiredLikeToAbove(newValues.required), - immutable: false, - } - }, aVariants.validator.optional()) + return new Value( + async (options) => { + const newValues = await getA(options) + return { + type: "union" as const, + description: null, + warning: null, + ...newValues, + variants: await aVariants.build(options as any), + immutable: false, + } + }, + aVariants.validator, + ) } static list(a: List) { diff --git a/sdk/base/lib/actions/input/builder/variants.ts b/sdk/base/lib/actions/input/builder/variants.ts index 6c0f839054..05124f45aa 100644 --- a/sdk/base/lib/actions/input/builder/variants.ts +++ b/sdk/base/lib/actions/input/builder/variants.ts @@ -1,6 +1,54 @@ +import { DeepPartial } from "../../../types" import { ValueSpec, ValueSpecUnion } from "../inputSpecTypes" -import { LazyBuild, InputSpec } from "./inputSpec" -import { Parser, anyOf, literals, object } from "ts-matches" +import { + LazyBuild, + InputSpec, + ExtractInputSpecType, + ExtractPartialInputSpecType, +} from "./inputSpec" +import { Parser, anyOf, literal, object } from "ts-matches" + +export type UnionRes< + Store, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + K extends keyof VariantValues & string = keyof VariantValues & string, +> = { + [key in keyof VariantValues]: { + selection: key + value: ExtractInputSpecType + other?: { + [key2 in Exclude]?: DeepPartial< + ExtractInputSpecType + > + } + } +}[K] + +export type PartialUnionRes< + Store, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + K extends keyof VariantValues & string = keyof VariantValues & string, +> = { + [key in keyof VariantValues]: { + selection?: key + value?: ExtractPartialInputSpecType + other?: { + [key2 in Exclude]?: DeepPartial< + ExtractInputSpecType + > + } + } +}[K] /** * Used in the the Value.select { @link './value.ts' } @@ -44,18 +92,24 @@ export const pruning = Value.union( description: '- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n', warning: null, - required: true, default: "disabled", }, pruningSettingsVariants ); ``` */ -export class Variants { - static text: any +export class Variants< + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, + Store, +> { private constructor( public build: LazyBuild, - public validator: Parser, + public validator: Parser>, ) {} static of< VariantValues extends { @@ -67,26 +121,15 @@ export class Variants { Store = never, >(a: VariantValues) { const validator = anyOf( - ...Object.entries(a).map(([name, { spec }]) => + ...Object.entries(a).map(([id, { spec }]) => object({ - selection: literals(name), + selection: literal(id), value: spec.validator, }), ), ) as Parser - return new Variants< - { - [K in keyof VariantValues]: { - selection: K - // prettier-ignore - value: - VariantValues[K]["spec"] extends (InputSpec | InputSpec) ? B : - never - } - }[keyof VariantValues], - Store - >(async (options) => { + return new Variants(async (options) => { const variants = {} as { [K in keyof VariantValues]: { name: string @@ -118,6 +161,6 @@ export class Variants { ``` */ withStore() { - return this as any as Variants + return this as any as Variants } } diff --git a/sdk/base/lib/actions/input/inputSpecConstants.ts b/sdk/base/lib/actions/input/inputSpecConstants.ts index 3beaefd512..57bf8a79bb 100644 --- a/sdk/base/lib/actions/input/inputSpecConstants.ts +++ b/sdk/base/lib/actions/input/inputSpecConstants.ts @@ -10,35 +10,34 @@ import { Variants } from "./builder/variants" export const customSmtp = InputSpec.of, never>({ server: Value.text({ name: "SMTP Server", - required: { - default: null, - }, + required: true, + default: null, }), port: Value.number({ name: "Port", - required: { default: 587 }, + required: true, + default: 587, min: 1, max: 65535, integer: true, }), from: Value.text({ name: "From Address", - required: { - default: null, - }, + required: true, + default: null, placeholder: "test@example.com", inputmode: "email", patterns: [Patterns.email], }), login: Value.text({ name: "Login", - required: { - default: null, - }, + required: true, + default: null, }), password: Value.text({ name: "Password", required: false, + default: null, masked: true, }), }) @@ -54,7 +53,7 @@ export const smtpInputSpec = Value.filteredUnion( { name: "SMTP", description: "Optionally provide an SMTP server for sending emails", - required: { default: "disabled" }, + default: "disabled", }, Variants.of({ disabled: { name: "Disabled", spec: InputSpec.of({}) }, @@ -66,6 +65,7 @@ export const smtpInputSpec = Value.filteredUnion( description: "A custom from address for this service. If not provided, the system from address will be used.", required: false, + default: null, placeholder: "test@example.com", inputmode: "email", patterns: [Patterns.email], diff --git a/sdk/base/lib/actions/input/inputSpecTypes.ts b/sdk/base/lib/actions/input/inputSpecTypes.ts index ee9189ae36..362a56ea10 100644 --- a/sdk/base/lib/actions/input/inputSpecTypes.ts +++ b/sdk/base/lib/actions/input/inputSpecTypes.ts @@ -115,7 +115,6 @@ export type ValueSpecSelect = { description: string | null warning: string | null type: "select" - required: boolean default: string | null disabled: false | string | string[] immutable: boolean @@ -158,7 +157,6 @@ export type ValueSpecUnion = { } > disabled: false | string | string[] - required: boolean default: string | null immutable: boolean } diff --git a/sdk/base/lib/actions/setupActions.ts b/sdk/base/lib/actions/setupActions.ts index 62d6cedd96..0812255691 100644 --- a/sdk/base/lib/actions/setupActions.ts +++ b/sdk/base/lib/actions/setupActions.ts @@ -1,5 +1,8 @@ import { InputSpec } from "./input/builder" -import { ExtractInputSpecType } from "./input/builder/inputSpec" +import { + ExtractInputSpecType, + ExtractPartialInputSpecType, +} from "./input/builder/inputSpec" import * as T from "../types" import { once } from "../util" @@ -20,7 +23,10 @@ export type GetInput< > = (options: { effects: T.Effects }) => Promise< - null | void | undefined | (ExtractInputSpecType & Record) + | null + | void + | undefined + | (ExtractPartialInputSpecType & Record) > export type MaybeFn = T | ((options: { effects: T.Effects }) => Promise) @@ -52,15 +58,13 @@ export class Action< | Record | InputSpec | InputSpec, - Type extends - ExtractInputSpecType = ExtractInputSpecType, > { private constructor( readonly id: Id, private readonly metadataFn: MaybeFn, private readonly inputSpec: InputSpecType, - private readonly getInputFn: GetInput, - private readonly runFn: Run, + private readonly getInputFn: GetInput>, + private readonly runFn: Run>, ) {} static withInput< Id extends T.ActionId, @@ -69,15 +73,13 @@ export class Action< | Record | InputSpec | InputSpec, - Type extends - ExtractInputSpecType = ExtractInputSpecType, >( id: Id, metadata: MaybeFn>, inputSpec: InputSpecType, - getInput: GetInput, - run: Run, - ): Action { + getInput: GetInput>, + run: Run>, + ): Action { return new Action( id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })), @@ -90,7 +92,7 @@ export class Action< id: Id, metadata: MaybeFn>, run: Run<{}>, - ): Action { + ): Action { return new Action( id, mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })), @@ -114,7 +116,7 @@ export class Action< } async run(options: { effects: T.Effects - input: Type + input: ExtractInputSpecType }): Promise { return (await this.runFn(options)) || null } @@ -122,13 +124,13 @@ export class Action< export class Actions< Store, - AllActions extends Record>, + AllActions extends Record>, > { private constructor(private readonly actions: AllActions) {} static of(): Actions { return new Actions({}) } - addAction>( + addAction>( action: A, ): Actions { return new Actions({ ...this.actions, [action.id]: action }) diff --git a/sdk/base/lib/types.ts b/sdk/base/lib/types.ts index d7ab1e51b0..ab1acaa877 100644 --- a/sdk/base/lib/types.ts +++ b/sdk/base/lib/types.ts @@ -86,7 +86,7 @@ export namespace ExpectedExports { export type actions = Actions< any, - Record> + Record> > } export type ABI = { diff --git a/sdk/base/package-lock.json b/sdk/base/package-lock.json index 91c4706763..4c8f583640 100644 --- a/sdk/base/package-lock.json +++ b/sdk/base/package-lock.json @@ -14,7 +14,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2" }, "devDependencies": { @@ -3897,9 +3897,10 @@ "dev": true }, "node_modules/ts-matches": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.5.1.tgz", - "integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.0.0.tgz", + "integrity": "sha512-vR4hhz9bYMW30qIJUuLaeAWlsR54vse6ZI2riVhVLMBE6/vss43jwrOvbHheiyU7e26ssT/yWx69aJHD2REJSA==", + "license": "MIT" }, "node_modules/ts-morph": { "version": "18.0.0", diff --git a/sdk/base/package.json b/sdk/base/package.json index e38cc5ac69..509e621ded 100644 --- a/sdk/base/package.json +++ b/sdk/base/package.json @@ -27,7 +27,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2" }, "prettier": { diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index f41961d166..28a14e1e55 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -1,7 +1,4 @@ -import { - RequiredDefault, - Value, -} from "../../base/lib/actions/input/builder/value" +import { Value } from "../../base/lib/actions/input/builder/value" import { InputSpec, ExtractInputSpecType, @@ -141,9 +138,7 @@ export class StartSdk { ...startSdkEffectWrapper, action: { run: actions.runAction, - request: < - T extends Action>, - >( + request: >( effects: T.Effects, packageId: T.PackageId, action: T, @@ -157,9 +152,7 @@ export class StartSdk { severity, options: options, }), - requestOwn: < - T extends Action>, - >( + requestOwn: >( effects: T.Effects, action: T, severity: T.ActionSeverity, @@ -1060,14 +1053,14 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | RandomString | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'World' } - * @example required: { default: { charset: 'abcdefg', len: 16 } } + * @description optionally provide a default value. + * @type { string | RandomString | null } + * @example default: null + * @example default: 'World' + * @example default: { charset: 'abcdefg', len: 16 } */ - required: RequiredDefault + default: DefaultString | null + required: boolean /** * @description Mask (aka camouflage) text input with dots: ● ● ● * @default false @@ -1110,15 +1103,12 @@ export class StartSdk { description?: string | null /** Presents a warning prompt before permitting the value to change. */ warning?: string | null - /** - * @description Unlike other "required" fields, for textarea this is a simple boolean. - */ + default: string | null required: boolean minLength?: number | null maxLength?: number | null placeholder?: string | null disabled?: false | string - generate?: null | RandomString } >, ) => Value.dynamicTextarea(getA), @@ -1131,13 +1121,13 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: number | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 7 } + * @description optionally provide a default value. + * @type { number | null } + * @example default: null + * @example default: 7 */ - required: RequiredDefault + default: number | null + required: boolean min?: number | null max?: number | null /** @@ -1167,13 +1157,13 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'ffffff' } + * @description optionally provide a default value. + * @type { string | null } + * @example default: null + * @example default: 'ffffff' */ - required: RequiredDefault + default: string | null + required: boolean disabled?: false | string } >, @@ -1187,13 +1177,13 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: '1985-12-16 18:00:00.000' } + * @description optionally provide a default value. + * @type { string | null } + * @example default: null + * @example default: '1985-12-16 18:00:00.000' */ - required: RequiredDefault + default: string + required: boolean /** * @description Informs the browser how to behave and which date/time component to display. * @default "datetime-local" @@ -1205,7 +1195,7 @@ export class StartSdk { } >, ) => Value.dynamicDatetime(getA), - dynamicSelect: ( + dynamicSelect: >( getA: LazyBuild< Store, { @@ -1214,13 +1204,12 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value from the list of values. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'radio1' } + * @description provide a default value from the list of values. + * @type { default: string } + * @example default: 'radio1' */ - required: RequiredDefault + default: keyof Variants & string + required: boolean /** * @description A mapping of unique radio options to their human readable display format. * @example @@ -1232,7 +1221,7 @@ export class StartSdk { } * ``` */ - values: Record + values: Variants /** * @options * - false - The field can be modified. @@ -1282,27 +1271,37 @@ export class StartSdk { >, ) => Value.dynamicMultiselect(getA), filteredUnion: < - Required extends RequiredDefault, - Type extends Record, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, >( getDisabledFn: LazyBuild, a: { name: string description?: string | null warning?: string | null - required: Required + default: keyof VariantValues & string }, - aVariants: Variants | Variants, + aVariants: + | Variants + | Variants, ) => - Value.filteredUnion( + Value.filteredUnion( getDisabledFn, a, aVariants, ), dynamicUnion: < - Required extends RequiredDefault, - Type extends Record, + VariantValues extends { + [K in string]: { + name: string + spec: InputSpec | InputSpec + } + }, >( getA: LazyBuild< Store, @@ -1312,13 +1311,12 @@ export class StartSdk { /** Presents a warning prompt before permitting the value to change. */ warning?: string | null /** - * @description Determines if the field is required. If so, optionally provide a default value from the list of variants. - * @type { false | { default: string | null } } - * @example required: false - * @example required: { default: null } - * @example required: { default: 'variant1' } + * @description provide a default value from the list of variants. + * @type { string } + * @example default: 'variant1' */ - required: Required + default: keyof VariantValues & string + required: boolean /** * @options * - false - The field can be modified. @@ -1329,8 +1327,10 @@ export class StartSdk { disabled: false | string | string[] } >, - aVariants: Variants | Variants, - ) => Value.dynamicUnion(getA, aVariants), + aVariants: + | Variants + | Variants, + ) => Value.dynamicUnion(getA, aVariants), }, Variants: { of: < diff --git a/sdk/package/lib/test/inputSpecBuilder.test.ts b/sdk/package/lib/test/inputSpecBuilder.test.ts index f9d4321a63..195acb40a8 100644 --- a/sdk/package/lib/test/inputSpecBuilder.test.ts +++ b/sdk/package/lib/test/inputSpecBuilder.test.ts @@ -17,7 +17,8 @@ describe("builder tests", () => { "peer-tor-address": Value.text({ name: "Peer tor address", description: "The Tor address of the peer interface", - required: { default: null }, + required: true, + default: null, }), }).build({} as any) expect(bitcoinPropertiesBuilt).toMatchObject({ @@ -55,7 +56,8 @@ describe("values", () => { test("text", async () => { const value = Value.text({ name: "Testing", - required: { default: null }, + required: true, + default: null, }) const validator = value.validator const rawIs = await value.build({} as any) @@ -66,7 +68,8 @@ describe("values", () => { test("text with default", async () => { const value = Value.text({ name: "Testing", - required: { default: "this is a default value" }, + required: true, + default: "this is a default value", }) const validator = value.validator const rawIs = await value.build({} as any) @@ -78,6 +81,7 @@ describe("values", () => { const value = Value.text({ name: "Testing", required: false, + default: null, }) const validator = value.validator const rawIs = await value.build({} as any) @@ -89,6 +93,7 @@ describe("values", () => { const value = Value.color({ name: "Testing", required: false, + default: null, description: null, warning: null, }) @@ -99,7 +104,8 @@ describe("values", () => { test("datetime", async () => { const value = Value.datetime({ name: "Testing", - required: { default: null }, + required: true, + default: null, description: null, warning: null, inputmode: "date", @@ -114,6 +120,7 @@ describe("values", () => { const value = Value.datetime({ name: "Testing", required: false, + default: null, description: null, warning: null, inputmode: "date", @@ -128,6 +135,7 @@ describe("values", () => { const value = Value.textarea({ name: "Testing", required: false, + default: null, description: null, warning: null, minLength: null, @@ -136,12 +144,13 @@ describe("values", () => { }) const validator = value.validator validator.unsafeCast("test text") - testOutput()(null) + testOutput()(null) }) test("number", async () => { const value = Value.number({ name: "Testing", - required: { default: null }, + required: true, + default: null, integer: false, description: null, warning: null, @@ -159,6 +168,7 @@ describe("values", () => { const value = Value.number({ name: "Testing", required: false, + default: null, integer: false, description: null, warning: null, @@ -175,7 +185,7 @@ describe("values", () => { test("select", async () => { const value = Value.select({ name: "Testing", - required: { default: null }, + default: "a", values: { a: "A", b: "B", @@ -192,7 +202,7 @@ describe("values", () => { test("nullable select", async () => { const value = Value.select({ name: "Testing", - required: false, + default: "a", values: { a: "A", b: "B", @@ -203,8 +213,7 @@ describe("values", () => { const validator = value.validator validator.unsafeCast("a") validator.unsafeCast("b") - validator.unsafeCast(null) - testOutput()(null) + testOutput()(null) }) test("multiselect", async () => { const value = Value.multiselect({ @@ -250,7 +259,7 @@ describe("values", () => { const value = Value.union( { name: "Testing", - required: { default: null }, + default: "a", description: null, warning: null, }, @@ -271,7 +280,16 @@ describe("values", () => { const validator = value.validator validator.unsafeCast({ selection: "a", value: { b: false } }) type Test = typeof validator._TYPE - testOutput()(null) + testOutput< + Test, + { + selection: "a" + value: { + b: boolean + } + other?: {} + } + >()(null) }) describe("dynamic", () => { @@ -301,7 +319,8 @@ describe("values", () => { test("text", async () => { const value = Value.dynamicText(async () => ({ name: "Testing", - required: { default: null }, + required: true, + default: null, })) const validator = value.validator const rawIs = await value.build({} as any) @@ -317,7 +336,8 @@ describe("values", () => { test("text with default", async () => { const value = Value.dynamicText(async () => ({ name: "Testing", - required: { default: "this is a default value" }, + required: true, + default: "this is a default value", })) const validator = value.validator validator.unsafeCast("test text") @@ -333,6 +353,7 @@ describe("values", () => { const value = Value.dynamicText(async () => ({ name: "Testing", required: false, + default: null, })) const validator = value.validator const rawIs = await value.build({} as any) @@ -349,6 +370,7 @@ describe("values", () => { const value = Value.dynamicColor(async () => ({ name: "Testing", required: false, + default: null, description: null, warning: null, })) @@ -414,7 +436,8 @@ describe("values", () => { return { name: "Testing", - required: { default: null }, + required: true, + default: null, inputmode: "date", } }, @@ -436,6 +459,7 @@ describe("values", () => { const value = Value.dynamicTextarea(async () => ({ name: "Testing", required: false, + default: null, description: null, warning: null, minLength: null, @@ -444,8 +468,7 @@ describe("values", () => { })) const validator = value.validator validator.unsafeCast("test text") - expect(() => validator.unsafeCast(null)).toThrowError() - testOutput()(null) + testOutput()(null) expect(await value.build(fakeOptions)).toMatchObject({ name: "Testing", required: false, @@ -454,7 +477,8 @@ describe("values", () => { test("number", async () => { const value = Value.dynamicNumber(() => ({ name: "Testing", - required: { default: null }, + required: true, + default: null, integer: false, description: null, warning: null, @@ -477,7 +501,7 @@ describe("values", () => { test("select", async () => { const value = Value.dynamicSelect(() => ({ name: "Testing", - required: { default: null }, + default: "a", values: { a: "A", b: "B", @@ -489,11 +513,9 @@ describe("values", () => { validator.unsafeCast("a") validator.unsafeCast("b") validator.unsafeCast("c") - validator.unsafeCast(null) - testOutput()(null) + testOutput()(null) expect(await value.build(fakeOptions)).toMatchObject({ name: "Testing", - required: true, }) }) test("multiselect", async () => { @@ -529,7 +551,7 @@ describe("values", () => { () => ["a", "c"], { name: "Testing", - required: { default: null }, + default: "a", description: null, warning: null, }, @@ -563,8 +585,28 @@ describe("values", () => { type Test = typeof validator._TYPE testOutput< Test, - | { selection: "a"; value: { b: boolean } } - | { selection: "b"; value: { b: boolean } } + | { + selection: "a" + value: { + b: boolean + } + other?: { + b?: { + b?: boolean + } + } + } + | { + selection: "b" + value: { + b: boolean + } + other?: { + a?: { + b?: boolean + } + } + } >()(null) const built = await value.build({} as any) @@ -596,7 +638,7 @@ describe("values", () => { () => ({ disabled: ["a", "c"], name: "Testing", - required: { default: null }, + default: "b", description: null, warning: null, }), @@ -630,10 +672,28 @@ describe("values", () => { type Test = typeof validator._TYPE testOutput< Test, - | { selection: "a"; value: { b: boolean } } - | { selection: "b"; value: { b: boolean } } - | null - | undefined + | { + selection: "a" + value: { + b: boolean + } + other?: { + b?: { + b?: boolean + } + } + } + | { + selection: "b" + value: { + b: boolean + } + other?: { + a?: { + b?: boolean + } + } + } >()(null) const built = await value.build({} as any) @@ -728,6 +788,7 @@ describe("Nested nullable values", () => { description: "If no name is provided, the name from inputSpec will be used", required: false, + default: null, }), }) const validator = value.validator @@ -743,6 +804,7 @@ describe("Nested nullable values", () => { description: "If no name is provided, the name from inputSpec will be used", required: false, + default: null, warning: null, placeholder: null, integer: false, @@ -765,6 +827,7 @@ describe("Nested nullable values", () => { description: "If no name is provided, the name from inputSpec will be used", required: false, + default: null, warning: null, }), }) @@ -780,7 +843,7 @@ describe("Nested nullable values", () => { name: "Temp Name", description: "If no name is provided, the name from inputSpec will be used", - required: false, + default: "a", warning: null, values: { a: "A", @@ -791,7 +854,7 @@ describe("Nested nullable values", () => { name: "Temp Name", description: "If no name is provided, the name from inputSpec will be used", - required: false, + default: "a", warning: null, values: { a: "A", @@ -799,10 +862,9 @@ describe("Nested nullable values", () => { }).build({} as any) const validator = value.validator - validator.unsafeCast({ a: null }) validator.unsafeCast({ a: "a" }) expect(() => validator.unsafeCast({ a: "4" })).toThrowError() - testOutput()(null) + testOutput()(null) }) test("Testing multiselect", async () => { const value = InputSpec.of({ diff --git a/sdk/package/lib/test/output.test.ts b/sdk/package/lib/test/output.test.ts index 37636e52f1..53d006274a 100644 --- a/sdk/package/lib/test/output.test.ts +++ b/sdk/package/lib/test/output.test.ts @@ -87,7 +87,7 @@ describe("Inputs", () => { dbcache: 5, pruning: { selection: "disabled", - value: {}, + value: { disabled: {} }, }, blockfilters: { blockfilterindex: false, diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index 615bcfdc91..80e5b3564c 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -80,7 +80,8 @@ export class FileHelper { protected constructor( readonly path: string, readonly writeData: (dataIn: A) => string, - readonly readData: (stringValue: string) => A, + readonly readData: (stringValue: string) => unknown, + readonly validate: (value: unknown) => A, ) {} /** @@ -97,10 +98,7 @@ export class FileHelper { return null } - /** - * Reads the file from disk and converts it to structured data. - */ - private async readOnce(): Promise { + private async readFile(): Promise { if (!(await exists(this.path))) { return null } @@ -109,6 +107,15 @@ export class FileHelper { ) } + /** + * Reads the file from disk and converts it to structured data. + */ + private async readOnce(): Promise { + const data = await this.readFile() + if (!data) return null + return this.validate(data) + } + private async readConst(effects: T.Effects): Promise { const watch = this.readWatch() const res = await watch.next() @@ -156,22 +163,22 @@ export class FileHelper { * Accepts full structured data and performs a merge with the existing file on disk if it exists. */ async write(data: A) { - const fileData = (await this.readOnce()) || {} + const fileData = (await this.readFile()) || {} const mergeData = merge({}, fileData, data) - return await this.writeFile(mergeData) + return await this.writeFile(this.validate(mergeData)) } /** * Accepts partial structured data and performs a merge with the existing file on disk. */ - async merge(data: Partial) { + async merge(data: T.DeepPartial) { const fileData = - (await this.readOnce()) || + (await this.readFile()) || (() => { throw new Error(`${this.path}: does not exist`) })() const mergeData = merge({}, fileData, data) - return await this.writeFile(mergeData) + return await this.writeFile(this.validate(mergeData)) } /** @@ -179,7 +186,7 @@ export class FileHelper { * Like one behaviour of another dependency or something similar. */ withPath(path: string) { - return new FileHelper(path, this.writeData, this.readData) + return new FileHelper(path, this.writeData, this.readData, this.validate) } /** @@ -190,9 +197,10 @@ export class FileHelper { static raw( path: string, toFile: (dataIn: A) => string, - fromFile: (rawData: string) => A, + fromFile: (rawData: string) => unknown, + validate: (data: unknown) => A, ) { - return new FileHelper(path, toFile, fromFile) + return new FileHelper(path, toFile, fromFile, validate) } /** * Create a File Helper for a .json file. @@ -200,12 +208,9 @@ export class FileHelper { static json(path: string, shape: matches.Validator) { return new FileHelper( path, - (inData) => { - return JSON.stringify(inData, null, 2) - }, - (inString) => { - return shape.unsafeCast(JSON.parse(inString)) - }, + (inData) => JSON.stringify(inData, null, 2), + (inString) => JSON.parse(inString), + (data) => shape.unsafeCast(data), ) } /** @@ -217,12 +222,9 @@ export class FileHelper { ) { return new FileHelper( path, - (inData) => { - return TOML.stringify(inData as any) - }, - (inString) => { - return shape.unsafeCast(TOML.parse(inString)) - }, + (inData) => TOML.stringify(inData as any), + (inString) => TOML.parse(inString), + (data) => shape.unsafeCast(data), ) } /** @@ -234,12 +236,9 @@ export class FileHelper { ) { return new FileHelper( path, - (inData) => { - return YAML.stringify(inData, null, 2) - }, - (inString) => { - return shape.unsafeCast(YAML.parse(inString)) - }, + (inData) => YAML.stringify(inData, null, 2), + (inString) => YAML.parse(inString), + (data) => shape.unsafeCast(data), ) } } diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index 2cc87d3749..0b06e79db6 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,12 +1,12 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.13", + "version": "0.3.6-alpha.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.13", + "version": "0.3.6-alpha.16", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -15,7 +15,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2" }, "devDependencies": { @@ -3918,9 +3918,10 @@ "dev": true }, "node_modules/ts-matches": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.5.1.tgz", - "integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.0.0.tgz", + "integrity": "sha512-vR4hhz9bYMW30qIJUuLaeAWlsR54vse6ZI2riVhVLMBE6/vss43jwrOvbHheiyU7e26ssT/yWx69aJHD2REJSA==", + "license": "MIT" }, "node_modules/ts-morph": { "version": "18.0.0", diff --git a/sdk/package/package.json b/sdk/package/package.json index 661263f703..bbcab9830f 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.3.6-alpha.13", + "version": "0.3.6-alpha.17", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", @@ -33,7 +33,7 @@ "isomorphic-fetch": "^3.0.0", "lodash.merge": "^4.6.2", "mime-types": "^2.1.35", - "ts-matches": "^5.5.1", + "ts-matches": "^6.0.0", "yaml": "^2.2.2", "@iarna/toml": "^2.2.5", "@noble/curves": "^1.4.0", diff --git a/sdk/package/scripts/oldSpecToBuilder.ts b/sdk/package/scripts/oldSpecToBuilder.ts index 04128f2bd6..11ef303407 100644 --- a/sdk/package/scripts/oldSpecToBuilder.ts +++ b/sdk/package/scripts/oldSpecToBuilder.ts @@ -85,6 +85,7 @@ const {InputSpec, List, Value, Variants} = sdk description: value.description || null, warning: value.warning || null, required: !(value.nullable || false), + default: value.default, placeholder: value.placeholder || null, maxLength: null, minLength: null, @@ -96,12 +97,8 @@ const {InputSpec, List, Value, Variants} = sdk return `${rangeToTodoComment(value?.range)}Value.text(${JSON.stringify( { name: value.name || null, - // prettier-ignore - required: ( - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable - ), + default: value.default || null, + required: !value.nullable, description: value.description || null, warning: value.warning || null, masked: value.masked || false, @@ -130,12 +127,8 @@ const {InputSpec, List, Value, Variants} = sdk name: value.name || null, description: value.description || null, warning: value.warning || null, - // prettier-ignore - required: ( - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable - ), + default: value.default || null, + required: !value.nullable, min: null, max: null, step: null, @@ -174,13 +167,7 @@ const {InputSpec, List, Value, Variants} = sdk name: value.name || null, description: value.description || null, warning: value.warning || null, - - // prettier-ignore - required:( - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable - ), + default: value.default, values, }, null, @@ -207,14 +194,7 @@ const {InputSpec, List, Value, Variants} = sdk name: ${JSON.stringify(value.name || null)}, description: ${JSON.stringify(value.tag.description || null)}, warning: ${JSON.stringify(value.tag.warning || null)}, - - // prettier-ignore - required: ${JSON.stringify( - // prettier-ignore - value.default != null ? {default: value.default} : - value.nullable === false ? {default: null} : - !value.nullable, - )}, + default: ${JSON.stringify(value.default)}, }, ${variants})` } case "list": { @@ -341,12 +321,7 @@ const {InputSpec, List, Value, Variants} = sdk value?.spec?.tag?.description || null, )}, warning: ${JSON.stringify(value?.spec?.tag?.warning || null)}, - required: ${JSON.stringify( - // prettier-ignore - 'default' in value?.spec ? {default: value?.spec?.default} : - !!value?.spec?.tag?.nullable || false ? {default: null} : - false, - )}, + default: ${JSON.stringify(value?.spec?.default || null)}, }, ${variants}) `, ) diff --git a/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts b/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts index 743d3546b8..8594330772 100644 --- a/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts +++ b/web/projects/ui/src/app/components/backup-drives/backup-drives.component.ts @@ -268,25 +268,29 @@ const cifsSpec = ISB.InputSpec.of({ 'The hostname of your target device on the Local Area Network.', warning: null, placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, - required: { default: null }, + required: true, + default: null, patterns: [], }), path: ISB.Value.text({ name: 'Path', description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`, placeholder: 'e.g. my-shared-folder or /Desktop/my-folder', - required: { default: null }, + required: true, + default: null, }), username: ISB.Value.text({ name: 'Username', description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`, - required: { default: null }, + required: true, + default: null, placeholder: 'My Network Folder', }), password: ISB.Value.text({ name: 'Password', description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`, required: false, + default: null, masked: true, placeholder: 'My Network Folder', }), diff --git a/web/projects/ui/src/app/components/form/form-select/form-select.component.html b/web/projects/ui/src/app/components/form/form-select/form-select.component.html index fe2b561c7b..9149e8844c 100644 --- a/web/projects/ui/src/app/components/form/form-select/form-select.component.html +++ b/web/projects/ui/src/app/components/form/form-select/form-select.component.html @@ -2,13 +2,12 @@ [tuiHintContent]="spec | hint" [disabled]="disabled" [readOnly]="readOnly" - [tuiTextfieldCleaner]="!spec.required" + [tuiTextfieldCleaner]="false" [pseudoInvalid]="invalid" [(ngModel)]="selected" (focusedChange)="onFocus($event)" > - {{ spec.name }} - * + {{ spec.name }}*