@alchemy/cli 0.6.2 → 0.7.0-alpha.11
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/README.md +114 -94
- package/dist/{auth-S4DTOWW3.js → auth-FQFFNNZZ.js} +10 -8
- package/dist/auth-ZME2NTXG.js +16 -0
- package/dist/{chunk-TK3HZ5UT.js → chunk-3FDXTEBV.js} +3 -3
- package/dist/{chunk-ATX65U7J.js → chunk-A6BWLRFV.js} +572 -41
- package/dist/{chunk-NBDWF4ZQ.js → chunk-AFD4Y42F.js} +32 -22
- package/dist/{chunk-FFMNT74F.js → chunk-EKBSQAEG.js} +125 -76
- package/dist/chunk-HGZTGSXO.js +261 -0
- package/dist/{chunk-KDMIWPZH.js → chunk-JMDBEC5L.js} +1 -1
- package/dist/{chunk-UMKDYHMO.js → chunk-KJ5VM7FE.js} +54 -99
- package/dist/{chunk-56ZVYB4G.js → chunk-OLVYWGY6.js} +263 -222
- package/dist/chunk-PXPURTNF.js +64 -0
- package/dist/errors-VUVL3B2E.js +54 -0
- package/dist/index.js +6727 -2168
- package/dist/{interactive-UGD7GYJM.js → interactive-ECDBQ2E6.js} +56 -61
- package/dist/{onboarding-IP4R44EQ.js → onboarding-ATR4YYG3.js} +14 -11
- package/dist/resolve-O64VTY3W.js +50 -0
- package/package.json +11 -6
- package/scripts/postinstall.cjs +69 -1
- package/dist/auth-QB3BA7AN.js +0 -17
- package/dist/chunk-BAAQ7ELR.js +0 -143
- package/dist/chunk-JQRGILIS.js +0 -53
- package/dist/chunk-T5Z2GJUX.js +0 -331
- package/dist/credential-storage-T6FFW7DG.js +0 -14
- package/dist/resolve-HXKHDOJZ.js +0 -31
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isJSONMode,
|
|
6
6
|
quiet,
|
|
7
7
|
rgb
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OLVYWGY6.js";
|
|
9
9
|
|
|
10
10
|
// src/lib/terminal-ui.ts
|
|
11
11
|
import * as readline from "readline";
|
|
@@ -48,6 +48,9 @@ function suspendStdinKeypressListeners() {
|
|
|
48
48
|
}
|
|
49
49
|
async function runListPrompt(opts) {
|
|
50
50
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
51
|
+
if (opts.allowMultiple) {
|
|
52
|
+
return { value: opts.initialValues ?? [], cancelled: false };
|
|
53
|
+
}
|
|
51
54
|
const initial = opts.initialValue ?? opts.options.find((o) => !o.disabled)?.value ?? null;
|
|
52
55
|
return { value: initial, cancelled: false };
|
|
53
56
|
}
|
|
@@ -61,7 +64,7 @@ async function runListPrompt(opts) {
|
|
|
61
64
|
0,
|
|
62
65
|
opts.options.findIndex((o) => o.value === opts.initialValue && !o.disabled)
|
|
63
66
|
);
|
|
64
|
-
const selected =
|
|
67
|
+
const selected = new Set(opts.initialValues ?? []);
|
|
65
68
|
const maxVisible = 8;
|
|
66
69
|
let renderedLines = 0;
|
|
67
70
|
const getFiltered = () => {
|
|
@@ -104,10 +107,11 @@ async function runListPrompt(opts) {
|
|
|
104
107
|
const active = start + i === cursor;
|
|
105
108
|
const disabled = option.disabled === true;
|
|
106
109
|
const selectedMark = opts.allowMultiple ? selected.has(option.value) ? ansi.green("\u25C6") : ansi.dim("\u25C7") : active ? ansi.cyan("\u25C6") : ansi.dim("\u25C7");
|
|
110
|
+
const activeMark = active ? ansi.cyan("\u203A") : " ";
|
|
107
111
|
const label = optionLabel(option);
|
|
108
112
|
const value = disabled ? ansi.dim(label) : label;
|
|
109
113
|
const hint = option.hint ? ` ${ansi.dim(`\u2014 ${option.hint}`)}` : "";
|
|
110
|
-
lines.push(` ${ansi.dim(FLOW_PIPE)}
|
|
114
|
+
lines.push(` ${ansi.dim(FLOW_PIPE)} ${activeMark} ${selectedMark} ${value}${hint}`);
|
|
111
115
|
}
|
|
112
116
|
if (filtered.length > maxVisible) {
|
|
113
117
|
lines.push(` ${ansi.dim(FLOW_PIPE)} ${ansi.dim(`${filtered.length} options`)}`);
|
|
@@ -217,16 +221,26 @@ async function promptText(opts) {
|
|
|
217
221
|
const question = ` ${ansi.cyan("\u25C6")} ${opts.message}${opts.placeholder ? ` ${ansi.dim(`(${opts.placeholder})`)}` : ""}: `;
|
|
218
222
|
const previousRawMode = stdin.isRaw;
|
|
219
223
|
if (previousRawMode) stdin.setRawMode(false);
|
|
224
|
+
const ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
220
225
|
const value = await new Promise((resolve) => {
|
|
221
226
|
rl.on("SIGINT", () => resolve(null));
|
|
222
227
|
rl.question(question, (answer) => resolve(answer));
|
|
228
|
+
if (opts.abortSignal) {
|
|
229
|
+
const onAbort = () => resolve(ABORTED);
|
|
230
|
+
if (opts.abortSignal.aborted) {
|
|
231
|
+
onAbort();
|
|
232
|
+
} else {
|
|
233
|
+
opts.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
223
236
|
});
|
|
224
237
|
rl.close();
|
|
225
238
|
restoreKeypressListeners();
|
|
226
239
|
if (previousRawMode) stdin.setRawMode(true);
|
|
227
|
-
if (opts.clearAfterSubmit) {
|
|
240
|
+
if (opts.clearAfterSubmit || value === ABORTED) {
|
|
228
241
|
clearRenderedLines(2);
|
|
229
242
|
}
|
|
243
|
+
if (value === ABORTED) return "";
|
|
230
244
|
if (value === null) {
|
|
231
245
|
printCancel(opts.cancelMessage);
|
|
232
246
|
return null;
|
|
@@ -281,6 +295,7 @@ async function promptMultiselect(opts) {
|
|
|
281
295
|
const result = await runListPrompt({
|
|
282
296
|
message: opts.message,
|
|
283
297
|
options: opts.options,
|
|
298
|
+
initialValues: opts.initialValues,
|
|
284
299
|
allowMultiple: true,
|
|
285
300
|
required: opts.required,
|
|
286
301
|
commitLabel: "Selected"
|
|
@@ -362,29 +377,24 @@ async function withSpinner(label, doneLabel, fn) {
|
|
|
362
377
|
if (isJSONMode() || quiet) return fn();
|
|
363
378
|
return runWithSpinner(label, doneLabel, fn);
|
|
364
379
|
}
|
|
365
|
-
function
|
|
380
|
+
function printKeyValue(pairs, withBottomPadding = true) {
|
|
366
381
|
if (isJSONMode()) return;
|
|
382
|
+
console.log("");
|
|
367
383
|
if (pairs.length === 0) {
|
|
368
|
-
|
|
369
|
-
|
|
384
|
+
if (withBottomPadding) {
|
|
385
|
+
console.log("");
|
|
386
|
+
}
|
|
370
387
|
return;
|
|
371
388
|
}
|
|
372
|
-
const
|
|
373
|
-
const
|
|
389
|
+
const maxLen = Math.max(...pairs.map(([k]) => stripAnsi(k).length));
|
|
390
|
+
for (const [key, value] of pairs) {
|
|
374
391
|
const visibleKeyLen = stripAnsi(key).length;
|
|
375
|
-
const paddedKey = key + " ".repeat(Math.max(0,
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const bottom = `\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`;
|
|
381
|
-
console.log(` ${ansi2.dim(top)}`);
|
|
382
|
-
for (const row of contentRows) {
|
|
383
|
-
const visibleLen = stripAnsi(row).length;
|
|
384
|
-
const padded = row + " ".repeat(Math.max(0, contentWidth - visibleLen));
|
|
385
|
-
console.log(` ${ansi2.dim("\u2502")} ${padded} ${ansi2.dim("\u2502")}`);
|
|
392
|
+
const paddedKey = key + " ".repeat(Math.max(0, maxLen - visibleKeyLen));
|
|
393
|
+
console.log(` ${ansi2.dim(paddedKey)} ${value}`);
|
|
394
|
+
}
|
|
395
|
+
if (withBottomPadding) {
|
|
396
|
+
console.log("");
|
|
386
397
|
}
|
|
387
|
-
console.log(` ${ansi2.dim(bottom)}`);
|
|
388
398
|
}
|
|
389
399
|
function emptyState(message) {
|
|
390
400
|
if (isJSONMode()) return;
|
|
@@ -543,7 +553,7 @@ export {
|
|
|
543
553
|
successBadge,
|
|
544
554
|
failBadge,
|
|
545
555
|
withSpinner,
|
|
546
|
-
|
|
556
|
+
printKeyValue,
|
|
547
557
|
emptyState,
|
|
548
558
|
printSyntaxJSON,
|
|
549
559
|
printTable,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
getBaseDomain
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OLVYWGY6.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/auth.ts
|
|
8
8
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -130,7 +130,7 @@ ${SHARED_STYLE}
|
|
|
130
130
|
// src/lib/auth.ts
|
|
131
131
|
var AUTH_PORT = 16424;
|
|
132
132
|
var AUTH_CALLBACK_PATH = "/callback";
|
|
133
|
-
var
|
|
133
|
+
var DEFAULT_EXPIRES_IN_SECONDS = 90 * 24 * 60 * 60;
|
|
134
134
|
function getAuthBaseUrl() {
|
|
135
135
|
return process.env.ALCHEMY_AUTH_URL || `https://auth.${getBaseDomain()}`;
|
|
136
136
|
}
|
|
@@ -140,40 +140,56 @@ function generateCodeVerifier() {
|
|
|
140
140
|
function deriveCodeChallenge(verifier) {
|
|
141
141
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
142
142
|
}
|
|
143
|
-
function
|
|
144
|
-
return randomBytes(32).toString("base64url");
|
|
145
|
-
}
|
|
146
|
-
function getAuthorizeUrl(port, codeChallenge, state) {
|
|
143
|
+
function getLoginUrl(port, codeChallenge) {
|
|
147
144
|
const base = getAuthBaseUrl();
|
|
148
|
-
const
|
|
149
|
-
url.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
url
|
|
154
|
-
url.searchParams.set("state", state);
|
|
155
|
-
return url.toString();
|
|
156
|
-
}
|
|
157
|
-
function prepareBrowserLogin(port = AUTH_PORT) {
|
|
158
|
-
const codeVerifier = generateCodeVerifier();
|
|
159
|
-
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
160
|
-
const state = generateState();
|
|
161
|
-
return {
|
|
162
|
-
authorizeUrl: getAuthorizeUrl(port, codeChallenge, state),
|
|
163
|
-
codeVerifier,
|
|
164
|
-
state
|
|
165
|
-
};
|
|
145
|
+
const redirect = encodeURIComponent(`http://localhost:${port}${AUTH_CALLBACK_PATH}`);
|
|
146
|
+
let url = `${base}/login?redirectUrl=${redirect}&_t=${Date.now()}`;
|
|
147
|
+
if (codeChallenge) {
|
|
148
|
+
url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
|
|
149
|
+
}
|
|
150
|
+
return url;
|
|
166
151
|
}
|
|
167
152
|
function openBrowser(url) {
|
|
168
153
|
const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
169
154
|
execFile(cmd, [url]);
|
|
170
155
|
}
|
|
156
|
+
function closeServer(args) {
|
|
157
|
+
const { server, sockets } = args;
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
server.close(() => resolve());
|
|
160
|
+
for (const socket of sockets) {
|
|
161
|
+
socket.destroy();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async function sendCallbackResponse(args) {
|
|
166
|
+
const { server, sockets, req, res, statusCode, body } = args;
|
|
167
|
+
req.socket.setKeepAlive(false);
|
|
168
|
+
res.shouldKeepAlive = false;
|
|
169
|
+
res.writeHead(statusCode, {
|
|
170
|
+
"Content-Type": "text/html",
|
|
171
|
+
Connection: "close"
|
|
172
|
+
});
|
|
173
|
+
await new Promise((resolve) => {
|
|
174
|
+
res.end(body, () => resolve());
|
|
175
|
+
});
|
|
176
|
+
await closeServer({ server, sockets });
|
|
177
|
+
}
|
|
171
178
|
function waitForCallback(port, timeoutMs = 12e4) {
|
|
172
|
-
|
|
179
|
+
let cancelFn;
|
|
180
|
+
const promise = new Promise((resolve, reject) => {
|
|
181
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
173
182
|
const timer = setTimeout(() => {
|
|
174
183
|
server.close();
|
|
184
|
+
for (const socket of sockets) {
|
|
185
|
+
socket.destroy();
|
|
186
|
+
}
|
|
175
187
|
reject(new Error("Authentication timed out. Please try again."));
|
|
176
188
|
}, timeoutMs);
|
|
189
|
+
cancelFn = () => {
|
|
190
|
+
clearTimeout(timer);
|
|
191
|
+
closeServer({ server, sockets });
|
|
192
|
+
};
|
|
177
193
|
const server = createServer((req, res) => {
|
|
178
194
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
179
195
|
if (url.pathname !== AUTH_CALLBACK_PATH) {
|
|
@@ -185,35 +201,52 @@ function waitForCallback(port, timeoutMs = 12e4) {
|
|
|
185
201
|
if (error) {
|
|
186
202
|
const description = url.searchParams.get("error_description") || error;
|
|
187
203
|
clearTimeout(timer);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
204
|
+
void sendCallbackResponse({
|
|
205
|
+
server,
|
|
206
|
+
sockets,
|
|
207
|
+
req,
|
|
208
|
+
res,
|
|
209
|
+
statusCode: 200,
|
|
210
|
+
body: authErrorHtml(description)
|
|
211
|
+
}).finally(() => {
|
|
212
|
+
reject(new Error(`Authentication failed: ${description}`));
|
|
213
|
+
});
|
|
192
214
|
return;
|
|
193
215
|
}
|
|
194
216
|
const code = url.searchParams.get("code");
|
|
195
217
|
if (!code) {
|
|
196
218
|
clearTimeout(timer);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
void sendCallbackResponse({
|
|
220
|
+
server,
|
|
221
|
+
sockets,
|
|
222
|
+
req,
|
|
223
|
+
res,
|
|
224
|
+
statusCode: 400,
|
|
225
|
+
body: "Missing auth code"
|
|
226
|
+
}).finally(() => {
|
|
227
|
+
reject(new Error("Authentication callback missing auth code."));
|
|
228
|
+
});
|
|
201
229
|
return;
|
|
202
230
|
}
|
|
203
231
|
clearTimeout(timer);
|
|
204
232
|
resolve({
|
|
205
233
|
code,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
server
|
|
216
|
-
|
|
234
|
+
sendSuccess: () => sendCallbackResponse({
|
|
235
|
+
server,
|
|
236
|
+
sockets,
|
|
237
|
+
req,
|
|
238
|
+
res,
|
|
239
|
+
statusCode: 200,
|
|
240
|
+
body: AUTH_SUCCESS_HTML
|
|
241
|
+
}),
|
|
242
|
+
sendError: (message) => sendCallbackResponse({
|
|
243
|
+
server,
|
|
244
|
+
sockets,
|
|
245
|
+
req,
|
|
246
|
+
res,
|
|
247
|
+
statusCode: 500,
|
|
248
|
+
body: authErrorHtml(message)
|
|
249
|
+
})
|
|
217
250
|
});
|
|
218
251
|
});
|
|
219
252
|
server.on("error", (err) => {
|
|
@@ -229,60 +262,75 @@ function waitForCallback(port, timeoutMs = 12e4) {
|
|
|
229
262
|
reject(err);
|
|
230
263
|
}
|
|
231
264
|
});
|
|
265
|
+
server.on("connection", (socket) => {
|
|
266
|
+
sockets.add(socket);
|
|
267
|
+
socket.on("close", () => {
|
|
268
|
+
sockets.delete(socket);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
232
271
|
server.listen(port, "127.0.0.1");
|
|
233
272
|
server.unref();
|
|
234
273
|
});
|
|
274
|
+
return { promise, cancel: () => cancelFn() };
|
|
235
275
|
}
|
|
236
276
|
async function exchangeCodeForToken(code, port, options) {
|
|
237
277
|
const baseUrl = getAuthBaseUrl();
|
|
238
278
|
const redirectUri = `http://localhost:${port}${AUTH_CALLBACK_PATH}`;
|
|
239
|
-
const body =
|
|
240
|
-
grant_type: "authorization_code",
|
|
279
|
+
const body = {
|
|
241
280
|
code,
|
|
242
|
-
redirect_uri: redirectUri
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
281
|
+
redirect_uri: redirectUri
|
|
282
|
+
};
|
|
283
|
+
if (options?.expiresInSeconds) {
|
|
284
|
+
body.expires_in_seconds = options.expiresInSeconds;
|
|
285
|
+
}
|
|
286
|
+
if (options?.codeVerifier) {
|
|
287
|
+
body.code_verifier = options.codeVerifier;
|
|
288
|
+
}
|
|
289
|
+
const response = await fetch(`${baseUrl}/api/cli/token`, {
|
|
247
290
|
method: "POST",
|
|
248
|
-
headers: { "Content-Type": "application/
|
|
249
|
-
body:
|
|
291
|
+
headers: { "Content-Type": "application/json" },
|
|
292
|
+
body: JSON.stringify(body)
|
|
250
293
|
});
|
|
251
294
|
if (!response.ok) {
|
|
252
295
|
const errBody = await response.json().catch(() => ({}));
|
|
253
|
-
|
|
254
|
-
|
|
296
|
+
throw new Error(
|
|
297
|
+
errBody.error || `Token exchange failed (HTTP ${response.status})`
|
|
298
|
+
);
|
|
255
299
|
}
|
|
256
300
|
const data = await response.json();
|
|
257
|
-
if (!data.
|
|
258
|
-
throw new Error("Token exchange response missing
|
|
301
|
+
if (!data.authToken) {
|
|
302
|
+
throw new Error("Token exchange response missing authToken");
|
|
259
303
|
}
|
|
260
|
-
|
|
261
|
-
|
|
304
|
+
return { token: data.authToken, expiresAt: data.expiresAt };
|
|
305
|
+
}
|
|
306
|
+
function prepareLogin(port = AUTH_PORT) {
|
|
307
|
+
const codeVerifier = generateCodeVerifier();
|
|
308
|
+
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
309
|
+
const loginUrl = getLoginUrl(port, codeChallenge);
|
|
310
|
+
const handle = waitForCallback(port);
|
|
311
|
+
return { loginUrl, callbackPromise: handle.promise, codeVerifier, port, cancel: handle.cancel };
|
|
262
312
|
}
|
|
263
|
-
async function
|
|
264
|
-
const port = options?.port ?? AUTH_PORT;
|
|
265
|
-
const { authorizeUrl, codeVerifier, state } = prepared ?? prepareBrowserLogin(port);
|
|
266
|
-
const callbackPromise = waitForCallback(port);
|
|
313
|
+
async function completeLogin(prepared, options) {
|
|
267
314
|
if (!options?.skipBrowserOpen) {
|
|
268
|
-
openBrowser(
|
|
269
|
-
}
|
|
270
|
-
const callback = await callbackPromise;
|
|
271
|
-
if (callback.state !== state) {
|
|
272
|
-
callback.sendError("State mismatch \u2014 possible CSRF attack.");
|
|
273
|
-
throw new Error("OAuth state mismatch. Authentication aborted.");
|
|
315
|
+
openBrowser(prepared.loginUrl);
|
|
274
316
|
}
|
|
317
|
+
const callback = await prepared.callbackPromise;
|
|
275
318
|
try {
|
|
276
|
-
const result = await exchangeCodeForToken(callback.code, port, {
|
|
277
|
-
|
|
319
|
+
const result = await exchangeCodeForToken(callback.code, prepared.port, {
|
|
320
|
+
expiresInSeconds: options?.expiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS,
|
|
321
|
+
codeVerifier: prepared.codeVerifier
|
|
278
322
|
});
|
|
279
|
-
callback.sendSuccess();
|
|
323
|
+
await callback.sendSuccess();
|
|
280
324
|
return result;
|
|
281
325
|
} catch (err) {
|
|
282
|
-
callback.sendError("Failed to complete authentication. Please try again.");
|
|
326
|
+
await callback.sendError("Failed to complete authentication. Please try again.");
|
|
283
327
|
throw err;
|
|
284
328
|
}
|
|
285
329
|
}
|
|
330
|
+
async function performBrowserLogin(port = AUTH_PORT, options) {
|
|
331
|
+
const prepared = prepareLogin(port);
|
|
332
|
+
return completeLogin(prepared, { expiresInSeconds: options?.expiresInSeconds });
|
|
333
|
+
}
|
|
286
334
|
async function revokeToken(token) {
|
|
287
335
|
const baseUrl = getAuthBaseUrl();
|
|
288
336
|
try {
|
|
@@ -307,12 +355,13 @@ async function revokeToken(token) {
|
|
|
307
355
|
|
|
308
356
|
export {
|
|
309
357
|
AUTH_PORT,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
prepareBrowserLogin,
|
|
358
|
+
DEFAULT_EXPIRES_IN_SECONDS,
|
|
359
|
+
getLoginUrl,
|
|
313
360
|
openBrowser,
|
|
314
361
|
waitForCallback,
|
|
315
362
|
exchangeCodeForToken,
|
|
363
|
+
prepareLogin,
|
|
364
|
+
completeLogin,
|
|
316
365
|
performBrowserLogin,
|
|
317
366
|
revokeToken
|
|
318
367
|
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
isRevealMode
|
|
5
|
+
} from "./chunk-OLVYWGY6.js";
|
|
6
|
+
|
|
7
|
+
// src/lib/secrets.ts
|
|
8
|
+
function maskSecret(value) {
|
|
9
|
+
if (value.length <= 8) return "\u2022".repeat(value.length);
|
|
10
|
+
return value.slice(0, 4) + "\u2022".repeat(value.length - 8) + value.slice(-4);
|
|
11
|
+
}
|
|
12
|
+
function maskIf(value) {
|
|
13
|
+
return isRevealMode() ? value : maskSecret(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/lib/config.ts
|
|
17
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
18
|
+
import { createHash, randomUUID } from "crypto";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { join, dirname } from "path";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
var KEY_MAP = {
|
|
23
|
+
"api-key": "api_key",
|
|
24
|
+
api_key: "api_key",
|
|
25
|
+
"access-key": "access_key",
|
|
26
|
+
access_key: "access_key",
|
|
27
|
+
"webhook-api-key": "webhook_api_key",
|
|
28
|
+
webhook_api_key: "webhook_api_key",
|
|
29
|
+
network: "network",
|
|
30
|
+
verbose: "verbose",
|
|
31
|
+
"wallet-key-file": "wallet_key_file",
|
|
32
|
+
wallet_key_file: "wallet_key_file",
|
|
33
|
+
"wallet-address": "wallet_address",
|
|
34
|
+
wallet_address: "wallet_address",
|
|
35
|
+
x402: "x402",
|
|
36
|
+
"auth-token": "auth_token",
|
|
37
|
+
auth_token: "auth_token",
|
|
38
|
+
"auth-token-expires-at": "auth_token_expires_at",
|
|
39
|
+
auth_token_expires_at: "auth_token_expires_at",
|
|
40
|
+
"solana-wallet-key-file": "solana_wallet_key_file",
|
|
41
|
+
solana_wallet_key_file: "solana_wallet_key_file",
|
|
42
|
+
"solana-wallet-address": "solana_wallet_address",
|
|
43
|
+
solana_wallet_address: "solana_wallet_address",
|
|
44
|
+
"evm-gas-sponsored": "evm_gas_sponsored",
|
|
45
|
+
evm_gas_sponsored: "evm_gas_sponsored",
|
|
46
|
+
"evm-gas-policy-id": "evm_gas_policy_id",
|
|
47
|
+
evm_gas_policy_id: "evm_gas_policy_id",
|
|
48
|
+
"solana-fee-sponsored": "solana_fee_sponsored",
|
|
49
|
+
solana_fee_sponsored: "solana_fee_sponsored",
|
|
50
|
+
"solana-fee-policy-id": "solana_fee_policy_id",
|
|
51
|
+
solana_fee_policy_id: "solana_fee_policy_id",
|
|
52
|
+
"delegated-wallet": "delegated_wallet",
|
|
53
|
+
delegated_wallet: "delegated_wallet",
|
|
54
|
+
"active-signer": "active_signer",
|
|
55
|
+
active_signer: "active_signer",
|
|
56
|
+
"wallet-client-instance-name": "wallet_client_instance_name",
|
|
57
|
+
wallet_client_instance_name: "wallet_client_instance_name"
|
|
58
|
+
};
|
|
59
|
+
var SAFE_ID_RE = /^[A-Za-z0-9:_-]{1,128}$/;
|
|
60
|
+
var SAFE_NETWORK_RE = /^[A-Za-z0-9:_-]{1,128}$/;
|
|
61
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
62
|
+
var MAX_SECRET_LEN = 512;
|
|
63
|
+
var MAX_APP_NAME_LEN = 128;
|
|
64
|
+
var MAX_WALLET_CLIENT_INSTANCE_NAME_LEN = 64;
|
|
65
|
+
var CONTROL_CHAR_RE = /[\u0000-\u001f\u007f]/;
|
|
66
|
+
var WALLET_CLIENT_INSTANCE_ID_NAMESPACE = "alchemy-cli:wallet-client-instance-name:v1";
|
|
67
|
+
var safeTextSchema = (maxLen) => z.string().min(1).max(maxLen).refine((value) => !CONTROL_CHAR_RE.test(value));
|
|
68
|
+
var appConfigSchema = z.object({
|
|
69
|
+
id: z.string().regex(SAFE_ID_RE),
|
|
70
|
+
name: safeTextSchema(MAX_APP_NAME_LEN),
|
|
71
|
+
apiKey: safeTextSchema(MAX_SECRET_LEN),
|
|
72
|
+
webhookApiKey: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0)
|
|
73
|
+
}).strip();
|
|
74
|
+
var MAX_PATH_LEN = 4096;
|
|
75
|
+
var configSchema = z.object({
|
|
76
|
+
api_key: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
77
|
+
access_key: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
78
|
+
webhook_api_key: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
79
|
+
app: appConfigSchema.optional().catch(void 0),
|
|
80
|
+
network: z.string().regex(SAFE_NETWORK_RE).optional().catch(void 0),
|
|
81
|
+
verbose: z.boolean().optional().catch(void 0),
|
|
82
|
+
wallet_key_file: safeTextSchema(MAX_PATH_LEN).optional().catch(void 0),
|
|
83
|
+
wallet_address: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
84
|
+
x402: z.boolean().optional().catch(void 0),
|
|
85
|
+
auth_token: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
86
|
+
auth_token_expires_at: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
87
|
+
siwe_token: safeTextSchema(MAX_PATH_LEN).optional().catch(void 0),
|
|
88
|
+
siwe_token_expires_at: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
89
|
+
solana_wallet_key_file: safeTextSchema(MAX_PATH_LEN).optional().catch(void 0),
|
|
90
|
+
solana_wallet_address: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
91
|
+
evm_gas_sponsored: z.boolean().optional().catch(void 0),
|
|
92
|
+
evm_gas_policy_id: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
93
|
+
solana_fee_sponsored: z.boolean().optional().catch(void 0),
|
|
94
|
+
solana_fee_policy_id: safeTextSchema(MAX_SECRET_LEN).optional().catch(void 0),
|
|
95
|
+
delegated_wallet: z.boolean().optional().catch(void 0),
|
|
96
|
+
active_signer: z.enum(["session", "local"]).optional().catch(void 0),
|
|
97
|
+
wallet_client_instance_id: z.string().regex(UUID_RE).optional().catch(void 0),
|
|
98
|
+
wallet_client_instance_name: z.string().transform(normalizeWhitespace).pipe(safeTextSchema(MAX_WALLET_CLIENT_INSTANCE_NAME_LEN)).optional().catch(void 0)
|
|
99
|
+
}).strip();
|
|
100
|
+
function normalizeWhitespace(value) {
|
|
101
|
+
return value.trim().replace(/\s+/g, " ");
|
|
102
|
+
}
|
|
103
|
+
function sanitizeConfig(input) {
|
|
104
|
+
const parsed = configSchema.safeParse(input);
|
|
105
|
+
if (!parsed.success) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
return parsed.data;
|
|
109
|
+
}
|
|
110
|
+
function applyLegacyMigration(cfg) {
|
|
111
|
+
if (cfg.active_signer === void 0 && cfg.delegated_wallet === true) {
|
|
112
|
+
const { delegated_wallet: _, ...rest } = cfg;
|
|
113
|
+
return { ...rest, active_signer: "session" };
|
|
114
|
+
}
|
|
115
|
+
return cfg;
|
|
116
|
+
}
|
|
117
|
+
function getHome() {
|
|
118
|
+
return process.env.HOME || homedir();
|
|
119
|
+
}
|
|
120
|
+
function configPath() {
|
|
121
|
+
if (process.env.ALCHEMY_CONFIG) return process.env.ALCHEMY_CONFIG;
|
|
122
|
+
const configHome = process.env.XDG_CONFIG_HOME || join(getHome(), ".config");
|
|
123
|
+
return join(configHome, "alchemy", "config.json");
|
|
124
|
+
}
|
|
125
|
+
function configDir() {
|
|
126
|
+
return dirname(configPath());
|
|
127
|
+
}
|
|
128
|
+
function load() {
|
|
129
|
+
const p = configPath();
|
|
130
|
+
if (!existsSync(p)) return {};
|
|
131
|
+
try {
|
|
132
|
+
const data = readFileSync(p, "utf-8");
|
|
133
|
+
return applyLegacyMigration(sanitizeConfig(JSON.parse(data)));
|
|
134
|
+
} catch {
|
|
135
|
+
console.error(`warning: could not parse config file at ${p} \u2014 using defaults`);
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function save(cfg) {
|
|
140
|
+
const p = configPath();
|
|
141
|
+
const sanitized = sanitizeConfig(cfg);
|
|
142
|
+
delete sanitized.delegated_wallet;
|
|
143
|
+
mkdirSync(dirname(p), { recursive: true, mode: 493 });
|
|
144
|
+
writeFileSync(p, JSON.stringify(sanitized, null, 2) + "\n", {
|
|
145
|
+
mode: 384
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function normalizeWalletClientInstanceName(value) {
|
|
149
|
+
const normalized = normalizeWhitespace(value);
|
|
150
|
+
const parsed = safeTextSchema(MAX_WALLET_CLIENT_INSTANCE_NAME_LEN).safeParse(normalized);
|
|
151
|
+
return parsed.success ? parsed.data : void 0;
|
|
152
|
+
}
|
|
153
|
+
function deriveWalletClientInstanceIdFromName(name) {
|
|
154
|
+
const normalized = normalizeWalletClientInstanceName(name);
|
|
155
|
+
if (normalized === void 0) {
|
|
156
|
+
throw new Error("Invalid wallet client instance name.");
|
|
157
|
+
}
|
|
158
|
+
const digest = createHash("sha256").update(WALLET_CLIENT_INSTANCE_ID_NAMESPACE).update("\0").update(normalized.toLowerCase()).digest();
|
|
159
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
160
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
161
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
162
|
+
const hex = bytes.toString("hex");
|
|
163
|
+
return [
|
|
164
|
+
hex.slice(0, 8),
|
|
165
|
+
hex.slice(8, 12),
|
|
166
|
+
hex.slice(12, 16),
|
|
167
|
+
hex.slice(16, 20),
|
|
168
|
+
hex.slice(20, 32)
|
|
169
|
+
].join("-");
|
|
170
|
+
}
|
|
171
|
+
function getOrCreateWalletClientInstance(input = {}) {
|
|
172
|
+
const cfg = load();
|
|
173
|
+
const instanceName = input.instanceName === void 0 ? cfg.wallet_client_instance_name : normalizeWalletClientInstanceName(input.instanceName);
|
|
174
|
+
if (input.instanceName !== void 0 && instanceName === void 0) {
|
|
175
|
+
throw new Error("Invalid wallet client instance name.");
|
|
176
|
+
}
|
|
177
|
+
if (instanceName !== void 0) {
|
|
178
|
+
const walletClientInstanceId2 = deriveWalletClientInstanceIdFromName(instanceName);
|
|
179
|
+
save({
|
|
180
|
+
...cfg,
|
|
181
|
+
wallet_client_instance_id: walletClientInstanceId2,
|
|
182
|
+
wallet_client_instance_name: instanceName
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
id: walletClientInstanceId2,
|
|
186
|
+
name: instanceName
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (cfg.wallet_client_instance_id) {
|
|
190
|
+
return { id: cfg.wallet_client_instance_id };
|
|
191
|
+
}
|
|
192
|
+
const walletClientInstanceId = randomUUID();
|
|
193
|
+
save({
|
|
194
|
+
...cfg,
|
|
195
|
+
wallet_client_instance_id: walletClientInstanceId
|
|
196
|
+
});
|
|
197
|
+
return { id: walletClientInstanceId };
|
|
198
|
+
}
|
|
199
|
+
function get(cfg, key) {
|
|
200
|
+
if (key === "app") {
|
|
201
|
+
if (!cfg.app) return void 0;
|
|
202
|
+
return `${cfg.app.name} (${cfg.app.id})`;
|
|
203
|
+
}
|
|
204
|
+
const mapped = KEY_MAP[key];
|
|
205
|
+
if (!mapped) return void 0;
|
|
206
|
+
const value = cfg[mapped];
|
|
207
|
+
if (value === void 0) return void 0;
|
|
208
|
+
if (typeof value === "boolean") return String(value);
|
|
209
|
+
if (typeof value === "string") return value;
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
function validKeys() {
|
|
213
|
+
return [
|
|
214
|
+
"api-key",
|
|
215
|
+
"access-key",
|
|
216
|
+
"webhook-api-key",
|
|
217
|
+
"network",
|
|
218
|
+
"verbose",
|
|
219
|
+
"wallet-key-file",
|
|
220
|
+
"x402",
|
|
221
|
+
"evm-gas-sponsored",
|
|
222
|
+
"evm-gas-policy-id",
|
|
223
|
+
"solana-fee-sponsored",
|
|
224
|
+
"solana-fee-policy-id"
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
function toMap(cfg) {
|
|
228
|
+
const m = {};
|
|
229
|
+
if (cfg.api_key) m["api-key"] = maskIf(cfg.api_key);
|
|
230
|
+
if (cfg.access_key) m["access-key"] = maskIf(cfg.access_key);
|
|
231
|
+
if (cfg.webhook_api_key) m["webhook-api-key"] = maskIf(cfg.webhook_api_key);
|
|
232
|
+
if (cfg.app) m["app"] = `${cfg.app.name} (${cfg.app.id})`;
|
|
233
|
+
if (cfg.network) m["network"] = cfg.network;
|
|
234
|
+
if (cfg.verbose !== void 0) m["verbose"] = String(cfg.verbose);
|
|
235
|
+
if (cfg.wallet_key_file) m["wallet-key-file"] = cfg.wallet_key_file;
|
|
236
|
+
if (cfg.wallet_address) m["wallet-address"] = cfg.wallet_address;
|
|
237
|
+
if (cfg.x402 !== void 0) m["x402"] = String(cfg.x402);
|
|
238
|
+
if (cfg.auth_token) m["auth-token"] = maskIf(cfg.auth_token);
|
|
239
|
+
if (cfg.auth_token_expires_at) m["auth-token-expires-at"] = cfg.auth_token_expires_at;
|
|
240
|
+
if (cfg.solana_wallet_key_file) m["solana-wallet-key-file"] = cfg.solana_wallet_key_file;
|
|
241
|
+
if (cfg.solana_wallet_address) m["solana-wallet-address"] = cfg.solana_wallet_address;
|
|
242
|
+
if (cfg.evm_gas_sponsored !== void 0) m["evm-gas-sponsored"] = String(cfg.evm_gas_sponsored);
|
|
243
|
+
if (cfg.evm_gas_policy_id) m["evm-gas-policy-id"] = cfg.evm_gas_policy_id;
|
|
244
|
+
if (cfg.solana_fee_sponsored !== void 0) m["solana-fee-sponsored"] = String(cfg.solana_fee_sponsored);
|
|
245
|
+
if (cfg.solana_fee_policy_id) m["solana-fee-policy-id"] = cfg.solana_fee_policy_id;
|
|
246
|
+
if (cfg.active_signer !== void 0) m["active-signer"] = cfg.active_signer;
|
|
247
|
+
return m;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export {
|
|
251
|
+
maskIf,
|
|
252
|
+
KEY_MAP,
|
|
253
|
+
configPath,
|
|
254
|
+
configDir,
|
|
255
|
+
load,
|
|
256
|
+
save,
|
|
257
|
+
getOrCreateWalletClientInstance,
|
|
258
|
+
get,
|
|
259
|
+
validKeys,
|
|
260
|
+
toMap
|
|
261
|
+
};
|