@@ -90,7 +90,7 @@ function createTemplateSeeder(
9090 kernelManager : KernelManager ,
9191 templates : Record < string , ProjectTemplate > ,
9292 envRegistry : EnvironmentDriverRegistry ,
93- publishCtx : { controlDriver : IDataDriver ; getStorage : ( ) => any | null ; storageAdapter : string ; keyPrefix : string } ,
93+ publishCtx : { controlDriver : IDataDriver ; getStorage : ( ) => any | Promise < any > | null ; storageAdapter : string ; keyPrefix : string } ,
9494) : TemplateSeeder {
9595 const seedBundleForProject = async ( projectId : string , bundle : any ) : Promise < void > => {
9696 const items = bundle ? extractMetadataItems ( bundle ) : [ ] ;
@@ -275,14 +275,32 @@ function createTemplateSeeder(
275275 // step, the seeded data only lives in this project's metadata
276276 // DB on the cloud container's filesystem and is invisible to
277277 // any other process.
278+ let publishStatus : { stage : string ; ok : boolean ; error ?: string ; detail ?: any } = {
279+ stage : 'start' , ok : false ,
280+ } ;
278281 try {
279- const storage = publishCtx . getStorage ( ) ;
280- if ( storage ) {
282+ const storage = await Promise . resolve ( publishCtx . getStorage ( ) ) ;
283+ publishStatus . stage = 'getStorage' ;
284+ publishStatus . detail = {
285+ hasStorage : ! ! storage ,
286+ storageType : storage ? ( storage . constructor ?. name ?? typeof storage ) : 'null' ,
287+ bundleHasObjects : Array . isArray ( bundle ?. objects ) ? bundle . objects . length : 0 ,
288+ bundleHasApps : Array . isArray ( bundle ?. apps ) ? bundle . apps . length : 0 ,
289+ bundleHasViews : Array . isArray ( bundle ?. views ) ? bundle . views . length : 0 ,
290+ } ;
291+ if ( ! storage ) {
292+ publishStatus . error = 'no-file-storage-service' ;
293+ console . warn (
294+ `[MultiProjectPlugin] No file-storage service registered; skipping artifact publish for project ${ projectId } .` ,
295+ ) ;
296+ } else {
297+ publishStatus . stage = 'findProject' ;
281298 const projectRow = await ( publishCtx . controlDriver as any ) . findOne (
282299 'sys_project' ,
283300 { where : { id : projectId } } ,
284301 ) ;
285302 if ( projectRow ) {
303+ publishStatus . stage = 'publishProjectRevision' ;
286304 const { publishProjectRevision } = await import ( './cloud-artifact-helpers.js' ) ;
287305 await publishProjectRevision ( {
288306 driver : publishCtx . controlDriver ,
@@ -293,15 +311,34 @@ function createTemplateSeeder(
293311 bundle,
294312 note : 'template-seed' ,
295313 } ) ;
314+ publishStatus . ok = true ;
315+ publishStatus . stage = 'done' ;
316+ console . log ( `[MultiProjectPlugin] Published artifact bundle for project ${ projectId } ` ) ;
317+ } else {
318+ publishStatus . error = 'project-not-found' ;
296319 }
297320 }
298321 } catch ( err : any ) {
299- // eslint-disable-next-line no-console
322+ publishStatus . error = err ?. message ?? String ( err ) ;
323+ publishStatus . detail = { ...( publishStatus . detail ?? { } ) , stack : err ?. stack } ;
300324 console . error (
301325 `[MultiProjectPlugin] Failed to publish seeded artifact for project ${ projectId } :` ,
302326 err ?. stack ?? err ?. message ?? err ,
303327 ) ;
304328 }
329+ // Persist publish diagnostic into sys_project.metadata.publishStatus so
330+ // operators can see why publish skipped without container logs.
331+ try {
332+ const cur = await ( publishCtx . controlDriver as any ) . findOne ( 'sys_project' , { where : { id : projectId } } ) ;
333+ const meta = cur && typeof cur . metadata === 'string'
334+ ? JSON . parse ( cur . metadata )
335+ : ( cur ?. metadata ?? { } ) ;
336+ await ( publishCtx . controlDriver as any ) . update (
337+ 'sys_project' ,
338+ { where : { id : projectId } } ,
339+ { metadata : JSON . stringify ( { ...meta , publishStatus, publishStatusAt : new Date ( ) . toISOString ( ) } ) } ,
340+ ) ;
341+ } catch { /* best-effort diagnostic */ }
305342 } ;
306343
307344 return {
@@ -486,14 +523,20 @@ export class MultiProjectPlugin implements Plugin {
486523 controlDriver : this . config . controlDriver ,
487524 storageAdapter : ( process . env . OS_STORAGE_ADAPTER ?? 'local' ) . toLowerCase ( ) ,
488525 keyPrefix : process . env . OS_STORAGE_KEY_PREFIX ?? 'artifacts' ,
489- getStorage : ( ) => {
526+ getStorage : async ( ) => {
490527 // Resolve once-per-call so the storage plugin (registered
491528 // in parallel) is available even when MultiProjectPlugin
492- // initialised first.
529+ // initialised first. Try sync getService first; fall back
530+ // to async getServiceAsync (StorageServicePlugin may register
531+ // via async lifecycle on cold start).
493532 try {
494533 const sync = ( hostKernel as any ) . getService ?.( 'file-storage' ) ;
495534 if ( sync && typeof sync . upload === 'function' ) return sync ;
496- } catch { /* async-only — fall through */ }
535+ } catch { /* fall through */ }
536+ try {
537+ const async = await ( hostKernel as any ) . getServiceAsync ?.( 'file-storage' ) ;
538+ if ( async && typeof async . upload === 'function' ) return async ;
539+ } catch { /* not registered yet */ }
497540 return null ;
498541 } ,
499542 } ,
0 commit comments