From fc61b7847daf9ae97d1ae2f51340fa47ca1563cc Mon Sep 17 00:00:00 2001 From: Matt Armstrong Date: Thu, 12 Feb 2026 17:45:18 -0800 Subject: [PATCH] fix(cli): use correct registry root for dependency asset copying The `dx components add` command failed to copy assets for dependencies that resolved to a different registry than the primary component. This happened because `add_component` used a single `registry_root` (that of the primary component) to calculate the relative paths of assets for all components, matching the legacy assumption that all components came from the same source tree. However, dependencies can be resolved from different registries. For example, if `date_picker` is requested from a specific git revision but depends on `calendar` (referenced as a builtin), `calendar` is loaded from the default registry cache. Attempting to strip the `date_picker` registry root from the `calendar` asset path fails, causing the command to error or silently skip assets. This patch: - Adds a `registry_root` field to `ResolvedComponent` to track the origin of each component instance. - Updates `read_component` and `discover_components` to populate and propagate this field during loading. - Updates `copy_global_assets` to use the component's own stored `registry_root` for path calculations instead of the argument passed to `add_component`. --- packages/cli/src/cli/component.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/cli/component.rs b/packages/cli/src/cli/component.rs index 1ac537d346..3d208d70bf 100644 --- a/packages/cli/src/cli/component.rs +++ b/packages/cli/src/cli/component.rs @@ -477,7 +477,7 @@ impl ComponentRegistry { async fn read_components(&self) -> Result> { let path = self.resolve().await?; - let root = read_component(&path).await?; + let root = read_component(&path, &path).await?; let mut components = discover_components(root).await?; // Filter out any virtual components with members @@ -495,6 +495,8 @@ impl ComponentRegistry { /// A component that has been downloaded and resolved at a specific path #[derive(Clone, Debug, PartialEq, Eq, Hash)] struct ResolvedComponent { + /// The root of the registry that this component belongs to + registry_root: PathBuf, path: PathBuf, component: Component, } @@ -598,7 +600,9 @@ async fn add_component( // Copy any global assets let assets_root = global_assets_root(assets_path, config).await?; - copy_global_assets(registry_root, &assets_root, component).await?; + // Copy any global assets + let assets_root = global_assets_root(assets_path, config).await?; + copy_global_assets(&assets_root, component).await?; // Add the module to the components mod.rs let mod_rs_path = components_root.join("mod.rs"); @@ -742,7 +746,7 @@ async fn ensure_components_module_exists(components_dir: &Path) -> Result } /// Read a component from the given path -async fn read_component(path: &Path) -> Result { +async fn read_component(path: &Path, registry_root: &Path) -> Result { let json_path = path.join("component.json"); let bytes = tokio::fs::read(&json_path).await.with_context(|| { format!( @@ -754,6 +758,7 @@ async fn read_component(path: &Path) -> Result { let component = serde_json::from_slice(&bytes)?; let absolute_path = dunce::canonicalize(path)?; Ok(ResolvedComponent { + registry_root: registry_root.to_path_buf(), path: absolute_path, component, }) @@ -762,15 +767,19 @@ async fn read_component(path: &Path) -> Result { /// Recursively discover all components starting from the root component async fn discover_components(root: ResolvedComponent) -> Result> { // Create a queue of members to read - let mut queue = root.member_paths(); + let mut queue = root + .member_paths() + .into_iter() + .map(|path| (path, root.registry_root.clone())) + .collect::>(); // The list of discovered components let mut components = vec![root]; // The set of pending read tasks let mut pending = JoinSet::new(); loop { // First, spawn tasks for all queued paths - while let Some(root_path) = queue.pop() { - pending.spawn(async move { read_component(&root_path).await }); + while let Some((root_path, registry_root)) = queue.pop() { + pending.spawn(async move { read_component(&root_path, ®istry_root).await }); } // Then try to join the next task let Some(component) = pending.join_next().await else { @@ -778,7 +787,12 @@ async fn discover_components(root: ResolvedComponent) -> Result Result Result<()> { + let registry_root = &component.registry_root; let canonical_registry_root = dunce::canonicalize(registry_root)?; for path in &component.global_assets { let src = component.path.join(path);