@@ -3122,15 +3122,19 @@ async def install_from_marketplace(
31223122 root_prefix = None
31233123 app_prefix = None
31243124
3125+ # Match on path SEGMENTS, never substrings. GitHub names the
3126+ # zip root "{repo}-{ref with / as -}", so a ref named after the
3127+ # app it carries ("feature/invoice-tracker") produces a root
3128+ # folder ENDING in the app id. A substring search then resolves
3129+ # the prefix to the repo root and extracts the whole
3130+ # marketplace, leaving no manifest.json where one is expected.
31253131 for name in zf .namelist ():
3132+ parts = name .split ("/" )
31263133 if root_prefix is None :
3127- root_prefix = name .split ("/" )[0 ] + "/"
3128- # Look for the app folder: root/{app_id}/
3129- if f"/{ app_id } /" in name :
3130- if app_prefix is None :
3131- # Find the prefix up to and including the app folder
3132- idx = name .index (f"{ app_id } /" )
3133- app_prefix = name [: idx + len (app_id ) + 1 ]
3134+ root_prefix = parts [0 ] + "/"
3135+ # The app folder is exactly root/{app_id}/
3136+ if len (parts ) > 2 and parts [1 ] == app_id :
3137+ app_prefix = f"{ root_prefix } { app_id } /"
31343138 break
31353139
31363140 if not app_prefix :
@@ -3179,14 +3183,40 @@ async def install_from_marketplace(
31793183 # projects (root manifest.json, livingUIVersion 2, PocketBase
31803184 # backend). Legacy V1 apps (config/manifest.json, FastAPI
31813185 # backend) are rejected until re-published in the current format.
3186+ # Say WHICH check failed. A missing manifest is usually an
3187+ # extraction/layout fault on our side, not a stale publish, and
3188+ # reporting both as "legacy V1" sends people to fix the wrong repo.
31823189 mf = project_path / "manifest.json"
31833190 is_v2 = False
3184- if mf .exists ():
3191+ reason = ""
3192+ if not mf .exists ():
3193+ if (project_path / "config" / "manifest.json" ).exists ():
3194+ # config/manifest.json + FastAPI backend == the real V1.
3195+ reason = (
3196+ f"'{ app_id } ' is in the legacy V1 format and needs to "
3197+ "be re-published in the current format in the "
3198+ "marketplace"
3199+ )
3200+ else :
3201+ reason = (
3202+ f"no manifest.json at the root of '{ app_id } ' after "
3203+ f"extraction (looked in { project_path .name } )"
3204+ )
3205+ else :
31853206 try :
3186- is_v2 = json .loads (mf .read_text ()).get ("livingUIVersion" ) == 2
3187- except Exception :
3188- is_v2 = False
3207+ version = json .loads (mf .read_text ()).get ("livingUIVersion" )
3208+ is_v2 = version == 2
3209+ if not is_v2 :
3210+ reason = (
3211+ f"'{ app_id } ' declares livingUIVersion "
3212+ f"{ version !r} ; this platform runs 2 (legacy V1 "
3213+ "apps must be re-published in the current format "
3214+ "in the marketplace)"
3215+ )
3216+ except Exception as e :
3217+ reason = f"manifest.json for '{ app_id } ' is unreadable: { e } "
31893218 if not is_v2 :
3219+ logger .error (f"[LIVING_UI:MARKETPLACE] Compatibility gate: { reason } " )
31903220 shutil .rmtree (project_path , ignore_errors = True )
31913221 if preserved_hold is not None :
31923222 # Adoption: give the scaffold its requirements/factory
@@ -3201,10 +3231,8 @@ async def install_from_marketplace(
32013231 return {
32023232 "status" : "error" ,
32033233 "error" : (
3204- f"Marketplace app '{ app_id } ' is in the legacy V1 "
3205- "format and cannot run on this platform. It "
3206- "needs to be re-published in the current format in the "
3207- "marketplace."
3234+ f"Marketplace app '{ app_id } ' cannot run on this "
3235+ f"platform: { reason } ."
32083236 ),
32093237 }
32103238
0 commit comments