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 shop = use('internal/shop')
var pkg = use('package')
var build = use('build')
var fd = use('fd') var fd = use('fd')
if (args.length < 1) { var locator = null
log.console("Usage: cell add <locator> [alias]") var alias = null
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon@main") for (var i = 0; i < args.length; i++) {
log.console(" cell add github.com/user/repo@v1.0.0 myalias") if (args[i] == '--help' || args[i] == '-h') {
$stop() log.console("Usage: cell add <locator> [alias]")
return 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 // Resolve relative paths to absolute paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) { if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
@@ -21,8 +46,58 @@ if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || f
locator = resolved 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: // Usage:
// cell build Build dynamic libraries for all packages // cell build Build dynamic libraries for all packages in shop
// cell build -p <pkg> Build dynamic library for specific package // 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 -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 build = use('build')
var shop = use('internal/shop') var shop = use('internal/shop')
@@ -12,7 +14,9 @@ var fd = use('fd')
var target = null var target = null
var target_package = 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++) { for (var i = 0; i < args.length; i++) {
if (args[i] == '-t' || args[i] == '--target') { if (args[i] == '-t' || args[i] == '--target') {
@@ -23,6 +27,7 @@ for (var i = 0; i < args.length; i++) {
$stop() $stop()
} }
} else if (args[i] == '-p' || args[i] == '--package') { } else if (args[i] == '-p' || args[i] == '--package') {
// Legacy support for -p flag
if (i + 1 < args.length) { if (i + 1 < args.length) {
target_package = args[++i] target_package = args[++i]
} else { } else {
@@ -40,6 +45,10 @@ for (var i = 0; i < args.length; i++) {
log.error('-b requires a buildtype (release, debug, minsize)') log.error('-b requires a buildtype (release, debug, minsize)')
$stop() $stop()
} }
} else if (args[i] == '--force') {
force_rebuild = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--list-targets') { } else if (args[i] == '--list-targets') {
log.console('Available targets:') log.console('Available targets:')
var targets = build.list_targets() var targets = build.list_targets()
@@ -47,6 +56,19 @@ for (var i = 0; i < args.length; i++) {
log.console(' ' + targets[t]) log.console(' ' + targets[t])
} }
$stop() $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 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)) { for (var i = 0; i < args.length; i++) {
log.console("No build directory found at " + build_dir) if (args[i] == '--build') {
$stop() clean_build = true
return } 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...") // Default to --build if nothing specified
if (!clean_build && !clean_fetch) {
// Remove the build directory clean_build = true
try {
fd.rm(build_dir)
log.console("Build directory removed: " + build_dir)
} catch (e) {
log.error(e)
} }
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) log.console(content)
} else { } else {
// Fallback if man file doesn't exist // 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("")
log.console("Usage: cell <command> [arguments]") log.console("Usage: cell <command> [arguments]")
log.console("") log.console("")
log.console("Commands:") log.console("Package Management:")
log.console(" init Initialize a new Cell project") log.console(" install <locator> Install a package and its dependencies")
log.console(" get Fetch and add a module dependency") log.console(" update [locator] Update packages from remote sources")
log.console(" update Update a dependency to a new version") log.console(" remove <locator> Remove a package from the shop")
log.console(" vendor Copy all dependencies locally") log.console(" add <locator> Add a dependency to current package")
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("") 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 // 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 shop = use('internal/shop')
var build = use('build') var build = use('build')
var pkg = use('package')
var fd = use('fd') var fd = use('fd')
if (args.length < 1) { 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() $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 // Resolve relative paths to absolute paths
// Local paths like '.' or '../foo' need to be converted 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 + "...") 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 gather_packages(pkg_locator) {
function install_package(pkg_locator, visited) {
if (visited[pkg_locator]) return if (visited[pkg_locator]) return
visited[pkg_locator] = true visited[pkg_locator] = true
// First, add to lock.toml packages_to_install.push(pkg_locator)
shop.update(pkg_locator)
// Try to read dependencies
// Extract/symlink the package so we can read its cell.toml
shop.extract(pkg_locator)
// Now get direct dependencies and install them first
try { 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) var deps = pkg.dependencies(pkg_locator)
if (deps) { if (deps) {
for (var alias in deps) { for (var alias in deps) {
var dep_locator = deps[alias] var dep_locator = deps[alias]
log.console("Installing dependency " + dep_locator) gather_packages(dep_locator)
install_package(dep_locator, visited)
} }
} }
} catch (e) { } catch (e) {
// Package might not have dependencies or cell.toml issue // 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, {}) // Gather all packages
log.console("Installed " + locator) 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() $stop()

View File

@@ -986,8 +986,15 @@ Shop.update = function(pkg) {
log.console(`checking ${pkg}`) log.console(`checking ${pkg}`)
if (info == 'local') return { if (info == 'local') {
updated: time.number() // 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 var local_commit = lock_entry ? lock_entry.commit : null

215
list.ce
View File

@@ -1,85 +1,170 @@
// list installed packages // cell list [<scope>] - List packages and dependencies
// cell list -> list packages installed in this package //
// cell list all -> list all packages (including those that are there due to installed packages) // Usage:
// cell list package <name> -> list the packages for the package <name> // 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 shop = use('internal/shop')
var pkg = use('package') var pkg = use('package')
var link = use('link')
var fd = use('fd')
var mode = 'local' var mode = 'local'
var target_pkg = null var target_pkg = null
if (args && args.length > 0) { if (args && args.length > 0) {
if (args[0] == 'all') { if (args[0] == 'shop') {
mode = 'all' mode = 'shop'
} else if (args[0] == 'shop') { } else if (args[0] == '--help' || args[0] == '-h') {
mode = 'shop' log.console("Usage: cell list [<scope>]")
} else if (args[0] == 'package') { log.console("")
if (args.length < 2) { log.console("List packages and dependencies.")
log.console("Usage: cell list package <name>") log.console("")
$stop() log.console("Scopes:")
return log.console(" (none) List dependencies of current package")
} log.console(" shop List all packages in shop with status")
mode = 'package' log.console(" <locator> List dependency tree for a package")
target_pkg = args[1] $stop()
} else { } else {
log.console("Usage:") mode = 'package'
log.console(" cell list : list local packages") target_pkg = args[0]
log.console(" cell list all : list all recursive packages")
log.console(" cell list package <name>: list dependencies of <name>") // Resolve local paths
log.console(" cell list shop : list all packages in shop") if (target_pkg == '.' || target_pkg.startsWith('./') || target_pkg.startsWith('../') || fd.is_dir(target_pkg)) {
$stop() var resolved = fd.realpath(target_pkg)
return 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') { if (mode == 'local') {
log.console("Installed Packages (Local):") log.console("Dependencies:")
print_deps(null) print_deps(null)
} else if (mode == 'package') { } else if (mode == 'package') {
// Resolve alias to canonical package path log.console("Dependencies for " + target_pkg + ":")
var canon = shop.get_canonical_package(target_pkg, null) print_deps(target_pkg)
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)")
} else if (mode == 'shop') { } else if (mode == 'shop') {
log.console("Shop Packages:") log.console("Shop packages:")
var all = shop.list_packages() log.console("")
if (all.length == 0)
log.console(" (none)")
else
all.forEach(package => log.console(" " + package))
}
function print_deps(ctx) { var packages = shop.list_packages()
var deps = pkg.dependencies(ctx) if (packages.length == 0) {
var aliases = [] log.console(" (none)")
for (var k in deps) aliases.push(k) } else {
aliases.sort() packages.sort()
if (aliases.length == 0) { // Group by type
log.console(" (none)") var local_pkgs = []
} else { var linked_pkgs = []
for (var i = 0; i < aliases.length; i++) { var remote_pkgs = []
var alias = aliases[i]
var locator = deps[alias] for (var p of packages) {
log.console(" " + alias + " -> " + locator) 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() $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 shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var fd = use('fd') var fd = use('fd')
if (args.length < 1) { var target_pkg = null
log.console("Usage: cell remove <alias|path>") var prune = false
$stop() var dry_run = false
return
}
var pkg = args[0] for (var i = 0; i < args.length; i++) {
if (args[i] == '--prune') {
// Resolve relative paths to absolute paths prune = true
if (pkg == '.' || pkg.startsWith('./') || pkg.startsWith('../') || fd.is_dir(pkg)) { } else if (args[i] == '--dry-run') {
var resolved = fd.realpath(pkg) dry_run = true
if (resolved) { } else if (args[i] == '--help' || args[i] == '-h') {
pkg = resolved 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() $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 // cell update [<locator>] - 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.
// //
// Usage: // Usage:
// cell update - Update all packages // cell update Update all packages in shop
// cell update <package> - Update a specific package // 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 shop = use('internal/shop')
var build = use('build')
var fd = use('fd') var fd = use('fd')
var target_pkg = null var target_pkg = null
var run_build = false
var target_triple = null
var follow_links = false
// Parse arguments // Parse arguments
for (var i = 0; i < args.length; i++) { for (var i = 0; i < args.length; i++) {
if (args[i] == '--help' || args[i] == '-h') { 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("Update packages from remote sources.")
log.console("") log.console("")
log.console("Arguments:") log.console("Options:")
log.console(" package Optional package name to update. If omitted, updates all.") log.console(" --build Run build after updating")
log.console("") log.console(" --target <triple> Target platform for build (requires --build)")
log.console("This command checks for updates to all packages and downloads") log.console(" --follow-links Update link targets instead of origins")
log.console("new versions. For local packages, ensures the symlink is correct.")
$stop() $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('-')) { } else if (!args[i].startsWith('-')) {
target_pkg = args[i] target_pkg = args[i]
// Resolve relative paths to absolute paths // 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) function update_and_fetch(pkg)
{ {
var lock = shop.load_lock() var lock = shop.load_lock()
var old_entry = lock[pkg] var old_entry = lock[pkg]
var old_commit = old_entry ? old_entry.commit : null 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) {
if (new_entry.commit) { if (new_entry.commit) {
var old_str = old_commit ? old_commit.substring(0, 8) : "(new)" var old_str = old_commit ? old_commit.substring(0, 8) : "(new)"
log.console(" " + pkg + " " + old_str + " -> " + new_entry.commit.substring(0, 8)) log.console(" " + effective_pkg + " " + old_str + " -> " + new_entry.commit.substring(0, 8))
shop.fetch(pkg) shop.fetch(effective_pkg)
} else { } else {
// Local package - just ensure symlink is correct // Local package - just ensure symlink is correct
log.console(" " + pkg + " (local)") log.console(" " + effective_pkg + " (local)")
} }
shop.extract(pkg) shop.extract(effective_pkg)
shop.build_package_scripts(pkg) shop.build_package_scripts(effective_pkg)
return true return effective_pkg
} }
return false return null
} }
var updated_packages = []
if (target_pkg) { 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 + ".") log.console("Updated " + target_pkg + ".")
else } else {
log.console(target_pkg + " is up to date.") log.console(target_pkg + " is up to date.")
}
} else { } else {
var packages = shop.list_packages() var packages = shop.list_packages()
var pkg_count = packages.length var pkg_count = packages.length
log.console("Checking for updates (" + text(pkg_count) + " package" + (pkg_count == 1 ? "" : "s") + ")...") 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++) { for (var i = 0; i < packages.length; i++) {
var pkg = packages[i] var pkg = packages[i]
if (pkg == 'core') continue if (pkg == 'core') continue
if (update_and_fetch(pkg)) { var updated = update_and_fetch(pkg)
updated_count++ if (updated) {
updated_packages.push(updated)
} }
} }
if (updated_count > 0) { if (updated_packages.length > 0) {
log.console("Updated " + text(updated_count) + " package" + (updated_count == 1 ? "" : "s") + ".") log.console("Updated " + text(updated_packages.length) + " package" + (updated_packages.length == 1 ? "" : "s") + ".")
} else { } else {
log.console("All packages are up to date.") 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() $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()