@ascorbic/pds 0.2.1 → 0.2.2
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/cli.js +163 -211
- package/dist/index.js +38 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -339,6 +339,63 @@ const secretCommand = defineCommand({
|
|
|
339
339
|
}
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/cli/utils/cli-helpers.ts
|
|
344
|
+
/**
|
|
345
|
+
* Shared CLI utilities for PDS commands
|
|
346
|
+
*/
|
|
347
|
+
/**
|
|
348
|
+
* Prompt for text input, exiting on cancel
|
|
349
|
+
*/
|
|
350
|
+
async function promptText(options) {
|
|
351
|
+
const result = await p.text(options);
|
|
352
|
+
if (p.isCancel(result)) {
|
|
353
|
+
p.cancel("Cancelled");
|
|
354
|
+
process.exit(0);
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Prompt for confirmation, exiting on cancel
|
|
360
|
+
*/
|
|
361
|
+
async function promptConfirm(options) {
|
|
362
|
+
const result = await p.confirm(options);
|
|
363
|
+
if (p.isCancel(result)) {
|
|
364
|
+
p.cancel("Cancelled");
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Prompt for selection, exiting on cancel
|
|
371
|
+
*/
|
|
372
|
+
async function promptSelect(options) {
|
|
373
|
+
const result = await p.select(options);
|
|
374
|
+
if (p.isCancel(result)) {
|
|
375
|
+
p.cancel("Cancelled");
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get target PDS URL based on mode
|
|
382
|
+
*/
|
|
383
|
+
function getTargetUrl(isDev, pdsHostname) {
|
|
384
|
+
if (isDev) return `http://localhost:${process.env.PORT ? parseInt(process.env.PORT) ?? "5173" : "5173"}`;
|
|
385
|
+
if (!pdsHostname) throw new Error("PDS_HOSTNAME not configured in wrangler.jsonc");
|
|
386
|
+
return `https://${pdsHostname}`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Extract domain from URL
|
|
390
|
+
*/
|
|
391
|
+
function getDomain(url) {
|
|
392
|
+
try {
|
|
393
|
+
return new URL(url).hostname;
|
|
394
|
+
} catch {
|
|
395
|
+
return url;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
342
399
|
//#endregion
|
|
343
400
|
//#region src/cli/utils/handle-resolver.ts
|
|
344
401
|
/**
|
|
@@ -456,6 +513,22 @@ var DidResolver = class {
|
|
|
456
513
|
function slugifyHandle(handle) {
|
|
457
514
|
return handle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + "-pds";
|
|
458
515
|
}
|
|
516
|
+
const defaultWorkerName = "my-pds";
|
|
517
|
+
/**
|
|
518
|
+
* Prompt for worker name with validation
|
|
519
|
+
*/
|
|
520
|
+
async function promptWorkerName(handle, currentWorkerName) {
|
|
521
|
+
const placeholder = currentWorkerName && currentWorkerName !== defaultWorkerName ? currentWorkerName : slugifyHandle(handle);
|
|
522
|
+
return promptText({
|
|
523
|
+
message: "Cloudflare Worker name:",
|
|
524
|
+
placeholder,
|
|
525
|
+
initialValue: placeholder,
|
|
526
|
+
validate: (v) => {
|
|
527
|
+
if (!v) return "Worker name is required";
|
|
528
|
+
if (!/^[a-z0-9-]+$/.test(v)) return "Worker name can only contain lowercase letters, numbers, and hyphens";
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
459
532
|
/**
|
|
460
533
|
* Run wrangler types to regenerate TypeScript types
|
|
461
534
|
*/
|
|
@@ -486,28 +559,24 @@ const initCommand = defineCommand({
|
|
|
486
559
|
},
|
|
487
560
|
args: { production: {
|
|
488
561
|
type: "boolean",
|
|
489
|
-
description: "Deploy secrets to Cloudflare
|
|
562
|
+
description: "Deploy secrets to Cloudflare?",
|
|
490
563
|
default: false
|
|
491
564
|
} },
|
|
492
565
|
async run({ args }) {
|
|
493
566
|
p.intro("🦋 PDS Setup");
|
|
494
567
|
const isProduction = args.production;
|
|
495
|
-
if (isProduction) p.log.info("Production mode: secrets will be deployed
|
|
496
|
-
|
|
568
|
+
if (isProduction) p.log.info("Production mode: secrets will be deployed to Cloudflare");
|
|
569
|
+
p.log.info("Let's set up your new home in the Atmosphere!");
|
|
497
570
|
const wranglerVars = getVars();
|
|
498
571
|
const devVars = readDevVars();
|
|
499
572
|
const currentVars = {
|
|
500
573
|
...devVars,
|
|
501
574
|
...wranglerVars
|
|
502
575
|
};
|
|
503
|
-
const isMigrating = await
|
|
504
|
-
message: "Are you migrating an existing Bluesky account?
|
|
576
|
+
const isMigrating = await promptConfirm({
|
|
577
|
+
message: "Are you migrating an existing Bluesky/ATProto account?",
|
|
505
578
|
initialValue: false
|
|
506
579
|
});
|
|
507
|
-
if (p.isCancel(isMigrating)) {
|
|
508
|
-
p.cancel("Setup cancelled");
|
|
509
|
-
process.exit(0);
|
|
510
|
-
}
|
|
511
580
|
let did;
|
|
512
581
|
let handle;
|
|
513
582
|
let hostname;
|
|
@@ -516,28 +585,24 @@ const initCommand = defineCommand({
|
|
|
516
585
|
const currentWorkerName = getWorkerName();
|
|
517
586
|
if (isMigrating) {
|
|
518
587
|
p.log.info("Time to pack your bags! 🧳");
|
|
519
|
-
p.log.info("Your account will be inactive until you'
|
|
588
|
+
p.log.info("Your new account will be inactive until you're ready to go live.");
|
|
520
589
|
let hostedDomains = [
|
|
521
590
|
".bsky.social",
|
|
522
591
|
".bsky.network",
|
|
523
592
|
".bsky.team"
|
|
524
593
|
];
|
|
525
|
-
const isHostedHandle = (h) => hostedDomains.some((domain) => h
|
|
594
|
+
const isHostedHandle = (h) => hostedDomains.some((domain) => h?.endsWith(domain));
|
|
526
595
|
let resolvedDid = null;
|
|
527
596
|
let existingHandle = null;
|
|
528
597
|
let attempts = 0;
|
|
529
598
|
const MAX_ATTEMPTS = 3;
|
|
530
599
|
while (!resolvedDid && attempts < MAX_ATTEMPTS) {
|
|
531
600
|
attempts++;
|
|
532
|
-
const currentHandle = await
|
|
601
|
+
const currentHandle = await promptText({
|
|
533
602
|
message: "Your current Bluesky/ATProto handle:",
|
|
534
603
|
placeholder: "example.bsky.social",
|
|
535
604
|
validate: (v) => !v ? "Handle is required" : void 0
|
|
536
605
|
});
|
|
537
|
-
if (p.isCancel(currentHandle)) {
|
|
538
|
-
p.cancel("Cancelled");
|
|
539
|
-
process.exit(0);
|
|
540
|
-
}
|
|
541
606
|
existingHandle = currentHandle;
|
|
542
607
|
const spinner$1 = p.spinner();
|
|
543
608
|
spinner$1.start("Finding you in the Atmosphere...");
|
|
@@ -545,7 +610,7 @@ const initCommand = defineCommand({
|
|
|
545
610
|
if (!resolvedDid) {
|
|
546
611
|
spinner$1.stop("Not found");
|
|
547
612
|
p.log.error(`Failed to resolve handle "${currentHandle}"`);
|
|
548
|
-
|
|
613
|
+
if (await promptSelect({
|
|
549
614
|
message: "What would you like to do?",
|
|
550
615
|
options: [{
|
|
551
616
|
value: "retry",
|
|
@@ -554,26 +619,14 @@ const initCommand = defineCommand({
|
|
|
554
619
|
value: "manual",
|
|
555
620
|
label: "Enter DID manually"
|
|
556
621
|
}]
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const manualDid = await p.text({
|
|
564
|
-
message: "Enter your DID:",
|
|
565
|
-
placeholder: "did:plc:...",
|
|
566
|
-
validate: (v) => {
|
|
567
|
-
if (!v) return "DID is required";
|
|
568
|
-
if (!v.startsWith("did:")) return "DID must start with did:";
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
if (p.isCancel(manualDid)) {
|
|
572
|
-
p.cancel("Cancelled");
|
|
573
|
-
process.exit(0);
|
|
622
|
+
}) === "manual") resolvedDid = await promptText({
|
|
623
|
+
message: "Enter your DID:",
|
|
624
|
+
placeholder: "did:plc:...",
|
|
625
|
+
validate: (v) => {
|
|
626
|
+
if (!v) return "DID is required";
|
|
627
|
+
if (!v.startsWith("did:")) return "DID must start with did:";
|
|
574
628
|
}
|
|
575
|
-
|
|
576
|
-
}
|
|
629
|
+
});
|
|
577
630
|
} else {
|
|
578
631
|
try {
|
|
579
632
|
const pdsService = (await new DidResolver().resolve(resolvedDid))?.service?.find((s) => s.type === "AtprotoPersonalDataServer" || s.id === "#atproto_pds");
|
|
@@ -587,7 +640,7 @@ const initCommand = defineCommand({
|
|
|
587
640
|
} catch {}
|
|
588
641
|
spinner$1.stop(`Found you! ${resolvedDid}`);
|
|
589
642
|
if (isHostedHandle(existingHandle)) {
|
|
590
|
-
const theirDomain = hostedDomains.find((d) => existingHandle
|
|
643
|
+
const theirDomain = hostedDomains.find((d) => existingHandle?.endsWith(d));
|
|
591
644
|
const domainExample = theirDomain ? `*${theirDomain}` : "*.bsky.social";
|
|
592
645
|
p.log.warn(`You'll need a custom domain for your new handle (not ${domainExample}). You can set this up after transferring your data.`);
|
|
593
646
|
}
|
|
@@ -604,95 +657,48 @@ const initCommand = defineCommand({
|
|
|
604
657
|
}
|
|
605
658
|
}
|
|
606
659
|
did = resolvedDid;
|
|
607
|
-
|
|
608
|
-
handle = await p.text({
|
|
660
|
+
handle = await promptText({
|
|
609
661
|
message: "New account handle (must be a domain you control):",
|
|
610
662
|
placeholder: "example.com",
|
|
611
|
-
initialValue:
|
|
663
|
+
initialValue: existingHandle && !isHostedHandle(existingHandle) ? existingHandle : currentVars.HANDLE || "",
|
|
612
664
|
validate: (v) => {
|
|
613
665
|
if (!v) return "Handle is required";
|
|
614
666
|
if (isHostedHandle(v)) return "You need a custom domain - hosted handles like *.bsky.social won't work";
|
|
615
667
|
}
|
|
616
668
|
});
|
|
617
|
-
|
|
618
|
-
p.cancel("Cancelled");
|
|
619
|
-
process.exit(0);
|
|
620
|
-
}
|
|
621
|
-
hostname = await p.text({
|
|
669
|
+
hostname = await promptText({
|
|
622
670
|
message: "Domain where you'll deploy your PDS:",
|
|
623
671
|
placeholder: handle,
|
|
624
672
|
initialValue: currentVars.PDS_HOSTNAME || handle,
|
|
625
673
|
validate: (v) => !v ? "Hostname is required" : void 0
|
|
626
674
|
});
|
|
627
|
-
|
|
628
|
-
p.cancel("Cancelled");
|
|
629
|
-
process.exit(0);
|
|
630
|
-
}
|
|
631
|
-
const defaultWorkerName = currentWorkerName || slugifyHandle(handle);
|
|
632
|
-
workerName = await p.text({
|
|
633
|
-
message: "Cloudflare Worker name:",
|
|
634
|
-
placeholder: defaultWorkerName,
|
|
635
|
-
initialValue: defaultWorkerName,
|
|
636
|
-
validate: (v) => {
|
|
637
|
-
if (!v) return "Worker name is required";
|
|
638
|
-
if (!/^[a-z0-9-]+$/.test(v)) return "Worker name can only contain lowercase letters, numbers, and hyphens";
|
|
639
|
-
}
|
|
640
|
-
});
|
|
641
|
-
if (p.isCancel(workerName)) {
|
|
642
|
-
p.cancel("Cancelled");
|
|
643
|
-
process.exit(0);
|
|
644
|
-
}
|
|
675
|
+
workerName = await promptWorkerName(handle, currentWorkerName);
|
|
645
676
|
initialActive = "false";
|
|
646
677
|
} else {
|
|
647
678
|
p.log.info("A fresh start in the Atmosphere! ✨");
|
|
648
|
-
hostname = await
|
|
679
|
+
hostname = await promptText({
|
|
649
680
|
message: "Domain where you'll deploy your PDS:",
|
|
650
681
|
placeholder: "pds.example.com",
|
|
651
682
|
initialValue: currentVars.PDS_HOSTNAME || "",
|
|
652
683
|
validate: (v) => !v ? "Hostname is required" : void 0
|
|
653
684
|
});
|
|
654
|
-
|
|
655
|
-
p.cancel("Cancelled");
|
|
656
|
-
process.exit(0);
|
|
657
|
-
}
|
|
658
|
-
handle = await p.text({
|
|
685
|
+
handle = await promptText({
|
|
659
686
|
message: "Account handle:",
|
|
660
687
|
placeholder: hostname,
|
|
661
688
|
initialValue: currentVars.HANDLE || hostname,
|
|
662
689
|
validate: (v) => !v ? "Handle is required" : void 0
|
|
663
690
|
});
|
|
664
|
-
if (p.isCancel(handle)) {
|
|
665
|
-
p.cancel("Cancelled");
|
|
666
|
-
process.exit(0);
|
|
667
|
-
}
|
|
668
691
|
const didDefault = "did:web:" + hostname;
|
|
669
|
-
did = await
|
|
692
|
+
did = await promptText({
|
|
670
693
|
message: "Account DID:",
|
|
671
694
|
placeholder: didDefault,
|
|
672
695
|
initialValue: currentVars.DID || didDefault,
|
|
673
696
|
validate: (v) => {
|
|
674
697
|
if (!v) return "DID is required";
|
|
675
|
-
if (!v.startsWith("did:")) return "DID must start with did:";
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
if (p.isCancel(did)) {
|
|
679
|
-
p.cancel("Cancelled");
|
|
680
|
-
process.exit(0);
|
|
681
|
-
}
|
|
682
|
-
const defaultWorkerName = currentWorkerName || slugifyHandle(handle);
|
|
683
|
-
workerName = await p.text({
|
|
684
|
-
message: "Cloudflare Worker name:",
|
|
685
|
-
placeholder: defaultWorkerName,
|
|
686
|
-
initialValue: defaultWorkerName,
|
|
687
|
-
validate: (v) => {
|
|
688
|
-
if (!v) return "Worker name is required";
|
|
689
|
-
if (!/^[a-z0-9-]+$/.test(v)) return "Worker name can only contain lowercase letters, numbers, and hyphens";
|
|
698
|
+
if (!v.startsWith("did:")) return "DID must start with 'did:'";
|
|
690
699
|
}
|
|
691
700
|
});
|
|
692
|
-
|
|
693
|
-
p.cancel("Cancelled");
|
|
694
|
-
process.exit(0);
|
|
695
|
-
}
|
|
701
|
+
workerName = await promptWorkerName(handle, currentWorkerName);
|
|
696
702
|
initialActive = "true";
|
|
697
703
|
if (handle === hostname) p.note([
|
|
698
704
|
"Your handle matches your PDS hostname, so your PDS will",
|
|
@@ -719,55 +725,32 @@ const initCommand = defineCommand({
|
|
|
719
725
|
].join("\n"), "Identity Setup 🪪");
|
|
720
726
|
}
|
|
721
727
|
const spinner = p.spinner();
|
|
722
|
-
|
|
723
|
-
let signingKey;
|
|
724
|
-
let signingKeyPublic;
|
|
725
|
-
let jwtSecret;
|
|
726
|
-
let passwordHash;
|
|
727
|
-
if (isProduction) {
|
|
728
|
-
authToken = await getOrGenerateSecret("AUTH_TOKEN", devVars, async () => {
|
|
729
|
-
spinner.start("Generating auth token...");
|
|
730
|
-
const token = generateAuthToken();
|
|
731
|
-
spinner.stop("Auth token generated");
|
|
732
|
-
return token;
|
|
733
|
-
});
|
|
734
|
-
signingKey = await getOrGenerateSecret("SIGNING_KEY", devVars, async () => {
|
|
735
|
-
spinner.start("Generating signing keypair...");
|
|
736
|
-
const { privateKey } = await generateSigningKeypair();
|
|
737
|
-
spinner.stop("Signing keypair generated");
|
|
738
|
-
return privateKey;
|
|
739
|
-
});
|
|
740
|
-
signingKeyPublic = await derivePublicKey(signingKey);
|
|
741
|
-
jwtSecret = await getOrGenerateSecret("JWT_SECRET", devVars, async () => {
|
|
742
|
-
spinner.start("Generating JWT secret...");
|
|
743
|
-
const secret = generateJwtSecret();
|
|
744
|
-
spinner.stop("JWT secret generated");
|
|
745
|
-
return secret;
|
|
746
|
-
});
|
|
747
|
-
passwordHash = await getOrGenerateSecret("PASSWORD_HASH", devVars, async () => {
|
|
748
|
-
const password = await promptPassword(handle);
|
|
749
|
-
spinner.start("Hashing password...");
|
|
750
|
-
const hash = await hashPassword(password);
|
|
751
|
-
spinner.stop("Password hashed");
|
|
752
|
-
return hash;
|
|
753
|
-
});
|
|
754
|
-
} else {
|
|
755
|
-
const password = await promptPassword(handle);
|
|
756
|
-
spinner.start("Hashing password...");
|
|
757
|
-
passwordHash = await hashPassword(password);
|
|
758
|
-
spinner.stop("Password hashed");
|
|
759
|
-
spinner.start("Generating JWT secret...");
|
|
760
|
-
jwtSecret = generateJwtSecret();
|
|
761
|
-
spinner.stop("JWT secret generated");
|
|
728
|
+
const authToken = await getOrGenerateSecret("AUTH_TOKEN", devVars, async () => {
|
|
762
729
|
spinner.start("Generating auth token...");
|
|
763
|
-
|
|
730
|
+
const token = generateAuthToken();
|
|
764
731
|
spinner.stop("Auth token generated");
|
|
732
|
+
return token;
|
|
733
|
+
});
|
|
734
|
+
const signingKey = await getOrGenerateSecret("SIGNING_KEY", devVars, async () => {
|
|
765
735
|
spinner.start("Generating signing keypair...");
|
|
766
|
-
const
|
|
767
|
-
signingKey = keypair.privateKey;
|
|
768
|
-
signingKeyPublic = keypair.publicKey;
|
|
736
|
+
const { privateKey } = await generateSigningKeypair();
|
|
769
737
|
spinner.stop("Signing keypair generated");
|
|
770
|
-
|
|
738
|
+
return privateKey;
|
|
739
|
+
});
|
|
740
|
+
const signingKeyPublic = await derivePublicKey(signingKey);
|
|
741
|
+
const jwtSecret = await getOrGenerateSecret("JWT_SECRET", devVars, async () => {
|
|
742
|
+
spinner.start("Generating JWT secret...");
|
|
743
|
+
const secret = generateJwtSecret();
|
|
744
|
+
spinner.stop("JWT secret generated");
|
|
745
|
+
return secret;
|
|
746
|
+
});
|
|
747
|
+
const passwordHash = await getOrGenerateSecret("PASSWORD_HASH", devVars, async () => {
|
|
748
|
+
const password = await promptPassword(handle);
|
|
749
|
+
spinner.start("Hashing password...");
|
|
750
|
+
const hash = await hashPassword(password);
|
|
751
|
+
spinner.stop("Password hashed");
|
|
752
|
+
return hash;
|
|
753
|
+
});
|
|
771
754
|
spinner.start("Updating wrangler.jsonc...");
|
|
772
755
|
setWorkerName(workerName);
|
|
773
756
|
setVars({
|
|
@@ -844,15 +827,10 @@ const initCommand = defineCommand({
|
|
|
844
827
|
*/
|
|
845
828
|
async function getOrGenerateSecret(name, devVars, generate) {
|
|
846
829
|
if (devVars[name]) {
|
|
847
|
-
|
|
830
|
+
if (await p.confirm({
|
|
848
831
|
message: `Use ${name} from .dev.vars?`,
|
|
849
832
|
initialValue: true
|
|
850
|
-
});
|
|
851
|
-
if (p.isCancel(useExisting)) {
|
|
852
|
-
p.cancel("Cancelled");
|
|
853
|
-
process.exit(0);
|
|
854
|
-
}
|
|
855
|
-
if (useExisting) return devVars[name];
|
|
833
|
+
}) === true) return devVars[name];
|
|
856
834
|
}
|
|
857
835
|
return generate();
|
|
858
836
|
}
|
|
@@ -1063,31 +1041,6 @@ var PDSClient = class {
|
|
|
1063
1041
|
}
|
|
1064
1042
|
};
|
|
1065
1043
|
|
|
1066
|
-
//#endregion
|
|
1067
|
-
//#region src/cli/utils/cli-helpers.ts
|
|
1068
|
-
/**
|
|
1069
|
-
* Shared CLI utilities for PDS commands
|
|
1070
|
-
*/
|
|
1071
|
-
/**
|
|
1072
|
-
* Get target PDS URL based on mode
|
|
1073
|
-
*/
|
|
1074
|
-
function getTargetUrl(isDev, pdsHostname) {
|
|
1075
|
-
const LOCAL_PDS_URL = "http://localhost:5173";
|
|
1076
|
-
if (isDev) return LOCAL_PDS_URL;
|
|
1077
|
-
if (!pdsHostname) throw new Error("PDS_HOSTNAME not configured in wrangler.jsonc");
|
|
1078
|
-
return `https://${pdsHostname}`;
|
|
1079
|
-
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Extract domain from URL
|
|
1082
|
-
*/
|
|
1083
|
-
function getDomain(url) {
|
|
1084
|
-
try {
|
|
1085
|
-
return new URL(url).hostname;
|
|
1086
|
-
} catch {
|
|
1087
|
-
return url;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
1044
|
//#endregion
|
|
1092
1045
|
//#region src/cli/commands/migrate.ts
|
|
1093
1046
|
function detectPackageManager() {
|
|
@@ -1098,11 +1051,10 @@ function detectPackageManager() {
|
|
|
1098
1051
|
return "npm";
|
|
1099
1052
|
}
|
|
1100
1053
|
const brightNote$1 = (lines) => lines.map((l) => `\x1b[0m${l}`).join("\n");
|
|
1101
|
-
const bold$1 = (text) => pc.bold(text);
|
|
1102
1054
|
/**
|
|
1103
1055
|
* Format number with commas
|
|
1104
1056
|
*/
|
|
1105
|
-
function
|
|
1057
|
+
function num(n) {
|
|
1106
1058
|
return n.toLocaleString();
|
|
1107
1059
|
}
|
|
1108
1060
|
/**
|
|
@@ -1131,7 +1083,8 @@ const migrateCommand = defineCommand({
|
|
|
1131
1083
|
}
|
|
1132
1084
|
},
|
|
1133
1085
|
async run({ args }) {
|
|
1134
|
-
const
|
|
1086
|
+
const packageManager = detectPackageManager();
|
|
1087
|
+
const pm = packageManager === "npm" ? "npm run" : packageManager;
|
|
1135
1088
|
const isDev = args.dev;
|
|
1136
1089
|
const vars = getVars();
|
|
1137
1090
|
let targetUrl;
|
|
@@ -1198,6 +1151,7 @@ const migrateCommand = defineCommand({
|
|
|
1198
1151
|
const sourceDomain = getDomain(sourcePdsUrl);
|
|
1199
1152
|
spinner.stop(`Found your account at ${sourceDomain}`);
|
|
1200
1153
|
spinner.start("Checking account status...");
|
|
1154
|
+
const pdsDisplayName = sourceDomain.endsWith(".bsky.network") ? "bsky.social" : sourceDomain;
|
|
1201
1155
|
let status;
|
|
1202
1156
|
try {
|
|
1203
1157
|
status = await targetClient.getAccountStatus();
|
|
@@ -1212,7 +1166,7 @@ const migrateCommand = defineCommand({
|
|
|
1212
1166
|
if (status.active) {
|
|
1213
1167
|
p.log.error("Cannot reset: account is active");
|
|
1214
1168
|
p.log.info("The --clean flag only works on deactivated accounts.");
|
|
1215
|
-
p.log.info("Your account is already live
|
|
1169
|
+
p.log.info("Your account is already live");
|
|
1216
1170
|
p.log.info("");
|
|
1217
1171
|
p.log.info("If you need to re-import, first deactivate:");
|
|
1218
1172
|
p.log.info(" pnpm pds deactivate");
|
|
@@ -1220,13 +1174,13 @@ const migrateCommand = defineCommand({
|
|
|
1220
1174
|
process.exit(1);
|
|
1221
1175
|
}
|
|
1222
1176
|
p.note(brightNote$1([
|
|
1223
|
-
bold
|
|
1177
|
+
pc.bold("This will permanently delete from your new PDS:"),
|
|
1224
1178
|
"",
|
|
1225
|
-
` • ${
|
|
1226
|
-
` • ${
|
|
1179
|
+
` • ${num(status.repoBlocks)} repository blocks`,
|
|
1180
|
+
` • ${num(status.importedBlobs)} imported images`,
|
|
1227
1181
|
" • All blob tracking data",
|
|
1228
1182
|
"",
|
|
1229
|
-
bold
|
|
1183
|
+
pc.bold(`Your data on ${pdsDisplayName} is NOT affected.`),
|
|
1230
1184
|
"You'll need to re-import everything."
|
|
1231
1185
|
]), "⚠️ Reset Migration Data");
|
|
1232
1186
|
const confirmReset = await p.confirm({
|
|
@@ -1240,7 +1194,7 @@ const migrateCommand = defineCommand({
|
|
|
1240
1194
|
spinner.start("Resetting migration state...");
|
|
1241
1195
|
try {
|
|
1242
1196
|
const result = await targetClient.resetMigration();
|
|
1243
|
-
spinner.stop(`Deleted ${
|
|
1197
|
+
spinner.stop(`Deleted ${num(result.blocksDeleted)} blocks, ${num(result.blobsCleared)} blobs`);
|
|
1244
1198
|
} catch (err) {
|
|
1245
1199
|
spinner.stop("Reset failed");
|
|
1246
1200
|
p.log.error(err instanceof Error ? err.message : "Could not reset migration");
|
|
@@ -1251,12 +1205,12 @@ const migrateCommand = defineCommand({
|
|
|
1251
1205
|
status = await targetClient.getAccountStatus();
|
|
1252
1206
|
}
|
|
1253
1207
|
if (status.active) {
|
|
1254
|
-
p.log.warn(
|
|
1208
|
+
p.log.warn(`Your account is already active at ${targetDomain}!`);
|
|
1255
1209
|
p.log.info("No migration needed - your PDS is live.");
|
|
1256
|
-
p.outro("All good!
|
|
1210
|
+
p.outro("All good!");
|
|
1257
1211
|
return;
|
|
1258
1212
|
}
|
|
1259
|
-
spinner.start(`Fetching your account details from ${
|
|
1213
|
+
spinner.start(`Fetching your account details from ${pdsDisplayName}...`);
|
|
1260
1214
|
const sourceClient = new PDSClient(sourcePdsUrl);
|
|
1261
1215
|
try {
|
|
1262
1216
|
await sourceClient.describeRepo(did);
|
|
@@ -1277,10 +1231,10 @@ const migrateCommand = defineCommand({
|
|
|
1277
1231
|
`@${handle} (${did.slice(0, 20)}...)`,
|
|
1278
1232
|
"",
|
|
1279
1233
|
"✓ Repository imported",
|
|
1280
|
-
`◐
|
|
1234
|
+
`◐ Media: ${num(status.importedBlobs)}/${num(status.expectedBlobs)} images and videos transferred`
|
|
1281
1235
|
].join("\n"), "Migration Progress");
|
|
1282
1236
|
const continueTransfer = await p.confirm({
|
|
1283
|
-
message: "Continue transferring images?",
|
|
1237
|
+
message: "Continue transferring images and video?",
|
|
1284
1238
|
initialValue: true
|
|
1285
1239
|
});
|
|
1286
1240
|
if (p.isCancel(continueTransfer) || !continueTransfer) {
|
|
@@ -1289,14 +1243,14 @@ const migrateCommand = defineCommand({
|
|
|
1289
1243
|
}
|
|
1290
1244
|
} else if (needsRepoImport) {
|
|
1291
1245
|
p.log.info("Time to pack your bags!");
|
|
1292
|
-
p.log.info("Let's
|
|
1246
|
+
p.log.info("Let's clone your account to its new home in the Atmosphere.");
|
|
1293
1247
|
const statsLines = profileStats ? [
|
|
1294
|
-
` 📝 ${
|
|
1295
|
-
` 👥 ${
|
|
1296
|
-
` ...plus all your images, likes
|
|
1297
|
-
] : [` 📝 Posts, follows, images, likes
|
|
1248
|
+
` 📝 ${num(profileStats.postsCount)} posts`,
|
|
1249
|
+
` 👥 ${num(profileStats.followsCount)} follows`,
|
|
1250
|
+
` ...plus all your images, likes and preferences`
|
|
1251
|
+
] : [` 📝 Posts, follows, images, likes and preferences`];
|
|
1298
1252
|
p.note(brightNote$1([
|
|
1299
|
-
bold
|
|
1253
|
+
pc.bold(`@${handle}`) + ` (${did.slice(0, 20)}...)`,
|
|
1300
1254
|
"",
|
|
1301
1255
|
`Currently at: ${sourceDomain}`,
|
|
1302
1256
|
`Moving to: ${targetDomain}`,
|
|
@@ -1304,9 +1258,9 @@ const migrateCommand = defineCommand({
|
|
|
1304
1258
|
"What you're bringing:",
|
|
1305
1259
|
...statsLines
|
|
1306
1260
|
]), "Your Bluesky Account 🦋");
|
|
1307
|
-
p.log.info(
|
|
1261
|
+
p.log.info(`This will copy your data - nothing is changed or deleted on your current PDS.`);
|
|
1308
1262
|
const proceed = await p.confirm({
|
|
1309
|
-
message: "Ready to start
|
|
1263
|
+
message: "Ready to start moving?",
|
|
1310
1264
|
initialValue: true
|
|
1311
1265
|
});
|
|
1312
1266
|
if (p.isCancel(proceed) || !proceed) {
|
|
@@ -1314,19 +1268,17 @@ const migrateCommand = defineCommand({
|
|
|
1314
1268
|
process.exit(0);
|
|
1315
1269
|
}
|
|
1316
1270
|
} else {
|
|
1317
|
-
p.log.success("
|
|
1318
|
-
showNextSteps(pm,
|
|
1271
|
+
p.log.success("Everything looks good!");
|
|
1272
|
+
showNextSteps(pm, pdsDisplayName);
|
|
1319
1273
|
p.outro("Welcome to your new home in the Atmosphere! 🦋");
|
|
1320
1274
|
return;
|
|
1321
1275
|
}
|
|
1322
|
-
const
|
|
1323
|
-
const passwordPrompt = isBlueskyPds ? "Your current Bluesky password:" : `Your ${sourceDomain} password:`;
|
|
1324
|
-
const password = await p.password({ message: passwordPrompt });
|
|
1276
|
+
const password = await p.password({ message: `Your password for ${pdsDisplayName}:` });
|
|
1325
1277
|
if (p.isCancel(password)) {
|
|
1326
1278
|
p.cancel("Migration cancelled.");
|
|
1327
1279
|
process.exit(0);
|
|
1328
1280
|
}
|
|
1329
|
-
spinner.start(`Logging in to ${
|
|
1281
|
+
spinner.start(`Logging in to ${pdsDisplayName}...`);
|
|
1330
1282
|
try {
|
|
1331
1283
|
const session = await sourceClient.createSession(did, password);
|
|
1332
1284
|
sourceClient.setAuthToken(session.accessJwt);
|
|
@@ -1339,7 +1291,7 @@ const migrateCommand = defineCommand({
|
|
|
1339
1291
|
process.exit(1);
|
|
1340
1292
|
}
|
|
1341
1293
|
if (needsRepoImport) {
|
|
1342
|
-
spinner.start(
|
|
1294
|
+
spinner.start(`Exporting your repository from ${pdsDisplayName}...`);
|
|
1343
1295
|
let carBytes;
|
|
1344
1296
|
try {
|
|
1345
1297
|
carBytes = await sourceClient.getRepo(did);
|
|
@@ -1350,7 +1302,7 @@ const migrateCommand = defineCommand({
|
|
|
1350
1302
|
p.outro("Migration cancelled.");
|
|
1351
1303
|
process.exit(1);
|
|
1352
1304
|
}
|
|
1353
|
-
spinner.start(`
|
|
1305
|
+
spinner.start(`Importing to ${targetDomain}...`);
|
|
1354
1306
|
try {
|
|
1355
1307
|
await targetClient.importRepo(carBytes);
|
|
1356
1308
|
spinner.stop("Repository imported");
|
|
@@ -1391,7 +1343,7 @@ const migrateCommand = defineCommand({
|
|
|
1391
1343
|
totalBlobs += page.blobs.length;
|
|
1392
1344
|
countCursor = page.cursor;
|
|
1393
1345
|
} while (countCursor);
|
|
1394
|
-
spinner.message(`Transferring
|
|
1346
|
+
spinner.message(`Transferring media ${progressBar(0, totalBlobs)}`);
|
|
1395
1347
|
do {
|
|
1396
1348
|
const page = await targetClient.listMissingBlobs(100, cursor);
|
|
1397
1349
|
cursor = page.cursor;
|
|
@@ -1399,40 +1351,40 @@ const migrateCommand = defineCommand({
|
|
|
1399
1351
|
const { bytes, mimeType } = await sourceClient.getBlob(did, blob.cid);
|
|
1400
1352
|
await targetClient.uploadBlob(bytes, mimeType);
|
|
1401
1353
|
synced++;
|
|
1402
|
-
spinner.message(`Transferring
|
|
1354
|
+
spinner.message(`Transferring media ${progressBar(synced, totalBlobs)}`);
|
|
1403
1355
|
} catch (err) {
|
|
1404
1356
|
synced++;
|
|
1405
1357
|
failedBlobs.push(blob.cid);
|
|
1406
|
-
spinner.message(`Transferring
|
|
1358
|
+
spinner.message(`Transferring media ${progressBar(synced, totalBlobs)}`);
|
|
1407
1359
|
}
|
|
1408
1360
|
} while (cursor);
|
|
1409
1361
|
if (failedBlobs.length > 0) {
|
|
1410
|
-
spinner.stop(`Transferred ${
|
|
1362
|
+
spinner.stop(`Transferred ${num(synced - failedBlobs.length)} images and videos (${failedBlobs.length} failed)`);
|
|
1411
1363
|
p.log.warn(`Run 'pds migrate' again to retry failed transfers.`);
|
|
1412
|
-
} else spinner.stop(`Transferred ${
|
|
1364
|
+
} else spinner.stop(`Transferred ${num(synced)} images and videos`);
|
|
1413
1365
|
}
|
|
1414
1366
|
spinner.start("Verifying migration...");
|
|
1415
1367
|
const finalStatus = await targetClient.getAccountStatus();
|
|
1416
1368
|
spinner.stop("Verification complete");
|
|
1417
|
-
if (finalStatus.importedBlobs >= finalStatus.expectedBlobs) p.log.success("All packed and moved!
|
|
1369
|
+
if (finalStatus.importedBlobs >= finalStatus.expectedBlobs) p.log.success("All packed and moved!");
|
|
1418
1370
|
else {
|
|
1419
1371
|
p.log.warn(`Migration partially complete. ${finalStatus.expectedBlobs - finalStatus.importedBlobs} images remaining.`);
|
|
1420
1372
|
p.log.info("Run 'pds migrate' again to continue.");
|
|
1421
1373
|
}
|
|
1422
|
-
showNextSteps(pm,
|
|
1374
|
+
showNextSteps(pm, pdsDisplayName);
|
|
1423
1375
|
p.outro("Welcome to your new home in the Atmosphere! 🦋");
|
|
1424
1376
|
}
|
|
1425
1377
|
});
|
|
1426
1378
|
function showNextSteps(pm, sourceDomain) {
|
|
1427
1379
|
p.note(brightNote$1([
|
|
1428
|
-
bold
|
|
1429
|
-
"Two more steps to go live
|
|
1380
|
+
pc.bold("Your data is safe in your new PDS."),
|
|
1381
|
+
"Two more steps to go live:",
|
|
1430
1382
|
"",
|
|
1431
|
-
bold
|
|
1383
|
+
pc.bold("1. Update your identity"),
|
|
1432
1384
|
" Tell the network where you live now.",
|
|
1433
1385
|
` (Requires email verification from ${sourceDomain})`,
|
|
1434
1386
|
"",
|
|
1435
|
-
bold
|
|
1387
|
+
pc.bold("2. Flip the switch"),
|
|
1436
1388
|
` ${pm} pds activate`,
|
|
1437
1389
|
"",
|
|
1438
1390
|
"Docs: https://atproto.com/guides/account-migration"
|