better commands

This commit is contained in:
2025-12-27 13:51:11 -06:00
parent c8831c85d0
commit 4f076bc868
10 changed files with 382 additions and 24 deletions

17
add.ce
View File

@@ -1,17 +1,26 @@
// cell get <locator> [alias] - Add and install a package with its dependencies
// cell add <locator> [alias] - Add and install a package with its dependencies
var shop = use('internal/shop')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell get <locator> [alias]")
log.console("Usage: cell add <locator> [alias]")
log.console("Examples:")
log.console(" cell get gitea.pockle.world/john/prosperon@main")
log.console(" cell get github.com/user/repo@v1.0.0 myalias")
log.console(" cell add gitea.pockle.world/john/prosperon@main")
log.console(" cell add github.com/user/repo@v1.0.0 myalias")
$stop()
return
}
var locator = args[0]
// Resolve relative paths to absolute paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
var alias = args.length > 1 ? args[1] : null
shop.get(locator, alias)

122
clone.ce Normal file
View File

@@ -0,0 +1,122 @@
// cell clone <origin> <path>
// Clones a cell package <origin> to the local <path>, and links it.
var shop = use('internal/shop')
var link = use('link')
var fd = use('fd')
var http = use('http')
var miniz = use('miniz')
if (args.length < 2) {
log.console("Usage: cell clone <origin> <path>")
log.console("Clones a cell package to a local path and links it.")
$stop()
return
}
var origin = args[0]
var target_path = args[1]
// Resolve target path to absolute
if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith('../')) {
var resolved = fd.realpath(target_path)
if (resolved) {
target_path = resolved
} else {
// Path doesn't exist yet, resolve relative to cwd
var cwd = fd.realpath('.')
if (target_path == '.') {
target_path = cwd
} else if (target_path.startsWith('./')) {
target_path = cwd + target_path.substring(1)
} else if (target_path.startsWith('../')) {
// Go up one directory from cwd
var parent = cwd.substring(0, cwd.lastIndexOf('/'))
target_path = parent + target_path.substring(2)
}
}
}
// Check if target already exists
if (fd.is_dir(target_path)) {
log.console("Error: " + target_path + " already exists")
$stop()
return
}
log.console("Cloning " + origin + " to " + target_path + "...")
// Get the latest commit
var info = shop.resolve_package_info(origin)
if (!info || info == 'local') {
log.console("Error: " + origin + " is not a remote package")
$stop()
return
}
// Update to get the commit hash
var update_result = shop.update(origin)
if (!update_result) {
log.console("Error: Could not fetch " + origin)
$stop()
return
}
// Fetch and extract to the target path
var lock = shop.load_lock()
var entry = lock[origin]
if (!entry || !entry.commit) {
log.console("Error: No commit found for " + origin)
$stop()
return
}
var download_url = shop.get_download_url(origin, entry.commit)
log.console("Downloading from " + download_url)
try {
var zip_blob = http.fetch(download_url)
// Extract zip to target path
var zip = miniz.read(zip_blob)
if (!zip) {
log.console("Error: Failed to read zip archive")
$stop()
return
}
// Create target directory
fd.mkdir(target_path)
var count = zip.count()
for (var i = 0; i < count; i++) {
if (zip.is_directory(i)) continue
var filename = zip.get_filename(i)
var parts = filename.split('/')
if (parts.length <= 1) continue
// Skip the first directory (repo-commit prefix)
parts.shift()
var rel_path = parts.join('/')
var full_path = target_path + '/' + rel_path
var dir_path = full_path.substring(0, full_path.lastIndexOf('/'))
// Ensure directory exists
if (!fd.is_dir(dir_path)) {
fd.mkdir(dir_path)
}
fd.slurpwrite(full_path, zip.slurp(filename))
}
log.console("Extracted to " + target_path)
// Link the origin to the cloned path
link.add(origin, target_path, shop)
log.console("Linked " + origin + " -> " + target_path)
} catch (e) {
log.console("Error: " + e.message)
if (e.stack) log.console(e.stack)
}
$stop()

View File

