clean up cmd line

This commit is contained in:
2026-01-09 05:37:37 -06:00
parent 8403883b9d
commit d044bde4f9
12 changed files with 1507 additions and 183 deletions

99
add.ce
View File

@@ -1,18 +1,43 @@
// cell add <locator> [alias] - Add and install a package with its dependencies
// cell add <locator> [alias] - Add a dependency to the current package
//
// Usage:
// cell add <locator> Add a dependency using default alias
// cell add <locator> <alias> Add a dependency with custom alias
//
// This adds the dependency to cell.toml and installs it to the shop.
var shop = use('internal/shop')
var pkg = use('package')
var build = use('build')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell add <locator> [alias]")
log.console("Examples:")
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 = null
var alias = null
for (var i = 0; i < args.length; i++) {
if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell add <locator> [alias]")
log.console("")
log.console("Add a dependency to the current package.")
log.console("")
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon")
log.console(" cell add gitea.pockle.world/john/cell-image image")
log.console(" cell add ../local-package")
$stop()
} else if (!args[i].startsWith('-')) {
if (!locator) {
locator = args[i]
} else if (!alias) {
alias = args[i]
}
}
}
var locator = args[0]
if (!locator) {
log.console("Usage: cell add <locator> [alias]")
$stop()
}
// Resolve relative paths to absolute paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
@@ -21,8 +46,58 @@ if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || f
locator = resolved
}
}
var alias = args.length > 1 ? args[1] : null
shop.get(locator, alias)
// Generate default alias from locator
if (!alias) {
// Use the last component of the locator as alias
var parts = locator.split('/')
alias = parts[parts.length - 1]
// Remove any version suffix
if (alias.includes('@')) {
alias = alias.split('@')[0]
}
}
$stop()
// Check we're in a package directory
var cwd = fd.realpath('.')
if (!fd.is_file(cwd + '/cell.toml')) {
log.error("Not in a package directory (no cell.toml found)")
$stop()
}
log.console("Adding " + locator + " as '" + alias + "'...")
// Add to local project's cell.toml
try {
pkg.add_dependency(null, locator, alias)
log.console(" Added to cell.toml")
} catch (e) {
log.error("Failed to update cell.toml: " + e)
$stop()
}
// Install to shop
try {
shop.get(locator)
shop.extract(locator)
// Build scripts
shop.build_package_scripts(locator)
// Build C code if any
try {
var target = build.detect_host_target()
build.build_dynamic(locator, target, 'release')
} catch (e) {
// Not all packages have C code
}
log.console(" Installed to shop")
} catch (e) {
log.error("Failed to install: " + e)
$stop()
}
log.console("Added " + alias + " (" + locator + ")")
$stop()

View File

