From 270521a01e785adde7cefeccbaec9ca01d328a6c Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 8 Dec 2025 13:22:07 -0600 Subject: [PATCH] fix link and remove --- scripts/link.ce | 199 +++++++++++++++++++++++++++------------------- scripts/remove.ce | 4 +- scripts/shop.cm | 110 ++++++++++++++++++++----- 3 files changed, 209 insertions(+), 104 deletions(-) diff --git a/scripts/link.ce b/scripts/link.ce index b4462069..510520ea 100644 --- a/scripts/link.ce +++ b/scripts/link.ce @@ -1,20 +1,27 @@ // link [args] // Commands: -// add // list // delete // clear +// [package] +// +// Examples: +// cell link ../cell-steam (Links package defined in ../cell-steam to that path) +// cell link my-pkg ../cell-steam (Links my-pkg to ../cell-steam) +// cell link my-pkg other-pkg (Links my-pkg to other-pkg) var shop = use('shop') var fd = use('fd') +var toml = use('toml') if (args.length < 1) { - log.console("Usage: link [args]") + log.console("Usage: link [args] or link [package] ") log.console("Commands:") - log.console(" add Link an alias to a local path or package") log.console(" list List all active links") log.console(" delete Remove a link") log.console(" clear Remove all links") + log.console(" Link the package in to ") + log.console(" Link to (path or package)") $_.stop() return } @@ -29,100 +36,126 @@ if (cmd == 'list') { count++ } if (count == 0) log.console("No links.") -} else if (cmd == 'add') { - if (args.length < 3) { - log.console("Usage: link add ") - $_.stop() - return - } - - var target_pkg = args[1] - var target_path = args[2] - - // Resolve target path to absolute - if (fd.is_dir(target_path)) { - target_path = fd.realpath(target_path) - } else { - log.console("Warning: Path '" + target_path + "' does not exist or is not a directory.") - // Try to resolve anyway if relative - if (!target_path.startsWith('/')) { - target_path = fd.realpath('.') + '/' + target_path - } - } - - var canonical_path = null - - // Check if it's an alias in cell.toml - var config = shop.load_config() - if (config.dependencies && config.dependencies[target_pkg]) { - var pkg = config.dependencies[target_pkg] - canonical_path = pkg - } else { - // Assume it's a canonical package name - canonical_path = target_pkg - } - - // Strip version - if (canonical_path.includes('@')) { - canonical_path = canonical_path.split('@')[0] - } - if (canonical_path.startsWith('/')) { - canonical_path = canonical_path.substring(1) - } - - shop.add_link(canonical_path, target_path) - - // Update to apply the link - // If it was an alias, update the alias - if (config.dependencies && config.dependencies[target_pkg]) { - shop.update(target_pkg) - } else { - // If it was a raw package, update it directly - shop.update(canonical_path) - } - -} else if (cmd == 'delete') { +} else if (cmd == 'delete' || cmd == 'rm') { if (args.length < 2) { - log.console("Usage: link delete ") + log.console("Usage: link delete ") $_.stop() return } var target = args[1] - var canonical_path = null - var config = shop.load_config() - if (config.dependencies && config.dependencies[target]) { - var pkg = config.dependencies[target] - canonical_path = pkg - } else { - canonical_path = target - } - - if (canonical_path.includes('@')) { - canonical_path = canonical_path.split('@')[0] - } - if (canonical_path.startsWith('/')) { - canonical_path = canonical_path.substring(1) - } - - if (shop.remove_link(canonical_path)) { - // Update to restore original (download/unpack) - // If we can map back to an alias, use it, otherwise update canonical - if (config.dependencies && config.dependencies[target]) { - shop.update(target) - } else { - shop.update(canonical_path) - } + // Try to remove directly + if (shop.remove_link(target)) { + // Update to restore original if possible + log.console("Restoring " + target + "...") + shop.update(target) } else { log.console("No link found for " + target) } } else if (cmd == 'clear') { shop.clear_links() - log.console("Links cleared. Run 'cell update' or 'link delete ' to restore packages if needed.") + log.console("Links cleared. Run 'cell update' to restore packages if needed.") } else { - log.console("Unknown command: " + cmd) + // Linking logic + var pkg_name = null + var target_path = null + + // Check for 'add' compatibility + var start_idx = 0 + if (cmd == 'add') { + start_idx = 1 + } + + // Parse arguments + // usage: link [pkg] + // valid inputs: + // link ../foo + // link pkg ../foo + // link pkg remote-pkg + + var arg1 = args[start_idx] + var arg2 = (args.length > start_idx + 1) ? args[start_idx + 1] : null + + if (!arg1) { + log.console("Error: specific target or package required") + $_.stop() + return + } + + if (arg2) { + // Two arguments: explicit package name and target + pkg_name = arg1 + target_path = arg2 + } else { + // One argument: assume it's a path, and we need to infer the package name + target_path = arg1 + + // Resolve path to check for cell.toml + var resolved = target_path + if (fd.is_dir(resolved)) resolved = fd.realpath(resolved) + + var toml_path = resolved + '/cell.toml' + if (!fd.is_file(toml_path)) { + log.console("Error: No cell.toml found at " + resolved + ". Cannot infer package name.") + log.console("If you meant to link an alias, provide the target: link ") + $_.stop() + return + } + + // Read package name + try { + var content = toml.decode(text(fd.slurp(toml_path))) + if (content.package) { + pkg_name = content.package + // If package name has version, strip it? + // Usually package = "name", version = "..." in cell.toml? + // Or package = "name" + // Standard is just name. + } else { + log.console("Error: cell.toml at " + resolved + " does not define a 'package' name.") + $_.stop() + return + } + } catch (e) { + log.console("Error reading cell.toml: " + e) + $_.stop() + return + } + + // Ensure target path is fully resolved since we inferred it + if (fd.is_dir(target_path)) target_path = fd.realpath(target_path) + } + + // Process the link + + // 1. Resolve target if it is a directory + if (target_path != "." && fd.is_dir(target_path)) { + target_path = fd.realpath(target_path) + } else if (target_path == ".") { + target_path = fd.realpath(target_path) + } else if (target_path.startsWith('/') || target_path.startsWith('./') || target_path.startsWith('../')) { + // It looks like a path but doesn't exist? + log.console("Warning: Link target '" + target_path + "' does not exist locally. Linking as alias anyway.") + if (target_path.startsWith('./') || target_path.startsWith('../')) { + // Resolve relative path roughly? + var cwd = fd.realpath('.') + // simple concat (fd.realpath typically converts to abspath, but if file missing it might fail or return same) + // Assuming user wants strict path if they used relative. + // Using fd.realpath on CWD + path is safer if we want absolute. + // But we don't have path manipulation lib easily exposed except implicit logic. + // Leaving as provided string if not existing dir. + } + } + + // 2. Add link + shop.add_link(pkg_name, target_path) + + // 3. Update shop + // "Doing this effectively adds another item to the shop" + // We trigger update to ensure the shop recognizes the new link + shop.update(pkg_name) } $_.stop() \ No newline at end of file diff --git a/scripts/remove.ce b/scripts/remove.ce index 394791b2..f47b33b7 100644 --- a/scripts/remove.ce +++ b/scripts/remove.ce @@ -1,9 +1,9 @@ -// cell remove - Remove a package from dependencies +// cell remove - Remove a package from dependencies or shop var shop = use('shop') if (args.length < 1) { - log.console("Usage: cell remove ") + log.console("Usage: cell remove ") $_.stop() return } diff --git a/scripts/shop.cm b/scripts/shop.cm index 1cab4259..63b20802 100644 --- a/scripts/shop.cm +++ b/scripts/shop.cm @@ -595,11 +595,15 @@ Shop.resolve_package_info = function(pkg) { var links = Shop.load_links() if (links[path]) { - return { type: 'package', path: links[path] } + var resolved = links[path] + if (resolved.startsWith('/')) { + return { type: 'local', path: resolved } + } + return { type: 'package', path: resolved } } if (pkg.startsWith('/')) { - return { type: 'package', path: pkg } + return { type: 'local', path: pkg } } if (pkg.includes('gitea.')) { return { type: 'gitea' } @@ -609,6 +613,33 @@ Shop.resolve_package_info = function(pkg) { return { type: 'unknown' } } +// Resolve a filesystem path to a package identifier in the shop +Shop.resolve_path_to_package = function(path_str) { + var abs = fd.realpath(path_str) + if (!abs) return null + + var modules_dir = get_modules_dir() + + // Case 1: Path is inside the modules directory (e.g. downloaded package) + if (abs.startsWith(modules_dir + '/')) { + var rel = abs.substring(modules_dir.length + 1) + // Remove trailing slash if any + if (rel.endsWith('/')) rel = rel.substring(0, rel.length - 1) + return rel + } + + // Case 2: Path is a local directory linked into the shop + // Local links are stored as modules_dir + abs_path (mirrored) + var target = modules_dir + abs + if (fd.is_dir(target)) { + if (abs.startsWith('/')) return abs.substring(1) + return abs + } + + // Case 3: Path is not linked. Return null. + return null +} + // Verify if a package name is valid and return status Shop.verify_package_name = function(pkg) { if (!pkg) throw new Error("Empty package name") @@ -1443,34 +1474,75 @@ function install_zip(zip_blob, target_dir) { // High-level: Remove a package and clean up // Like `bun remove` -Shop.remove = function(alias) { +Shop.remove = function(alias_or_path) { var config = Shop.load_config() - if (!config || !config.dependencies || !config.dependencies[alias]) { - log.error("Dependency not found: " + alias) - return false + var is_dependency = config && config.dependencies && config.dependencies[alias_or_path] + + var target_pkg = null + var locator = null + + if (is_dependency) { + locator = config.dependencies[alias_or_path] + var parsed = Shop.parse_package(locator) + target_pkg = parsed.path + + // Remove from config + delete config.dependencies[alias_or_path] + Shop.save_config(config) + } else { + // Check if it's a resolvable path + var resolved = Shop.resolve_path_to_package(alias_or_path) + if (resolved) { + target_pkg = resolved + } else { + // Check if alias_or_path exists directly in modules dir? + var direct_path = get_modules_dir() + '/' + alias_or_path + if (fd.exists(direct_path)) { + target_pkg = alias_or_path + } + } } - - var locator = config.dependencies[alias] - var parsed = Shop.parse_package(locator) - var target_dir = get_modules_dir() + '/' + parsed.path - - // Remove from config - delete config.dependencies[alias] - Shop.save_config(config) + + if (!target_pkg) { + log.error("Package/Dependency not found: " + alias_or_path) + return false + } + + var target_dir = get_modules_dir() + '/' + target_pkg // Remove from lock var lock = Shop.load_lock() - if (lock[locator]) delete lock[locator] - if (lock[alias]) delete lock[alias] // Cleanup old format - Shop.save_lock(lock) + var lock_changed = false + + if (locator && lock[locator]) { + delete lock[locator] + lock_changed = true + } + if (lock[alias_or_path]) { + delete lock[alias_or_path] + lock_changed = true + } + + // Scan for any entry pointing to this target + for (var k in lock) { + if (lock[k].package == target_pkg || lock[k].package == '/' + target_pkg) { + delete lock[k] + lock_changed = true + } + } + + if (lock_changed) Shop.save_lock(lock) // Remove directory - if (fd.is_dir(target_dir)) { + if (fd.is_link(target_dir)) { + log.console("Unlinking " + target_dir) + fd.unlink(target_dir) + } else if (fd.is_dir(target_dir)) { log.console("Removing " + target_dir) fd.rmdir(target_dir) } - log.console("Removed " + alias) + log.console("Removed " + (is_dependency ? alias_or_path : target_pkg)) return true }