From 3b233dbcb95edec98fce14cd33abf4a4888a10b2 Mon Sep 17 00:00:00 2001 From: Edward Tan Date: Fri, 2 Oct 2026 13:21:28 -0700 Subject: [PATCH] Find an installed app's bundle in App Hub's new layout as well as the old App Hub moves installed bundles out of their apps' storage, from //bundle to /.bundles//bundle (OctoSense-App-Hub fix/bundles-outside-storage). The shell built the old path in three places: the agents' manifest lookup (apps.rs), an installed app's tools (host_tools/script_apps.rs) and the storage lifecycle's manifest (app_storage/lifecycle.rs). apps::installed_bundle finds the bundle in either place, the new one first, so this works with the App Hub the shells pin today and after the pin moves to one that has moved its installs. --- crates/shell/src/app_storage/lifecycle.rs | 4 +-- crates/shell/src/apps.rs | 35 ++++++++++++++++++++-- crates/shell/src/host_tools/script_apps.rs | 2 +- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/crates/shell/src/app_storage/lifecycle.rs b/crates/shell/src/app_storage/lifecycle.rs index d7c8c06e..2c5db2e5 100644 --- a/crates/shell/src/app_storage/lifecycle.rs +++ b/crates/shell/src/app_storage/lifecycle.rs @@ -62,12 +62,12 @@ pub fn register_native_specs(storage: &Storage) { } /// A script app's `manifest.json` under App Hub's data root: an installed -/// app's `//bundle/`, or the newest unpacked build of a system app +/// app's bundle ([`crate::apps::installed_bundle`]), or the newest unpacked build of a system app /// (`/.system///`). `None` before either exists (a system /// app the Card runner has not unpacked yet). pub fn script_manifest(root: &Path, manifest_id: &str) -> Option { super::validate_app_id(manifest_id).ok()?; - let installed = root.join(manifest_id).join("bundle").join("manifest.json"); + let installed = crate::apps::installed_bundle(root, manifest_id).join("manifest.json"); let path = if installed.is_file() { installed } else { diff --git a/crates/shell/src/apps.rs b/crates/shell/src/apps.rs index 21957c97..ba597546 100644 --- a/crates/shell/src/apps.rs +++ b/crates/shell/src/apps.rs @@ -24,7 +24,7 @@ use makepad_app_module::AppModule; use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Hosting { @@ -317,7 +317,7 @@ fn read_script_agent_apps(root: &Path) -> Vec { } } for app in octosense_app_hub_app::installed_apps(&root) { - if let Some(a) = script_agent_app(&root.join(&app.id).join("bundle").join("manifest.json"), &app.id, &app.name) { + if let Some(a) = script_agent_app(&installed_bundle(&root, &app.id).join("manifest.json"), &app.id, &app.name) { out.push(a); } } @@ -661,6 +661,20 @@ impl AppRegistry { } } +/// An installed store app's bundle under App Hub's apps root. App Hub keeps +/// it outside the app's storage, in `/.bundles//bundle` +/// (`octosense_app_hub::installed_bundle_dir`); a device whose App Hub has +/// not moved it yet still has it in `//bundle`. The new place +/// wins when both exist. +pub(crate) fn installed_bundle(root: &Path, app_id: &str) -> PathBuf { + let current = root.join(".bundles").join(app_id).join("bundle"); + if current.is_dir() { + current + } else { + root.join(app_id).join("bundle") + } +} + #[cfg(test)] mod tests { /// The assistant pane's own process is a registry row (F10 starts it @@ -674,6 +688,23 @@ mod tests { assert!(crate::shell::launcher::apps().iter().all(|a| a.id != "apps.aichat" && a.id != "aichat")); } + /// App Hub keeps an installed bundle in `/.bundles//bundle` + /// (outside the app's storage); a device App Hub has not updated yet + /// still has it in `//bundle`. Either is found, the new one + /// first. + #[test] + fn an_installed_bundle_is_found_in_either_layout() { + let root = std::env::temp_dir().join(format!("shell-installed-bundle-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&root); + let legacy = root.join("dev.example.app/bundle"); + std::fs::create_dir_all(&legacy).unwrap(); + assert_eq!(installed_bundle(&root, "dev.example.app"), legacy); + let current = root.join(".bundles/dev.example.app/bundle"); + std::fs::create_dir_all(¤t).unwrap(); + assert_eq!(installed_bundle(&root, "dev.example.app"), current, "the new layout wins"); + assert_eq!(installed_bundle(&root, "dev.example.none"), root.join("dev.example.none/bundle")); + let _ = std::fs::remove_dir_all(&root); + } use super::*; /// ADR 0004 ยง3: no script app takes a native app's id or namespace. diff --git a/crates/shell/src/host_tools/script_apps.rs b/crates/shell/src/host_tools/script_apps.rs index 61c293cc..56558760 100644 --- a/crates/shell/src/host_tools/script_apps.rs +++ b/crates/shell/src/host_tools/script_apps.rs @@ -149,7 +149,7 @@ fn admitted_bundle(app: &str) -> Result<(PathBuf, PathBuf), String> { let root = octosense_appstore::data_root_if_set().ok_or("App Hub has no apps root yet")?; let bundle = match octosense_appstore::system::system_app(app) { Some(system) => octosense_appstore::system::prepare(&root, &system)?.0, - None => root.join(app).join("bundle"), + None => crate::apps::installed_bundle(&root, app), }; Ok((root, bundle)) }