@@ -1,9 +1,11 @@
// cell build [options] - Build dynamic libraries locally for the current machine
// cell build [<locator>] - Build dynamic libraries locally for the current machine
//
// Usage:
// cell build Build dynamic libraries for all packages
// cell build -p <pkg> Build dynamic library for specific package
// cell build Build dynamic libraries for all packages in shop
// cell build . Build dynamic library for current directory package
// cell build <locator> Build dynamic library for specific package
// cell build -t <target> Cross-compile dynamic libraries for target platform
// cell build -b <type> Build type: release (default), debug, or minsize
var build = use('build')
var shop = use('internal/shop')
@@ -12,7 +14,9 @@ var fd = use('fd')
var target = null
var target_package = null
var buildtype = 'debug'
var buildtype = 'release'
var force_rebuild = false
var dry_run = false
for (var i = 0; i < args.length; i++) {
if (args[i] == '-t' || args[i] == '--target') {
@@ -23,6 +27,7 @@ for (var i = 0; i < args.length; i++) {
$stop()
}
} else if (args[i] == '-p' || args[i] == '--package') {
// Legacy support for -p flag
if (i + 1 < args.length) {
target_package = args[++i]
} else {
@@ -40,6 +45,10 @@ for (var i = 0; i < args.length; i++) {
log.error('-b requires a buildtype (release, debug, minsize)')
$stop()
}
} else if (args[i] == '--force') {
force_rebuild = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--list-targets') {
log.console('Available targets:')
var targets = build.list_targets()
@@ -47,6 +56,19 @@ for (var i = 0; i < args.length; i++) {
log.console(' ' + targets[t])
}
$stop()
} else if (!args[i].startsWith('-') && !target_package) {
// Positional argument - treat as package locator
target_package = args[i]
}
}
// Resolve local paths to absolute paths
if (target_package) {
if (target_package == '.' || target_package.startsWith('./') || target_package.startsWith('../') || fd.is_dir(target_package)) {
var resolved = fd.realpath(target_package)
if (resolved) {
target_package = resolved
}
}
}

226
clean.ce
View File

@@ -1,26 +1,218 @@
// cell clean - Remove build artifacts from global shop
// cell clean [<scope>] - Remove cached material to force refetch/rebuild
//
// Usage:
// cell clean Clean build outputs for current directory package
// cell clean . Clean build outputs for current directory package
// cell clean <locator> Clean build outputs for specific package
// cell clean shop Clean entire shop
// cell clean world Clean all world packages
//
// Options:
// --build Remove build outputs only (default)
// --fetch Remove fetched sources only
// --all Remove both build outputs and fetched sources
// --deep Apply to full dependency closure
// --dry-run Show what would be deleted
var fd = use('fd')
var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var build_dir = shop.get_shop_path() + '/build'
var scope = null
var clean_build = false
var clean_fetch = false
var deep = false
var dry_run = false
if (!fd.is_dir(build_dir)) {
log.console("No build directory found at " + build_dir)
$stop()
return
for (var i = 0; i < args.length; i++) {
if (args[i] == '--build') {
clean_build = true
} else if (args[i] == '--fetch') {
clean_fetch = true
} else if (args[i] == '--all') {
clean_build = true
clean_fetch = true
} else if (args[i] == '--deep') {
deep = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell clean [<scope>] [options]")
log.console("")
log.console("Remove cached material to force refetch/rebuild.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Clean specific package")
log.console(" shop Clean entire shop")
log.console(" world Clean all world packages")
log.console("")
log.console("Options:")
log.console(" --build Remove build outputs only (default)")
log.console(" --fetch Remove fetched sources only")
log.console(" --all Remove both build outputs and fetched sources")
log.console(" --deep Apply to full dependency closure")
log.console(" --dry-run Show what would be deleted")
$stop()
} else if (!args[i].startsWith('-')) {
scope = args[i]
}
}
log.console("Cleaning build artifacts...")
// Remove the build directory
try {
fd.rm(build_dir)
log.console("Build directory removed: " + build_dir)
} catch (e) {
log.error(e)
// Default to --build if nothing specified
if (!clean_build && !clean_fetch) {
clean_build = true
}
log.console("Clean complete!")
// Default scope to current directory
if (!scope) {
scope = '.'
}
$stop()
// Resolve local paths for single package scope
var is_shop_scope = (scope == 'shop')
var is_world_scope = (scope == 'world')
if (!is_shop_scope && !is_world_scope) {
if (scope == '.' || scope.startsWith('./') || scope.startsWith('../') || fd.is_dir(scope)) {
var resolved = fd.realpath(scope)
if (resolved) {
scope = resolved
}
}
}
var files_to_delete = []
var dirs_to_delete = []
// Gather packages to clean
var packages_to_clean = []
if (is_shop_scope) {
packages_to_clean = shop.list_packages()
} else if (is_world_scope) {
// For now, world is the same as shop
packages_to_clean = shop.list_packages()
} else {
// Single package
packages_to_clean.push(scope)
if (deep) {
try {
var deps = pkg.gather_dependencies(scope)
for (var dep of deps) {
packages_to_clean.push(dep)
}
} catch (e) {
// Skip if can't read dependencies
}
}
}
// Gather files to clean
var lib_dir = shop.get_lib_dir()
var build_dir = shop.get_build_dir()
var packages_dir = shop.get_package_dir('').replace(/\/$/, '') // Get base packages dir
if (clean_build) {
if (is_shop_scope) {
// Clean entire build and lib directories
if (fd.is_dir(build_dir)) {
dirs_to_delete.push(build_dir)
}
if (fd.is_dir(lib_dir)) {
dirs_to_delete.push(lib_dir)
}
} else {
// Clean specific package libraries
for (var p of packages_to_clean) {
if (p == 'core') continue
var lib_name = shop.lib_name_for_package(p)
var dylib_ext = '.dylib'
var lib_path = lib_dir + '/' + lib_name + dylib_ext
if (fd.is_file(lib_path)) {
files_to_delete.push(lib_path)
}
// Also check for .so and .dll
var so_path = lib_dir + '/' + lib_name + '.so'
var dll_path = lib_dir + '/' + lib_name + '.dll'
if (fd.is_file(so_path)) {
files_to_delete.push(so_path)
}
if (fd.is_file(dll_path)) {
files_to_delete.push(dll_path)
}
}
}
}
if (clean_fetch) {
if (is_shop_scope) {
// Clean entire packages directory (dangerous!)
if (fd.is_dir(packages_dir)) {
dirs_to_delete.push(packages_dir)
}
} else {
// Clean specific package directories
for (var p of packages_to_clean) {
if (p == 'core') continue
var pkg_dir = shop.get_package_dir(p)
if (fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)) {
dirs_to_delete.push(pkg_dir)
}
}
}
}
// Execute or report
if (dry_run) {
log.console("Would delete:")
if (files_to_delete.length == 0 && dirs_to_delete.length == 0) {
log.console(" (nothing to clean)")
} else {
for (var f of files_to_delete) {
log.console(" [file] " + f)
}
for (var d of dirs_to_delete) {
log.console(" [dir] " + d)
}
}
} else {
var deleted_count = 0
for (var f of files_to_delete) {
try {
fd.unlink(f)
log.console("Deleted: " + f)
deleted_count++
} catch (e) {
log.error("Failed to delete " + f + ": " + e)
}
}
for (var d of dirs_to_delete) {
try {
if (fd.is_link(d)) {
fd.unlink(d)
} else {
fd.rmdir(d, 1) // recursive
}
log.console("Deleted: " + d)
deleted_count++
} catch (e) {
log.error("Failed to delete " + d + ": " + e)
}
}
if (deleted_count == 0) {
log.console("Nothing to clean.")
} else {
log.console("")
log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.")
}
}
$stop()

236
graph.ce Normal file
View File

@@ -0,0 +1,236 @@
// cell graph [<locator>] - Emit dependency graph
//
// Usage:
// cell graph Graph current directory package
// cell graph . Graph current directory package
// cell graph <locator> Graph specific package
// cell graph --world Graph all packages in shop (world set)
//
// Options:
// --format <fmt> Output format: tree (default), dot, json
// --resolved Show resolved view with links applied (default)
// --locked Show lock view without links
// --world Graph all packages in shop
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var fd = use('fd')
var json = use('json')
var target_locator = null
var format = 'tree'
var show_locked = false
var show_world = false
for (var i = 0; i < args.length; i++) {
if (args[i] == '--format' || args[i] == '-f') {
if (i + 1 < args.length) {
format = args[++i]
if (format != 'tree' && format != 'dot' && format != 'json') {
log.error('Invalid format: ' + format + '. Must be tree, dot, or json')
$stop()
}
} else {
log.error('--format requires a format type')
$stop()
}
} else if (args[i] == '--resolved') {
show_locked = false
} else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--world') {
show_world = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell graph [<locator>] [options]")
log.console("")
log.console("Emit the dependency graph.")
log.console("")
log.console("Options:")
log.console(" --format <fmt> Output format: tree (default), dot, json")
log.console(" --resolved Show resolved view with links applied (default)")
log.console(" --locked Show lock view without links")
log.console(" --world Graph all packages in shop")
$stop()
} else if (!args[i].startsWith('-')) {
target_locator = args[i]
}
}
var links = show_locked ? {} : link.load()
// Get effective locator (after links)
function get_effective(locator) {
return links[locator] || locator
}
// Build graph data structure
var nodes = {}
var edges = []
function add_node(locator) {
if (nodes[locator]) return
var lock = shop.load_lock()
var lock_entry = lock[locator]
var link_target = links[locator]
var info = shop.resolve_package_info(locator)
nodes[locator] = {
id: locator,
effective: get_effective(locator),
linked: link_target != null,
local: info == 'local',
commit: lock_entry && lock_entry.commit ? lock_entry.commit.substring(0, 8) : null
}
}
function gather_graph(locator, visited) {
if (visited[locator]) return
visited[locator] = true
add_node(locator)
try {
var deps = pkg.dependencies(locator)
if (deps) {
for (var alias in deps) {
var dep_locator = deps[alias]
add_node(dep_locator)
edges.push({ from: locator, to: dep_locator, alias: alias })
gather_graph(dep_locator, visited)
}
}
} catch (e) {
// Package might not have dependencies
}
}
// Gather graph from roots
var roots = []
if (show_world) {
// Use all packages in shop as roots
var packages = shop.list_packages()
for (var p of packages) {
if (p != 'core') {
roots.push(p)
}
}
} else {
// Default to current directory
if (!target_locator) {
target_locator = '.'
}
// Resolve local paths
if (target_locator == '.' || target_locator.startsWith('./') || target_locator.startsWith('../') || fd.is_dir(target_locator)) {
var resolved = fd.realpath(target_locator)
if (resolved) {
target_locator = resolved
}
}
roots.push(target_locator)
}
for (var root of roots) {
gather_graph(root, {})
}
// Output based on format
if (format == 'tree') {
function print_tree(locator, prefix, is_last, visited) {
if (visited[locator]) {
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
return
}
visited[locator] = true
var node = nodes[locator]
var suffix = ""
if (node.linked) suffix += " -> " + node.effective
if (node.commit) suffix += " @" + node.commit
if (node.local) suffix += " (local)"
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)
// Get children
var children = []
for (var e of edges) {
if (e.from == locator) {
children.push(e)
}
}
for (var i = 0; i < children.length; i++) {
var child_prefix = prefix + (is_last ? " " : "| ")
print_tree(children[i].to, child_prefix, i == children.length - 1, visited)
}
}
for (var i = 0; i < roots.length; i++) {
log.console(roots[i])
var children = []
for (var e of edges) {
if (e.from == roots[i]) {
children.push(e)
}
}
for (var j = 0; j < children.length; j++) {
print_tree(children[j].to, "", j == children.length - 1, {})
}
if (i < roots.length - 1) log.console("")
}
} else if (format == 'dot') {
log.console("digraph dependencies {")
log.console(" rankdir=TB;")
log.console(" node [shape=box];")
log.console("")
// Node definitions
for (var id in nodes) {
var node = nodes[id]
var label = id
if (node.commit) label += "\\n@" + node.commit
var attrs = 'label="' + label + '"'
if (node.linked) attrs += ', style=dashed'
if (node.local) attrs += ', color=blue'
// Safe node ID for dot
var safe_id = id.replaceAll(/[^a-zA-Z0-9]/g, '_')
log.console(' ' + safe_id + ' [' + attrs + '];')
}
log.console("")
// Edges
for (var e of edges) {
var from_id = e.from.replaceAll(/[^a-zA-Z0-9]/g, '_')
var to_id = e.to.replaceAll(/[^a-zA-Z0-9]/g, '_')
var label = e.alias != e.to ? 'label="' + e.alias + '"' : ''
log.console(' ' + from_id + ' -> ' + to_id + (label ? ' [' + label + ']' : '') + ';')
}
log.console("}")
} else if (format == 'json') {
var output = {
nodes: [],
edges: []
}
for (var id in nodes) {
output.nodes.push(nodes[id])
}
output.edges = edges
log.console(json.encode(output))
}
$stop()