@@ -3,6 +3,7 @@
var shop = use('internal/shop')
var build = use('build')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell install <locator>")
@@ -12,21 +13,50 @@ if (args.length < 1) {
var locator = args[0]
// Resolve relative paths to absolute paths
// Local paths like '.' or '../foo' need to be converted to absolute paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
log.console("Installing " + locator + "...")
if (!shop.update(locator)) {
log.console("Failed to install " + locator)
$stop()
return
var pkg = use('package')
// Recursive install function that handles dependencies
function install_package(pkg_locator, visited) {
if (visited[pkg_locator]) return
visited[pkg_locator] = true
// First, add to lock.toml
shop.update(pkg_locator)
// Extract/symlink the package so we can read its cell.toml
shop.extract(pkg_locator)
// Now get direct dependencies and install them first
try {
var deps = pkg.dependencies(pkg_locator)
if (deps) {
for (var alias in deps) {
var dep_locator = deps[alias]
log.console("Installing dependency " + dep_locator)
install_package(dep_locator, visited)
}
}
} catch (e) {
// Package might not have dependencies or cell.toml issue
log.console("Warning: Could not read dependencies for " + pkg_locator + ": " + e.message)
}
var deps = shop.list_packages(locator)
for (var dep of deps) {
log.console("Installing dependency " + dep)
shop.update(dep)
build.build_package(dep)
// Build the package after all dependencies are installed
build.build_package(pkg_locator)
}
build.build_package(locator)
install_package(locator, {})
log.console("Installed " + locator)
$stop()

View File

@@ -224,6 +224,10 @@ function get_canonical_package(alias, package_context) {
// guaranteed to be validated
function safe_package_path(pkg)
{
// For absolute paths, replace / with _ to create a valid directory name
// Also replace @ with _
if (pkg && pkg.startsWith('/'))
return pkg.replaceAll('/', '_').replaceAll('@', '_')
return pkg.replaceAll('@', '_')
}
@@ -459,7 +463,15 @@ function resolve_locator(path, ctx)
}
// check in ctx package
var ctx_path = get_packages_dir() + '/' + safe_package_path(ctx) + '/' + path
// If ctx is an absolute path (starts with /), use it directly
// Otherwise, look it up in the packages directory
var ctx_dir
if (ctx.startsWith('/')) {
ctx_dir = ctx
} else {
ctx_dir = get_packages_dir() + '/' + safe_package_path(ctx)
}
var ctx_path = ctx_dir + '/' + path
if (fd.is_file(ctx_path)) {
var fn = resolve_mod_fn(ctx_path, ctx)
@@ -818,7 +830,7 @@ function get_cache_path(pkg, commit) {
function get_package_abs_dir(package)
{
return get_packages_dir() + '/' + package
return get_packages_dir() + '/' + safe_package_path(package)
}
// Fetch the latest commit hash from remote for a package
@@ -1195,4 +1207,29 @@ Shop.audit_packages = function() {
return bad
}
// Parse a package locator and return info about it
// Returns { path: canonical_path, name: package_name, type: 'local'|'gitea'|null }
Shop.parse_package = function(locator) {
if (!locator) return null
// Strip version suffix if present
var clean = locator
if (locator.includes('@')) {
clean = locator.split('@')[0]
}
var info = Shop.resolve_package_info(clean)
if (!info) return null
// Extract package name (last component of path)
var parts = clean.split('/')
var name = parts[parts.length - 1]
return {
path: clean,
name: name,
type: info
}
}
return Shop

View File

@@ -20,11 +20,14 @@ function get_packages_dir() {
// return the safe path for the package
function safe_package_path(pkg) {
// For absolute paths, replace / with _ to create a valid directory name
if (pkg && pkg.startsWith('/'))
return pkg.replaceAll('/', '_').replaceAll('@', '_')
return pkg.replaceAll('@', '_')
}
function get_package_abs_dir(package) {
return get_packages_dir() + '/' + package
return get_packages_dir() + '/' + safe_package_path(package)
}
function ensure_dir(path) {

View File

@@ -4,9 +4,26 @@ var fd = use('fd')
var toml = use('toml')
var os = use('os')
// Convert package name to a safe directory name
// For absolute paths (local packages), replace / with _
// For remote packages, keep slashes as they use nested directories
function safe_package_path(pkg) {
if (!pkg) return pkg
if (pkg.startsWith('/'))
return pkg.replaceAll('/', '_').replaceAll('@', '_')
return pkg.replaceAll('@', '_')
}
function get_path(name)
{
return os.global_shop_path + '/packages/' + name
// If name is null, return the current project directory
if (!name)
return fd.realpath('.')
// If name is already an absolute path, use it directly
if (name.startsWith('/'))
return name
// Remote packages use nested directories, so don't transform slashes
return os.global_shop_path + '/packages/' + name.replaceAll('@', '_')
}
package.load_config = function(name)

View File

@@ -1,6 +1,7 @@
// cell remove <alias|path> - Remove a package from dependencies or shop
var shop = use('internal/shop')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell remove <alias|path>")
@@ -8,6 +9,16 @@ if (args.length < 1) {
return
}
shop.remove(args[0])
var pkg = args[0]
// Resolve relative paths to absolute paths
if (pkg == '.' || pkg.startsWith('./') || pkg.startsWith('../') || fd.is_dir(pkg)) {
var resolved = fd.realpath(pkg)
if (resolved) {
pkg = resolved
}
}
shop.remove(pkg)
$stop()

81
search.ce Normal file
View File

@@ -0,0 +1,81 @@
// cell search <query>
// Searches for packages matching <query>, or actors or modules within them.
var shop = use('internal/shop')
var pkg = use('package')
if (args.length < 1) {
log.console("Usage: cell search <query>")
log.console("Searches for packages, actors, or modules matching the query.")
$stop()
return
}
var query = args[0].toLowerCase()
var found_packages = []
var found_modules = []
var found_actors = []
// Search through all installed packages
var packages = shop.list_packages()
for (var package_name of packages) {
// Check if package name matches
if (package_name.toLowerCase().includes(query)) {
found_packages.push(package_name)
}
// Search modules and actors within the package
try {
var modules = pkg.list_modules(package_name)
for (var mod of modules) {
if (mod.toLowerCase().includes(query)) {
found_modules.push(package_name + ':' + mod)
}
}
var actors = pkg.list_programs(package_name)
for (var actor of actors) {
if (actor.toLowerCase().includes(query)) {
found_actors.push(package_name + ':' + actor)
}
}
} catch (e) {
// Skip packages that can't be read
}
}
// Print results
var total = found_packages.length + found_modules.length + found_actors.length
if (total == 0) {
log.console("No results found for '" + query + "'")
} else {
log.console("Found " + text(total) + " result(s) for '" + query + "':")
log.console("")
if (found_packages.length > 0) {
log.console("Packages:")
for (var p of found_packages) {
log.console(" " + p)
}
log.console("")
}
if (found_modules.length > 0) {
log.console("Modules:")
for (var m of found_modules) {
log.console(" " + m)
}
log.console("")
}
if (found_actors.length > 0) {
log.console("Actors:")
for (var a of found_actors) {
log.console(" " + a)
}
}
}
$stop()

33
unlink.ce Normal file
View File

@@ -0,0 +1,33 @@
// cell unlink <origin>
// Unlinks <origin> from its <target> from a previous link.
var link = use('link')
var shop = use('internal/shop')
if (args.length < 1) {
log.console("Usage: cell unlink <origin>")
log.console("Removes a link and restores the original package.")
$stop()
return
}
var origin = args[0]
if (link.remove(origin)) {
log.console("Removed link for " + origin)
// Try to restore the original package
log.console("Restoring " + origin + "...")
try {
shop.fetch(origin)
shop.extract(origin)
log.console("Restored " + origin)
} catch (e) {
log.console("Could not restore: " + e.message)
log.console("Run 'cell update " + origin + "' to restore")
}
} else {
log.console("No link found for " + origin)
}
$stop()

View File

@@ -9,6 +9,7 @@
// cell update <package> - Update a specific package
var shop = use('internal/shop')
var fd = use('fd')
var target_pkg = null
@@ -26,6 +27,13 @@ for (var i = 0; i < args.length; i++) {
$stop()
} else if (!args[i].startsWith('-')) {
target_pkg = args[i]
// Resolve relative paths to absolute paths
if (target_pkg == '.' || target_pkg.startsWith('./') || target_pkg.startsWith('../') || fd.is_dir(target_pkg)) {
var resolved = fd.realpath(target_pkg)
if (resolved) {
target_pkg = resolved
}
}
}
}
@@ -37,9 +45,16 @@ function update_and_fetch(pkg)
var new_entry = shop.update(pkg)
if (new_entry && new_entry.commit) {
log.console(" " + pkg + " " + old_commit.substring(0, 8) + " -> " + new_entry.commit.substring(0, 8))
if (new_entry) {
if (new_entry.commit) {
var old_str = old_commit ? old_commit.substring(0, 8) : "(new)"
log.console(" " + pkg + " " + old_str + " -> " + new_entry.commit.substring(0, 8))
shop.fetch(pkg)
} else {
// Local package - just ensure symlink is correct
log.console(" " + pkg + " (local)")
}
shop.extract(pkg)
shop.build_package_scripts(pkg)
return true
}