From 1a69d24fde9dbdabe7b37bef6baa51832b7d3601 Mon Sep 17 00:00:00 2001 From: Dusty Date: Thu, 22 Oct 2026 00:39:58 -0700 Subject: [PATCH] fix(desktop): install a release from before the toolkit, which has no shared or toolkit manifest #784 added shared and toolkit to the packages whose manifests preparation reads, but the latest release, v0.2.0, predates it: shared is plain source and there is no toolkit. A desktop built from main failed every new install with 'shared/package.json: No such file or directory'. Those two are now read only when the release has them; the four every release has stay required. --- CHANGELOG.md | 7 +++++ desktop/src-tauri/src/preparation.rs | 42 ++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da2b039241..b6feabb2bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,13 @@ apps, which nobody could connect there. An app's own page, reached from an older available yet and that an administrator has to finish setting it up, instead of naming the setting. The administrator's page for the app still names `COMPOSIO_API_KEY`. +### A new desktop install of v0.2.0 gets past Install again + +A desktop app built from `main` stopped every new install at "Installing OpenBot" with +`shared/package.json: No such file or directory`. It had started reading `shared` and `toolkit` as +packages, which v0.2.0 does not have: there `shared` is plain source and there is no `toolkit`. It now +reads them only when the release includes them. + ### A new desktop install starts its server again A desktop install of v0.2.0 stopped at start with `server stopped straight away (exit status: 1)` and diff --git a/desktop/src-tauri/src/preparation.rs b/desktop/src-tauri/src/preparation.rs index 0747dae969..595d161a57 100644 --- a/desktop/src-tauri/src/preparation.rs +++ b/desktop/src-tauri/src/preparation.rs @@ -12,6 +12,17 @@ pub const FILE: &str = ".openbot-prepared.json"; const DEPENDENCIES: &str = ".openbot-dependencies.json"; const LAUNCH: &str = ".openbot-launched.json"; const PACKAGES: [&str; 6] = ["", "server", "app", "worker", "shared", "toolkit"]; +/// Packages a release has only since #784. v0.2.0, still the latest release when this desktop was +/// built, has `shared` as plain source and no `toolkit`; requiring their manifests failed every new +/// install with "shared/package.json: No such file or directory". +const LATER_PACKAGES: [&str; 2] = ["shared", "toolkit"]; + +/// The packages this release actually has: every one in `PACKAGES`, less a later one it predates. +fn packages(root: &Path) -> impl Iterator + '_ { + PACKAGES.into_iter().filter(move |package| { + !LATER_PACKAGES.contains(package) || root.join(package).join("package.json").is_file() + }) +} #[derive(Serialize, Deserialize)] struct Dependencies { @@ -124,7 +135,7 @@ fn dependency_fingerprint(root: &Path) -> Result { hash_files( root, std::iter::once(PathBuf::from("bun.lock")) - .chain(PACKAGES.map(|path| Path::new(path).join("package.json"))), + .chain(packages(root).map(|path| Path::new(path).join("package.json"))), ) } @@ -165,7 +176,7 @@ pub fn invalidate(root: &Path) -> Result<(), Problem> { /// Record resolved package manifests, not just the existence of a partial node_modules directory. fn package_files(root: &Path) -> Result, Problem> { let mut files = BTreeSet::new(); - for package in PACKAGES { + for package in packages(root) { let directory = root.join(package); let manifest: serde_json::Value = serde_json::from_slice( &std::fs::read(directory.join("package.json")).map_err(|e| required(e.to_string()))?, @@ -354,6 +365,33 @@ pub fn image_settings( mod tests { use super::*; + #[test] + fn a_release_from_before_the_toolkit_needs_no_shared_or_toolkit_manifest() { + let root = crate::test_support::temp_root("preparation-v020-layout"); + for package in ["", "server", "app", "worker"] { + std::fs::create_dir_all(root.join(package)).unwrap(); + std::fs::write(root.join(package).join("package.json"), "{}").unwrap(); + } + // v0.2.0: `shared` is source with no manifest, and there is no `toolkit`. + std::fs::create_dir_all(root.join("shared")).unwrap(); + assert_eq!( + packages(&root).collect::>(), + ["", "server", "app", "worker"] + ); + std::fs::write(root.join("bun.lock"), "lock").unwrap(); + assert!(dependency_fingerprint(&root).is_ok()); + assert!(package_files(&root).is_ok()); + // A release that has them is held to them. + for package in ["shared", "toolkit"] { + std::fs::create_dir_all(root.join(package)).unwrap(); + std::fs::write(root.join(package).join("package.json"), "{}").unwrap(); + } + assert_eq!(packages(&root).count(), PACKAGES.len()); + // The packages every release has stay required. + std::fs::remove_file(root.join("server").join("package.json")).unwrap(); + assert!(dependency_fingerprint(&root).is_err()); + } + struct Fixture { root: PathBuf, bun: PathBuf,