44
help.ce
View File

@@ -27,21 +27,41 @@ if (stat && stat.isFile) {
log.console(content)
} else {
// Fallback if man file doesn't exist
log.console("cell - The Cell module system for Prosperon")
log.console("cell - The Cell package manager")
log.console("")
log.console("Usage: cell <command> [arguments]")
log.console("")
log.console("Commands:")
log.console(" init Initialize a new Cell project")
log.console(" get Fetch and add a module dependency")
log.console(" update Update a dependency to a new version")
log.console(" vendor Copy all dependencies locally")
log.console(" build Compile all modules to bytecode")
log.console(" patch Create a patch for a module")
log.console(" config Manage system and actor configurations")
log.console(" help Show this help message")
log.console("Package Management:")
log.console(" install <locator> Install a package and its dependencies")
log.console(" update [locator] Update packages from remote sources")
log.console(" remove <locator> Remove a package from the shop")
log.console(" add <locator> Add a dependency to current package")
log.console("")
log.console("Run 'cell help <command>' for more information on a command.")
log.console("Building:")
log.console(" build [locator] Build dynamic libraries for packages")
log.console(" clean [scope] Remove build artifacts")
log.console("")
log.console("Linking (Local Development):")
log.console(" link <origin> <target> Link a package to a local path")
log.console(" unlink <origin> Remove a package link")
log.console(" clone <origin> <path> Clone and link a package locally")
log.console("")
log.console("Information:")
log.console(" list [scope] List packages and dependencies")
log.console(" ls [locator] List modules and actors in a package")
log.console(" why <locator> Show reverse dependencies")
log.console(" search <query> Search for packages, modules, or actors")
log.console("")
log.console("Diagnostics:")
log.console(" resolve [locator] Print fully resolved dependency closure")
log.console(" graph [locator] Emit dependency graph (tree, dot, json)")
log.console(" verify [scope] Verify integrity and consistency")
log.console("")
log.console("Other:")
log.console(" help [command] Show help for a command")
log.console(" version Show cell version")
log.console("")
log.console("Run 'cell <command> --help' for more information on a command.")
}
$stop()
$stop()

