@@ -36,7 +36,7 @@ import { ChangelogGenerator } from './lib/changelog-generator.js';
3636import { IconFontGenerator } from './lib/icon-font-generator.js' ;
3737// Remove the explicit process import since it's globally available
3838import internalServer from './lib/internal.middleware' ;
39- import { safeNextImport , isNextAvailable } from './lib/next-import.js' ;
39+ import { safeNextImport } from './lib/next-import.js' ;
4040import { PP_DEV_CONFIG_NAMES , PP_WATCH_CONFIG_NAMES } from './constants.js' ;
4141
4242const cli = cac ( 'pp-dev' ) ;
@@ -506,21 +506,25 @@ cli
506506 . action ( async ( root : string , options : ServerOptions & GlobalCLIOptions ) => {
507507 filterDuplicateOptions ( options ) ;
508508
509- let nextApp : any = null ;
509+ let nextApp : ReturnType < typeof import ( 'next' ) . default > | null = null ;
510510 let httpServer : any = null ;
511511 let configWatcher : ConfigWatcher | null = null ;
512512 let isRestarting = false ;
513513
514- const projectRoot = root
515- ? path . resolve ( process . cwd ( ) , root )
516- : process . cwd ( ) ;
517514 const logger = createLogger ( ) ;
518515
519516 const startNextServer = async ( ) => {
520- if ( isRestarting ) return ;
517+ if ( isRestarting ) {
518+ return ;
519+ }
520+
521521 isRestarting = true ;
522522
523523 try {
524+ // Import Next.js first – with logger available for error reporting
525+ const { next, constants } = await safeNextImport ( ) ;
526+ const { PHASE_DEVELOPMENT_SERVER } = constants ;
527+
524528 // Clean up existing server if any
525529 if ( httpServer ) {
526530 logger . info ( colors . yellow ( '🛑 Stopping existing Next.js server...' ) ) ;
@@ -535,32 +539,23 @@ cli
535539 // Clean up existing Next.js app if any
536540 if ( nextApp && typeof nextApp . close === 'function' ) {
537541 await nextApp . close ( ) ;
542+
538543 nextApp = null ;
539544 }
540545
541546 // Clear config cache
542547 const { clearConfigCache } = await import ( './config.js' ) ;
543548 clearConfigCache ( ) ;
544549
545- // Check if Next.js is available before proceeding
546- if ( ! ( await isNextAvailable ( ) ) ) {
547- throw new Error (
548- 'Next.js is required but not available. Please install Next.js as a dependency:\n' +
549- 'npm install next@^16\n\n' +
550- 'This package requires Next.js >=13 <17 as a peer dependency.' ,
551- ) ;
552- }
553-
554- const { next } = await safeNextImport ( ) ;
555550 const { join, basename } = await import ( 'path' ) ;
556551 const { createServer } = await import ( 'http' ) ;
557552
558553 const importConfig = await import ( 'next/dist/server/config.js' ) ;
559554
560- const loadConfig =
561- importConfig . default . default ||
562- importConfig [ 'module.exports' ] . default ||
563- importConfig . default ;
555+ const loadConfig : typeof import ( 'next/dist/server/config.js' ) . default =
556+ ( importConfig as any ) . default . default ||
557+ ( importConfig as any ) [ 'module.exports' ] . default ||
558+ ( importConfig as any ) . default ;
564559
565560 const opts = cleanOptions ( options ) ;
566561
@@ -585,10 +580,10 @@ cli
585580 logger . info ( projectRoot ) ;
586581
587582 // Get pp-dev config from Next.js app config
588- const config = await loadConfig ( 'development' , projectRoot ) ;
583+ const config = await loadConfig ( PHASE_DEVELOPMENT_SERVER , projectRoot ) ;
589584
590585 // Extract pp-dev configuration from Next.js config
591- let ppDevConfig = config ?. experimental ?. ppDev || config ?. ppDev || { } ;
586+ let ppDevConfig = config ?. ppDev || { } ;
592587
593588 // If no pp-dev config found in Next.js config, try to load from standalone config file
594589 if ( Object . keys ( ppDevConfig ) . length === 0 ) {
@@ -628,25 +623,36 @@ cli
628623 const {
629624 backendBaseURL = process . env . MI_BACKEND_URL ||
630625 'http://localhost:8080' ,
631- portalPageId = parseInt ( process . env . MI_PORTAL_PAGE_ID || '1' ) ,
626+ appId : originalAppId ,
627+ portalPageId,
632628 templateLess = true ,
633629 v7Features = true ,
634630 disableSSLValidation = false ,
635631 enableProxyCache = true ,
636632 proxyCacheTTL = 600000 ,
637633 personalAccessToken = process . env . MI_ACCESS_TOKEN ,
638- distZip = false ,
639- syncBackupsDir = './backups' ,
640634 miHudLess = false ,
641635 } = ppDevConfig ;
642636
637+ const appId : number =
638+ originalAppId ??
639+ portalPageId ??
640+ ( process . env . MI_APP_ID
641+ ? parseInt ( process . env . MI_APP_ID )
642+ : undefined ) ??
643+ ( process . env . MI_PORTAL_PAGE_ID
644+ ? parseInt ( process . env . MI_PORTAL_PAGE_ID )
645+ : undefined ) ??
646+ 1 ;
647+
643648 // Get template name from config, package.json, or fallback to project directory name
644- let templateName = ppDevConfig . templateName ;
649+ let templateName = null ;
645650
646651 if ( ! templateName ) {
647652 try {
648653 const { getPkg } = await import ( './config.js' ) ;
649654 const pkg = getPkg ( ) ;
655+
650656 templateName = pkg . name ;
651657 } catch ( error ) {
652658 // Fallback to project directory name
658664 const pathPagePrefix = '/p' ; // templateLess = true - use /p
659665 const pathTemplatePrefix = '/pl' ; // templateLess = false && v7Features = true - use /pl
660666
661- let base = templateLess ? pathPagePrefix : pathTemplatePrefix ;
662- base += `/${ templateName } ` ;
667+ const configBasePath = config ?. basePath ;
668+ let base = '' ;
669+
670+ if ( configBasePath ) {
671+ base = configBasePath ;
672+ } else {
673+ base = templateLess ? pathPagePrefix : pathTemplatePrefix ;
674+ base += `/${ templateName } ` ;
675+ }
663676
664677 nextApp = next ( {
665678 dev : true ,
@@ -669,18 +682,12 @@ cli
669682 conf : {
670683 ...config ,
671684 basePath : base ,
672- assetPrefix : base , // Fixed: Make assetPrefix consistent with basePath
685+ assetPrefix : ` ${ templateLess ? pathPagePrefix : '/pt' } / ${ templateName } ` , // Fixed: Make assetPrefix consistent with basePath
673686 } ,
674687 } ) ;
675688
676689 await nextApp . prepare ( ) ;
677690
678- // Default to templateLess = true for Next.js development
679- // const templateLess =
680- // typeof ppDevConfig.templateLess === "boolean"
681- // ? ppDevConfig.templateLess
682- // : true;
683-
684691 if ( ! base . endsWith ( '/' ) ) {
685692 base += '/' ;
686693 }
691698 ) ;
692699 }
693700
694- const baseWithoutTrailingSlash = base . substring (
695- 0 ,
696- base . lastIndexOf ( '/' ) ,
697- ) ;
698-
699701 // Log the configuration
700702 logger . info ( colors . green ( '✅ Next.js app prepared successfully' ) ) ;
701703 logger . info (
707709
708710 if ( backendBaseURL ) {
709711 logger . info ( colors . blue ( `🌐 Backend URL: ${ backendBaseURL } ` ) ) ;
710- logger . info ( colors . blue ( `🆔 Portal Page ID: ${ portalPageId } ` ) ) ;
712+ logger . info ( colors . blue ( `🆔 Custom App ID: ${ appId } ` ) ) ;
711713 }
712714
713715 // Get the Next.js request handler
735737 const isInternalNextRoute =
736738 originalPathname . startsWith ( '/_next/' ) ||
737739 originalPathname === '/favicon.ico' ||
738- originalPathname . startsWith ( '/__nextjs_' ) ||
739- originalPathname . startsWith ( '/api/' ) ;
740+ originalPathname . startsWith ( '/__nextjs_' ) ;
740741
741742 if ( isInternalNextRoute ) {
742743 // For internal routes, only apply essential middlewares (skip proxy, cache, etc.)
@@ -791,17 +792,16 @@ cli
791792 processNextJSRequest ( ) ;
792793
793794 async function processNextJSRequest ( ) {
794- // Handle base path requests
795+ // Handle base path requests - pass full path to Next.js so it can apply basePath routing
795796 if ( originalPathname . startsWith ( base ) ) {
796- // Strip the base path for Next.js
797- const nextPath = originalPathname . substring ( base . length ) ;
798-
799- req . url = nextPath || '/' ;
800- parsedUrl = parse ( nextPath , true ) ;
797+ // Keep full path - Next.js expects req.url to include basePath for proper routing
798+ parsedUrl = parse ( originalUrl , true ) ;
801799 } else if ( originalPathname === base . replace ( / \/ $ / , '' ) ) {
802- // Handle base path without trailing slash
803- req . url = '/' ;
804- parsedUrl = parse ( '/' , true ) ;
800+ // Path without trailing slash - redirect to canonical URL with trailing slash
801+ const redirectUrl = originalUrl . replace ( originalPathname , base ) ;
802+ res . writeHead ( 302 , { Location : redirectUrl } ) ;
803+ res . end ( ) ;
804+ return ;
805805 } else if (
806806 originalPathname . startsWith ( '/_next/' ) ||
807807 originalPathname === '/favicon.ico' ||
849849 '$1$2' ,
850850 ) ,
851851 } ,
852- portalPageId,
853- appId : portalPageId ,
852+ portalPageId : appId ,
853+ appId,
854854 templateLess,
855855 disableSSLValidation,
856856 v7Features,
931931 '/_next' ,
932932 '/favicon.ico' ,
933933 '/__nextjs_' ,
934- '/api ' ,
934+ '/installHook.js.map ' ,
935935 ] ,
936936 disableSSLValidation,
937937 miAPI : mi ,
@@ -1011,7 +1011,7 @@ cli
10111011 logger . info (
10121012 colors . blue ( `🔧 MiAPI initialized for backend: ${ backendBaseURL } ` ) ,
10131013 ) ;
1014- logger . info ( colors . blue ( `🔧 Portal Page ID: ${ portalPageId } ` ) ) ;
1014+ logger . info ( colors . blue ( `🔧 Custom App ID: ${ appId } ` ) ) ;
10151015 }
10161016
10171017 httpServer . listen ( port , host , ( ) => {
0 commit comments