clean up cmd line
This commit is contained in:
91
add.ce
91
add.ce
@@ -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) {
|
||||
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@main")
|
||||
log.console(" cell add github.com/user/repo@v1.0.0 myalias")
|
||||
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()
|
||||
return
|
||||
} 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]
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
30
build.ce
30
build.ce
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
216
clean.ce
216
clean.ce
@@ -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)
|
||||
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()
|
||||
return
|
||||
} else if (!args[i].startsWith('-')) {
|
||||
scope = args[i]
|
||||
}
|
||||
}
|
||||
|
||||
log.console("Cleaning build artifacts...")
|
||||
// Default to --build if nothing specified
|
||||
if (!clean_build && !clean_fetch) {
|
||||
clean_build = true
|
||||
}
|
||||
|
||||
// Remove the build directory
|
||||
// Default scope to current directory
|
||||
if (!scope) {
|
||||
scope = '.'
|
||||
}
|
||||
|
||||
// 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 {
|
||||
fd.rm(build_dir)
|
||||
log.console("Build directory removed: " + build_dir)
|
||||
var deps = pkg.gather_dependencies(scope)
|
||||
for (var dep of deps) {
|
||||
packages_to_clean.push(dep)
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
// Skip if can't read dependencies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.console("Clean complete!")
|
||||
// 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
236
graph.ce
Normal 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()
|
||||
42
help.ce
42
help.ce
@@ -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()
|
||||
133
install.ce
133
install.ce
@@ -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)
|
||||
packages_to_install.push(pkg_locator)
|
||||
|
||||
// Extract/symlink the package so we can read its cell.toml
|
||||
shop.extract(pkg_locator)
|
||||
|
||||
// Now get direct dependencies and install them first
|
||||
// Try 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
|
||||
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()
|
||||
|
||||
@@ -986,9 +986,16 @@ Shop.update = function(pkg) {
|
||||
|
||||
log.console(`checking ${pkg}`)
|
||||
|
||||
if (info == 'local') return {
|
||||
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
|
||||
var remote_commit = fetch_remote_hash(pkg)
|
||||
|
||||
199
list.ce
199
list.ce
@@ -1,84 +1,169 @@
|
||||
// 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') {
|
||||
if (args[0] == 'shop') {
|
||||
mode = 'shop'
|
||||
} else if (args[0] == 'package') {
|
||||
if (args.length < 2) {
|
||||
log.console("Usage: cell list package <name>")
|
||||
} 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()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
||||
if (mode == 'local') {
|
||||
log.console("Installed Packages (Local):")
|
||||
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)")
|
||||
} 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))
|
||||
}
|
||||
|
||||
function print_deps(ctx) {
|
||||
var deps = pkg.dependencies(ctx)
|
||||
var aliases = []
|
||||
for (var k in deps) aliases.push(k)
|
||||
aliases.sort()
|
||||
|
||||
if (aliases.length == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
log.console(indent + " (none)")
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < aliases.length; i++) {
|
||||
var alias = aliases[i]
|
||||
var locator = deps[alias]
|
||||
log.console(" " + alias + " -> " + locator)
|
||||
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("Dependencies:")
|
||||
print_deps(null)
|
||||
} else if (mode == 'package') {
|
||||
log.console("Dependencies for " + target_pkg + ":")
|
||||
print_deps(target_pkg)
|
||||
} else if (mode == 'shop') {
|
||||
log.console("Shop packages:")
|
||||
log.console("")
|
||||
|
||||
var packages = shop.list_packages()
|
||||
if (packages.length == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
packages.sort()
|
||||
|
||||
// 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)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
99
remove.ce
99
remove.ce
@@ -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>")
|
||||
var target_pkg = null
|
||||
var prune = false
|
||||
var dry_run = false
|
||||
|
||||
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()
|
||||
return
|
||||
} else if (!args[i].startsWith('-')) {
|
||||
target_pkg = args[i]
|
||||
}
|
||||
}
|
||||
|
||||
var pkg = args[0]
|
||||
if (!target_pkg) {
|
||||
log.console("Usage: cell remove <locator> [options]")
|
||||
$stop()
|
||||
}
|
||||
|
||||
// Resolve relative paths to absolute paths
|
||||
if (pkg == '.' || pkg.startsWith('./') || pkg.startsWith('../') || fd.is_dir(pkg)) {
|
||||
var resolved = fd.realpath(pkg)
|
||||
if (target_pkg == '.' || target_pkg.startsWith('./') || target_pkg.startsWith('../') || fd.is_dir(target_pkg)) {
|
||||
var resolved = fd.realpath(target_pkg)
|
||||
if (resolved) {
|
||||
pkg = resolved
|
||||
target_pkg = resolved
|
||||
}
|
||||
}
|
||||
|
||||
shop.remove(pkg)
|
||||
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
201
resolve.ce
Normal 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()
|
||||
113
update.ce
113
update.ce
@@ -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
257
verify.ce
Normal 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()
|
||||
Reference in New Issue
Block a user