View File

@@ -1,17 +1,65 @@
// cell install <locator> - Install a package to the shop
// Does not modify the current project's cell.toml
//
// Usage:
// cell install <locator> Install a package and its dependencies
// cell install . Install current directory package
//
// Options:
// --target <triple> Build for target platform
// --refresh Refresh floating refs before locking
// --dry-run Show what would be installed
var shop = use('internal/shop')
var build = use('build')
var pkg = use('package')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell install <locator>")
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
$stop()
return
}
var locator = args[0]
var locator = null
var target_triple = null
var refresh = false
var dry_run = false
for (var i = 0; i < args.length; i++) {
if (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < args.length) {
target_triple = args[++i]
} else {
log.error('--target requires a triple')
$stop()
}
} else if (args[i] == '--refresh') {
refresh = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Install a package and its dependencies to the shop.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
$stop()
} else if (!args[i].startsWith('-')) {
locator = args[i]
}
}
if (!locator) {
log.console("Usage: cell install <locator>")
$stop()
}
// Resolve relative paths to absolute paths
// Local paths like '.' or '../foo' need to be converted to absolute paths
@@ -22,41 +70,86 @@ if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || f
}
}
// Default target
if (!target_triple) {
target_triple = build.detect_host_target()
}
log.console("Installing " + locator + "...")
var pkg = use('package')
// Gather all packages that will be installed
var packages_to_install = []
var visited = {}
// Recursive install function that handles dependencies
function install_package(pkg_locator, visited) {
function gather_packages(pkg_locator) {
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
packages_to_install.push(pkg_locator)
// Try to read dependencies
try {
// For packages not yet extracted, we need to update and extract first to read deps
var lock = shop.load_lock()
if (!lock[pkg_locator]) {
if (!dry_run) {
shop.update(pkg_locator)
shop.extract(pkg_locator)
}
}
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)
gather_packages(dep_locator)
}
}
} catch (e) {
// Package might not have dependencies or cell.toml issue
log.console("Warning: Could not read dependencies for " + pkg_locator + ": " + e.message)
if (!dry_run) {
log.console("Warning: Could not read dependencies for " + pkg_locator + ": " + e.message)
}
}
// Build the package after all dependencies are installed
build.build_package(pkg_locator)
}
install_package(locator, {})
log.console("Installed " + locator)
// Gather all packages
gather_packages(locator)
if (dry_run) {
log.console("Would install:")
for (var p of packages_to_install) {
var lock = shop.load_lock()
var exists = lock[p] != null
log.console(" " + p + (exists ? " (already installed)" : ""))
}
$stop()
}
// Install each package
function install_package(pkg_locator) {
// Update lock entry
shop.update(pkg_locator)
// Extract/symlink the package
shop.extract(pkg_locator)
// Build scripts
shop.build_package_scripts(pkg_locator)
// Build C code
try {
build.build_dynamic(pkg_locator, target_triple, 'release')
} catch (e) {
// Not all packages have C code
}
}
for (var p of packages_to_install) {
log.console(" Installing " + p + "...")
install_package(p)
}
log.console("Installed " + text(packages_to_install.length) + " package(s).")
$stop()

View File

