@@ -532,6 +532,28 @@ async function getWorkerSubdomain(
532532 return null ;
533533}
534534
535+ /**
536+ * Add a custom domain to a Cloudflare Worker
537+ */
538+ async function addWorkerCustomDomain (
539+ token : string ,
540+ accountId : string ,
541+ workerName : string ,
542+ zoneId : string ,
543+ hostname : string ,
544+ ) : Promise < boolean > {
545+ const data = await cfApi ( `/accounts/${ accountId } /workers/domains` , token , {
546+ method : "PUT" ,
547+ body : JSON . stringify ( {
548+ hostname,
549+ service : workerName ,
550+ environment : "production" ,
551+ zone_id : zoneId ,
552+ } ) ,
553+ } ) ;
554+ return data . success ;
555+ }
556+
535557/**
536558 * Run the Cloudflare Workers setup flow
537559 */
@@ -585,9 +607,104 @@ async function runWorkersSetup(token: string): Promise<void> {
585607 saveWorkerNameToEnv ( workerName ) ;
586608 success ( "Saved CLOUDFLARE_WORKER_NAME to .env.local" ) ;
587609
610+ // Step 3: Custom domain setup
611+ log ( "" ) ;
612+ log ( "Step 3: Custom domain setup..." ) ;
613+ log ( "" ) ;
614+
615+ const wantCustomDomain = await promptYesNo (
616+ "Would you like to add a custom domain?" ,
617+ true ,
618+ ) ;
619+
620+ let customDomain : string | null = null ;
621+ let selectedZone : { id : string ; name : string } | null = null ;
622+
623+ if ( wantCustomDomain ) {
624+ // List zones
625+ const zones = await listZones ( token ) ;
626+
627+ if ( zones . length === 0 ) {
628+ warn ( "No domains found in your Cloudflare account." ) ;
629+ log ( "" ) ;
630+ log ( "To add a domain to Cloudflare:" ) ;
631+ log ( " 1. Go to https://dash.cloudflare.com/" ) ;
632+ log ( " 2. Click 'Add a site'" ) ;
633+ log ( " 3. Follow the setup wizard" ) ;
634+ log ( "" ) ;
635+ log ( "You can add a custom domain later in the Cloudflare dashboard." ) ;
636+ } else {
637+ log ( "" ) ;
638+ log ( "Your domains in Cloudflare:" ) ;
639+ zones . forEach ( ( zone , i ) => {
640+ log ( ` ${ i + 1 } . ${ zone . name } (${ zone . status } )` ) ;
641+ } ) ;
642+ log ( "" ) ;
643+
644+ const choice = await prompt ( `Select a domain [1-${ zones . length } ]: ` ) ;
645+ const choiceNum = parseInt ( choice , 10 ) ;
646+
647+ if ( choiceNum >= 1 && choiceNum <= zones . length ) {
648+ selectedZone = zones [ choiceNum - 1 ] ;
649+
650+ // Ask about subdomain
651+ const useSubdomain = await promptYesNo (
652+ `Use a subdomain (e.g., app.${ selectedZone . name } )? Otherwise will use root domain.` ,
653+ false ,
654+ ) ;
655+
656+ if ( useSubdomain ) {
657+ const subdomainName = await prompt ( "Enter subdomain (e.g., app, www): " ) ;
658+ customDomain = subdomainName
659+ ? `${subdomainName } . $ { selectedZone . name } `
660+ : ` app . $ { selectedZone . name } `;
661+ } else {
662+ customDomain = selectedZone.name;
663+ }
664+
665+ log("");
666+ log(` Configuring custom domain : ${customDomain } `);
667+
668+ // Check SSL mode first
669+ const currentSslMode = await getSslMode(token, selectedZone.id);
670+ if (currentSslMode === "flexible") {
671+ warn(` SSL mode is "flexible " - this may cause issues . `);
672+ log("Changing to \"full\"...");
673+ const sslSuccess = await setSslMode(token, selectedZone.id, "full");
674+ if (sslSuccess) {
675+ success("SSL mode set to \"full\"");
676+ } else {
677+ warn("Could not change SSL mode automatically.");
678+ log(` Please set SSL / TLS mode to "Full" at:`);
679+ log(` https://dash.cloudflare.com → ${selectedZone.name} → SSL/TLS`);
680+ }
681+ } else if ( currentSslMode ) {
682+ success ( `SSL mode is "${ currentSslMode } " (OK)` ) ;
683+ }
684+
685+ // Try to add custom domain
686+ // Note: The worker must exist first, so we'll try but it may fail
687+ const domainAdded = await addWorkerCustomDomain (
688+ token ,
689+ accountId ,
690+ workerName ,
691+ selectedZone . id ,
692+ customDomain ,
693+ ) ;
694+
695+ if ( domainAdded ) {
696+ success ( `Custom domain configured: ${ customDomain } ` ) ;
697+ } else {
698+ info ( "Custom domain will be configured after first deploy." ) ;
699+ log ( "(The worker must exist before adding a custom domain)" ) ;
700+ }
701+ }
702+ }
703+ }
704+
588705 // Offer to deploy
589706 log ( "" ) ;
590- log ( "Step 3 : Deploy static files..." ) ;
707+ log ( "Step 4 : Deploy static files..." ) ;
591708 log ( "" ) ;
592709 const shouldDeploy = await promptYesNo (
593710 "Would you like to build and deploy static files now?" ,
@@ -616,6 +733,27 @@ async function runWorkersSetup(token: string): Promise<void> {
616733
617734 if ( deployResult . status === 0 ) {
618735 success ( "Deployment complete!" ) ;
736+
737+ // If we had a custom domain that failed earlier, try again now
738+ if ( customDomain && selectedZone ) {
739+ log ( "" ) ;
740+ log ( "Configuring custom domain..." ) ;
741+ const domainAdded = await addWorkerCustomDomain (
742+ token ,
743+ accountId ,
744+ workerName ,
745+ selectedZone . id ,
746+ customDomain ,
747+ ) ;
748+
749+ if ( domainAdded ) {
750+ success ( `Custom domain configured: ${ customDomain } ` ) ;
751+ } else {
752+ warn ( "Could not configure custom domain automatically." ) ;
753+ log ( "Add it manually in the Cloudflare dashboard:" ) ;
754+ log ( ` Workers & Pages → ${ workerName } → Settings → Domains & Routes` ) ;
755+ }
756+ }
619757 } else {
620758 warn ( "Deployment failed. Try running manually:" ) ;
621759 log ( ` npx @get-convex/self-static-hosting upload --cloudflare-workers --worker-name ${ workerName } --prod` ) ;
@@ -625,6 +763,11 @@ async function runWorkersSetup(token: string): Promise<void> {
625763 log ( ` npm run build` ) ;
626764 log ( ` npx @get-convex/self-static-hosting upload --cloudflare-workers --worker-name ${ workerName } --prod` ) ;
627765 }
766+ } else if ( customDomain && selectedZone ) {
767+ log ( "" ) ;
768+ info ( "After deploying, add your custom domain:" ) ;
769+ log ( ` Workers & Pages → ${ workerName } → Settings → Domains & Routes` ) ;
770+ log ( ` Or run this wizard again after deploying.` ) ;
628771 }
629772
630773 // Build the URL
@@ -640,10 +783,15 @@ async function runWorkersSetup(token: string): Promise<void> {
640783 log ( "Your configuration:" ) ;
641784 log ( ` Worker: ${ workerName } ` ) ;
642785 log ( ` URL: ${ workerUrl } ` ) ;
786+ if ( customDomain ) {
787+ log ( ` Custom domain: https://${ customDomain } ` ) ;
788+ }
643789 log ( "" ) ;
644- log ( "To add a custom domain, go to:" ) ;
645- log ( " https://dash.cloudflare.com -> Workers & Pages -> your worker -> Settings -> Triggers" ) ;
646- log ( "" ) ;
790+ if ( ! customDomain ) {
791+ log ( "To add a custom domain later:" ) ;
792+ log ( " Workers & Pages → your worker → Settings → Domains & Routes" ) ;
793+ log ( "" ) ;
794+ }
647795 log ( "To deploy in the future, run:" ) ;
648796 log ( ` npx @get-convex/self-static-hosting upload --build --prod --cloudflare-workers` ) ;
649797 log ( "" ) ;
0 commit comments