@sundaeswap/sprinkles 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Sprinkle/__tests__/encryption.test.js +3 -1
- package/dist/cjs/Sprinkle/__tests__/encryption.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js +3 -37
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/field-utils.test.js +170 -0
- package/dist/cjs/Sprinkle/__tests__/field-utils.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +242 -87
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/formatting.test.js +97 -0
- package/dist/cjs/Sprinkle/__tests__/formatting.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js +9 -5
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js.map +1 -1
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +9 -0
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/cjs/Sprinkle/index.js +135 -91
- package/dist/cjs/Sprinkle/index.js.map +1 -1
- package/dist/cjs/Sprinkle/menus/array-menu.js +195 -0
- package/dist/cjs/Sprinkle/menus/array-menu.js.map +1 -0
- package/dist/cjs/Sprinkle/menus/field-menu.js +161 -0
- package/dist/cjs/Sprinkle/menus/field-menu.js.map +1 -0
- package/dist/cjs/Sprinkle/menus/index.js +33 -0
- package/dist/cjs/Sprinkle/menus/index.js.map +1 -0
- package/dist/cjs/Sprinkle/menus/object-menu.js +324 -0
- package/dist/cjs/Sprinkle/menus/object-menu.js.map +1 -0
- package/dist/cjs/Sprinkle/prompts.js +68 -2
- package/dist/cjs/Sprinkle/prompts.js.map +1 -1
- package/dist/cjs/Sprinkle/type-guards.js +48 -1
- package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
- package/dist/cjs/Sprinkle/types.js +24 -0
- package/dist/cjs/Sprinkle/types.js.map +1 -1
- package/dist/cjs/Sprinkle/utils/field-utils.js +154 -0
- package/dist/cjs/Sprinkle/utils/field-utils.js.map +1 -0
- package/dist/cjs/Sprinkle/utils/formatting.js +126 -0
- package/dist/cjs/Sprinkle/utils/formatting.js.map +1 -0
- package/dist/cjs/Sprinkle/utils/index.js +56 -0
- package/dist/cjs/Sprinkle/utils/index.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/encryption.test.js +3 -1
- package/dist/esm/Sprinkle/__tests__/encryption.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js +3 -37
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/field-utils.test.js +168 -0
- package/dist/esm/Sprinkle/__tests__/field-utils.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +243 -88
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/formatting.test.js +95 -0
- package/dist/esm/Sprinkle/__tests__/formatting.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js +9 -5
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js.map +1 -1
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +9 -0
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
- package/dist/esm/Sprinkle/index.js +102 -93
- package/dist/esm/Sprinkle/index.js.map +1 -1
- package/dist/esm/Sprinkle/menus/array-menu.js +190 -0
- package/dist/esm/Sprinkle/menus/array-menu.js.map +1 -0
- package/dist/esm/Sprinkle/menus/field-menu.js +155 -0
- package/dist/esm/Sprinkle/menus/field-menu.js.map +1 -0
- package/dist/esm/Sprinkle/menus/index.js +8 -0
- package/dist/esm/Sprinkle/menus/index.js.map +1 -0
- package/dist/esm/Sprinkle/menus/object-menu.js +318 -0
- package/dist/esm/Sprinkle/menus/object-menu.js.map +1 -0
- package/dist/esm/Sprinkle/prompts.js +59 -1
- package/dist/esm/Sprinkle/prompts.js.map +1 -1
- package/dist/esm/Sprinkle/type-guards.js +42 -0
- package/dist/esm/Sprinkle/type-guards.js.map +1 -1
- package/dist/esm/Sprinkle/types.js +24 -0
- package/dist/esm/Sprinkle/types.js.map +1 -1
- package/dist/esm/Sprinkle/utils/field-utils.js +145 -0
- package/dist/esm/Sprinkle/utils/field-utils.js.map +1 -0
- package/dist/esm/Sprinkle/utils/formatting.js +118 -0
- package/dist/esm/Sprinkle/utils/formatting.js.map +1 -0
- package/dist/esm/Sprinkle/utils/index.js +7 -0
- package/dist/esm/Sprinkle/utils/index.js.map +1 -0
- package/dist/types/Sprinkle/index.d.ts +9 -3
- package/dist/types/Sprinkle/index.d.ts.map +1 -1
- package/dist/types/Sprinkle/menus/array-menu.d.ts +31 -0
- package/dist/types/Sprinkle/menus/array-menu.d.ts.map +1 -0
- package/dist/types/Sprinkle/menus/field-menu.d.ts +34 -0
- package/dist/types/Sprinkle/menus/field-menu.d.ts.map +1 -0
- package/dist/types/Sprinkle/menus/index.d.ts +10 -0
- package/dist/types/Sprinkle/menus/index.d.ts.map +1 -0
- package/dist/types/Sprinkle/menus/object-menu.d.ts +34 -0
- package/dist/types/Sprinkle/menus/object-menu.d.ts.map +1 -0
- package/dist/types/Sprinkle/prompts.d.ts +25 -0
- package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
- package/dist/types/Sprinkle/type-guards.d.ts +24 -1
- package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
- package/dist/types/Sprinkle/types.d.ts +53 -0
- package/dist/types/Sprinkle/types.d.ts.map +1 -1
- package/dist/types/Sprinkle/utils/field-utils.d.ts +47 -0
- package/dist/types/Sprinkle/utils/field-utils.d.ts.map +1 -0
- package/dist/types/Sprinkle/utils/formatting.d.ts +30 -0
- package/dist/types/Sprinkle/utils/formatting.d.ts.map +1 -0
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Sprinkle/__tests__/encryption.test.ts +2 -0
- package/src/Sprinkle/__tests__/enhancements.test.ts +3 -42
- package/src/Sprinkle/__tests__/field-utils.test.ts +191 -0
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +252 -103
- package/src/Sprinkle/__tests__/formatting.test.ts +115 -0
- package/src/Sprinkle/__tests__/show-menu.test.ts +14 -8
- package/src/Sprinkle/__tests__/tx-dialog.test.ts +9 -0
- package/src/Sprinkle/index.ts +131 -119
- package/src/Sprinkle/menus/array-menu.ts +191 -0
- package/src/Sprinkle/menus/field-menu.ts +145 -0
- package/src/Sprinkle/menus/index.ts +12 -0
- package/src/Sprinkle/menus/object-menu.ts +336 -0
- package/src/Sprinkle/prompts.ts +71 -1
- package/src/Sprinkle/type-guards.ts +42 -0
- package/src/Sprinkle/types.ts +43 -0
- package/src/Sprinkle/utils/field-utils.ts +158 -0
- package/src/Sprinkle/utils/formatting.ts +127 -0
- package/src/Sprinkle/utils/index.ts +17 -0
package/src/Sprinkle/index.ts
CHANGED
|
@@ -8,12 +8,16 @@ import {
|
|
|
8
8
|
import { CborSet, VkeyWitness, TxCBOR } from "@blaze-cardano/core";
|
|
9
9
|
import {
|
|
10
10
|
selectCancellable,
|
|
11
|
+
selectWithClear,
|
|
11
12
|
inputCancellable,
|
|
13
|
+
inputWithClear,
|
|
12
14
|
passwordCancellable,
|
|
15
|
+
passwordWithClear,
|
|
13
16
|
confirmCancellable,
|
|
14
17
|
searchCancellable,
|
|
15
18
|
select,
|
|
16
19
|
} from "./prompts.js";
|
|
20
|
+
import colors from "yoctocolors-cjs";
|
|
17
21
|
import { type TSchema, Type, OptionalKind } from "@sinclair/typebox";
|
|
18
22
|
import * as fs from "fs";
|
|
19
23
|
import * as path from "path";
|
|
@@ -29,6 +33,11 @@ export type {
|
|
|
29
33
|
IProfileEntry,
|
|
30
34
|
TxDialogResult,
|
|
31
35
|
TxDialogOptions,
|
|
36
|
+
FieldState,
|
|
37
|
+
RequiredFieldCount,
|
|
38
|
+
FieldMenuResult,
|
|
39
|
+
ObjectMenuResult,
|
|
40
|
+
ArrayMenuResult,
|
|
32
41
|
} from "./types.js";
|
|
33
42
|
export { UserCancelledError } from "./types.js";
|
|
34
43
|
import type {
|
|
@@ -67,6 +76,11 @@ import {
|
|
|
67
76
|
isTuple,
|
|
68
77
|
isUnion,
|
|
69
78
|
isSensitive,
|
|
79
|
+
isNull,
|
|
80
|
+
isNullable,
|
|
81
|
+
unwrapNullable,
|
|
82
|
+
hasDefault,
|
|
83
|
+
getDefault,
|
|
70
84
|
} from "./type-guards.js";
|
|
71
85
|
export {
|
|
72
86
|
isOptional,
|
|
@@ -81,6 +95,11 @@ export {
|
|
|
81
95
|
isTuple,
|
|
82
96
|
isUnion,
|
|
83
97
|
isSensitive,
|
|
98
|
+
isNull,
|
|
99
|
+
isNullable,
|
|
100
|
+
unwrapNullable,
|
|
101
|
+
hasDefault,
|
|
102
|
+
getDefault,
|
|
84
103
|
} from "./type-guards.js";
|
|
85
104
|
|
|
86
105
|
// Import schemas for use in this file
|
|
@@ -122,6 +141,10 @@ import {
|
|
|
122
141
|
mergeSignatures,
|
|
123
142
|
} from "./tx-dialog.js";
|
|
124
143
|
|
|
144
|
+
// Import menu modules
|
|
145
|
+
import { promptObject, promptArray } from "./menus/index.js";
|
|
146
|
+
import { formatPath } from "./utils/formatting.js";
|
|
147
|
+
|
|
125
148
|
export interface IMenuAction<S extends TSchema> {
|
|
126
149
|
title: string;
|
|
127
150
|
action: (sprinkle: Sprinkle<S>) => Promise<Sprinkle<S> | void>;
|
|
@@ -503,10 +526,15 @@ export class Sprinkle<S extends TSchema> {
|
|
|
503
526
|
// --- Menu ---
|
|
504
527
|
|
|
505
528
|
async showMenu(menu: IMenu<S>): Promise<void> {
|
|
506
|
-
return this._showMenu(menu, true);
|
|
529
|
+
return this._showMenu(menu, true, [menu.title]);
|
|
507
530
|
}
|
|
508
531
|
|
|
509
|
-
private async _showMenu(menu: IMenu<S>, main: boolean): Promise<void> {
|
|
532
|
+
private async _showMenu(menu: IMenu<S>, main: boolean, path: string[], clearPrevious = false): Promise<void> {
|
|
533
|
+
// Clear previous breadcrumb if coming back from action/submenu
|
|
534
|
+
if (clearPrevious) {
|
|
535
|
+
process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
|
|
536
|
+
}
|
|
537
|
+
|
|
510
538
|
if (menu.beforeShow) {
|
|
511
539
|
await menu.beforeShow(this);
|
|
512
540
|
}
|
|
@@ -523,11 +551,17 @@ export class Sprinkle<S extends TSchema> {
|
|
|
523
551
|
choices.push({ name: "Settings & Profiles", value: -5 });
|
|
524
552
|
choices.push({ name: "Exit", value: -1 });
|
|
525
553
|
}
|
|
526
|
-
|
|
554
|
+
|
|
555
|
+
// Show breadcrumb
|
|
556
|
+
const breadcrumb = path.join(" > ");
|
|
557
|
+
console.log(colors.dim("🍞 " + breadcrumb));
|
|
558
|
+
|
|
559
|
+
const selectionResult = await selectWithClear({
|
|
527
560
|
message: "Select an option:",
|
|
528
561
|
choices: choices,
|
|
529
562
|
});
|
|
530
563
|
// Handle escape (null) as Back
|
|
564
|
+
// Don't clear here - let the caller's clearPrevious handle it
|
|
531
565
|
if (selectionResult === null) {
|
|
532
566
|
return;
|
|
533
567
|
}
|
|
@@ -539,9 +573,18 @@ export class Sprinkle<S extends TSchema> {
|
|
|
539
573
|
{
|
|
540
574
|
title: "View settings",
|
|
541
575
|
action: async () => {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
);
|
|
576
|
+
const jsonStr = JSON.stringify(this.getDisplaySettings(), bigIntReplacer, 2);
|
|
577
|
+
const jsonLines = jsonStr.split("\n").length;
|
|
578
|
+
console.log(jsonStr);
|
|
579
|
+
|
|
580
|
+
// Wait for user to press Enter
|
|
581
|
+
await selectWithClear({
|
|
582
|
+
message: "Press Enter to continue...",
|
|
583
|
+
choices: [{ name: "Continue", value: "continue" }],
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Clear the JSON output
|
|
587
|
+
process.stdout.write("\x1b[1A\x1b[2K".repeat(jsonLines) + "\x1b[G");
|
|
545
588
|
},
|
|
546
589
|
},
|
|
547
590
|
{
|
|
@@ -591,24 +634,29 @@ export class Sprinkle<S extends TSchema> {
|
|
|
591
634
|
},
|
|
592
635
|
],
|
|
593
636
|
};
|
|
594
|
-
await this._showMenu(settingsMenu, false);
|
|
595
|
-
await this._showMenu(menu, main);
|
|
637
|
+
await this._showMenu(settingsMenu, false, [...path, "Settings & Profiles"], true);
|
|
638
|
+
await this._showMenu(menu, main, path, true);
|
|
596
639
|
return;
|
|
597
640
|
}
|
|
598
641
|
if (selection === -1) {
|
|
642
|
+
// Don't clear here - let the caller's clearPrevious handle it
|
|
599
643
|
return;
|
|
600
644
|
}
|
|
601
645
|
const selectedItem = menu.items[selection]!;
|
|
602
646
|
if ("action" in selectedItem) {
|
|
647
|
+
// Update breadcrumb to show current action
|
|
648
|
+
process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
|
|
649
|
+
console.log(colors.dim("🍞 " + [...path, selectedItem.title].join(" > ")));
|
|
650
|
+
|
|
603
651
|
const result = await selectedItem.action(this);
|
|
604
652
|
if (result instanceof Sprinkle) {
|
|
605
653
|
this.settings = result.settings;
|
|
606
654
|
this.saveSettings();
|
|
607
655
|
}
|
|
608
|
-
await this._showMenu(menu, main);
|
|
656
|
+
await this._showMenu(menu, main, path, true);
|
|
609
657
|
} else {
|
|
610
|
-
await this._showMenu(selectedItem, false);
|
|
611
|
-
await this._showMenu(menu, main);
|
|
658
|
+
await this._showMenu(selectedItem, false, [...path, selectedItem.title], true);
|
|
659
|
+
await this._showMenu(menu, main, path, true);
|
|
612
660
|
}
|
|
613
661
|
return;
|
|
614
662
|
}
|
|
@@ -789,7 +837,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
789
837
|
choices.push({ name: "Submit transaction", value: "submit" });
|
|
790
838
|
choices.push({ name: "Cancel", value: "cancel" });
|
|
791
839
|
|
|
792
|
-
const selection = await
|
|
840
|
+
const selection = await selectWithClear({
|
|
793
841
|
message: "Select an option:",
|
|
794
842
|
choices,
|
|
795
843
|
});
|
|
@@ -1002,68 +1050,20 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1002
1050
|
}
|
|
1003
1051
|
}
|
|
1004
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* Edit an existing struct value using a menu-based interface.
|
|
1055
|
+
* This is now unified with FillInStruct - both use the same menu system.
|
|
1056
|
+
* @param type - The TypeBox schema
|
|
1057
|
+
* @param current - The current value to edit
|
|
1058
|
+
* @returns The edited value
|
|
1059
|
+
*/
|
|
1005
1060
|
async EditStruct<U extends TSchema>(
|
|
1006
1061
|
type: U,
|
|
1007
1062
|
current: TExact<U>,
|
|
1008
1063
|
): Promise<TExact<U>> {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
async _editStruct<U extends TSchema>(
|
|
1013
|
-
type: U,
|
|
1014
|
-
path: string[],
|
|
1015
|
-
current?: TExact<U>,
|
|
1016
|
-
): Promise<TExact<U>> {
|
|
1017
|
-
if (isObject(type)) {
|
|
1018
|
-
const obj = {} as Record<string, unknown>;
|
|
1019
|
-
const fields = type["properties"] as Record<string, U>;
|
|
1020
|
-
const menuItems: TMenuItem<S>[] = [];
|
|
1021
|
-
const currentRecord = current as Record<string, unknown>;
|
|
1022
|
-
for (const [field, fieldType] of Object.entries(fields)) {
|
|
1023
|
-
if (current && field in currentRecord) {
|
|
1024
|
-
obj[field] = currentRecord[field] as TExact<U>;
|
|
1025
|
-
}
|
|
1026
|
-
const menuTitle = Sprinkle.ExtractMessage(
|
|
1027
|
-
fieldType,
|
|
1028
|
-
`Edit ${field} at ${path.join(".")}`,
|
|
1029
|
-
);
|
|
1030
|
-
if (
|
|
1031
|
-
isOptional(fieldType) &&
|
|
1032
|
-
current &&
|
|
1033
|
-
currentRecord[field] !== undefined
|
|
1034
|
-
) {
|
|
1035
|
-
menuItems.push({
|
|
1036
|
-
title: `Clear ${field}`,
|
|
1037
|
-
action: async (sprinkle: Sprinkle<S>) => {
|
|
1038
|
-
obj[field] = undefined;
|
|
1039
|
-
return sprinkle;
|
|
1040
|
-
},
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
menuItems.push({
|
|
1044
|
-
title: menuTitle,
|
|
1045
|
-
action: async (sprinkle: Sprinkle<S>) => {
|
|
1046
|
-
const fieldValue = await sprinkle._editStruct(
|
|
1047
|
-
fieldType,
|
|
1048
|
-
path.concat([field]),
|
|
1049
|
-
current && field in currentRecord
|
|
1050
|
-
? (currentRecord[field] as TExact<U>)
|
|
1051
|
-
: undefined,
|
|
1052
|
-
);
|
|
1053
|
-
obj[field] = fieldValue;
|
|
1054
|
-
return sprinkle;
|
|
1055
|
-
},
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
const editMenu: IMenu<S> = {
|
|
1059
|
-
title: "Test",
|
|
1060
|
-
items: menuItems,
|
|
1061
|
-
};
|
|
1062
|
-
await this._showMenu(editMenu, false);
|
|
1063
|
-
return obj as TExact<U>;
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
return this._fillInStruct<U>(type, path, {}, current);
|
|
1064
|
+
// Use FillInStruct with current values as defaults
|
|
1065
|
+
// The menu system will show existing values and allow editing
|
|
1066
|
+
return this.FillInStruct(type, current);
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
1069
|
async FillInStruct<U extends TSchema>(
|
|
@@ -1092,10 +1092,11 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1092
1092
|
>;
|
|
1093
1093
|
}
|
|
1094
1094
|
if (isOptional(type)) {
|
|
1095
|
-
const
|
|
1095
|
+
const pathDisplay = formatPath(path) || "value";
|
|
1096
|
+
const shouldSet = await selectWithClear({
|
|
1096
1097
|
message: Sprinkle.ExtractMessage(
|
|
1097
1098
|
type,
|
|
1098
|
-
`Set value for ${
|
|
1099
|
+
`Set value for ${pathDisplay}?`,
|
|
1099
1100
|
),
|
|
1100
1101
|
choices: [
|
|
1101
1102
|
{ name: "Yes", value: true },
|
|
@@ -1116,6 +1117,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1116
1117
|
}
|
|
1117
1118
|
|
|
1118
1119
|
if (isUnion(type)) {
|
|
1120
|
+
const pathDisplay = formatPath(path) || "value";
|
|
1119
1121
|
const choices = [];
|
|
1120
1122
|
const resolved = this.resolveType(type, path, defs);
|
|
1121
1123
|
for (const variant of resolved.anyOf) {
|
|
@@ -1124,13 +1126,12 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1124
1126
|
value: variant,
|
|
1125
1127
|
});
|
|
1126
1128
|
}
|
|
1127
|
-
const selectionResult = await
|
|
1129
|
+
const selectionResult = await selectWithClear({
|
|
1128
1130
|
message: Sprinkle.ExtractMessage(
|
|
1129
1131
|
resolved,
|
|
1130
|
-
`Enter a choice for ${
|
|
1132
|
+
`Enter a choice for ${pathDisplay}`,
|
|
1131
1133
|
),
|
|
1132
1134
|
choices: choices,
|
|
1133
|
-
default: def ? `${def}` : undefined,
|
|
1134
1135
|
});
|
|
1135
1136
|
if (selectionResult === null) {
|
|
1136
1137
|
throw new UserCancelledError();
|
|
@@ -1142,7 +1143,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1142
1143
|
if (isString(type)) {
|
|
1143
1144
|
// Special handling for hot wallet private key - offer generation option
|
|
1144
1145
|
if (type.title === "Hot Wallet Private Key") {
|
|
1145
|
-
const choice = await
|
|
1146
|
+
const choice = await selectWithClear({
|
|
1146
1147
|
message: "Hot wallet setup:",
|
|
1147
1148
|
choices: [
|
|
1148
1149
|
{ name: "Enter existing private key", value: "existing" },
|
|
@@ -1157,7 +1158,7 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1157
1158
|
return Sprinkle.generateWalletFromMnemonic() as Promise<TExact<U>>;
|
|
1158
1159
|
}
|
|
1159
1160
|
// Fall through to password prompt for "existing" choice
|
|
1160
|
-
const answer = await
|
|
1161
|
+
const answer = await passwordWithClear({
|
|
1161
1162
|
message: "Enter your private key:",
|
|
1162
1163
|
});
|
|
1163
1164
|
if (answer === null) {
|
|
@@ -1166,18 +1167,19 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1166
1167
|
return answer as TExact<U>;
|
|
1167
1168
|
}
|
|
1168
1169
|
|
|
1170
|
+
const pathDisplay = formatPath(path) || "value";
|
|
1169
1171
|
const defaultString = (def ? def : this.defaults["string"]) as
|
|
1170
1172
|
| string
|
|
1171
1173
|
| undefined;
|
|
1172
1174
|
const message = Sprinkle.ExtractMessage(
|
|
1173
1175
|
type,
|
|
1174
|
-
`Enter a string for ${
|
|
1176
|
+
`Enter a string for ${pathDisplay}`,
|
|
1175
1177
|
);
|
|
1176
1178
|
let answer: string | null;
|
|
1177
1179
|
if (isSensitive(type)) {
|
|
1178
|
-
answer = await
|
|
1180
|
+
answer = await passwordWithClear({ message });
|
|
1179
1181
|
} else {
|
|
1180
|
-
answer = await
|
|
1182
|
+
answer = await inputWithClear({ message, default: defaultString });
|
|
1181
1183
|
if (answer !== null) {
|
|
1182
1184
|
this.defaults["string"] = answer;
|
|
1183
1185
|
}
|
|
@@ -1189,10 +1191,11 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1189
1191
|
}
|
|
1190
1192
|
|
|
1191
1193
|
if (isBigInt(type)) {
|
|
1192
|
-
const
|
|
1194
|
+
const pathDisplay = formatPath(path) || "value";
|
|
1195
|
+
const answer = await inputWithClear({
|
|
1193
1196
|
message: Sprinkle.ExtractMessage(
|
|
1194
1197
|
type,
|
|
1195
|
-
`Enter a bigint for ${
|
|
1198
|
+
`Enter a bigint for ${pathDisplay}`,
|
|
1196
1199
|
),
|
|
1197
1200
|
default: def ? (def as bigint).toString() : undefined,
|
|
1198
1201
|
validate: (s) => {
|
|
@@ -1215,47 +1218,55 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1215
1218
|
}
|
|
1216
1219
|
|
|
1217
1220
|
if (isObject(type)) {
|
|
1218
|
-
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1221
|
+
// Use menu-based editing for objects
|
|
1222
|
+
const defaults = def as Record<string, unknown> | undefined;
|
|
1223
|
+
const sprinkle = this;
|
|
1224
|
+
const result = await promptObject({
|
|
1225
|
+
type,
|
|
1226
|
+
path,
|
|
1227
|
+
defs,
|
|
1228
|
+
defaults,
|
|
1229
|
+
fillField: async <T extends TSchema>(
|
|
1230
|
+
fieldType: T,
|
|
1231
|
+
fieldPath: string[],
|
|
1232
|
+
fieldDefs: Record<string, TSchema>,
|
|
1233
|
+
fieldDef?: unknown,
|
|
1234
|
+
) => {
|
|
1235
|
+
return sprinkle._fillInStruct(fieldType, fieldPath, fieldDefs, fieldDef as TExact<T>);
|
|
1236
|
+
},
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
if (result.action === "cancel") {
|
|
1240
|
+
throw new UserCancelledError();
|
|
1230
1241
|
}
|
|
1231
|
-
|
|
1242
|
+
|
|
1243
|
+
return result.value as TExact<U>;
|
|
1232
1244
|
}
|
|
1233
1245
|
|
|
1234
|
-
//TODO: support starting with default values for arrays and allow removal of items
|
|
1235
1246
|
if (isArray(type)) {
|
|
1236
|
-
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
addMore = continueAnswer as boolean;
|
|
1247
|
+
// Use menu-based editing for arrays
|
|
1248
|
+
const defaults = def as unknown[] | undefined;
|
|
1249
|
+
const sprinkle = this;
|
|
1250
|
+
const result = await promptArray({
|
|
1251
|
+
type,
|
|
1252
|
+
path,
|
|
1253
|
+
defs,
|
|
1254
|
+
defaults,
|
|
1255
|
+
fillField: async <T extends TSchema>(
|
|
1256
|
+
itemType: T,
|
|
1257
|
+
itemPath: string[],
|
|
1258
|
+
itemDefs: Record<string, TSchema>,
|
|
1259
|
+
itemDef?: unknown,
|
|
1260
|
+
) => {
|
|
1261
|
+
return sprinkle._fillInStruct(itemType, itemPath, itemDefs, itemDef as TExact<T>);
|
|
1262
|
+
},
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
if (result.action === "back") {
|
|
1266
|
+
throw new UserCancelledError();
|
|
1257
1267
|
}
|
|
1258
|
-
|
|
1268
|
+
|
|
1269
|
+
return result.value as TExact<U>;
|
|
1259
1270
|
}
|
|
1260
1271
|
|
|
1261
1272
|
if (isTuple(type)) {
|
|
@@ -1274,8 +1285,9 @@ export class Sprinkle<S extends TSchema> {
|
|
|
1274
1285
|
return result as TExact<U>;
|
|
1275
1286
|
}
|
|
1276
1287
|
|
|
1288
|
+
const pathDisplay = formatPath(path) || "root";
|
|
1277
1289
|
throw new Error(
|
|
1278
|
-
`Unable to fill in struct for type at path ${
|
|
1290
|
+
`Unable to fill in struct for type at path ${pathDisplay}`,
|
|
1279
1291
|
);
|
|
1280
1292
|
}
|
|
1281
1293
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array menu module for menu-based struct editing.
|
|
3
|
+
* Shows array items and allows adding/removing/editing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Separator } from "@inquirer/core";
|
|
7
|
+
import type { TArray, TSchema } from "@sinclair/typebox";
|
|
8
|
+
import { selectWithClear } from "../prompts.js";
|
|
9
|
+
import type { ArrayMenuResult, FieldState } from "../types.js";
|
|
10
|
+
import { UserCancelledError } from "../types.js";
|
|
11
|
+
import { formatValuePreview, formatBreadcrumb } from "../utils/formatting.js";
|
|
12
|
+
import { promptFieldMenu, displayFullValue } from "./field-menu.js";
|
|
13
|
+
import type { FillFunction } from "./object-menu.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for the array menu prompt.
|
|
17
|
+
*/
|
|
18
|
+
export interface ArrayMenuOptions {
|
|
19
|
+
/** The array schema */
|
|
20
|
+
type: TArray;
|
|
21
|
+
/** Current path for display */
|
|
22
|
+
path: string[];
|
|
23
|
+
/** Type definitions for resolving refs */
|
|
24
|
+
defs: Record<string, TSchema>;
|
|
25
|
+
/** Existing array values */
|
|
26
|
+
defaults?: unknown[];
|
|
27
|
+
/** Function to fill individual items */
|
|
28
|
+
fillField: FillFunction;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Show a menu-based editor for an array.
|
|
33
|
+
* Allows adding, removing, and editing items.
|
|
34
|
+
*
|
|
35
|
+
* @param options - Array menu options
|
|
36
|
+
* @returns The edited array or back result
|
|
37
|
+
*/
|
|
38
|
+
export async function promptArray<T>(
|
|
39
|
+
options: ArrayMenuOptions,
|
|
40
|
+
): Promise<ArrayMenuResult<T>> {
|
|
41
|
+
const { type, path, defs, defaults = [], fillField } = options;
|
|
42
|
+
|
|
43
|
+
const itemType = type.items as TSchema;
|
|
44
|
+
|
|
45
|
+
// Initialize items from defaults
|
|
46
|
+
const items: T[] = [...(defaults as T[])];
|
|
47
|
+
|
|
48
|
+
// Main menu loop
|
|
49
|
+
while (true) {
|
|
50
|
+
// Build menu choices
|
|
51
|
+
const choices: Array<{ name: string; value: string } | Separator> = [];
|
|
52
|
+
|
|
53
|
+
// Add existing items
|
|
54
|
+
for (let i = 0; i < items.length; i++) {
|
|
55
|
+
const preview = formatValuePreview(items[i], 40);
|
|
56
|
+
choices.push({ name: `[${i}]: ${preview}`, value: `item:${i}` });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add separator and actions
|
|
60
|
+
choices.push(new Separator("\u2500\u2500\u2500 Actions \u2500\u2500\u2500"));
|
|
61
|
+
choices.push({ name: "Add item", value: "add" });
|
|
62
|
+
|
|
63
|
+
if (items.length > 0) {
|
|
64
|
+
choices.push({ name: "Remove item...", value: "remove" });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
choices.push({ name: `Done (${items.length} item${items.length === 1 ? "" : "s"})`, value: "done" });
|
|
68
|
+
choices.push({ name: "Back", value: "back" });
|
|
69
|
+
|
|
70
|
+
// Show menu
|
|
71
|
+
const breadcrumb = formatBreadcrumb(path);
|
|
72
|
+
const message = breadcrumb
|
|
73
|
+
? `Edit ${breadcrumb} [${items.length} items]:`
|
|
74
|
+
: `Edit array [${items.length} items]:`;
|
|
75
|
+
|
|
76
|
+
const selection = await selectWithClear({
|
|
77
|
+
message,
|
|
78
|
+
choices,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Handle escape - same as done
|
|
82
|
+
if (selection === null) {
|
|
83
|
+
return { action: "done", value: items };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle done
|
|
87
|
+
if (selection === "done") {
|
|
88
|
+
return { action: "done", value: items };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle back
|
|
92
|
+
if (selection === "back") {
|
|
93
|
+
return { action: "back" };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle add
|
|
97
|
+
if (selection === "add") {
|
|
98
|
+
try {
|
|
99
|
+
const newItem = await fillField(
|
|
100
|
+
itemType,
|
|
101
|
+
[...path, `[${items.length}]`],
|
|
102
|
+
defs,
|
|
103
|
+
undefined,
|
|
104
|
+
);
|
|
105
|
+
items.push(newItem as T);
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (e instanceof UserCancelledError) {
|
|
108
|
+
// Return to menu without adding
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle remove
|
|
117
|
+
if (selection === "remove") {
|
|
118
|
+
const removeChoices = items.map((item, i) => ({
|
|
119
|
+
name: `[${i}]: ${formatValuePreview(item, 40)}`,
|
|
120
|
+
value: i,
|
|
121
|
+
}));
|
|
122
|
+
removeChoices.push({ name: "Cancel", value: -1 } as { name: string; value: number });
|
|
123
|
+
|
|
124
|
+
const indexToRemove = await selectWithClear({
|
|
125
|
+
message: "Select item to remove:",
|
|
126
|
+
choices: removeChoices,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (indexToRemove !== null && indexToRemove !== -1) {
|
|
130
|
+
items.splice(indexToRemove as number, 1);
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Handle item selection
|
|
136
|
+
if (typeof selection === "string" && selection.startsWith("item:")) {
|
|
137
|
+
const index = parseInt(selection.slice(5), 10);
|
|
138
|
+
const currentValue = items[index];
|
|
139
|
+
|
|
140
|
+
// Create a FieldState for the item
|
|
141
|
+
const itemState: FieldState<T> = { status: "set", value: currentValue as T };
|
|
142
|
+
|
|
143
|
+
const menuResult = await promptFieldMenu({
|
|
144
|
+
fieldName: `[${index}]`,
|
|
145
|
+
fieldType: itemType,
|
|
146
|
+
state: itemState,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
switch (menuResult.action) {
|
|
150
|
+
case "edit": {
|
|
151
|
+
try {
|
|
152
|
+
const newValue = await fillField(
|
|
153
|
+
itemType,
|
|
154
|
+
[...path, `[${index}]`],
|
|
155
|
+
defs,
|
|
156
|
+
currentValue,
|
|
157
|
+
);
|
|
158
|
+
items[index] = newValue as T;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (e instanceof UserCancelledError) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case "view": {
|
|
169
|
+
await displayFullValue(`[${index}]`, currentValue);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case "clear": {
|
|
174
|
+
// Remove the item
|
|
175
|
+
items.splice(index, 1);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case "setNull": {
|
|
180
|
+
items[index] = null as T;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case "reset":
|
|
185
|
+
case "back":
|
|
186
|
+
// Do nothing
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|