@@ -986,8 +986,15 @@ Shop.update = function(pkg) {
log.console(`checking ${pkg}`)
if (info == 'local') return {
updated: time.number()
if (info == 'local') {
// Local packages always get a lock entry
var new_entry = {
type: 'local',
updated: time.number()
}
lock[pkg] = new_entry
Shop.save_lock(lock)
return new_entry
}
var local_commit = lock_entry ? lock_entry.commit : null

215
list.ce
View File

@@ -1,85 +1,170 @@
// list installed packages
// cell list -> list packages installed in this package
// cell list all -> list all packages (including those that are there due to installed packages)
// cell list package <name> -> list the packages for the package <name>
// cell list [<scope>] - List packages and dependencies
//
// Usage:
// cell list List dependencies of current package
// cell list shop List all packages in shop with status
// cell list <locator> List dependency tree for a package
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var fd = use('fd')
var mode = 'local'
var target_pkg = null
if (args && args.length > 0) {
if (args[0] == 'all') {
mode = 'all'
} else if (args[0] == 'shop') {
mode = 'shop'
} else if (args[0] == 'package') {
if (args.length < 2) {
log.console("Usage: cell list package <name>")
$stop()
return
}
mode = 'package'
target_pkg = args[1]
} else {
log.console("Usage:")
log.console(" cell list : list local packages")
log.console(" cell list all : list all recursive packages")
log.console(" cell list package <name>: list dependencies of <name>")
log.console(" cell list shop : list all packages in shop")
$stop()
return
if (args[0] == 'shop') {
mode = 'shop'
} else if (args[0] == '--help' || args[0] == '-h') {
log.console("Usage: cell list [<scope>]")
log.console("")
log.console("List packages and dependencies.")
log.console("")
log.console("Scopes:")
log.console(" (none) List dependencies of current package")
log.console(" shop List all packages in shop with status")
log.console(" <locator> List dependency tree for a package")
$stop()
} else {
mode = 'package'
target_pkg = args[0]
// Resolve local 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
}
}
}
}
var links = link.load()
var lock = shop.load_lock()
function print_deps(ctx, indent) {
indent = indent || ""
var deps
try {
deps = pkg.dependencies(ctx)
} catch (e) {
log.console(indent + " (could not read dependencies)")
return
}
if (!deps) {
log.console(indent + " (none)")
return
}
var aliases = []
for (var k in deps) aliases.push(k)
aliases.sort()
if (aliases.length == 0) {
log.console(indent + " (none)")
return
}
for (var i = 0; i < aliases.length; i++) {
var alias = aliases[i]
var locator = deps[alias]
var link_target = links[locator]
var lock_entry = lock[locator]
var line = indent + " " + alias
if (alias != locator) {
line += " -> " + locator
}
// Add status indicators
var status = []
if (link_target) {
status.push("linked -> " + link_target)
}
if (lock_entry && lock_entry.commit) {
status.push("@" + lock_entry.commit.substring(0, 8))
}
if (lock_entry && lock_entry.type == 'local') {
status.push("local")
}
if (!lock_entry) {
status.push("not installed")
}
if (status.length > 0) {
line += " [" + status.join(", ") + "]"
}
log.console(line)
}
}
if (mode == 'local') {
log.console("Installed Packages (Local):")
print_deps(null)
log.console("Dependencies:")
print_deps(null)
} else if (mode == 'package') {
// Resolve alias to canonical package path
var canon = shop.get_canonical_package(target_pkg, null)
if (!canon) {
log.console("Package '" + target_pkg + "' not found in local dependencies.")
} else {
log.console("Dependencies for " + target_pkg + " (" + canon + "):")
print_deps(canon)
}
} else if (mode == 'all') {
log.console("All Packages:")
var all = shop.list_packages(null)
// list_packages returns an array of package strings (locators)
// We want to perhaps sort them
all.sort()
for (var i = 0; i < all.length; i++) {
log.console(" " + all[i])
}
if (all.length == 0) log.console(" (none)")
log.console("Dependencies for " + target_pkg + ":")
print_deps(target_pkg)
} else if (mode == 'shop') {
log.console("Shop Packages:")
var all = shop.list_packages()
if (all.length == 0)
log.console(" (none)")
else
all.forEach(package => log.console(" " + package))
}
log.console("Shop packages:")
log.console("")
function print_deps(ctx) {
var deps = pkg.dependencies(ctx)
var aliases = []
for (var k in deps) aliases.push(k)
aliases.sort()
var packages = shop.list_packages()
if (packages.length == 0) {
log.console(" (none)")
} else {
packages.sort()
if (aliases.length == 0) {
log.console(" (none)")
} else {
for (var i = 0; i < aliases.length; i++) {
var alias = aliases[i]
var locator = deps[alias]
log.console(" " + alias + " -> " + locator)
}
// Group by type
var local_pkgs = []
var linked_pkgs = []
var remote_pkgs = []
for (var p of packages) {
if (p == 'core') continue
var lock_entry = lock[p]
var link_target = links[p]
if (link_target) {
linked_pkgs.push(p)
} else if (lock_entry && lock_entry.type == 'local') {
local_pkgs.push(p)
} else {
remote_pkgs.push(p)
}
}
if (linked_pkgs.length > 0) {
log.console("Linked packages:")
for (var p of linked_pkgs) {
var target = links[p]
log.console(" " + p + " -> " + target)
}
log.console("")
}
if (local_pkgs.length > 0) {
log.console("Local packages:")
for (var p of local_pkgs) {
log.console(" " + p)
}
log.console("")
}
if (remote_pkgs.length > 0) {
log.console("Remote packages:")
for (var p of remote_pkgs) {
var lock_entry = lock[p]
var commit = lock_entry && lock_entry.commit ? " @" + lock_entry.commit.substring(0, 8) : ""
log.console(" " + p + commit)
}
log.console("")
}
log.console("Total: " + text(packages.length) + " package(s)")
}
}
$stop()

109
remove.ce
View File

@@ -1,24 +1,105 @@
// cell remove <alias|path> - Remove a package from dependencies or shop
// cell remove <locator> - Remove a package from the shop
//
// Usage:
// cell remove <locator> Remove a package from the shop
// cell remove . Remove current directory package from shop
//
// Options:
// --prune Also remove packages no longer needed by any root
// --dry-run Show what would be removed
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell remove <alias|path>")
$stop()
return
}
var target_pkg = null
var prune = false
var dry_run = false
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
for (var i = 0; i < args.length; i++) {
if (args[i] == '--prune') {
prune = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell remove <locator> [options]")
log.console("")
log.console("Remove a package from the shop.")
log.console("")
log.console("Options:")
log.console(" --prune Also remove packages no longer needed by any root")
log.console(" --dry-run Show what would be removed")
$stop()
} else if (!args[i].startsWith('-')) {
target_pkg = args[i]
}
}
shop.remove(pkg)
if (!target_pkg) {
log.console("Usage: cell remove <locator> [options]")
$stop()
}
// 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
}
}
var packages_to_remove = [target_pkg]
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
var lock = shop.load_lock()
var all_packages = shop.list_packages()
// Build set of all needed packages (excluding target)
var needed = {}
for (var p of all_packages) {
if (p == target_pkg || p == 'core') continue
// Mark this package and its deps as needed
needed[p] = true
try {
var deps = pkg.gather_dependencies(p)
for (var dep of deps) {
needed[dep] = true
}
} catch (e) {
// Skip if can't read deps
}
}
// Find packages that are NOT needed
for (var p of all_packages) {
if (p == 'core') continue
if (!needed[p] && packages_to_remove.indexOf(p) < 0) {
packages_to_remove.push(p)
}
}
}
if (dry_run) {
log.console("Would remove:")
for (var p of packages_to_remove) {
log.console(" " + p)
}
} else {
for (var p of packages_to_remove) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
// Remove from shop
shop.remove(p)
}
log.console("Removed " + text(packages_to_remove.length) + " package(s).")
}
$stop()

