fix link and remove

This commit is contained in:
2025-12-08 13:22:07 -06:00
parent 401f69b503
commit 270521a01e
3 changed files with 209 additions and 104 deletions

View File

@@ -1,20 +1,27 @@
// link <command> [args]
// Commands:
// add <alias> <path>
// list
// delete <alias>
// clear
// [package] <path|target>
//
// 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 <command> [args]")
log.console("Usage: link <command> [args] or link [package] <target>")
log.console("Commands:")
log.console(" add <alias> <path> Link an alias to a local path or package")
log.console(" list List all active links")
log.console(" delete <alias> Remove a link")
log.console(" clear Remove all links")
log.console(" <path> Link the package in <path> to <path>")
log.console(" <alias> <target> Link <alias> to <target> (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 <alias|package> <path>")
$_.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 <alias|package>")
log.console("Usage: link delete <alias>")
$_.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 <alias>' 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] <target>
// 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 <alias> <target>")
$_.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()

View File

@@ -1,9 +1,9 @@
// cell remove <alias> - Remove a package from dependencies
// cell remove <alias|path> - Remove a package from dependencies or shop
var shop = use('shop')
if (args.length < 1) {
log.console("Usage: cell remove <alias>")
log.console("Usage: cell remove <alias|path>")
$_.stop()
return
}

View File

@@ -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
if (!target_pkg) {
log.error("Package/Dependency not found: " + alias_or_path)
return false
}
// Remove from config
delete config.dependencies[alias]
Shop.save_config(config)
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
}