@@ -13,9 +13,13 @@ import { Https } from '../utils/https';
1313import { installDependencies } from '../utils/install-dependencies' ;
1414import { getAvailableLocales } from '../utils/localization' ;
1515import { login , storeCredentials } from '../utils/login' ;
16+ import { promptForInfrastructureProject } from '../utils/prompt-infrastructure-project' ;
17+ import { setupNativeHosting } from '../utils/setup-native-hosting' ;
1618import { Telemetry } from '../utils/telemetry/telemetry' ;
1719import { writeEnv } from '../utils/write-env' ;
1820
21+ type HostingMode = 'next' | 'catalyst' ;
22+
1923interface Channel {
2024 id : number ;
2125 name : string ;
@@ -283,6 +287,116 @@ async function setupProject(options: {
283287 return { projectName, projectDir } ;
284288}
285289
290+ type HostingResolution =
291+ | { mode : 'next' }
292+ | { mode : 'catalyst' ; projectUuid : string ; accessToken : string } ;
293+
294+ async function resolveHostingMode ( {
295+ hostingFlag,
296+ storeHash,
297+ accessToken,
298+ cliApiOrigin,
299+ apiHostname,
300+ defaultProjectName,
301+ } : {
302+ hostingFlag ?: HostingMode ;
303+ storeHash ?: string ;
304+ accessToken ?: string ;
305+ cliApiOrigin : string ;
306+ apiHostname : string ;
307+ defaultProjectName ?: string ;
308+ } ) : Promise < HostingResolution > {
309+ if ( hostingFlag === 'next' ) {
310+ return { mode : 'next' } ;
311+ }
312+
313+ if ( ! storeHash || ! accessToken ) {
314+ if ( hostingFlag === 'catalyst' ) {
315+ console . error (
316+ colorize (
317+ 'red' ,
318+ '\n--hosting catalyst requires store credentials (store hash and access token)\n' ,
319+ ) ,
320+ ) ;
321+ process . exit ( 1 ) ;
322+ }
323+
324+ return { mode : 'next' } ;
325+ }
326+
327+ const cliApi = new CliApi ( {
328+ origin : cliApiOrigin ,
329+ storeHash,
330+ accessToken,
331+ apiHostname,
332+ } ) ;
333+
334+ let hasAccess = false ;
335+
336+ try {
337+ hasAccess = await cliApi . checkProjectsAccess ( ) ;
338+ } catch ( error ) {
339+ const message = error instanceof Error ? error . message : 'unknown error' ;
340+
341+ if ( hostingFlag === 'catalyst' ) {
342+ console . error (
343+ colorize (
344+ 'red' ,
345+ `\nFailed to verify Infrastructure Projects API access: ${ message } \nPlease try again.\n` ,
346+ ) ,
347+ ) ;
348+ process . exit ( 1 ) ;
349+ }
350+
351+ console . warn (
352+ colorize (
353+ 'yellow' ,
354+ `\nCould not verify Infrastructure Projects API access: ${ message } \nDefaulting to self-hosted (Next.js). Re-run create-catalyst if you intended to use BigCommerce-hosted.\n` ,
355+ ) ,
356+ ) ;
357+
358+ return { mode : 'next' } ;
359+ }
360+
361+ if ( hostingFlag === 'catalyst' && ! hasAccess ) {
362+ console . error (
363+ colorize (
364+ 'red' ,
365+ '\nThis store does not have access to the Infrastructure Projects API. Contact support@bigcommerce.com to enable it.\n' ,
366+ ) ,
367+ ) ;
368+ process . exit ( 1 ) ;
369+ }
370+
371+ let useNative = hostingFlag === 'catalyst' ;
372+
373+ if ( hostingFlag === undefined && hasAccess ) {
374+ useNative = await select ( {
375+ message : 'How would you like to host your Catalyst storefront?' ,
376+ choices : [
377+ {
378+ name : 'Self-hosted (Next.js)' ,
379+ value : false ,
380+ description : 'Use standard next dev / build / start' ,
381+ } ,
382+ {
383+ name : 'BigCommerce-hosted (Native)' ,
384+ value : true ,
385+ description : 'Use catalyst dev / build / deploy' ,
386+ } ,
387+ ] ,
388+ } ) ;
389+ }
390+
391+ if ( ! useNative ) {
392+ return { mode : 'next' } ;
393+ }
394+
395+ const project = await promptForInfrastructureProject ( cliApi , defaultProjectName ) ;
396+
397+ return { mode : 'catalyst' , projectUuid : project . uuid , accessToken } ;
398+ }
399+
286400function checkRequiredTools ( ) {
287401 try {
288402 execSync ( getPlatformCheckCommand ( 'git' ) , { stdio : 'ignore' } ) ;
@@ -316,6 +430,12 @@ export const create = new Command('create')
316430 . option ( '--reset-main' , 'Reset the main branch to the gh-ref' )
317431 . option ( '--repository <repository>' , 'GitHub repository to clone from' , 'bigcommerce/catalyst' )
318432 . option ( '--env <vars...>' , 'Arbitrary environment variables to set in .env.local' )
433+ . addOption (
434+ new Option (
435+ '--hosting <mode>' ,
436+ 'Hosting mode: "next" for self-hosted Next.js or "catalyst" for BigCommerce-hosted' ,
437+ ) . choices ( [ 'next' , 'catalyst' ] as const ) ,
438+ )
319439 . addOption (
320440 new Option ( '--bigcommerce-hostname <hostname>' , 'BigCommerce hostname' )
321441 . default ( 'bigcommerce.com' )
@@ -363,6 +483,16 @@ export const create = new Command('create')
363483 envVars . BIGCOMMERCE_STOREFRONT_API_TOKEN = storefrontToken ;
364484 } else {
365485 if ( ! storeHash || ! accessToken ) {
486+ if ( options . hosting === 'catalyst' ) {
487+ console . error (
488+ colorize (
489+ 'red' ,
490+ '\n--hosting catalyst requires store credentials (store hash and access token)\n' ,
491+ ) ,
492+ ) ;
493+ process . exit ( 1 ) ;
494+ }
495+
366496 // Create project without credentials
367497 console . log ( `\nCreating '${ projectName } ' at '${ projectDir } '\n` ) ;
368498 cloneCatalyst ( { repository, projectName, projectDir, ghRef, resetMain : options . resetMain } ) ;
@@ -418,6 +548,7 @@ export const create = new Command('create')
418548 origin : options . cliApiOrigin ,
419549 storeHash,
420550 accessToken,
551+ apiHostname : `api.${ options . bigcommerceHostname } ` ,
421552 } ) ;
422553
423554 // If we have channelId but no storefrontToken, just get the init data
@@ -523,9 +654,32 @@ export const create = new Command('create')
523654 if ( ! channelId ) throw new Error ( 'Something went wrong, channelId is not defined' ) ;
524655 if ( ! storefrontToken ) throw new Error ( 'Something went wrong, storefrontToken is not defined' ) ;
525656
657+ const hosting = await resolveHostingMode ( {
658+ hostingFlag : options . hosting ,
659+ storeHash,
660+ accessToken,
661+ cliApiOrigin : options . cliApiOrigin ,
662+ apiHostname : `api.${ options . bigcommerceHostname } ` ,
663+ defaultProjectName : projectName ,
664+ } ) ;
665+
666+ if ( hosting . mode === 'catalyst' ) {
667+ envVars . BIGCOMMERCE_ACCESS_TOKEN = hosting . accessToken ;
668+ }
669+
526670 // Create the project with all necessary configuration
527671 console . log ( `\nCreating '${ projectName } ' at '${ projectDir } '\n` ) ;
528672 cloneCatalyst ( { repository, projectName, projectDir, ghRef, resetMain : options . resetMain } ) ;
673+
674+ if ( hosting . mode === 'catalyst' ) {
675+ setupNativeHosting ( {
676+ projectDir,
677+ projectUuid : hosting . projectUuid ,
678+ storeHash,
679+ accessToken : hosting . accessToken ,
680+ } ) ;
681+ }
682+
529683 await installDependencies ( projectDir ) ;
530684
531685 // Write env vars
@@ -540,6 +694,14 @@ export const create = new Command('create')
540694 colorize ( 'green' , `\nSuccess! Created '${ projectName } ' at '${ projectDir } '\n` ) ,
541695 '\nNext steps:\n' ,
542696 colorize ( 'yellow' , `\ncd ${ projectName } && pnpm run dev\n` ) ,
697+ ...( hosting . mode === 'catalyst'
698+ ? [
699+ colorize (
700+ 'yellow' ,
701+ `\nRun 'cd ${ projectName } /core && pnpm run deploy' when ready to deploy to BigCommerce hosting.\n` ,
702+ ) ,
703+ ]
704+ : [ ] ) ,
543705 ) ;
544706 } ) ;
545707
0 commit comments