201
resolve.ce Normal file
View File

@@ -0,0 +1,201 @@
// cell resolve [<locator>] - Print fully resolved dependency closure
//
// Usage:
// cell resolve Resolve current directory package
// cell resolve . Resolve current directory package
// cell resolve <locator> Resolve specific package
//
// Options:
// --target <triple> Annotate builds for target platform
// --locked Show lock state without applying links
// --refresh Refresh floating refs before printing
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var build = use('build')
var fd = use('fd')
var target_locator = null
var target_triple = null
var show_locked = false
var refresh_first = false
for (var i = 0; i < args.length; i++) {
if (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < args.length) {
target_triple = args[++i]
} else {
log.error('--target requires a triple')
$stop()
}
} else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--refresh') {
refresh_first = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell resolve [<locator>] [options]")
log.console("")
log.console("Print the fully resolved dependency closure.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Annotate builds for target platform")
log.console(" --locked Show lock state without applying links")
log.console(" --refresh Refresh floating refs before printing")
$stop()
} else if (!args[i].startsWith('-')) {
target_locator = args[i]
}
}
// Default to current directory
if (!target_locator) {
target_locator = '.'
}
// Resolve local paths
if (target_locator == '.' || target_locator.startsWith('./') || target_locator.startsWith('../') || fd.is_dir(target_locator)) {
var resolved = fd.realpath(target_locator)
if (resolved) {
target_locator = resolved
}
}
// Check if it's a valid package
if (!fd.is_file(target_locator + '/cell.toml')) {
// Try to find it in the shop
var pkg_dir = shop.get_package_dir(target_locator)
if (!fd.is_file(pkg_dir + '/cell.toml')) {
log.error("Not a valid package: " + target_locator)
$stop()
}
}
// Detect target if not specified
if (!target_triple) {
target_triple = build.detect_host_target()
}
var lock = shop.load_lock()
var links = link.load()
// Gather all dependencies recursively
var all_deps = {}
var visited = {}
function gather_deps(locator, depth) {
if (visited[locator]) return
visited[locator] = true
all_deps[locator] = { depth: depth }
try {
var deps = pkg.dependencies(locator)
if (deps) {
for (var alias in deps) {
var dep_locator = deps[alias]
gather_deps(dep_locator, depth + 1)
}
}
} catch (e) {
// Package might not have dependencies
}
}
gather_deps(target_locator, 0)
// Print header
log.console("Resolved dependency closure for: " + target_locator)
log.console("Target: " + target_triple)
log.console("")
// Sort by depth then alphabetically
var sorted = []
for (var locator in all_deps) {
sorted.push({ locator: locator, depth: all_deps[locator].depth })
}
sorted.sort(function(a, b) {
if (a.depth != b.depth) return a.depth - b.depth
return a.locator < b.locator ? -1 : 1
})
for (var i = 0; i < sorted.length; i++) {
var locator = sorted[i].locator
var depth = sorted[i].depth
var indent = ""
for (var j = 0; j < depth; j++) indent += " "
// Get info about this package
var info = shop.resolve_package_info(locator)
var lock_entry = lock[locator]
var link_target = show_locked ? null : links[locator]
var effective_locator = link_target || locator
// Check status
var is_linked = link_target != null
var is_in_lock = lock_entry != null
var is_local = info == 'local'
// Check if fetched (package directory exists)
var pkg_dir = shop.get_package_dir(locator)
var is_fetched = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
// Check if built (library exists)
var lib_dir = shop.get_lib_dir()
var lib_name = shop.lib_name_for_package(locator)
var dylib_ext = '.dylib' // TODO: detect from target
var lib_path = lib_dir + '/' + lib_name + dylib_ext
var is_built = fd.is_file(lib_path)
// Format output
var status_parts = []
if (is_linked) status_parts.push("linked")
if (is_local) status_parts.push("local")
if (!is_in_lock) status_parts.push("not in lock")
if (!is_fetched) status_parts.push("not fetched")
if (is_built) status_parts.push("built")
var commit_str = ""
if (lock_entry && lock_entry.commit) {
commit_str = " @" + lock_entry.commit.substring(0, 8)
} else if (lock_entry && lock_entry.type == 'local') {
commit_str = " (local)"
}
var line = indent + locator + commit_str
if (is_linked && !show_locked) {
line += " -> " + link_target
}
if (status_parts.length > 0) {
line += " [" + status_parts.join(", ") + "]"
}
log.console(line)
// Show compilation inputs if requested (verbose)
if (depth == 0) {
try {
var cflags = pkg.get_flags(locator, 'CFLAGS', target_triple)
var ldflags = pkg.get_flags(locator, 'LDFLAGS', target_triple)
if (cflags.length > 0 || ldflags.length > 0) {
log.console(indent + " Compilation inputs:")
if (cflags.length > 0) {
log.console(indent + " CFLAGS: " + cflags.join(' '))
}
if (ldflags.length > 0) {
log.console(indent + " LDFLAGS: " + ldflags.join(' '))
}
}
} catch (e) {
// Skip if can't read config
}
}
}
log.console("")
log.console("Total: " + text(sorted.length) + " package(s)")
$stop()

