From e04b15973a1a80482063397cfd26527d6d3ee267 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 9 Jan 2026 07:36:48 -0600 Subject: [PATCH] link --- install.ce | 21 +++++++++++- internal/shop.cm | 12 +++++++ link.cm | 84 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/install.ce b/install.ce index 55bafc60..734e13d7 100644 --- a/install.ce +++ b/install.ce @@ -79,12 +79,20 @@ log.console("Installing " + locator + "...") // Gather all packages that will be installed var packages_to_install = [] +var skipped_packages = [] var visited = {} function gather_packages(pkg_locator) { if (visited[pkg_locator]) return visited[pkg_locator] = true + // Check if this is a local path that doesn't exist + if (pkg_locator.startsWith('/') && !fd.is_dir(pkg_locator)) { + skipped_packages.push(pkg_locator) + log.console(" Skipping missing local package: " + pkg_locator) + return + } + packages_to_install.push(pkg_locator) // Try to read dependencies @@ -123,6 +131,13 @@ if (dry_run) { var exists = lock[p] != null log.console(" " + p + (exists ? " (already installed)" : "")) } + if (skipped_packages.length > 0) { + log.console("") + log.console("Would skip (missing local paths):") + for (var p of skipped_packages) { + log.console(" " + p) + } + } $stop() } @@ -150,6 +165,10 @@ for (var p of packages_to_install) { install_package(p) } -log.console("Installed " + text(packages_to_install.length) + " package(s).") +var summary = "Installed " + text(packages_to_install.length) + " package(s)." +if (skipped_packages.length > 0) { + summary += " Skipped " + text(skipped_packages.length) + " missing local path(s)." +} +log.console(summary) $stop() diff --git a/internal/shop.cm b/internal/shop.cm index 24a9cbdb..099f404c 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -161,6 +161,13 @@ function abs_path_to_package(package_dir) if (package_dir.startsWith(packages_prefix)) return package_dir.substring(packages_prefix.length) + // Check if this local path is the target of a link + // If so, return the canonical package name (link origin) instead + var link_origin = link.get_origin(package_dir) + if (link_origin) { + return link_origin + } + // in this case, the dir is the package if (package_in_shop(package_dir)) return package_dir @@ -987,6 +994,11 @@ Shop.update = function(pkg) { log.console(`checking ${pkg}`) if (info == 'local') { + // Check if local path exists + if (!fd.is_dir(pkg)) { + log.console(` Local path does not exist: ${pkg}`) + return null + } // Local packages always get a lock entry var new_entry = { type: 'local', diff --git a/link.cm b/link.cm index 867066f1..da421352 100644 --- a/link.cm +++ b/link.cm @@ -90,7 +90,7 @@ Link.add = function(canonical, target, shop) { if (!lock[canonical]) { throw new Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical) } - + // Validate target is a valid package if (target.startsWith('/')) { // Local path - must have cell.toml @@ -101,14 +101,44 @@ Link.add = function(canonical, target, shop) { // Remote package target - ensure it's installed shop.get(target) } - + var links = Link.load() links[canonical] = target Link.save(links) - + // Create the symlink immediately Link.sync_one(canonical, target, shop) - + + // Install dependencies of the linked package + // Read the target's cell.toml to find its dependencies + var target_path = target.startsWith('/') ? target : get_package_abs_dir(target) + var toml_path = target_path + '/cell.toml' + if (fd.is_file(toml_path)) { + try { + var content = text(fd.slurp(toml_path)) + var cfg = toml.decode(content) + if (cfg.dependencies) { + for (var alias in cfg.dependencies) { + var dep_locator = cfg.dependencies[alias] + // Skip local dependencies that don't exist + if (dep_locator.startsWith('/') && !fd.is_dir(dep_locator)) { + log.console(" Skipping missing local dependency: " + dep_locator) + continue + } + // Install the dependency if not already in shop + try { + shop.get(dep_locator) + shop.extract(dep_locator) + } catch (e) { + log.console(" Warning: Could not install dependency " + dep_locator + ": " + e.message) + } + } + } + } catch (e) { + log.console(" Warning: Could not read dependencies from " + toml_path) + } + } + log.console("Linked " + canonical + " -> " + target) return true } @@ -177,12 +207,12 @@ Link.sync_one = function(canonical, target, shop) { return true } -// Sync all links - ensure all symlinks are in place +// Sync all links - ensure all symlinks are in place and dependencies are installed Link.sync_all = function(shop) { var links = Link.load() var count = 0 var errors = [] - + for (var canonical in links) { var target = links[canonical] try { @@ -196,14 +226,40 @@ Link.sync_all = function(shop) { errors.push(canonical + ': target ' + link_target + ' is not a valid package') continue } - + Link.sync_one(canonical, target, shop) + + // Install dependencies of the linked package + var toml_path = link_target + '/cell.toml' + try { + var content = text(fd.slurp(toml_path)) + var cfg = toml.decode(content) + if (cfg.dependencies) { + for (var alias in cfg.dependencies) { + var dep_locator = cfg.dependencies[alias] + // Skip local dependencies that don't exist + if (dep_locator.startsWith('/') && !fd.is_dir(dep_locator)) { + continue + } + // Install the dependency if not already in shop + try { + shop.get(dep_locator) + shop.extract(dep_locator) + } catch (e) { + // Silently continue - dependency may already be installed + } + } + } + } catch (e) { + // Could not read dependencies - continue anyway + } + count++ } catch (e) { errors.push(canonical + ': ' + e.message) } } - + return { synced: count, errors: errors } } @@ -219,4 +275,16 @@ Link.get_target = function(canonical) { return links[canonical] || null } +// Get the canonical package name that links to this target (reverse lookup) +// Returns null if no package links to this target +Link.get_origin = function(target) { + var links = Link.load() + for (var origin in links) { + if (links[origin] == target) { + return origin + } + } + return null +} + return Link