lunel-cli 0.1.79 → 0.1.81
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/index.js +125 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createInterface } from "readline";
|
|
|
15
15
|
const DEFAULT_PROXY_URL = normalizeGatewayUrl(process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev");
|
|
16
16
|
const MANAGER_URL = normalizeGatewayUrl(process.env.LUNEL_MANAGER_URL || "https://manager.lunel.dev");
|
|
17
17
|
const CLI_ARGS = process.argv.slice(2);
|
|
18
|
+
const APPLE_REVIEW_CODE = "abcd";
|
|
18
19
|
import { createRequire } from "module";
|
|
19
20
|
const __require = createRequire(import.meta.url);
|
|
20
21
|
const VERSION = __require("../package.json").version;
|
|
@@ -210,7 +211,12 @@ function parseExtraPortsFromArgs(args) {
|
|
|
210
211
|
}
|
|
211
212
|
return Array.from(parsed).sort((a, b) => a - b);
|
|
212
213
|
}
|
|
214
|
+
function hasFlag(args, flag) {
|
|
215
|
+
return args.includes(flag);
|
|
216
|
+
}
|
|
213
217
|
const EXTRA_PORTS = parseExtraPortsFromArgs(CLI_ARGS);
|
|
218
|
+
const USE_APPLE_REVIEW_CODE = hasFlag(CLI_ARGS, "--abcd-code");
|
|
219
|
+
const FORCE_NEW_CODE = hasFlag(CLI_ARGS, "--new-code");
|
|
214
220
|
const SCAN_PORTS = Array.from(new Set([...DEV_PORTS, ...EXTRA_PORTS])).sort((a, b) => a - b);
|
|
215
221
|
function samePortSet(a, b) {
|
|
216
222
|
if (a.length !== b.length)
|
|
@@ -298,18 +304,31 @@ async function readCliConfig() {
|
|
|
298
304
|
return {
|
|
299
305
|
version: 1,
|
|
300
306
|
deviceId: typeof parsed.deviceId === "string" && parsed.deviceId ? parsed.deviceId : generatePersistentSecret(32),
|
|
307
|
+
sessions: Array.isArray(parsed.sessions)
|
|
308
|
+
? parsed.sessions.filter((entry) => (!!entry
|
|
309
|
+
&& typeof entry.rootDir === "string"
|
|
310
|
+
&& typeof entry.sessionPassword === "string"
|
|
311
|
+
&& typeof entry.savedAt === "number")).map((entry) => ({
|
|
312
|
+
rootDir: entry.rootDir,
|
|
313
|
+
sessionCode: typeof entry.sessionCode === "string" ? entry.sessionCode : null,
|
|
314
|
+
sessionPassword: entry.sessionPassword,
|
|
315
|
+
savedAt: entry.savedAt,
|
|
316
|
+
}))
|
|
317
|
+
: [],
|
|
301
318
|
};
|
|
302
319
|
}
|
|
303
320
|
catch {
|
|
304
321
|
return {
|
|
305
322
|
version: 1,
|
|
306
323
|
deviceId: generatePersistentSecret(32),
|
|
324
|
+
sessions: [],
|
|
307
325
|
};
|
|
308
326
|
}
|
|
309
327
|
}
|
|
310
328
|
async function writeCliConfig(config) {
|
|
311
329
|
await fs.mkdir(path.dirname(CLI_CONFIG_PATH), { recursive: true });
|
|
312
330
|
await fs.writeFile(CLI_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
331
|
+
cliConfigPromise = Promise.resolve(config);
|
|
313
332
|
}
|
|
314
333
|
let cliConfigPromise = null;
|
|
315
334
|
async function getCliConfig() {
|
|
@@ -318,6 +337,34 @@ async function getCliConfig() {
|
|
|
318
337
|
}
|
|
319
338
|
return await cliConfigPromise;
|
|
320
339
|
}
|
|
340
|
+
function getSavedSessionForRoot(config, rootDir) {
|
|
341
|
+
const sessions = Array.isArray(config.sessions) ? config.sessions : [];
|
|
342
|
+
return sessions.find((entry) => entry.rootDir === rootDir) || null;
|
|
343
|
+
}
|
|
344
|
+
async function saveSessionForRoot(sessionCode, sessionPassword) {
|
|
345
|
+
const config = await getCliConfig();
|
|
346
|
+
const sessions = Array.isArray(config.sessions) ? [...config.sessions] : [];
|
|
347
|
+
const nextEntry = {
|
|
348
|
+
rootDir: ROOT_DIR,
|
|
349
|
+
sessionCode,
|
|
350
|
+
sessionPassword,
|
|
351
|
+
savedAt: Date.now(),
|
|
352
|
+
};
|
|
353
|
+
const deduped = sessions.filter((entry) => entry.rootDir !== ROOT_DIR);
|
|
354
|
+
deduped.unshift(nextEntry);
|
|
355
|
+
await writeCliConfig({
|
|
356
|
+
...config,
|
|
357
|
+
sessions: deduped.slice(0, 100),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async function clearSavedSessionForRoot() {
|
|
361
|
+
const config = await getCliConfig();
|
|
362
|
+
const sessions = Array.isArray(config.sessions) ? config.sessions : [];
|
|
363
|
+
await writeCliConfig({
|
|
364
|
+
...config,
|
|
365
|
+
sessions: sessions.filter((entry) => entry.rootDir !== ROOT_DIR),
|
|
366
|
+
});
|
|
367
|
+
}
|
|
321
368
|
// ============================================================================
|
|
322
369
|
// File System Handlers
|
|
323
370
|
// ============================================================================
|
|
@@ -2591,7 +2638,20 @@ async function getAssignedProxyUrl(password) {
|
|
|
2591
2638
|
url.searchParams.set("password", password);
|
|
2592
2639
|
const response = await fetch(url);
|
|
2593
2640
|
if (!response.ok) {
|
|
2594
|
-
|
|
2641
|
+
let message = `Failed to get proxy from manager: ${response.status}`;
|
|
2642
|
+
try {
|
|
2643
|
+
const payload = await response.json();
|
|
2644
|
+
if (payload.error) {
|
|
2645
|
+
message = payload.error;
|
|
2646
|
+
}
|
|
2647
|
+
else if (payload.reason) {
|
|
2648
|
+
message = payload.reason;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
catch {
|
|
2652
|
+
// ignore parse failures and use the fallback message
|
|
2653
|
+
}
|
|
2654
|
+
throw new Error(message);
|
|
2595
2655
|
}
|
|
2596
2656
|
const payload = await response.json();
|
|
2597
2657
|
if (typeof payload.proxyUrl !== "string" || !payload.proxyUrl) {
|
|
@@ -2599,6 +2659,30 @@ async function getAssignedProxyUrl(password) {
|
|
|
2599
2659
|
}
|
|
2600
2660
|
return normalizeGatewayUrl(payload.proxyUrl);
|
|
2601
2661
|
}
|
|
2662
|
+
async function revokePassword(password, reason = "revoked by cli --new-code") {
|
|
2663
|
+
const response = await fetch(new URL("/v2/revoke", MANAGER_URL), {
|
|
2664
|
+
method: "POST",
|
|
2665
|
+
headers: { "Content-Type": "application/json" },
|
|
2666
|
+
body: JSON.stringify({ password, reason }),
|
|
2667
|
+
});
|
|
2668
|
+
if (response.ok || response.status === 404) {
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
let message = `Failed to revoke previous session: ${response.status}`;
|
|
2672
|
+
try {
|
|
2673
|
+
const payload = await response.json();
|
|
2674
|
+
if (payload.error) {
|
|
2675
|
+
message = payload.error;
|
|
2676
|
+
}
|
|
2677
|
+
else if (payload.reason) {
|
|
2678
|
+
message = payload.reason;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
catch {
|
|
2682
|
+
// ignore parse failures and use the fallback message
|
|
2683
|
+
}
|
|
2684
|
+
throw new Error(message);
|
|
2685
|
+
}
|
|
2602
2686
|
function displayQR(code) {
|
|
2603
2687
|
console.log("\n");
|
|
2604
2688
|
qrcode.generate(code, { small: true }, (qr) => {
|
|
@@ -2870,8 +2954,10 @@ async function main() {
|
|
|
2870
2954
|
if (EXTRA_PORTS.length > 0) {
|
|
2871
2955
|
console.log(`Extra ports enabled: ${EXTRA_PORTS.join(", ")}`);
|
|
2872
2956
|
}
|
|
2957
|
+
let usedSavedSession = false;
|
|
2873
2958
|
try {
|
|
2874
|
-
await getCliConfig();
|
|
2959
|
+
const cliConfig = await getCliConfig();
|
|
2960
|
+
const savedSession = getSavedSessionForRoot(cliConfig, ROOT_DIR);
|
|
2875
2961
|
console.log("Checking PTY runtime...");
|
|
2876
2962
|
await ensurePtyBinaryReady();
|
|
2877
2963
|
console.log("PTY runtime ready.\n");
|
|
@@ -2890,18 +2976,48 @@ async function main() {
|
|
|
2890
2976
|
checkDataChannelBackpressure();
|
|
2891
2977
|
}
|
|
2892
2978
|
});
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2979
|
+
let sessionCodeToUse = null;
|
|
2980
|
+
let sessionPasswordToUse;
|
|
2981
|
+
if (USE_APPLE_REVIEW_CODE) {
|
|
2982
|
+
console.log(`Using fixed review code: ${APPLE_REVIEW_CODE}`);
|
|
2983
|
+
const assembled = await assembleWithCode(APPLE_REVIEW_CODE);
|
|
2984
|
+
sessionCodeToUse = assembled.code;
|
|
2985
|
+
sessionPasswordToUse = assembled.password;
|
|
2986
|
+
await saveSessionForRoot(sessionCodeToUse, sessionPasswordToUse);
|
|
2987
|
+
}
|
|
2988
|
+
else if (!FORCE_NEW_CODE && savedSession) {
|
|
2989
|
+
console.log(`Using saved session for ${ROOT_DIR}`);
|
|
2990
|
+
sessionCodeToUse = savedSession.sessionCode;
|
|
2991
|
+
sessionPasswordToUse = savedSession.sessionPassword;
|
|
2992
|
+
usedSavedSession = true;
|
|
2993
|
+
}
|
|
2994
|
+
else {
|
|
2995
|
+
if (FORCE_NEW_CODE && savedSession?.sessionPassword) {
|
|
2996
|
+
await revokePassword(savedSession.sessionPassword);
|
|
2997
|
+
await clearSavedSessionForRoot();
|
|
2998
|
+
}
|
|
2999
|
+
const qr = await createQrCode();
|
|
3000
|
+
currentSessionCode = qr.code;
|
|
3001
|
+
displayQR(qr.code);
|
|
3002
|
+
const assembled = await assembleWithCode(qr.code);
|
|
3003
|
+
sessionCodeToUse = assembled.code;
|
|
3004
|
+
sessionPasswordToUse = assembled.password;
|
|
3005
|
+
await saveSessionForRoot(sessionCodeToUse, sessionPasswordToUse);
|
|
3006
|
+
}
|
|
2897
3007
|
resetReplayBuffer();
|
|
2898
|
-
currentSessionCode =
|
|
2899
|
-
currentSessionPassword =
|
|
2900
|
-
currentPrimaryGateway = await getAssignedProxyUrl(
|
|
3008
|
+
currentSessionCode = sessionCodeToUse;
|
|
3009
|
+
currentSessionPassword = sessionPasswordToUse;
|
|
3010
|
+
currentPrimaryGateway = await getAssignedProxyUrl(sessionPasswordToUse);
|
|
2901
3011
|
activeGatewayUrl = currentPrimaryGateway;
|
|
2902
3012
|
await connectWebSocket();
|
|
2903
3013
|
}
|
|
2904
3014
|
catch (error) {
|
|
3015
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3016
|
+
if (!USE_APPLE_REVIEW_CODE &&
|
|
3017
|
+
usedSavedSession &&
|
|
3018
|
+
/invalid|revoked|not found|expired|password invalid|password revoked/i.test(message)) {
|
|
3019
|
+
await clearSavedSessionForRoot().catch(() => { });
|
|
3020
|
+
}
|
|
2905
3021
|
if (error instanceof Error) {
|
|
2906
3022
|
console.error(`Error: ${error.message}`);
|
|
2907
3023
|
if (error.stack)
|