123
update.ce
View File

@@ -1,30 +1,47 @@
// cell update - Update packages from remote sources
//
// This command checks for updates to all packages and downloads new versions.
// For local packages, ensures the symlink is correct.
// For remote packages, checks the remote for new commits.
// cell update [<locator>] - Update packages from remote sources
//
// Usage:
// cell update - Update all packages
// cell update <package> - Update a specific package
// cell update Update all packages in shop
// cell update . Update current directory package
// cell update <locator> Update a specific package
//
// Options:
// --build Run build after updating
// --target <triple> Target platform for build (requires --build)
// --follow-links Update link targets instead of origins
var shop = use('internal/shop')
var build = use('build')
var fd = use('fd')
var target_pkg = null
var run_build = false
var target_triple = null
var follow_links = false
// Parse arguments
for (var i = 0; i < args.length; i++) {
if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell update [package]")
log.console("Usage: cell update [<locator>] [options]")
log.console("")
log.console("Update packages from remote sources.")
log.console("")
log.console("Arguments:")
log.console(" package Optional package name to update. If omitted, updates all.")
log.console("")
log.console("This command checks for updates to all packages and downloads")
log.console("new versions. For local packages, ensures the symlink is correct.")
log.console("Options:")
log.console(" --build Run build after updating")
log.console(" --target <triple> Target platform for build (requires --build)")
log.console(" --follow-links Update link targets instead of origins")
$stop()
} else if (args[i] == '--build') {
run_build = true
} else if (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < args.length) {
target_triple = args[++i]
} else {
log.error('--target requires a triple')
$stop()
}
} else if (args[i] == '--follow-links') {
follow_links = true
} else if (!args[i].startsWith('-')) {
target_pkg = args[i]
// Resolve relative paths to absolute paths
@@ -37,56 +54,94 @@ for (var i = 0; i < args.length; i++) {
}
}
// Default target if building
if (run_build && !target_triple) {
target_triple = build.detect_host_target()
}
var link = use('link')
function update_and_fetch(pkg)
{
var lock = shop.load_lock()
var old_entry = lock[pkg]
var old_commit = old_entry ? old_entry.commit : null
var new_entry = shop.update(pkg)
// Handle follow-links option
var effective_pkg = pkg
if (follow_links) {
var link_target = link.get_target(pkg)
if (link_target) {
effective_pkg = link_target
log.console(" Following link: " + pkg + " -> " + effective_pkg)
}
}
var new_entry = shop.update(effective_pkg)
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)
log.console(" " + effective_pkg + " " + old_str + " -> " + new_entry.commit.substring(0, 8))
shop.fetch(effective_pkg)
} else {
// Local package - just ensure symlink is correct
log.console(" " + pkg + " (local)")
log.console(" " + effective_pkg + " (local)")
}
shop.extract(pkg)
shop.build_package_scripts(pkg)
return true
shop.extract(effective_pkg)
shop.build_package_scripts(effective_pkg)
return effective_pkg
}
return false
return null
}
var updated_packages = []
if (target_pkg) {
if (update_and_fetch(target_pkg))
var updated = update_and_fetch(target_pkg)
if (updated) {
updated_packages.push(updated)
log.console("Updated " + target_pkg + ".")
else
} else {
log.console(target_pkg + " is up to date.")
}
} else {
var packages = shop.list_packages()
var pkg_count = packages.length
log.console("Checking for updates (" + text(pkg_count) + " package" + (pkg_count == 1 ? "" : "s") + ")...")
var updated_count = 0
for (var i = 0; i < packages.length; i++) {
var pkg = packages[i]
if (pkg == 'core') continue
if (update_and_fetch(pkg)) {
updated_count++
var updated = update_and_fetch(pkg)
if (updated) {
updated_packages.push(updated)
}
}
if (updated_count > 0) {
log.console("Updated " + text(updated_count) + " package" + (updated_count == 1 ? "" : "s") + ".")
if (updated_packages.length > 0) {
log.console("Updated " + text(updated_packages.length) + " package" + (updated_packages.length == 1 ? "" : "s") + ".")
} else {
log.console("All packages are up to date.")
}
}
// Run build if requested
if (run_build && updated_packages.length > 0) {
log.console("")
log.console("Building updated packages...")
for (var pkg of updated_packages) {
try {
var lib = build.build_dynamic(pkg, target_triple, 'release')
if (lib) {
log.console(" Built: " + lib)
}
} catch (e) {
log.error(" Failed to build " + pkg + ": " + e)
}
}
}
$stop()

257
verify.ce Normal file
View File

@@ -0,0 +1,257 @@
// cell verify [<scope>] - Verify integrity and consistency
//
// Usage:
// cell verify Verify current directory package
// cell verify . Verify current directory package
// cell verify <locator> Verify specific package
// cell verify shop Verify entire shop
// cell verify world Verify all world roots
//
// Options:
// --deep Traverse full dependency closure
// --target <triple> Verify builds for specific target
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var build = use('build')
var fd = use('fd')
var scope = null
var deep = false
var target_triple = null
for (var i = 0; i < args.length; i++) {
if (args[i] == '--deep') {
deep = true
} else if (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < args.length) {
target_triple = args[++i]
} else {
log.error('--target requires a triple')
$stop()
}
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell verify [<scope>] [options]")
log.console("")
log.console("Verify integrity and consistency.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Verify specific package")
log.console(" shop Verify entire shop")
log.console(" world Verify all world roots")
log.console("")
log.console("Options:")
log.console(" --deep Traverse full dependency closure")
log.console(" --target <triple> Verify builds for specific target")
$stop()
} else if (!args[i].startsWith('-')) {
scope = args[i]
}
}
// Default to current directory
if (!scope) {
scope = '.'
}
// Detect target if not specified
if (!target_triple) {
target_triple = build.detect_host_target()
}
var errors = []
var warnings = []
var checked = 0
function add_error(msg) {
errors.push(msg)
}
function add_warning(msg) {
warnings.push(msg)
}
// Verify a single package
function verify_package(locator) {
checked++
var lock = shop.load_lock()
var lock_entry = lock[locator]
var links = link.load()
var link_target = links[locator]
// Check lock entry exists
if (!lock_entry) {
add_error(locator + ": not in lock")
}
// Check package directory exists
var pkg_dir = shop.get_package_dir(locator)
var dir_exists = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
if (!dir_exists) {
add_error(locator + ": package directory missing at " + pkg_dir)
return
}
// Check cell.toml exists
if (!fd.is_file(pkg_dir + '/cell.toml')) {
add_error(locator + ": missing cell.toml")
return
}
// For linked packages, verify link target
if (link_target) {
if (link_target.startsWith('/')) {
// Local path target
if (!fd.is_dir(link_target)) {
add_error(locator + ": link target does not exist: " + link_target)
} else if (!fd.is_file(link_target + '/cell.toml')) {
add_error(locator + ": link target is not a valid package: " + link_target)
}
} else {
// Package target
var target_dir = shop.get_package_dir(link_target)
if (!fd.is_dir(target_dir) && !fd.is_link(target_dir)) {
add_error(locator + ": link target package not found: " + link_target)
}
}
// Check symlink is correct
if (fd.is_link(pkg_dir)) {
var current_target = fd.readlink(pkg_dir)
var expected_target = link_target.startsWith('/') ? link_target : shop.get_package_dir(link_target)
if (current_target != expected_target) {
add_warning(locator + ": symlink target mismatch (expected " + expected_target + ", got " + current_target + ")")
}
} else {
add_warning(locator + ": linked but directory is not a symlink")
}
}
// Check build output exists
var lib_dir = shop.get_lib_dir()
var lib_name = shop.lib_name_for_package(locator)
var dylib_ext = '.dylib' // TODO: detect from target
var lib_path = lib_dir + '/' + lib_name + dylib_ext
// Only check for builds if package has C files
try {
var c_files = pkg.get_c_files(locator, target_triple, true)
if (c_files && c_files.length > 0) {
if (!fd.is_file(lib_path)) {
add_warning(locator + ": library not built at " + lib_path)
}
}
} catch (e) {
// Skip build check if can't determine C files
}
}
// Check for link cycles
function check_link_cycles() {
var links = link.load()
function follow_chain(origin, visited) {
if (visited[origin]) {
return origin // cycle detected
}
visited[origin] = true
var target = links[origin]
if (target && links[target]) {
return follow_chain(target, visited)
}
return null
}
for (var origin in links) {
var cycle_start = follow_chain(origin, {})
if (cycle_start) {
add_error("Link cycle detected starting from: " + origin)
}
}
}
// Check for dangling links
function check_dangling_links() {
var links = link.load()
for (var origin in links) {
var target = links[origin]
if (target.startsWith('/')) {
if (!fd.is_dir(target)) {
add_error("Dangling link: " + origin + " -> " + target + " (target does not exist)")
}
}
}
}
// Gather packages to verify
var packages_to_verify = []
if (scope == 'shop') {
packages_to_verify = shop.list_packages()
} else if (scope == 'world') {
// For now, world is the same as shop
// In future, this could be a separate concept
packages_to_verify = shop.list_packages()
} else {
// Single package
var locator = scope
// Resolve local paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
if (deep) {
// Gather all dependencies
var all_deps = pkg.gather_dependencies(locator)
packages_to_verify.push(locator)
for (var dep of all_deps) {
packages_to_verify.push(dep)
}
} else {
packages_to_verify.push(locator)
}
}
log.console("Verifying " + text(packages_to_verify.length) + " package(s)...")
log.console("")
// Run verification
check_link_cycles()
check_dangling_links()
for (var p of packages_to_verify) {
if (p == 'core') continue
verify_package(p)
}
// Print results
if (warnings.length > 0) {
log.console("Warnings:")
for (var w of warnings) {
log.console(" " + w)
}
log.console("")
}
if (errors.length > 0) {
log.console("Errors:")
for (var e of errors) {
log.console(" " + e)
}
log.console("")
log.console("Verification FAILED: " + text(errors.length) + " error(s), " + text(warnings.length) + " warning(s)")
// Note: would use process.exit(1) if available
} else {
log.console("Verification PASSED: " + text(checked) + " package(s) checked, " + text(warnings.length) + " warning(s)")
}
$stop()