@sundaeswap/sprinkles 0.1.1
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/LICENSE +21 -0
- package/README.md +260 -0
- package/dist/cjs/Sprinkle/__tests__/bigint-reviver.test.js +40 -0
- package/dist/cjs/Sprinkle/__tests__/bigint-reviver.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/encryption.test.js +267 -0
- package/dist/cjs/Sprinkle/__tests__/encryption.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js +147 -0
- package/dist/cjs/Sprinkle/__tests__/enhancements.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/extract-message.test.js +60 -0
- package/dist/cjs/Sprinkle/__tests__/extract-message.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +131 -0
- package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/schemas.test.js +184 -0
- package/dist/cjs/Sprinkle/__tests__/schemas.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/settings-persistence.test.js +199 -0
- package/dist/cjs/Sprinkle/__tests__/settings-persistence.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js +108 -0
- package/dist/cjs/Sprinkle/__tests__/show-menu.test.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/test-helpers.js +16 -0
- package/dist/cjs/Sprinkle/__tests__/test-helpers.js.map +1 -0
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +271 -0
- package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -0
- package/dist/cjs/Sprinkle/index.js +954 -0
- package/dist/cjs/Sprinkle/index.js.map +1 -0
- package/dist/cjs/index.js +17 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Sprinkle/__tests__/bigint-reviver.test.js +38 -0
- package/dist/esm/Sprinkle/__tests__/bigint-reviver.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/encryption.test.js +264 -0
- package/dist/esm/Sprinkle/__tests__/encryption.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js +145 -0
- package/dist/esm/Sprinkle/__tests__/enhancements.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/extract-message.test.js +58 -0
- package/dist/esm/Sprinkle/__tests__/extract-message.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +130 -0
- package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/schemas.test.js +182 -0
- package/dist/esm/Sprinkle/__tests__/schemas.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/settings-persistence.test.js +196 -0
- package/dist/esm/Sprinkle/__tests__/settings-persistence.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js +106 -0
- package/dist/esm/Sprinkle/__tests__/show-menu.test.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/test-helpers.js +10 -0
- package/dist/esm/Sprinkle/__tests__/test-helpers.js.map +1 -0
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +269 -0
- package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -0
- package/dist/esm/Sprinkle/index.js +928 -0
- package/dist/esm/Sprinkle/index.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/Sprinkle/index.d.ts +205 -0
- package/dist/types/Sprinkle/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +85 -0
- package/src/Sprinkle/__tests__/bigint-reviver.test.ts +49 -0
- package/src/Sprinkle/__tests__/encryption.test.ts +266 -0
- package/src/Sprinkle/__tests__/enhancements.test.ts +154 -0
- package/src/Sprinkle/__tests__/extract-message.test.ts +60 -0
- package/src/Sprinkle/__tests__/fill-in-struct.test.ts +159 -0
- package/src/Sprinkle/__tests__/schemas.test.ts +215 -0
- package/src/Sprinkle/__tests__/settings-persistence.test.ts +181 -0
- package/src/Sprinkle/__tests__/show-menu.test.ts +123 -0
- package/src/Sprinkle/__tests__/test-helpers.ts +14 -0
- package/src/Sprinkle/__tests__/tx-dialog.test.ts +293 -0
- package/src/Sprinkle/index.ts +1215 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
NetworkSchema: true,
|
|
8
|
+
MultisigScriptModule: true,
|
|
9
|
+
MultisigScript: true,
|
|
10
|
+
ProviderSettingsSchema: true,
|
|
11
|
+
WalletSettingsSchema: true,
|
|
12
|
+
Sprinkle: true
|
|
13
|
+
};
|
|
14
|
+
exports.WalletSettingsSchema = exports.Sprinkle = exports.ProviderSettingsSchema = exports.NetworkSchema = exports.MultisigScriptModule = exports.MultisigScript = void 0;
|
|
15
|
+
var _query = require("@blaze-cardano/query");
|
|
16
|
+
var _sdk = require("@blaze-cardano/sdk");
|
|
17
|
+
var _core = require("@blaze-cardano/core");
|
|
18
|
+
var _prompts = require("@inquirer/prompts");
|
|
19
|
+
var _typebox = require("@sinclair/typebox");
|
|
20
|
+
Object.keys(_typebox).forEach(function (key) {
|
|
21
|
+
if (key === "default" || key === "__esModule") return;
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
23
|
+
if (key in exports && exports[key] === _typebox[key]) return;
|
|
24
|
+
Object.defineProperty(exports, key, {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _typebox[key];
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
var fs = _interopRequireWildcard(require("fs"));
|
|
32
|
+
var path = _interopRequireWildcard(require("path"));
|
|
33
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
34
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
35
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
36
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
37
|
+
const isOptional = t => t[_typebox.OptionalKind] === "Optional";
|
|
38
|
+
const isImport = t => t[_typebox.Kind] === "Import";
|
|
39
|
+
const isArray = t => t[_typebox.Kind] === "Array";
|
|
40
|
+
const isBigInt = t => t[_typebox.Kind] === "BigInt";
|
|
41
|
+
// const isBoolean = (t: TSchema): t is TBoolean => t[Kind] === "Boolean";
|
|
42
|
+
const isLiteral = t => t[_typebox.Kind] === "Literal";
|
|
43
|
+
// const isNumber = (t: TSchema): t is TNumber => t[Kind] === "Number";
|
|
44
|
+
const isObject = t => t[_typebox.Kind] === "Object";
|
|
45
|
+
// const isRecord = (t: TSchema): t is TRecord => t[Kind] === "Record";
|
|
46
|
+
const isRef = t => t[_typebox.Kind] === "Ref";
|
|
47
|
+
const isString = t => t[_typebox.Kind] === "String";
|
|
48
|
+
const isThis = t => t[_typebox.Kind] === "This";
|
|
49
|
+
// const isTuple = (t: TSchema): t is TTuple => t[Kind] === "Tuple";
|
|
50
|
+
const isUnion = t => t[_typebox.Kind] === "Union";
|
|
51
|
+
// const isAny = (t: TSchema): t is TAny => t[Kind] === "Any";
|
|
52
|
+
|
|
53
|
+
const isSensitive = t => isString(t) && t.sensitive === true;
|
|
54
|
+
const NetworkSchema = exports.NetworkSchema = _typebox.Type.Union([_typebox.Type.Literal("mainnet"), _typebox.Type.Literal("preview"), _typebox.Type.Literal("preprod")]);
|
|
55
|
+
const MultisigScriptModule = exports.MultisigScriptModule = _typebox.Type.Module({
|
|
56
|
+
MultisigScript: _typebox.Type.Union([_typebox.Type.Object({
|
|
57
|
+
Signature: _typebox.Type.Object({
|
|
58
|
+
key_hash: _typebox.Type.String()
|
|
59
|
+
}, {
|
|
60
|
+
ctor: 0n
|
|
61
|
+
})
|
|
62
|
+
}), _typebox.Type.Object({
|
|
63
|
+
AllOf: _typebox.Type.Object({
|
|
64
|
+
scripts: _typebox.Type.Array(_typebox.Type.Ref("MultisigScript"))
|
|
65
|
+
}, {
|
|
66
|
+
ctor: 1n
|
|
67
|
+
})
|
|
68
|
+
}), _typebox.Type.Object({
|
|
69
|
+
AnyOf: _typebox.Type.Object({
|
|
70
|
+
scripts: _typebox.Type.Array(_typebox.Type.Ref("MultisigScript"))
|
|
71
|
+
}, {
|
|
72
|
+
ctor: 2n
|
|
73
|
+
})
|
|
74
|
+
}), _typebox.Type.Object({
|
|
75
|
+
AtLeast: _typebox.Type.Object({
|
|
76
|
+
required: _typebox.Type.BigInt(),
|
|
77
|
+
scripts: _typebox.Type.Array(_typebox.Type.Ref("MultisigScript"))
|
|
78
|
+
}, {
|
|
79
|
+
ctor: 3n
|
|
80
|
+
})
|
|
81
|
+
}), _typebox.Type.Object({
|
|
82
|
+
Before: _typebox.Type.Object({
|
|
83
|
+
time: _typebox.Type.BigInt()
|
|
84
|
+
}, {
|
|
85
|
+
ctor: 4n
|
|
86
|
+
})
|
|
87
|
+
}), _typebox.Type.Object({
|
|
88
|
+
After: _typebox.Type.Object({
|
|
89
|
+
time: _typebox.Type.BigInt()
|
|
90
|
+
}, {
|
|
91
|
+
ctor: 5n
|
|
92
|
+
})
|
|
93
|
+
}), _typebox.Type.Object({
|
|
94
|
+
Script: _typebox.Type.Object({
|
|
95
|
+
script_hash: _typebox.Type.String()
|
|
96
|
+
}, {
|
|
97
|
+
ctor: 6n
|
|
98
|
+
})
|
|
99
|
+
})])
|
|
100
|
+
});
|
|
101
|
+
const MultisigScript = exports.MultisigScript = MultisigScriptModule.Import("MultisigScript");
|
|
102
|
+
const ProviderSettingsSchema = exports.ProviderSettingsSchema = _typebox.Type.Union([_typebox.Type.Object({
|
|
103
|
+
type: _typebox.Type.Literal("blockfrost"),
|
|
104
|
+
projectId: _typebox.Type.String({
|
|
105
|
+
minLength: 1,
|
|
106
|
+
title: "Blockfrost Project ID"
|
|
107
|
+
})
|
|
108
|
+
}), _typebox.Type.Object({
|
|
109
|
+
type: _typebox.Type.Literal("maestro"),
|
|
110
|
+
apiKey: _typebox.Type.String({
|
|
111
|
+
minLength: 1,
|
|
112
|
+
title: "Maestro API Key"
|
|
113
|
+
})
|
|
114
|
+
})]);
|
|
115
|
+
const WalletSettingsSchema = exports.WalletSettingsSchema = _typebox.Type.Union([_typebox.Type.Object({
|
|
116
|
+
type: _typebox.Type.Literal("hot"),
|
|
117
|
+
privateKey: _typebox.Type.String({
|
|
118
|
+
minLength: 1,
|
|
119
|
+
title: "Hot Wallet Private Key"
|
|
120
|
+
})
|
|
121
|
+
}), _typebox.Type.Object({
|
|
122
|
+
type: _typebox.Type.Literal("cold"),
|
|
123
|
+
address: _typebox.Type.String({
|
|
124
|
+
minLength: 1,
|
|
125
|
+
title: "Cold Wallet Address"
|
|
126
|
+
})
|
|
127
|
+
})]);
|
|
128
|
+
class Sprinkle {
|
|
129
|
+
constructor(type, storagePath, options) {
|
|
130
|
+
_defineProperty(this, "storagePath", void 0);
|
|
131
|
+
_defineProperty(this, "settings", {});
|
|
132
|
+
_defineProperty(this, "type", void 0);
|
|
133
|
+
_defineProperty(this, "defaults", {});
|
|
134
|
+
_defineProperty(this, "options", void 0);
|
|
135
|
+
_defineProperty(this, "profileId", "");
|
|
136
|
+
_defineProperty(this, "profileMeta", {
|
|
137
|
+
name: "",
|
|
138
|
+
createdAt: "",
|
|
139
|
+
updatedAt: ""
|
|
140
|
+
});
|
|
141
|
+
this.type = type;
|
|
142
|
+
this.storagePath = storagePath;
|
|
143
|
+
this.options = options ?? {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- Profile path helpers ---
|
|
147
|
+
|
|
148
|
+
static sanitizeProfileId(name) {
|
|
149
|
+
return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/[\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "profile";
|
|
150
|
+
}
|
|
151
|
+
static profilesDir(storagePath) {
|
|
152
|
+
return path.join(storagePath, "profiles");
|
|
153
|
+
}
|
|
154
|
+
static profilePath(storagePath, id) {
|
|
155
|
+
if (!id || /[\/\\]/.test(id) || id.includes("..")) {
|
|
156
|
+
throw new Error(`Invalid profile ID: "${id}"`);
|
|
157
|
+
}
|
|
158
|
+
return path.join(storagePath, "profiles", `${id}.json`);
|
|
159
|
+
}
|
|
160
|
+
static activeProfilePath(storagePath) {
|
|
161
|
+
return path.join(storagePath, "active-profile");
|
|
162
|
+
}
|
|
163
|
+
static findAvailableId(profilesDir, baseId) {
|
|
164
|
+
let candidate = baseId;
|
|
165
|
+
let counter = 2;
|
|
166
|
+
while (fs.existsSync(path.join(profilesDir, `${candidate}.json`))) {
|
|
167
|
+
candidate = `${baseId}-${counter}`;
|
|
168
|
+
counter++;
|
|
169
|
+
}
|
|
170
|
+
return candidate;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- Profile I/O ---
|
|
174
|
+
|
|
175
|
+
scanProfiles() {
|
|
176
|
+
const dir = Sprinkle.profilesDir(this.storagePath);
|
|
177
|
+
if (!fs.existsSync(dir)) return [];
|
|
178
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith(".json"));
|
|
179
|
+
const entries = [];
|
|
180
|
+
for (const file of files) {
|
|
181
|
+
try {
|
|
182
|
+
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
183
|
+
const parsed = JSON.parse(content, Sprinkle.bigIntReviver);
|
|
184
|
+
if (parsed.meta?.name) {
|
|
185
|
+
entries.push({
|
|
186
|
+
id: file.replace(/\.json$/, ""),
|
|
187
|
+
meta: parsed.meta
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
// Skip malformed profile files
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return entries;
|
|
195
|
+
}
|
|
196
|
+
async loadProfile(id) {
|
|
197
|
+
const filePath = Sprinkle.profilePath(this.storagePath, id);
|
|
198
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
199
|
+
const parsed = JSON.parse(content, Sprinkle.bigIntReviver);
|
|
200
|
+
this.profileId = id;
|
|
201
|
+
this.profileMeta = parsed.meta;
|
|
202
|
+
this.settings = await this.decryptSettings(parsed.settings);
|
|
203
|
+
this.defaults = parsed.defaults ?? {};
|
|
204
|
+
// Update active profile pointer
|
|
205
|
+
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), id, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
saveProfile() {
|
|
208
|
+
if (!this.profileId) {
|
|
209
|
+
throw new Error("Cannot save profile: no profile is loaded. Call loadProfile() or create a profile first.");
|
|
210
|
+
}
|
|
211
|
+
const filePath = Sprinkle.profilePath(this.storagePath, this.profileId);
|
|
212
|
+
const dir = path.dirname(filePath);
|
|
213
|
+
if (!fs.existsSync(dir)) {
|
|
214
|
+
fs.mkdirSync(dir, {
|
|
215
|
+
recursive: true
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
this.profileMeta.updatedAt = new Date().toISOString();
|
|
219
|
+
const settingsToSave = this.encryptSettings(this.settings);
|
|
220
|
+
const jsonContent = JSON.stringify({
|
|
221
|
+
meta: this.profileMeta,
|
|
222
|
+
settings: settingsToSave,
|
|
223
|
+
defaults: this.defaults
|
|
224
|
+
}, Sprinkle.bigIntReplacer, 2);
|
|
225
|
+
fs.writeFileSync(filePath, jsonContent, "utf-8");
|
|
226
|
+
}
|
|
227
|
+
async promptProfileMeta(defaultName, defaultDescription) {
|
|
228
|
+
const name = await (0, _prompts.input)({
|
|
229
|
+
message: "Profile name:",
|
|
230
|
+
default: defaultName,
|
|
231
|
+
validate: v => v.trim().length > 0 ? true : "Name cannot be empty"
|
|
232
|
+
});
|
|
233
|
+
const description = await (0, _prompts.input)({
|
|
234
|
+
message: "Profile description (optional):",
|
|
235
|
+
default: defaultDescription ?? ""
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
name,
|
|
239
|
+
description: description || undefined
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async createProfile() {
|
|
243
|
+
const {
|
|
244
|
+
name,
|
|
245
|
+
description
|
|
246
|
+
} = await this.promptProfileMeta();
|
|
247
|
+
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
248
|
+
const id = Sprinkle.findAvailableId(profilesDir, Sprinkle.sanitizeProfileId(name));
|
|
249
|
+
const now = new Date().toISOString();
|
|
250
|
+
this.profileId = id;
|
|
251
|
+
this.profileMeta = {
|
|
252
|
+
name,
|
|
253
|
+
description,
|
|
254
|
+
createdAt: now,
|
|
255
|
+
updatedAt: now
|
|
256
|
+
};
|
|
257
|
+
this.defaults = {};
|
|
258
|
+
this.settings = await this.FillInStruct(this.type);
|
|
259
|
+
this.saveProfile();
|
|
260
|
+
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), id, "utf-8");
|
|
261
|
+
}
|
|
262
|
+
async migrateIfNeeded() {
|
|
263
|
+
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
264
|
+
const legacyPath = Sprinkle.SettingsPath(this.storagePath);
|
|
265
|
+
if (!fs.existsSync(profilesDir)) {
|
|
266
|
+
if (fs.existsSync(legacyPath)) {
|
|
267
|
+
// Migrate legacy settings.json -> profiles/default.json
|
|
268
|
+
const content = fs.readFileSync(legacyPath, "utf-8");
|
|
269
|
+
const parsed = JSON.parse(content, Sprinkle.bigIntReviver);
|
|
270
|
+
const now = new Date().toISOString();
|
|
271
|
+
const profileData = {
|
|
272
|
+
meta: {
|
|
273
|
+
name: "Default",
|
|
274
|
+
createdAt: now,
|
|
275
|
+
updatedAt: now
|
|
276
|
+
},
|
|
277
|
+
settings: parsed.settings,
|
|
278
|
+
defaults: parsed.defaults ?? {}
|
|
279
|
+
};
|
|
280
|
+
fs.mkdirSync(profilesDir, {
|
|
281
|
+
recursive: true
|
|
282
|
+
});
|
|
283
|
+
fs.writeFileSync(Sprinkle.profilePath(this.storagePath, "default"), JSON.stringify(profileData, Sprinkle.bigIntReplacer, 2), "utf-8");
|
|
284
|
+
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), "default", "utf-8");
|
|
285
|
+
// Backup legacy file
|
|
286
|
+
fs.renameSync(legacyPath, `${legacyPath}.bak`);
|
|
287
|
+
} else {
|
|
288
|
+
fs.mkdirSync(profilesDir, {
|
|
289
|
+
recursive: true
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async selectOrCreateProfile() {
|
|
295
|
+
const profiles = this.scanProfiles();
|
|
296
|
+
if (profiles.length === 0) {
|
|
297
|
+
await this.createProfile();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (profiles.length === 1) {
|
|
301
|
+
await this.loadProfile(profiles[0].id);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Multiple profiles: check active-profile pointer
|
|
306
|
+
const pointerPath = Sprinkle.activeProfilePath(this.storagePath);
|
|
307
|
+
if (fs.existsSync(pointerPath)) {
|
|
308
|
+
const activeId = fs.readFileSync(pointerPath, "utf-8").trim();
|
|
309
|
+
if (profiles.some(p => p.id === activeId)) {
|
|
310
|
+
await this.loadProfile(activeId);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// No valid pointer - show selector
|
|
316
|
+
await this.selectProfile(profiles);
|
|
317
|
+
}
|
|
318
|
+
async selectProfile(profiles) {
|
|
319
|
+
const available = profiles ?? this.scanProfiles();
|
|
320
|
+
const selection = await (0, _prompts.select)({
|
|
321
|
+
message: "Select a profile:",
|
|
322
|
+
choices: available.map(p => ({
|
|
323
|
+
name: p.meta.description ? `${p.meta.name} - ${p.meta.description}` : p.meta.name,
|
|
324
|
+
value: p.id
|
|
325
|
+
}))
|
|
326
|
+
});
|
|
327
|
+
await this.loadProfile(selection);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// --- Profile management (CRUD) ---
|
|
331
|
+
|
|
332
|
+
async duplicateProfile() {
|
|
333
|
+
const {
|
|
334
|
+
name,
|
|
335
|
+
description
|
|
336
|
+
} = await this.promptProfileMeta(`${this.profileMeta.name} (copy)`, this.profileMeta.description);
|
|
337
|
+
const profilesDir = Sprinkle.profilesDir(this.storagePath);
|
|
338
|
+
const id = Sprinkle.findAvailableId(profilesDir, Sprinkle.sanitizeProfileId(name));
|
|
339
|
+
const now = new Date().toISOString();
|
|
340
|
+
// Write duplicate with current settings
|
|
341
|
+
const settingsToSave = this.encryptSettings(this.settings);
|
|
342
|
+
const jsonContent = JSON.stringify({
|
|
343
|
+
meta: {
|
|
344
|
+
name,
|
|
345
|
+
description,
|
|
346
|
+
createdAt: now,
|
|
347
|
+
updatedAt: now
|
|
348
|
+
},
|
|
349
|
+
settings: settingsToSave,
|
|
350
|
+
defaults: this.defaults
|
|
351
|
+
}, Sprinkle.bigIntReplacer, 2);
|
|
352
|
+
fs.writeFileSync(path.join(profilesDir, `${id}.json`), jsonContent, "utf-8");
|
|
353
|
+
console.log(`Profile "${name}" created as a copy.`);
|
|
354
|
+
}
|
|
355
|
+
async renameProfile() {
|
|
356
|
+
const {
|
|
357
|
+
name,
|
|
358
|
+
description
|
|
359
|
+
} = await this.promptProfileMeta(this.profileMeta.name, this.profileMeta.description);
|
|
360
|
+
const newId = Sprinkle.sanitizeProfileId(name);
|
|
361
|
+
const oldId = this.profileId;
|
|
362
|
+
this.profileMeta.name = name;
|
|
363
|
+
this.profileMeta.description = description;
|
|
364
|
+
if (newId !== oldId) {
|
|
365
|
+
const newPath = Sprinkle.profilePath(this.storagePath, newId);
|
|
366
|
+
if (fs.existsSync(newPath)) {
|
|
367
|
+
console.log(`A profile with ID "${newId}" already exists. Choose a different name.`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// Save to new path, remove old file
|
|
371
|
+
this.profileId = newId;
|
|
372
|
+
this.saveProfile();
|
|
373
|
+
fs.unlinkSync(Sprinkle.profilePath(this.storagePath, oldId));
|
|
374
|
+
fs.writeFileSync(Sprinkle.activeProfilePath(this.storagePath), newId, "utf-8");
|
|
375
|
+
} else {
|
|
376
|
+
this.saveProfile();
|
|
377
|
+
}
|
|
378
|
+
console.log(`Profile renamed to "${name}".`);
|
|
379
|
+
}
|
|
380
|
+
async deleteProfile() {
|
|
381
|
+
const profiles = this.scanProfiles();
|
|
382
|
+
const others = profiles.filter(p => p.id !== this.profileId);
|
|
383
|
+
if (others.length === 0) {
|
|
384
|
+
console.log("Cannot delete the only profile.");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const toDelete = await (0, _prompts.select)({
|
|
388
|
+
message: "Select a profile to delete:",
|
|
389
|
+
choices: [...others.map(p => ({
|
|
390
|
+
name: p.meta.description ? `${p.meta.name} - ${p.meta.description}` : p.meta.name,
|
|
391
|
+
value: p.id
|
|
392
|
+
})), {
|
|
393
|
+
name: "Cancel",
|
|
394
|
+
value: "__cancel__"
|
|
395
|
+
}]
|
|
396
|
+
});
|
|
397
|
+
if (toDelete === "__cancel__") return;
|
|
398
|
+
const profileToDelete = others.find(p => p.id === toDelete);
|
|
399
|
+
const confirmed = await (0, _prompts.confirm)({
|
|
400
|
+
message: `Delete profile "${profileToDelete?.meta.name ?? toDelete}"? This cannot be undone.`,
|
|
401
|
+
default: false
|
|
402
|
+
});
|
|
403
|
+
if (confirmed) {
|
|
404
|
+
fs.unlinkSync(Sprinkle.profilePath(this.storagePath, toDelete));
|
|
405
|
+
console.log(`Profile "${profileToDelete?.meta.name ?? toDelete}" deleted.`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// --- Menu ---
|
|
410
|
+
|
|
411
|
+
async showMenu(menu) {
|
|
412
|
+
return this._showMenu(menu, true);
|
|
413
|
+
}
|
|
414
|
+
async _showMenu(menu, main) {
|
|
415
|
+
if (menu.beforeShow) {
|
|
416
|
+
await menu.beforeShow(this);
|
|
417
|
+
}
|
|
418
|
+
const choices = menu.items.map((item, index) => {
|
|
419
|
+
if ("action" in item) {
|
|
420
|
+
return {
|
|
421
|
+
name: item.title,
|
|
422
|
+
value: index
|
|
423
|
+
};
|
|
424
|
+
} else {
|
|
425
|
+
return {
|
|
426
|
+
name: `${item.title} Submenu`,
|
|
427
|
+
value: index
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
if (!main) {
|
|
432
|
+
choices.push({
|
|
433
|
+
name: "Back",
|
|
434
|
+
value: -1
|
|
435
|
+
});
|
|
436
|
+
} else {
|
|
437
|
+
choices.push({
|
|
438
|
+
name: "Settings & Profiles",
|
|
439
|
+
value: -5
|
|
440
|
+
});
|
|
441
|
+
choices.push({
|
|
442
|
+
name: "Exit",
|
|
443
|
+
value: -1
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
const selection = await (0, _prompts.select)({
|
|
447
|
+
message: "Select an option:",
|
|
448
|
+
choices: choices
|
|
449
|
+
});
|
|
450
|
+
if (selection === -5) {
|
|
451
|
+
const settingsMenu = {
|
|
452
|
+
title: "Settings & Profiles",
|
|
453
|
+
items: [{
|
|
454
|
+
title: "Edit settings",
|
|
455
|
+
action: async () => {
|
|
456
|
+
this.settings = await this.EditStruct(this.type, this.settings);
|
|
457
|
+
this.saveSettings();
|
|
458
|
+
}
|
|
459
|
+
}, {
|
|
460
|
+
title: "Switch profile",
|
|
461
|
+
action: async () => {
|
|
462
|
+
this.saveSettings();
|
|
463
|
+
const profiles = this.scanProfiles();
|
|
464
|
+
if (profiles.length <= 1) {
|
|
465
|
+
console.log("No other profiles to switch to. Create a new one first.");
|
|
466
|
+
} else {
|
|
467
|
+
await this.selectProfile(profiles);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}, {
|
|
471
|
+
title: "Create new profile",
|
|
472
|
+
action: async () => {
|
|
473
|
+
await this.createProfile();
|
|
474
|
+
}
|
|
475
|
+
}, {
|
|
476
|
+
title: "Duplicate current profile",
|
|
477
|
+
action: async () => {
|
|
478
|
+
await this.duplicateProfile();
|
|
479
|
+
}
|
|
480
|
+
}, {
|
|
481
|
+
title: "Rename current profile",
|
|
482
|
+
action: async () => {
|
|
483
|
+
await this.renameProfile();
|
|
484
|
+
}
|
|
485
|
+
}, {
|
|
486
|
+
title: "Delete a profile",
|
|
487
|
+
action: async () => {
|
|
488
|
+
await this.deleteProfile();
|
|
489
|
+
}
|
|
490
|
+
}]
|
|
491
|
+
};
|
|
492
|
+
await this._showMenu(settingsMenu, false);
|
|
493
|
+
await this._showMenu(menu, main);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (selection === -1) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const selectedItem = menu.items[selection];
|
|
500
|
+
if ("action" in selectedItem) {
|
|
501
|
+
const result = await selectedItem.action(this);
|
|
502
|
+
if (result instanceof Sprinkle) {
|
|
503
|
+
this.settings = result.settings;
|
|
504
|
+
this.saveSettings();
|
|
505
|
+
}
|
|
506
|
+
await this._showMenu(menu, main);
|
|
507
|
+
} else {
|
|
508
|
+
await this._showMenu(selectedItem, false);
|
|
509
|
+
await this._showMenu(menu, main);
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
static async New(type, storagePath, options) {
|
|
514
|
+
const sprinkle = new Sprinkle(type, storagePath, options);
|
|
515
|
+
await sprinkle.migrateIfNeeded();
|
|
516
|
+
await sprinkle.selectOrCreateProfile();
|
|
517
|
+
sprinkle.saveSettings();
|
|
518
|
+
return sprinkle;
|
|
519
|
+
}
|
|
520
|
+
static async GetProvider(network, settings) {
|
|
521
|
+
switch (settings.type) {
|
|
522
|
+
case "blockfrost":
|
|
523
|
+
return new _query.Blockfrost({
|
|
524
|
+
network: `cardano-${network}`,
|
|
525
|
+
projectId: settings.projectId
|
|
526
|
+
});
|
|
527
|
+
case "maestro":
|
|
528
|
+
// Dynamic import - Maestro may or may not be exported depending on @blaze-cardano/query version
|
|
529
|
+
const queryModule = await Promise.resolve().then(() => _interopRequireWildcard(require("@blaze-cardano/query")));
|
|
530
|
+
if (!queryModule.Maestro) {
|
|
531
|
+
throw new Error("Maestro is not available in the installed version of @blaze-cardano/query. Please install a version that includes Maestro support.");
|
|
532
|
+
}
|
|
533
|
+
return new queryModule.Maestro({
|
|
534
|
+
network: network,
|
|
535
|
+
apiKey: settings.apiKey
|
|
536
|
+
});
|
|
537
|
+
default:
|
|
538
|
+
throw new Error("Invalid provider type");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
static async GetWallet(settings, provider) {
|
|
542
|
+
switch (settings.type) {
|
|
543
|
+
case "hot":
|
|
544
|
+
return _sdk.HotWallet.fromMasterkey(_sdk.Core.Bip32PrivateKeyHex(settings.privateKey), provider, provider.network);
|
|
545
|
+
case "cold":
|
|
546
|
+
return new _sdk.ColdWallet(_sdk.Core.Address.fromBech32(settings.address), provider.network, provider);
|
|
547
|
+
default:
|
|
548
|
+
throw new Error("Invalid wallet type");
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
static async GetBlaze(network, providerSettings, walletSettings) {
|
|
552
|
+
const provider = await Sprinkle.GetProvider(network, providerSettings);
|
|
553
|
+
const wallet = await Sprinkle.GetWallet(walletSettings, provider);
|
|
554
|
+
return _sdk.Blaze.from(provider, wallet);
|
|
555
|
+
}
|
|
556
|
+
static async SearchSelect(opts) {
|
|
557
|
+
return (0, _prompts.search)(opts);
|
|
558
|
+
}
|
|
559
|
+
static SettingsPath(storagePath) {
|
|
560
|
+
return `${storagePath}${path.sep}settings.json`;
|
|
561
|
+
}
|
|
562
|
+
async LoadSettings(type, storagePath) {
|
|
563
|
+
try {
|
|
564
|
+
// Check if the settings file exists
|
|
565
|
+
if (fs.existsSync(storagePath)) {
|
|
566
|
+
// Read and parse the JSON file
|
|
567
|
+
const fileContent = fs.readFileSync(storagePath, "utf-8");
|
|
568
|
+
const parsed = JSON.parse(fileContent, Sprinkle.bigIntReviver);
|
|
569
|
+
// Convert string representations back to BigInt where needed
|
|
570
|
+
this.settings = await this.decryptSettings(parsed.settings);
|
|
571
|
+
this.defaults = parsed.defaults;
|
|
572
|
+
} else {
|
|
573
|
+
this.defaults = {};
|
|
574
|
+
this.settings = await this.FillInStruct(type);
|
|
575
|
+
}
|
|
576
|
+
} catch (error) {
|
|
577
|
+
throw new Error(`Error loading settings from ${storagePath}: ${error}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
static bigIntReviver(key, value) {
|
|
581
|
+
if (typeof value === "string" && /^\d+n$/.test(value)) {
|
|
582
|
+
return BigInt(value.slice(0, -1));
|
|
583
|
+
}
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
static bigIntReplacer(_key, value) {
|
|
587
|
+
return typeof value === "bigint" ? `${value.toString()}n` : value;
|
|
588
|
+
}
|
|
589
|
+
static collectSensitivePaths(type, prefix = "") {
|
|
590
|
+
const paths = [];
|
|
591
|
+
if (isObject(type)) {
|
|
592
|
+
const fields = type["properties"];
|
|
593
|
+
for (const [field, fieldType] of Object.entries(fields)) {
|
|
594
|
+
const fieldPath = prefix ? `${prefix}.${field}` : field;
|
|
595
|
+
if (isSensitive(fieldType)) {
|
|
596
|
+
paths.push(fieldPath);
|
|
597
|
+
}
|
|
598
|
+
paths.push(...Sprinkle.collectSensitivePaths(fieldType, fieldPath));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (isUnion(type)) {
|
|
602
|
+
for (const variant of type.anyOf) {
|
|
603
|
+
paths.push(...Sprinkle.collectSensitivePaths(variant, prefix));
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return paths;
|
|
607
|
+
}
|
|
608
|
+
static getNestedValue(obj, path) {
|
|
609
|
+
return path.split(".").reduce((o, k) => o?.[k], obj);
|
|
610
|
+
}
|
|
611
|
+
static setNestedValue(obj, path, value) {
|
|
612
|
+
const keys = path.split(".");
|
|
613
|
+
const last = keys.pop();
|
|
614
|
+
const parent = keys.reduce((o, k) => o?.[k], obj);
|
|
615
|
+
if (parent && typeof parent === "object") {
|
|
616
|
+
parent[last] = value;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
encryptSettings(settings) {
|
|
620
|
+
if (!this.options.encryption) return settings;
|
|
621
|
+
const clone = JSON.parse(JSON.stringify(settings, Sprinkle.bigIntReplacer), Sprinkle.bigIntReviver);
|
|
622
|
+
const sensitivePaths = Sprinkle.collectSensitivePaths(this.type);
|
|
623
|
+
for (const p of sensitivePaths) {
|
|
624
|
+
const value = Sprinkle.getNestedValue(clone, p);
|
|
625
|
+
if (typeof value === "string" && value.length > 0) {
|
|
626
|
+
Sprinkle.setNestedValue(clone, p, this.options.encryption.encrypt(value));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return clone;
|
|
630
|
+
}
|
|
631
|
+
async decryptSettings(settings) {
|
|
632
|
+
if (!this.options.encryption) return settings;
|
|
633
|
+
const clone = JSON.parse(JSON.stringify(settings, Sprinkle.bigIntReplacer), Sprinkle.bigIntReviver);
|
|
634
|
+
const sensitivePaths = Sprinkle.collectSensitivePaths(this.type);
|
|
635
|
+
for (const p of sensitivePaths) {
|
|
636
|
+
const value = Sprinkle.getNestedValue(clone, p);
|
|
637
|
+
if (typeof value === "string" && value.length > 0) {
|
|
638
|
+
Sprinkle.setNestedValue(clone, p, await this.options.encryption.decrypt(value));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return clone;
|
|
642
|
+
}
|
|
643
|
+
saveSettings() {
|
|
644
|
+
this.saveProfile();
|
|
645
|
+
}
|
|
646
|
+
async TxDialog(blaze, tx, opts) {
|
|
647
|
+
const txCbor = tx.toCbor();
|
|
648
|
+
let expanded = false;
|
|
649
|
+
const showDialog = async () => {
|
|
650
|
+
if (expanded) {
|
|
651
|
+
console.log("Transaction CBOR:", txCbor);
|
|
652
|
+
} else {
|
|
653
|
+
console.log("Transaction CBOR:", `${String(txCbor).slice(0, 50)}...`);
|
|
654
|
+
}
|
|
655
|
+
const menuItems = [];
|
|
656
|
+
if (!expanded) {
|
|
657
|
+
menuItems.push({
|
|
658
|
+
title: "Expand CBOR",
|
|
659
|
+
action: async () => {
|
|
660
|
+
expanded = true;
|
|
661
|
+
await showDialog();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
menuItems.push({
|
|
666
|
+
title: "Copy CBOR to clipboard",
|
|
667
|
+
action: async () => {
|
|
668
|
+
try {
|
|
669
|
+
const {
|
|
670
|
+
default: clipboard
|
|
671
|
+
} = await Promise.resolve().then(() => _interopRequireWildcard(require("clipboardy")));
|
|
672
|
+
clipboard.writeSync(String(txCbor));
|
|
673
|
+
console.log("Transaction CBOR copied to clipboard.");
|
|
674
|
+
} catch (e) {
|
|
675
|
+
console.log("Failed to copy to clipboard, expanding instead.");
|
|
676
|
+
expanded = true;
|
|
677
|
+
await showDialog();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
if (blaze.wallet instanceof _sdk.HotWallet) {
|
|
682
|
+
menuItems.push({
|
|
683
|
+
title: "Sign and submit transaction",
|
|
684
|
+
action: async () => {
|
|
685
|
+
if (opts?.beforeSign) {
|
|
686
|
+
await opts.beforeSign();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Detect if stake key signature is required
|
|
690
|
+
let needsStakeKey = false;
|
|
691
|
+
try {
|
|
692
|
+
const wallet = blaze.wallet;
|
|
693
|
+
const addresses = await wallet.getUsedAddresses();
|
|
694
|
+
const userAddress = addresses[0];
|
|
695
|
+
if (userAddress) {
|
|
696
|
+
const stakeCredential = userAddress.asBase()?.getStakeCredential();
|
|
697
|
+
const stakeKeyHash = stakeCredential?.hash?.toString();
|
|
698
|
+
if (stakeKeyHash) {
|
|
699
|
+
const requiredSigners = tx.body().requiredSigners();
|
|
700
|
+
if (requiredSigners) {
|
|
701
|
+
const signerArray = Array.from(requiredSigners.values());
|
|
702
|
+
needsStakeKey = signerArray.some(signer => signer.toString() === stakeKeyHash);
|
|
703
|
+
}
|
|
704
|
+
const certs = tx.body().certs();
|
|
705
|
+
const hasCertificates = certs && certs.size() > 0;
|
|
706
|
+
const withdrawals = tx.body().withdrawals();
|
|
707
|
+
const hasWithdrawals = withdrawals && withdrawals.size > 0;
|
|
708
|
+
if (hasCertificates || hasWithdrawals) {
|
|
709
|
+
needsStakeKey = true;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (needsStakeKey) {
|
|
714
|
+
console.log("Transaction requires stake key signature.");
|
|
715
|
+
} else {
|
|
716
|
+
console.log("Transaction requires payment key signature only.");
|
|
717
|
+
}
|
|
718
|
+
} catch (error) {
|
|
719
|
+
console.warn("Could not determine stake key requirement, signing with payment key only.");
|
|
720
|
+
console.warn(`Error: ${error.message}`);
|
|
721
|
+
}
|
|
722
|
+
let signedTx;
|
|
723
|
+
if (needsStakeKey) {
|
|
724
|
+
const signed = await blaze.wallet.signTransaction(tx, true, true);
|
|
725
|
+
const ws = tx.witnessSet();
|
|
726
|
+
const vkeys = ws.vkeys()?.toCore() ?? [];
|
|
727
|
+
const signedKeys = signed.vkeys();
|
|
728
|
+
if (!signedKeys) {
|
|
729
|
+
throw new Error("signTransaction: no signed keys in wallet witness response");
|
|
730
|
+
}
|
|
731
|
+
if (signedKeys.toCore().some(([vkey]) => vkeys.some(([key2]) => vkey === key2))) {
|
|
732
|
+
throw new Error("signTransaction: some keys were already signed");
|
|
733
|
+
}
|
|
734
|
+
ws.setVkeys(_core.CborSet.fromCore([...signedKeys.toCore(), ...vkeys], _core.VkeyWitness.fromCore));
|
|
735
|
+
tx.setWitnessSet(ws);
|
|
736
|
+
signedTx = tx;
|
|
737
|
+
} else {
|
|
738
|
+
signedTx = await blaze.signTransaction(tx);
|
|
739
|
+
}
|
|
740
|
+
const txId = await blaze.submitTransaction(signedTx);
|
|
741
|
+
console.log(`Transaction submitted: ${txId}`);
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
const txMenu = {
|
|
746
|
+
title: "Transaction Menu",
|
|
747
|
+
items: menuItems
|
|
748
|
+
};
|
|
749
|
+
await this._showMenu(txMenu, false);
|
|
750
|
+
};
|
|
751
|
+
await showDialog();
|
|
752
|
+
}
|
|
753
|
+
async EditStruct(type, current) {
|
|
754
|
+
return this._editStruct(type, ["root"], current);
|
|
755
|
+
}
|
|
756
|
+
async _editStruct(type, path, current) {
|
|
757
|
+
if (isObject(type)) {
|
|
758
|
+
const obj = {};
|
|
759
|
+
const fields = type["properties"];
|
|
760
|
+
const menuItems = [];
|
|
761
|
+
const currentRecord = current;
|
|
762
|
+
for (const [field, fieldType] of Object.entries(fields)) {
|
|
763
|
+
if (current && field in currentRecord) {
|
|
764
|
+
obj[field] = currentRecord[field];
|
|
765
|
+
}
|
|
766
|
+
const menuTitle = Sprinkle.ExtractMessage(fieldType, `Edit ${field} at ${path.join(".")}`);
|
|
767
|
+
if (isOptional(fieldType) && current && currentRecord[field] !== undefined) {
|
|
768
|
+
menuItems.push({
|
|
769
|
+
title: `Clear ${field}`,
|
|
770
|
+
action: async sprinkle => {
|
|
771
|
+
obj[field] = undefined;
|
|
772
|
+
return sprinkle;
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
menuItems.push({
|
|
777
|
+
title: menuTitle,
|
|
778
|
+
action: async sprinkle => {
|
|
779
|
+
const fieldValue = await sprinkle._editStruct(fieldType, path.concat([field]), current && field in currentRecord ? currentRecord[field] : undefined);
|
|
780
|
+
obj[field] = fieldValue;
|
|
781
|
+
return sprinkle;
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const editMenu = {
|
|
786
|
+
title: "Test",
|
|
787
|
+
items: menuItems
|
|
788
|
+
};
|
|
789
|
+
await this._showMenu(editMenu, false);
|
|
790
|
+
return obj;
|
|
791
|
+
}
|
|
792
|
+
return this._fillInStruct(type, path, {}, current);
|
|
793
|
+
}
|
|
794
|
+
async FillInStruct(type, def) {
|
|
795
|
+
return this._fillInStruct(type, ["root"], {}, def);
|
|
796
|
+
}
|
|
797
|
+
async _fillInStruct(type, path, defs, def) {
|
|
798
|
+
if ("$ref" in type) {
|
|
799
|
+
defs = {
|
|
800
|
+
...defs,
|
|
801
|
+
...type["$defs"]
|
|
802
|
+
};
|
|
803
|
+
const resolvedType = defs[type["$ref"]];
|
|
804
|
+
if (!resolvedType) {
|
|
805
|
+
throw new Error(`Could not resolve type ${type["$ref"]} at ${path.join(".")}`);
|
|
806
|
+
}
|
|
807
|
+
return this._fillInStruct(resolvedType, path, defs, def);
|
|
808
|
+
}
|
|
809
|
+
if (isOptional(type)) {
|
|
810
|
+
const shouldSet = await (0, _prompts.select)({
|
|
811
|
+
message: Sprinkle.ExtractMessage(type, `Set value for ${path.join(".")}?`),
|
|
812
|
+
choices: [{
|
|
813
|
+
name: "Yes",
|
|
814
|
+
value: true
|
|
815
|
+
}, {
|
|
816
|
+
name: "Skip",
|
|
817
|
+
value: false
|
|
818
|
+
}],
|
|
819
|
+
default: def !== undefined
|
|
820
|
+
});
|
|
821
|
+
if (!shouldSet) {
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
// Unwrap the optional and fill the inner type
|
|
825
|
+
const innerType = {
|
|
826
|
+
...type
|
|
827
|
+
};
|
|
828
|
+
delete innerType[_typebox.OptionalKind];
|
|
829
|
+
return this._fillInStruct(innerType, path, defs, def);
|
|
830
|
+
}
|
|
831
|
+
if (isUnion(type)) {
|
|
832
|
+
const choices = [];
|
|
833
|
+
const resolved = this.resolveType(type, path, defs);
|
|
834
|
+
for (const variant of resolved.anyOf) {
|
|
835
|
+
choices.push({
|
|
836
|
+
name: Sprinkle.ExtractMessage(variant, `${variant}`),
|
|
837
|
+
value: variant
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
const selection = await (0, _prompts.select)({
|
|
841
|
+
message: Sprinkle.ExtractMessage(resolved, `Enter a choice for ${path.join(".")}`),
|
|
842
|
+
choices: choices,
|
|
843
|
+
default: def ? `${def}` : undefined
|
|
844
|
+
});
|
|
845
|
+
return this._fillInStruct(selection, path, defs);
|
|
846
|
+
}
|
|
847
|
+
if (isString(type)) {
|
|
848
|
+
const defaultString = def ? def : this.defaults["string"];
|
|
849
|
+
const message = Sprinkle.ExtractMessage(type, `Enter a string for ${path.join(".")}`);
|
|
850
|
+
let answer;
|
|
851
|
+
if (isSensitive(type)) {
|
|
852
|
+
answer = await (0, _prompts.password)({
|
|
853
|
+
message
|
|
854
|
+
});
|
|
855
|
+
} else {
|
|
856
|
+
answer = await (0, _prompts.input)({
|
|
857
|
+
message,
|
|
858
|
+
default: defaultString
|
|
859
|
+
});
|
|
860
|
+
this.defaults["string"] = answer;
|
|
861
|
+
}
|
|
862
|
+
return answer;
|
|
863
|
+
}
|
|
864
|
+
if (isBigInt(type)) {
|
|
865
|
+
const answer = await (0, _prompts.input)({
|
|
866
|
+
message: Sprinkle.ExtractMessage(type, `Enter a bigint for ${path.join(".")}`),
|
|
867
|
+
default: def ? def.toString() : undefined,
|
|
868
|
+
validate: s => {
|
|
869
|
+
try {
|
|
870
|
+
BigInt(s);
|
|
871
|
+
return true;
|
|
872
|
+
} catch {
|
|
873
|
+
return "Please enter a valid bigint.";
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
return BigInt(answer);
|
|
878
|
+
}
|
|
879
|
+
if (isLiteral(type)) {
|
|
880
|
+
return type.const;
|
|
881
|
+
}
|
|
882
|
+
if (isObject(type)) {
|
|
883
|
+
const obj = {};
|
|
884
|
+
const fields = type["properties"];
|
|
885
|
+
for (const [field, fieldType] of Object.entries(fields)) {
|
|
886
|
+
const fieldValue = await this._fillInStruct(fieldType, path.concat([field]), defs, def ? def[field] : undefined);
|
|
887
|
+
obj[field] = fieldValue;
|
|
888
|
+
}
|
|
889
|
+
return obj;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
//TODO: support starting with default values for arrays and allow removal of items
|
|
893
|
+
if (isArray(type)) {
|
|
894
|
+
const arr = [];
|
|
895
|
+
const itemType = type.items;
|
|
896
|
+
let addMore = true;
|
|
897
|
+
while (addMore) {
|
|
898
|
+
const itemValue = await this._fillInStruct(itemType, path.concat([`[${arr.length}]`]), defs);
|
|
899
|
+
arr.push(itemValue);
|
|
900
|
+
const continueAnswer = await (0, _prompts.select)({
|
|
901
|
+
message: `Add another item to ${path.join(".")}?`,
|
|
902
|
+
choices: [{
|
|
903
|
+
name: "Yes",
|
|
904
|
+
value: true
|
|
905
|
+
}, {
|
|
906
|
+
name: "No",
|
|
907
|
+
value: false
|
|
908
|
+
}]
|
|
909
|
+
});
|
|
910
|
+
addMore = continueAnswer;
|
|
911
|
+
}
|
|
912
|
+
return arr;
|
|
913
|
+
}
|
|
914
|
+
throw new Error(`Unable to fill in struct for type at path ${path.join(".")}`);
|
|
915
|
+
}
|
|
916
|
+
resolveType(type, path, defs) {
|
|
917
|
+
if (isRef(type) || isThis(type) || isImport(type)) {
|
|
918
|
+
defs = {
|
|
919
|
+
...defs,
|
|
920
|
+
...type.$defs
|
|
921
|
+
};
|
|
922
|
+
const realType = defs[type.$ref];
|
|
923
|
+
if (!realType) {
|
|
924
|
+
throw new Error(`Invalid type at ${path.join(".")}: Unrecognized reference ${type.$ref}`);
|
|
925
|
+
}
|
|
926
|
+
return this.resolveType(realType, path, defs);
|
|
927
|
+
}
|
|
928
|
+
return type;
|
|
929
|
+
}
|
|
930
|
+
static ExtractMessage(type, def) {
|
|
931
|
+
if ("title" in type) {
|
|
932
|
+
return type.title;
|
|
933
|
+
}
|
|
934
|
+
if ("description" in type) {
|
|
935
|
+
return type.description;
|
|
936
|
+
}
|
|
937
|
+
if (isLiteral(type)) {
|
|
938
|
+
return `${type.const}`;
|
|
939
|
+
}
|
|
940
|
+
if (isObject(type)) {
|
|
941
|
+
const fields = type["properties"];
|
|
942
|
+
if ("type" in fields) {
|
|
943
|
+
return Sprinkle.ExtractMessage(fields["type"], def);
|
|
944
|
+
}
|
|
945
|
+
const fieldNames = Object.keys(fields);
|
|
946
|
+
if (fieldNames.length === 1) {
|
|
947
|
+
return fieldNames[0];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return def;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
exports.Sprinkle = Sprinkle;
|
|
954
|
+
//# sourceMappingURL=index.js.map
|