@@ -51,6 +51,10 @@ export interface MultiProjectPluginConfig {
5151 cacheTTLMs ?: number ;
5252 maxSize ?: number ;
5353 ttlMs ?: number ;
54+ /** Direct storage adapter for artifact publishing. Bypasses kernel.getService('file-storage'). */
55+ storage ?: any ;
56+ /** Adapter name (e.g. 's3', 'local') — used when persisting sys_project_revision rows. */
57+ storageAdapterName ?: string ;
5458}
5559
5660interface ExtractedItem {
@@ -287,6 +291,7 @@ function createTemplateSeeder(
287291 bundleHasObjects : Array . isArray ( bundle ?. objects ) ? bundle . objects . length : 0 ,
288292 bundleHasApps : Array . isArray ( bundle ?. apps ) ? bundle . apps . length : 0 ,
289293 bundleHasViews : Array . isArray ( bundle ?. views ) ? bundle . views . length : 0 ,
294+ lastServiceKeys : ( publishCtx as any ) . _lastServiceKeys ?? null ,
290295 } ;
291296 if ( ! storage ) {
292297 publishStatus . error = 'no-file-storage-service' ;
@@ -335,10 +340,12 @@ function createTemplateSeeder(
335340 : ( cur ?. metadata ?? { } ) ;
336341 await ( publishCtx . controlDriver as any ) . update (
337342 'sys_project' ,
338- { where : { id : projectId } } ,
343+ projectId ,
339344 { metadata : JSON . stringify ( { ...meta , publishStatus, publishStatusAt : new Date ( ) . toISOString ( ) } ) } ,
340345 ) ;
341- } catch { /* best-effort diagnostic */ }
346+ } catch ( diagErr : any ) {
347+ console . error ( '[MultiProjectPlugin] Failed to write publishStatus diagnostic:' , diagErr ?. message ?? diagErr ) ;
348+ }
342349 } ;
343350
344351 return {
@@ -352,15 +359,45 @@ function createTemplateSeeder(
352359 } ,
353360
354361 async seed ( { projectId, templateId } ) {
362+ const writeDiag = async ( stage : string , extra : any = { } ) => {
363+ try {
364+ const cur = await ( publishCtx . controlDriver as any ) . findOne ( 'sys_project' , { where : { id : projectId } } ) ;
365+ const meta = cur && typeof cur . metadata === 'string' ? JSON . parse ( cur . metadata ) : ( cur ?. metadata ?? { } ) ;
366+ const seedDiag = { ...( meta . seedDiag ?? { } ) , [ stage ] : { at : new Date ( ) . toISOString ( ) , ...extra } } ;
367+ await ( publishCtx . controlDriver as any ) . update ( 'sys_project' , projectId , {
368+ metadata : JSON . stringify ( { ...meta , seedDiag } ) ,
369+ } ) ;
370+ } catch ( e : any ) { console . error ( '[MultiProjectPlugin] writeDiag failed' , stage , e ?. message ) ; }
371+ } ;
372+ await writeDiag ( 'seedCalled' , { templateId, templates : Object . keys ( templates ) } ) ;
355373 const template = templates [ templateId ] ;
356374 if ( ! template ) {
375+ await writeDiag ( 'seedNotFound' , { templateId } ) ;
357376 throw new Error (
358377 `Unknown template: '${ templateId } '. Available: [${ Object . keys ( templates ) . join ( ', ' ) } ]` ,
359378 ) ;
360379 }
361-
362- const bundle = await template . load ( ) ;
363- await seedBundleForProject ( projectId , bundle ) ;
380+ let bundle : any ;
381+ try {
382+ bundle = await template . load ( ) ;
383+ await writeDiag ( 'templateLoaded' , {
384+ objects : Array . isArray ( bundle ?. objects ) ? bundle . objects . length : 'n/a' ,
385+ apps : Array . isArray ( bundle ?. apps ) ? bundle . apps . length : 'n/a' ,
386+ views : Array . isArray ( bundle ?. views ) ? bundle . views . length : 'n/a' ,
387+ data : Array . isArray ( bundle ?. data ) ? bundle . data . length : 'n/a' ,
388+ bundleKeys : bundle ? Object . keys ( bundle ) : null ,
389+ } ) ;
390+ } catch ( e : any ) {
391+ await writeDiag ( 'templateLoadError' , { error : e ?. message , stack : e ?. stack ?. split ( '\n' ) . slice ( 0 , 5 ) } ) ;
392+ throw e ;
393+ }
394+ try {
395+ await seedBundleForProject ( projectId , bundle ) ;
396+ await writeDiag ( 'seedBundleDone' ) ;
397+ } catch ( e : any ) {
398+ await writeDiag ( 'seedBundleError' , { error : e ?. message , stack : e ?. stack ?. split ( '\n' ) . slice ( 0 , 5 ) } ) ;
399+ throw e ;
400+ }
364401 } ,
365402
366403 async seedBundle ( { projectId, bundle } ) {
@@ -515,28 +552,34 @@ export class MultiProjectPlugin implements Plugin {
515552 } ) ;
516553 this . kernelManager = kernelManager ;
517554
555+ const directStorage = this . config . storage ?? null ;
556+ const directStorageAdapter = this . config . storageAdapterName
557+ ?? ( process . env . OS_STORAGE_ADAPTER ?? 'local' ) . toLowerCase ( ) ;
518558 const seeder = createTemplateSeeder (
519559 kernelManager ,
520560 this . config . templates ?? { } ,
521561 envRegistry ,
522562 {
523563 controlDriver : this . config . controlDriver ,
524- storageAdapter : ( process . env . OS_STORAGE_ADAPTER ?? 'local' ) . toLowerCase ( ) ,
564+ storageAdapter : directStorageAdapter ,
525565 keyPrefix : process . env . OS_STORAGE_KEY_PREFIX ?? 'artifacts' ,
526566 getStorage : async ( ) => {
527- // Resolve once-per-call so the storage plugin (registered
528- // in parallel) is available even when MultiProjectPlugin
529- // initialised first. Try sync getService first; fall back
530- // to async getServiceAsync (StorageServicePlugin may register
531- // via async lifecycle on cold start).
567+ // 1. Direct storage adapter (preferred — set by cloud-stack
568+ // via resolveStorageFromEnv to bypass kernel.getService
569+ // lookup which is unreliable through the proxy wrapper).
570+ if ( directStorage && typeof directStorage . upload === 'function' ) {
571+ return directStorage ;
572+ }
573+ // 2. Fallback to kernel service lookup (for any stack that
574+ // forgot to pass `storage` in MultiProjectPluginConfig).
532575 try {
533576 const sync = ( hostKernel as any ) . getService ?.( 'file-storage' ) ;
534577 if ( sync && typeof sync . upload === 'function' ) return sync ;
535- } catch { /* fall through */ }
578+ } catch { /* ignore */ }
536579 try {
537580 const async = await ( hostKernel as any ) . getServiceAsync ?.( 'file-storage' ) ;
538581 if ( async && typeof async . upload === 'function' ) return async ;
539- } catch { /* not registered yet */ }
582+ } catch { /* ignore */ }
540583 return null ;
541584 } ,
542585 } ,
0 commit comments