From f0c2486a5c0c985625059f8583d8fc071602bd25 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 13:39:26 -0600 Subject: [PATCH 1/5] better path resolution --- add.ce | 27 ++----------- fetch.ce | 20 ++++++++-- install.ce | 37 +++++++----------- internal/engine.cm | 55 +++++++++++++++++---------- internal/os.c | 6 ++- internal/shop.cm | 94 +++++++++++++++++++++++++++++++++++++++++++--- package.cm | 22 +++++++++++ source/runtime.c | 5 +++ 8 files changed, 188 insertions(+), 78 deletions(-) diff --git a/add.ce b/add.ce index 28010596..3749242c 100644 --- a/add.ce +++ b/add.ce @@ -78,29 +78,6 @@ if (!fd.is_file(cwd + '/cell.toml')) { $stop() } -// Recursively find all cell packages in a directory -function find_packages(dir) { - var found = [] - var list = fd.readdir(dir) - if (!list) return found - if (fd.is_file(dir + '/cell.toml')) { - push(found, dir) - } - arrfor(list, function(item) { - if (item == '.' || item == '..' || item == '.cell' || item == '.git') return - var full = dir + '/' + item - var st = fd.stat(full) - var sub = null - if (st && st.isDirectory) { - sub = find_packages(full) - arrfor(sub, function(p) { - push(found, p) - }) - } - }) - return found -} - // If -r flag, find all packages recursively and add each if (recursive) { if (!locator) { @@ -111,7 +88,9 @@ if (recursive) { log.error(`${locator} is not a directory`) $stop() } - locators = find_packages(resolved) + locators = filter(pkg.find_packages(resolved), function(p) { + return p != cwd + }) if (length(locators) == 0) { log.console("No packages found in " + resolved) $stop() diff --git a/fetch.ce b/fetch.ce index a1cd1a35..cc526740 100644 --- a/fetch.ce +++ b/fetch.ce @@ -33,12 +33,26 @@ for (i = 0; i < length(args); i++) { var all_packages = shop.list_packages() var lock = shop.load_lock() var packages_to_fetch = [] +var _update = null if (target_pkg) { - // Fetch specific package + // Fetch specific package - auto-update if not in lock if (find(all_packages, target_pkg) == null) { - log.error("Package not found: " + target_pkg) - $stop() + log.console("Package not in lock, updating: " + target_pkg) + _update = function() { + shop.update(target_pkg) + } disruption { + log.error("Could not update package: " + target_pkg) + $stop() + } + _update() + // Reload after update + all_packages = shop.list_packages() + lock = shop.load_lock() + if (find(all_packages, target_pkg) == null) { + log.error("Package not found: " + target_pkg) + $stop() + } } push(packages_to_fetch, target_pkg) } else { diff --git a/install.ce b/install.ce index 05fb3df5..c5f2c818 100644 --- a/install.ce +++ b/install.ce @@ -79,28 +79,7 @@ if (locator && (locator == '.' || starts_with(locator, './') || starts_with(loca } } -// Recursively find all cell packages in a directory -function find_packages(dir) { - var found = [] - var list = fd.readdir(dir) - if (!list) return found - if (fd.is_file(dir + '/cell.toml')) { - push(found, dir) - } - arrfor(list, function(item) { - if (item == '.' || item == '..' || item == '.cell' || item == '.git') return - var full = dir + '/' + item - var st = fd.stat(full) - var sub = null - if (st && st.isDirectory) { - sub = find_packages(full) - arrfor(sub, function(p) { - push(found, p) - }) - } - }) - return found -} +var cwd = fd.realpath('.') // If -r flag, find all packages recursively and install each if (recursive) { @@ -112,7 +91,9 @@ if (recursive) { log.error(`${locator} is not a directory`) $stop() } - locators = find_packages(resolved) + locators = filter(pkg.find_packages(resolved), function(p) { + return p != cwd + }) if (length(locators) == 0) { log.console("No packages found in " + resolved) $stop() @@ -133,6 +114,16 @@ var visited = {} // Recursive mode: install all found packages and exit if (recursive) { + if (dry_run) { + log.console("Would install:") + arrfor(locators, function(loc) { + var lock = shop.load_lock() + var exists = lock[loc] != null + log.console(" " + loc + (exists ? " (already installed)" : "")) + }) + $stop() + } + arrfor(locators, function(loc) { log.console(" Installing " + loc + "...") var _inst = function() { diff --git a/internal/engine.cm b/internal/engine.cm index 81815b53..7c279d3c 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -1204,23 +1204,23 @@ if (ends_with(prog, '.ce')) prog = text(prog, 0, -3) var package = use_core('package') -// Find the .ce file -var prog_path = prog + ".ce" -var pkg_dir = null -var core_dir = null -if (!fd.is_file(prog_path)) { - pkg_dir = package.find_package_dir(".") - if (pkg_dir) - prog_path = pkg_dir + '/' + prog + '.ce' -} -if (!fd.is_file(prog_path)) { - // Check core packages - core_dir = core_path - prog_path = core_dir + '/' + prog + '.ce' -} -if (!fd.is_file(prog_path)) { - os.print(`Main program ${prog} could not be found\n`) - os.exit(1) +// Find the .ce file using unified resolver +var cwd_package = package.find_package_dir(".") +var prog_info = shop.resolve_program ? shop.resolve_program(prog, cwd_package) : null +var prog_path = null +if (prog_info) { + prog_path = prog_info.path +} else { + // Fallback: check CWD, package dir, and core + prog_path = prog + ".ce" + if (!fd.is_file(prog_path) && cwd_package) + prog_path = cwd_package + '/' + prog + '.ce' + if (!fd.is_file(prog_path)) + prog_path = core_path + '/' + prog + '.ce' + if (!fd.is_file(prog_path)) { + os.print(`Main program ${prog} could not be found\n`) + os.exit(1) + } } $_.clock(_ => { @@ -1244,18 +1244,33 @@ $_.clock(_ => { var pkg = file_info ? file_info.package : null - // Verify all transitive dependency packages are present + // Verify all transitive dependency packages are present, auto-install if missing var _deps = null var _di = 0 var _dep_dir = null + var _auto_install = null if (pkg) { _deps = package.gather_dependencies(pkg) _di = 0 while (_di < length(_deps)) { _dep_dir = package.get_dir(_deps[_di]) if (!fd.is_dir(_dep_dir)) { - log.error('missing dependency package: ' + _deps[_di]) - disrupt + log.console('installing missing dependency: ' + _deps[_di]) + _auto_install = function() { + shop.update(_deps[_di]) + shop.fetch(_deps[_di]) + shop.extract(_deps[_di]) + shop.build_package_scripts(_deps[_di]) + } disruption { + log.error('failed to install dependency: ' + _deps[_di]) + disrupt + } + _auto_install() + _dep_dir = package.get_dir(_deps[_di]) + if (!fd.is_dir(_dep_dir)) { + log.error('missing dependency package: ' + _deps[_di]) + disrupt + } } _di = _di + 1 } diff --git a/internal/os.c b/internal/os.c index 3638c059..4d3c542b 100644 --- a/internal/os.c +++ b/internal/os.c @@ -318,7 +318,9 @@ JSC_SCALL(os_system, ) JSC_CCALL(os_exit, - exit(0); + int code = 0; + if (argc > 0) JS_ToInt32(js, &code, argv[0]); + exit(code); ) static JSValue js_os_dylib_open(JSContext *js, JSValue self, int argc, JSValue *argv) @@ -714,7 +716,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, rusage, 0), MIST_FUNC_DEF(os, mallinfo, 0), MIST_FUNC_DEF(os, system, 1), - MIST_FUNC_DEF(os, exit, 0), + MIST_FUNC_DEF(os, exit, 1), MIST_FUNC_DEF(os, sleep, 1), MIST_FUNC_DEF(os, dylib_open, 1), MIST_FUNC_DEF(os, dylib_preload, 1), diff --git a/internal/shop.cm b/internal/shop.cm index 2d549f85..c9d6b657 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -778,6 +778,11 @@ function resolve_path(path, ctx) var ctx_path = null var alias = null var package_path = null + var lock = null + var best_pkg = null + var best_remainder = null + var shop_dir = null + var shop_file = null if (explicit) { if (is_internal_path(explicit.path) && ctx && explicit.package != ctx) @@ -823,6 +828,25 @@ function resolve_path(path, ctx) if (fd.is_file(package_path)) return {path: package_path, scope: SCOPE_PACKAGE, pkg: ctx} + // Shop package scanning: longest prefix match against lock.toml entries + lock = Shop.load_lock() + best_pkg = null + best_remainder = null + arrfor(array(lock), function(pkg_name) { + if (starts_with(path, pkg_name + '/')) { + if (!best_pkg || length(pkg_name) > length(best_pkg)) { + best_pkg = pkg_name + best_remainder = text(path, length(pkg_name) + 1) + } + } + }) + if (best_pkg && best_remainder) { + shop_dir = get_packages_dir() + '/' + safe_package_path(best_pkg) + shop_file = shop_dir + '/' + best_remainder + if (fd.is_file(shop_file)) + return {path: shop_file, scope: SCOPE_PACKAGE, pkg: best_pkg} + } + core_dir = Shop.get_core_dir() core_file_path = core_dir + '/' + path if (fd.is_file(core_file_path)) @@ -1298,6 +1322,64 @@ Shop.resolve_use_path = function(path, ctx) { return info.path } +// Resolve a program (.ce) path using the unified resolver. +// Returns {path, scope, pkg} or null. +// If the path looks like a remote package locator and is not found locally, +// attempts to auto-fetch and install it. +Shop.resolve_program = function(prog, package_context) { + var info = resolve_path(prog + '.ce', package_context) + if (info) return info + + // Auto-install: if the path matches a recognized remote locator, try fetching + var lock = Shop.load_lock() + var best_pkg = null + var best_remainder = null + var pkg_info = null + var parts = array(prog, '/') + var i = 0 + var candidate = null + var _auto = null + arrfor(array(lock), function(pkg_name) { + if (starts_with(prog, pkg_name + '/')) { + if (!best_pkg || length(pkg_name) > length(best_pkg)) { + best_pkg = pkg_name + best_remainder = text(prog, length(pkg_name) + 1) + } + } + }) + + // If not in lock, check if this looks like a fetchable package + if (!best_pkg) { + for (i = length(parts) - 1; i >= 1; i--) { + candidate = text(array(parts, 0, i), '/') + pkg_info = Shop.resolve_package_info(candidate) + if (pkg_info && pkg_info != 'local') { + best_pkg = candidate + best_remainder = text(array(parts, i), '/') + break + } + } + } + + if (best_pkg && best_remainder) { + log.console('fetching ' + best_pkg + '...') + _auto = function() { + Shop.update(best_pkg) + Shop.fetch(best_pkg) + Shop.extract(best_pkg) + Shop.build_package_scripts(best_pkg) + } disruption { + return null + } + _auto() + // Retry resolution + info = resolve_path(prog + '.ce', package_context) + if (info) return info + } + + return null +} + // Resolve a use() module path to {resolved_path, package, type} without compiling. // type is 'script', 'native', or null. Checks .cm files, C symbols, and aliases. Shop.resolve_import_info = function(path, ctx) { @@ -1410,7 +1492,7 @@ Shop.fetch = function(pkg) { if (actual_hash == expected_hash) { return { status: 'cached' } } - log.console("Zip hash mismatch for " + pkg + ", re-fetching...") + log.shop("Zip hash mismatch for " + pkg + ", re-fetching...") } else { // No hash stored yet - compute and store it actual_hash = text(crypto.blake2(zip_blob), 'h') @@ -1522,13 +1604,13 @@ Shop.update = function(pkg) { var lock_entry = lock[pkg] var info = Shop.resolve_package_info(pkg) - log.console(`checking ${pkg}`) + log.shop(`checking ${pkg}`) var new_entry = null if (info == 'local') { // Check if local path exists if (!fd.is_dir(pkg)) { - log.console(` Local path does not exist: ${pkg}`) + log.shop(` Local path does not exist: ${pkg}`) return null } // Local packages always get a lock entry @@ -1544,8 +1626,8 @@ Shop.update = function(pkg) { var local_commit = lock_entry ? lock_entry.commit : null var remote_commit = fetch_remote_hash(pkg) - log.console(`local commit: ${local_commit}`) - log.console(`remote commit: ${remote_commit}`) + log.shop(`local commit: ${local_commit}`) + log.shop(`remote commit: ${remote_commit}`) if (!remote_commit) { log.error("Could not resolve commit for " + pkg) @@ -1574,7 +1656,7 @@ function install_zip(zip_blob, target_dir) { if (fd.is_link(target_dir)) fd.unlink(target_dir) if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1) - log.console("Extracting to " + target_dir) + log.shop("Extracting to " + target_dir) ensure_dir(target_dir) var count = zip.count() diff --git a/package.cm b/package.cm index 506006c4..1c21ffc8 100644 --- a/package.cm +++ b/package.cm @@ -202,6 +202,28 @@ package.gather_dependencies = function(name) return array(all_deps) } +// Recursively find all cell packages (dirs with cell.toml) under a directory +package.find_packages = function(dir) { + var found = [] + var list = fd.readdir(dir) + if (!list) return found + if (fd.is_file(dir + '/cell.toml')) + push(found, dir) + arrfor(list, function(item) { + if (item == '.' || item == '..' || item == '.cell' || item == '.git') return + var full = dir + '/' + item + var st = fd.stat(full) + var sub = null + if (st && st.isDirectory) { + sub = package.find_packages(full) + arrfor(sub, function(p) { + push(found, p) + }) + } + }) + return found +} + package.list_files = function(pkg) { var dir = get_path(pkg) if (!fd.is_dir(dir)) return [] diff --git a/source/runtime.c b/source/runtime.c index 920d0754..41fbba95 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -8057,6 +8057,11 @@ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, } } + if (!made_substitution && JS_IsNull (cv_ref.val)) { + substitution = JS_NewString (ctx, "null"); + made_substitution = 1; + } + if (!made_substitution && !JS_IsNull (cv_ref.val)) { JSValue conv_text_val = JS_ToString (ctx, cv_ref.val); if (JS_IsText (conv_text_val)) { From 601a78b3c7570a6186b643847cdad7a96c3a39cb Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 14:10:24 -0600 Subject: [PATCH 2/5] package resolution --- internal/engine.cm | 31 +++++++++++++--- internal/shop.cm | 90 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/internal/engine.cm b/internal/engine.cm index 7c279d3c..853ea881 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -1224,7 +1224,18 @@ if (prog_info) { } $_.clock(_ => { - var file_info = shop.file_info ? shop.file_info(prog_path) : null + var _file_info_ok = false + var file_info = null + var _try_fi = function() { + file_info = shop.file_info ? shop.file_info(prog_path) : null + _file_info_ok = true + } disruption {} + _try_fi() + if (!_file_info_ok || !file_info) + file_info = {path: prog_path, is_module: false, is_actor: true, package: null, name: prog} + // If the unified resolver found the package, use that as the authoritative source + if (prog_info && prog_info.pkg) + file_info.package = prog_info.pkg var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : [] // Build env with runtime functions + capability injections @@ -1278,10 +1289,22 @@ $_.clock(_ => { env.use = function(path) { var ck = 'core/' + path + var _use_core_result = null + var _use_core_ok = false if (use_cache[ck]) return use_cache[ck] - var core_mod = use_core(path) - if (core_mod) return core_mod - return shop.use(path, pkg) + var _try_core = function() { + _use_core_result = use_core(path) + _use_core_ok = true + } disruption {} + _try_core() + if (_use_core_ok && _use_core_result) return _use_core_result + var _shop_use = function() { + return shop.use(path, pkg) + } disruption { + log.error(`use('${path}') failed (package: ${pkg})`) + disrupt + } + return _shop_use() } env.args = _cell.args.arg env.log = log diff --git a/internal/shop.cm b/internal/shop.cm index c9d6b657..ceea81ea 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -137,9 +137,6 @@ function split_explicit_package_import(path) if (package_in_shop(pkg_candidate)) return {package: pkg_candidate, path: mod_path} - - if (Shop.resolve_package_info(pkg_candidate)) - return {package: pkg_candidate, path: mod_path} } return null @@ -158,6 +155,8 @@ function abs_path_to_package(package_dir) } var packages_prefix = get_packages_dir() + '/' + var packages_prefix_abs = fd.realpath(get_packages_dir()) + if (packages_prefix_abs) packages_prefix_abs = packages_prefix_abs + '/' var core_dir = packages_prefix + core_package // Check if this is the core package directory (or its symlink target) @@ -176,6 +175,10 @@ function abs_path_to_package(package_dir) if (starts_with(package_dir, packages_prefix)) return text(package_dir, length(packages_prefix)) + // Also try absolute path comparison (package_dir may be absolute, packages_prefix relative) + if (packages_prefix_abs && starts_with(package_dir, packages_prefix_abs)) + return text(package_dir, length(packages_prefix_abs)) + // Check if this local path is the target of a link // If so, return the canonical package name (link origin) instead var link_origin = link.get_origin(package_dir) @@ -344,9 +347,11 @@ function get_policy() { // Get information about how to resolve a package // Local packages always start with / +// Remote packages must be exactly host/owner/repo (3 components) Shop.resolve_package_info = function(pkg) { if (starts_with(pkg, '/')) return 'local' - if (search(pkg, 'gitea') != null) return 'gitea' + var parts = array(pkg, '/') + if (length(parts) == 3 && search(parts[0], 'gitea') != null) return 'gitea' return null } @@ -1349,25 +1354,78 @@ Shop.resolve_program = function(prog, package_context) { }) // If not in lock, check if this looks like a fetchable package - if (!best_pkg) { - for (i = length(parts) - 1; i >= 1; i--) { - candidate = text(array(parts, 0, i), '/') - pkg_info = Shop.resolve_package_info(candidate) - if (pkg_info && pkg_info != 'local') { - best_pkg = candidate - best_remainder = text(array(parts, i), '/') - break - } + // For gitea-style URLs, the package root is host/owner/repo (3 components) + if (!best_pkg && length(parts) > 3) { + candidate = text(array(parts, 0, 3), '/') + pkg_info = Shop.resolve_package_info(candidate) + if (pkg_info && pkg_info != 'local') { + best_pkg = candidate + best_remainder = text(array(parts, 3), '/') } } if (best_pkg && best_remainder) { log.console('fetching ' + best_pkg + '...') _auto = function() { + // Install the package itself first Shop.update(best_pkg) Shop.fetch(best_pkg) Shop.extract(best_pkg) + // Install dependencies iteratively (each dep must be extracted before reading its deps) + var all_deps = {} + var queue = [best_pkg] + var qi = 0 + var current = null + var direct_deps = null + var dep_locator = null + var dep_dir = null + var build_mod = null + var target = null + var _build_c = null + var _read_deps = null + while (qi < length(queue)) { + current = queue[qi] + qi = qi + 1 + _read_deps = function() { + direct_deps = pkg_tools.dependencies(current) + } disruption { + direct_deps = null + } + _read_deps() + if (direct_deps) { + arrfor(array(direct_deps), function(alias) { + dep_locator = direct_deps[alias] + if (!all_deps[dep_locator]) { + all_deps[dep_locator] = true + dep_dir = pkg_tools.get_dir(dep_locator) + if (!fd.is_dir(dep_dir)) { + log.console(' installing dependency: ' + dep_locator) + Shop.update(dep_locator) + Shop.fetch(dep_locator) + Shop.extract(dep_locator) + } + push(queue, dep_locator) + } + }) + } + } + // Build scripts for all packages Shop.build_package_scripts(best_pkg) + arrfor(array(all_deps), function(dep) { + Shop.build_package_scripts(dep) + }) + // Build C modules + build_mod = use_cache['core/build'] + if (build_mod) { + _build_c = function() { + target = build_mod.detect_host_target() + arrfor(array(all_deps), function(dep) { + build_mod.build_dynamic(dep, target, 'release') + }) + build_mod.build_dynamic(best_pkg, target, 'release') + } disruption {} + _build_c() + } } disruption { return null } @@ -1600,10 +1658,16 @@ function get_package_zip(pkg) // Update: Check for new version, update lock, fetch and extract // Returns the new lock entry if updated, null if already up to date or failed Shop.update = function(pkg) { + Shop.verify_package_name(pkg) var lock = Shop.load_lock() var lock_entry = lock[pkg] var info = Shop.resolve_package_info(pkg) + if (!info) { + log.error("Not a valid package locator: " + pkg) + return null + } + log.shop(`checking ${pkg}`) var new_entry = null From 4ac92c8a87ab12aa9f12c0e9da8e90550c171c4a Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 14:44:48 -0600 Subject: [PATCH 3/5] reduce dups --- add.ce | 92 +++++--------------- audit.ce | 9 +- build.ce | 15 +--- clean.ce | 8 +- clone.ce | 23 +---- fetch.ce | 100 +++++---------------- graph.ce | 10 +-- install.ce | 210 +++++++------------------------------------- internal/engine.cm | 10 +-- internal/shop.cm | 212 ++++++++++++++++++++++----------------------- link.ce | 21 +---- list.ce | 10 +-- remove.ce | 9 +- resolve.ce | 8 +- update.ce | 121 ++++++++------------------ verify.ce | 9 +- 16 files changed, 232 insertions(+), 635 deletions(-) diff --git a/add.ce b/add.ce index 3749242c..ba2ce584 100644 --- a/add.ce +++ b/add.ce @@ -9,20 +9,18 @@ var shop = use('internal/shop') var pkg = use('package') -var build = use('build') var fd = use('fd') var locator = null var alias = null -var resolved = null -var parts = null -var cwd = null -var build_target = null var recursive = false +var cwd = fd.realpath('.') +var parts = null var locators = null var added = 0 var failed = 0 -var summary = null +var _add_dep = null +var _install = null array(args, function(arg) { if (arg == '--help' || arg == '-h') { @@ -52,89 +50,65 @@ if (!locator && !recursive) { $stop() } -// Resolve relative paths to absolute paths -if (locator && (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator))) { - resolved = fd.realpath(locator) - if (resolved) { - locator = resolved - } -} +if (locator) + locator = shop.resolve_locator(locator) // Generate default alias from locator if (!alias && locator) { - // Use the last component of the locator as alias parts = array(locator, '/') alias = parts[length(parts) - 1] - // Remove any version suffix - if (search(alias, '@') != null) { + if (search(alias, '@') != null) alias = array(alias, '@')[0] - } } // Check we're in a package directory -cwd = fd.realpath('.') if (!fd.is_file(cwd + '/cell.toml')) { log.error("Not in a package directory (no cell.toml found)") $stop() } -// If -r flag, find all packages recursively and add each +// Recursive mode if (recursive) { - if (!locator) { - locator = '.' - } - resolved = fd.realpath(locator) - if (!resolved || !fd.is_dir(resolved)) { + if (!locator) locator = '.' + locator = shop.resolve_locator(locator) + if (!fd.is_dir(locator)) { log.error(`${locator} is not a directory`) $stop() } - locators = filter(pkg.find_packages(resolved), function(p) { + locators = filter(pkg.find_packages(locator), function(p) { return p != cwd }) if (length(locators) == 0) { - log.console("No packages found in " + resolved) + log.console("No packages found in " + locator) $stop() } - log.console(`Found ${text(length(locators))} package(s) in ${resolved}`) + log.console(`Found ${text(length(locators))} package(s) in ${locator}`) + added = 0 + failed = 0 arrfor(locators, function(loc) { - // Generate alias from directory name var loc_parts = array(loc, '/') var loc_alias = loc_parts[length(loc_parts) - 1] - log.console(" Adding " + loc + " as '" + loc_alias + "'...") var _add = function() { pkg.add_dependency(null, loc, loc_alias) - shop.get(loc) - shop.extract(loc) - shop.build_package_scripts(loc) - var _build_c = function() { - build_target = build.detect_host_target() - build.build_dynamic(loc, build_target, 'release') - } disruption { - // Not all packages have C code - } - _build_c() - added++ + shop.sync(loc) + added = added + 1 } disruption { log.console(` Warning: Failed to add ${loc}`) - failed++ + failed = failed + 1 } _add() }) - summary = "Added " + text(added) + " package(s)." - if (failed > 0) { - summary += " Failed: " + text(failed) + "." - } - log.console(summary) + log.console("Added " + text(added) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) $stop() } +// Single package add log.console("Adding " + locator + " as '" + alias + "'...") -// Add to local project's cell.toml -var _add_dep = function() { +_add_dep = function() { pkg.add_dependency(null, locator, alias) log.console(" Added to cell.toml") } disruption { @@ -143,26 +117,8 @@ var _add_dep = function() { } _add_dep() -// Install to shop -var _install = function() { - shop.get(locator) - shop.extract(locator) - - // Build scripts - var script_result = shop.build_package_scripts(locator) - if (length(script_result.errors) > 0) { - log.console(" Warning: " + text(length(script_result.errors)) + " script(s) failed to compile") - } - - // Build C code if any - var _build_c = function() { - build_target = build.detect_host_target() - build.build_dynamic(locator, build_target, 'release') - } disruption { - // Not all packages have C code - } - _build_c() - +_install = function() { + shop.sync_with_deps(locator) log.console(" Installed to shop") } disruption { log.error("Failed to install") diff --git a/audit.ce b/audit.ce index 8b228098..948f76db 100644 --- a/audit.ce +++ b/audit.ce @@ -10,11 +10,9 @@ var shop = use('internal/shop') var pkg = use('package') -var fd = use('fd') var target_package = null var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--help' || args[i] == '-h') { @@ -30,12 +28,7 @@ for (i = 0; i < length(args); i++) { // Resolve local paths if (target_package) { - if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) { - resolved = fd.realpath(target_package) - if (resolved) { - target_package = resolved - } - } + target_package = shop.resolve_locator(target_package) } var packages = null diff --git a/build.ce b/build.ce index 7d3b445a..fd025aa3 100644 --- a/build.ce +++ b/build.ce @@ -22,7 +22,6 @@ var dry_run = false var i = 0 var targets = null var t = 0 -var resolved = null var lib = null var results = null var success = 0 @@ -74,15 +73,8 @@ for (i = 0; i < length(args); i++) { } } -// Resolve local paths to absolute paths -if (target_package) { - if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) { - resolved = fd.realpath(target_package) - if (resolved) { - target_package = resolved - } - } -} +if (target_package) + target_package = shop.resolve_locator(target_package) // Detect target if not specified if (!target) { @@ -97,10 +89,9 @@ if (target && !build.has_target(target)) { } var packages = shop.list_packages() -log.console('Preparing packages...') arrfor(packages, function(package) { if (package == 'core') return - shop.extract(package) + shop.sync(package, {no_build: true}) }) var _build = null diff --git a/clean.ce b/clean.ce index 89c80bc7..9b5cceed 100644 --- a/clean.ce +++ b/clean.ce @@ -24,7 +24,6 @@ var clean_fetch = false var deep = false var dry_run = false var i = 0 -var resolved = null var deps = null for (i = 0; i < length(args); i++) { @@ -76,12 +75,7 @@ var is_shop_scope = (scope == 'shop') var is_world_scope = (scope == 'world') if (!is_shop_scope && !is_world_scope) { - if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) { - resolved = fd.realpath(scope) - if (resolved) { - scope = resolved - } - } + scope = shop.resolve_locator(scope) } var files_to_delete = [] diff --git a/clone.ce b/clone.ce index 9f9b1afe..366e5407 100644 --- a/clone.ce +++ b/clone.ce @@ -7,10 +7,6 @@ var fd = use('fd') var http = use('http') var miniz = use('miniz') -var resolved = null -var cwd = null -var parent = null - if (length(args) < 2) { log.console("Usage: cell clone ") log.console("Clones a cell package to a local path and links it.") @@ -21,24 +17,7 @@ var origin = args[0] var target_path = args[1] // Resolve target path to absolute -if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) { - resolved = fd.realpath(target_path) - if (resolved) { - target_path = resolved - } else { - // Path doesn't exist yet, resolve relative to cwd - cwd = fd.realpath('.') - if (target_path == '.') { - target_path = cwd - } else if (starts_with(target_path, './')) { - target_path = cwd + text(target_path, 1) - } else if (starts_with(target_path, '../')) { - // Go up one directory from cwd - parent = fd.dirname(cwd) - target_path = parent + text(target_path, 2) - } - } -} +target_path = shop.resolve_locator(target_path) // Check if target already exists if (fd.is_dir(target_path)) { diff --git a/fetch.ce b/fetch.ce index cc526740..f2b5c45a 100644 --- a/fetch.ce +++ b/fetch.ce @@ -1,104 +1,46 @@ -// cell fetch - Fetch package zips from remote sources +// cell fetch - Sync packages from remote sources // -// This command ensures that the zip files on disk match what's in the lock file. -// For local packages, this is a no-op. -// For remote packages, downloads the zip if not present or hash mismatch. +// Ensures all packages are fetched, extracted, compiled, and ready to use. +// For local packages, this is a no-op (symlinks only). // // Usage: -// cell fetch - Fetch all packages -// cell fetch - Fetch a specific package +// cell fetch - Sync all packages +// cell fetch - Sync a specific package var shop = use('internal/shop') -// Parse arguments var target_pkg = null var i = 0 +var packages = null +var count = 0 for (i = 0; i < length(args); i++) { if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell fetch [package]") - log.console("Fetch package zips from remote sources.") + log.console("Sync packages from remote sources.") log.console("") log.console("Arguments:") - log.console(" package Optional package name to fetch. If omitted, fetches all.") - log.console("") - log.console("This command ensures that the zip files on disk match what's in") - log.console("the lock file. For local packages, this is a no-op.") + log.console(" package Optional package to sync. If omitted, syncs all.") $stop() } else if (!starts_with(args[i], '-')) { target_pkg = args[i] } } -var all_packages = shop.list_packages() -var lock = shop.load_lock() -var packages_to_fetch = [] -var _update = null - if (target_pkg) { - // Fetch specific package - auto-update if not in lock - if (find(all_packages, target_pkg) == null) { - log.console("Package not in lock, updating: " + target_pkg) - _update = function() { - shop.update(target_pkg) - } disruption { - log.error("Could not update package: " + target_pkg) - $stop() - } - _update() - // Reload after update - all_packages = shop.list_packages() - lock = shop.load_lock() - if (find(all_packages, target_pkg) == null) { - log.error("Package not found: " + target_pkg) - $stop() - } - } - push(packages_to_fetch, target_pkg) + target_pkg = shop.resolve_locator(target_pkg) + log.console("Syncing " + target_pkg + "...") + shop.sync(target_pkg) + log.console("Done.") } else { - // Fetch all packages - packages_to_fetch = all_packages + packages = shop.list_packages() + count = 0 + arrfor(packages, function(pkg) { + if (pkg == 'core') return + shop.sync(pkg) + count = count + 1 + }) + log.console("Synced " + text(count) + " package(s).") } -var remote_count = 0 -arrfor(packages_to_fetch, function(pkg) { - var entry = lock[pkg] - if (pkg != 'core' && (!entry || entry.type != 'local')) - remote_count++ -}, null, null) - -if (remote_count > 0) - log.console(`Fetching ${text(remote_count)} remote package(s)...`) - -var downloaded_count = 0 -var cached_count = 0 -var fail_count = 0 - -arrfor(packages_to_fetch, function(pkg) { - // Skip core (handled separately) - if (pkg == 'core') return - - var result = shop.fetch(pkg) - if (result.status == 'local') { - // Local packages are just symlinks, nothing to fetch - return - } else if (result.status == 'cached') { - cached_count++ - } else if (result.status == 'downloaded') { - log.console(" Downloaded: " + pkg) - downloaded_count++ - } else if (result.status == 'error') { - log.error(" Failed: " + pkg + (result.message ? " - " + result.message : "")) - fail_count++ - } -}, null, null) - -log.console("") -var parts = [] -if (downloaded_count > 0) push(parts, `${text(downloaded_count)} downloaded`) -if (cached_count > 0) push(parts, `${text(cached_count)} cached`) -if (fail_count > 0) push(parts, `${text(fail_count)} failed`) -if (length(parts) == 0) push(parts, "nothing to fetch") -log.console("Fetch complete: " + text(parts, ", ")) - $stop() diff --git a/graph.ce b/graph.ce index 07d24566..e8eb2cea 100644 --- a/graph.ce +++ b/graph.ce @@ -15,7 +15,6 @@ 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 @@ -23,7 +22,6 @@ var format = 'tree' var show_locked = false var show_world = false var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--format' || args[i] == '-f') { @@ -127,13 +125,7 @@ if (show_world) { target_locator = '.' } - // Resolve local paths - if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { - resolved = fd.realpath(target_locator) - if (resolved) { - target_locator = resolved - } - } + target_locator = shop.resolve_locator(target_locator) push(roots, target_locator) } diff --git a/install.ce b/install.ce index c5f2c818..41deb8df 100644 --- a/install.ce +++ b/install.ce @@ -1,4 +1,4 @@ -// cell install - Install a package to the shop +// cell install - Install a package and its dependencies // // Usage: // cell install Install a package and its dependencies @@ -6,34 +6,23 @@ // // Options: // --target Build for target platform -// --refresh Refresh floating refs before locking // --dry-run Show what would be installed // -r Recursively find and install all packages in directory var shop = use('internal/shop') -var build = use('build') var pkg = use('package') var fd = use('fd') -if (length(args) < 1) { - log.console("Usage: cell install [options]") - log.console("") - log.console("Options:") - log.console(" --target Build for target platform") - log.console(" --refresh Refresh floating refs before locking") - log.console(" --dry-run Show what would be installed") - log.console(" -r Recursively find and install all packages in directory") - $stop() -} - var locator = null var target_triple = null -var refresh = false var dry_run = false var recursive = false var i = 0 -var resolved = null var locators = null +var cwd = fd.realpath('.') +var lock = null +var installed = 0 +var failed = 0 for (i = 0; i < length(args); i++) { if (args[i] == '--target' || args[i] == '-t') { @@ -43,8 +32,6 @@ for (i = 0; i < length(args); i++) { 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] == '-r') { @@ -52,11 +39,10 @@ for (i = 0; i < length(args); i++) { } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell install [options]") log.console("") - log.console("Install a package and its dependencies to the shop.") + log.console("Install a package and its dependencies.") log.console("") log.console("Options:") log.console(" --target Build for target platform") - log.console(" --refresh Refresh floating refs before locking") log.console(" --dry-run Show what would be installed") log.console(" -r Recursively find and install all packages in directory") $stop() @@ -66,197 +52,65 @@ for (i = 0; i < length(args); i++) { } if (!locator && !recursive) { - log.console("Usage: cell install ") + log.console("Usage: cell install [options]") $stop() } -// Resolve relative paths to absolute paths -// Local paths like '.' or '../foo' need to be converted to absolute paths -if (locator && (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator))) { - resolved = fd.realpath(locator) - if (resolved) { - locator = resolved - } -} +if (locator) + locator = shop.resolve_locator(locator) -var cwd = fd.realpath('.') - -// If -r flag, find all packages recursively and install each +// Recursive mode: find all packages in directory and install each if (recursive) { - if (!locator) { - locator = '.' - } - resolved = fd.realpath(locator) - if (!resolved || !fd.is_dir(resolved)) { + if (!locator) locator = '.' + locator = shop.resolve_locator(locator) + if (!fd.is_dir(locator)) { log.error(`${locator} is not a directory`) $stop() } - locators = filter(pkg.find_packages(resolved), function(p) { + locators = filter(pkg.find_packages(locator), function(p) { return p != cwd }) if (length(locators) == 0) { - log.console("No packages found in " + resolved) + log.console("No packages found in " + locator) $stop() } - log.console(`Found ${text(length(locators))} package(s) in ${resolved}`) -} + log.console(`Found ${text(length(locators))} package(s) in ${locator}`) -// Default target -if (!target_triple) { - target_triple = build.detect_host_target() -} - -// Gather all packages that will be installed -var packages_to_install = [] -var skipped_packages = [] -var summary = null -var visited = {} - -// Recursive mode: install all found packages and exit -if (recursive) { if (dry_run) { log.console("Would install:") arrfor(locators, function(loc) { - var lock = shop.load_lock() - var exists = lock[loc] != null - log.console(" " + loc + (exists ? " (already installed)" : "")) + lock = shop.load_lock() + log.console(" " + loc + (lock[loc] ? " (already installed)" : "")) }) $stop() } + installed = 0 + failed = 0 arrfor(locators, function(loc) { log.console(" Installing " + loc + "...") var _inst = function() { - shop.update(loc) - shop.extract(loc) - shop.build_package_scripts(loc) - var _build_c = function() { - build.build_dynamic(loc, target_triple, 'release') - } disruption { - // Not all packages have C code - } - _build_c() - push(packages_to_install, loc) + shop.sync(loc, {target: target_triple}) + installed = installed + 1 } disruption { - push(skipped_packages, loc) + failed = failed + 1 log.console(` Warning: Failed to install ${loc}`) } _inst() }) - summary = "Installed " + text(length(packages_to_install)) + " package(s)." - if (length(skipped_packages) > 0) { - summary += " Failed: " + text(length(skipped_packages)) + "." - } - log.console(summary) + log.console("Installed " + text(installed) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) + $stop() +} + +// Single package install with dependencies +if (dry_run) { + log.console("Would install: " + locator + " (and dependencies)") $stop() } log.console("Installing " + locator + "...") - -function gather_packages(pkg_locator) { - var lock = null - var update_result = null - var deps = null - if (visited[pkg_locator]) return - visited[pkg_locator] = true - - // Check if this is a local path that doesn't exist - if (starts_with(pkg_locator, '/') && !fd.is_dir(pkg_locator)) { - push(skipped_packages, pkg_locator) - log.console(" Skipping missing local package: " + pkg_locator) - return - } - - push(packages_to_install, pkg_locator) - - // Try to read dependencies - var _gather = function() { - // For packages not yet extracted, we need to update and extract first to read deps - lock = shop.load_lock() - if (!lock[pkg_locator]) { - if (!dry_run) { - update_result = shop.update(pkg_locator) - if (update_result) { - shop.extract(pkg_locator) - } else { - // Update failed - package might not be fetchable - log.console("Warning: Could not fetch " + pkg_locator) - return - } - } - } else { - // Package is in lock, ensure it's extracted - if (!dry_run) { - shop.extract(pkg_locator) - } - } - - deps = pkg.dependencies(pkg_locator) - if (deps) { - arrfor(array(deps), function(alias) { - var dep_locator = deps[alias] - gather_packages(dep_locator) - }) - } - } disruption { - // Package might not have dependencies or cell.toml issue - if (!dry_run) { - log.console(`Warning: Could not read dependencies for ${pkg_locator}`) - } - } - _gather() -} - -// Gather all packages -gather_packages(locator) - -if (dry_run) { - log.console("Would install:") - arrfor(packages_to_install, function(p) { - var lock = shop.load_lock() - var exists = lock[p] != null - log.console(" " + p + (exists ? " (already installed)" : "")) - }) - if (length(skipped_packages) > 0) { - log.console("") - log.console("Would skip (missing local paths):") - arrfor(skipped_packages, function(p) { - log.console(" " + p) - }) - } - $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 - var _build_c = function() { - build.build_dynamic(pkg_locator, target_triple, 'release') - } disruption { - // Not all packages have C code - } - _build_c() -} - -arrfor(packages_to_install, function(p) { - log.console(" Installing " + p + "...") - install_package(p) -}) - -summary = "Installed " + text(length(packages_to_install)) + " package(s)." -if (length(skipped_packages) > 0) { - summary += " Skipped " + text(length(skipped_packages)) + " missing local path(s)." -} -log.console(summary) +shop.sync_with_deps(locator, {refresh: true, target: target_triple}) +log.console("Done.") $stop() diff --git a/internal/engine.cm b/internal/engine.cm index 853ea881..6d839d0c 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -1268,20 +1268,12 @@ $_.clock(_ => { if (!fd.is_dir(_dep_dir)) { log.console('installing missing dependency: ' + _deps[_di]) _auto_install = function() { - shop.update(_deps[_di]) - shop.fetch(_deps[_di]) - shop.extract(_deps[_di]) - shop.build_package_scripts(_deps[_di]) + shop.sync(_deps[_di]) } disruption { log.error('failed to install dependency: ' + _deps[_di]) disrupt } _auto_install() - _dep_dir = package.get_dir(_deps[_di]) - if (!fd.is_dir(_dep_dir)) { - log.error('missing dependency package: ' + _deps[_di]) - disrupt - } } _di = _di + 1 } diff --git a/internal/shop.cm b/internal/shop.cm index ceea81ea..e6e3949c 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -270,6 +270,17 @@ function safe_package_path(pkg) return replace(pkg, '@', '_') } +// Resolve a locator string to its canonical form +// Handles '.', './', '../', and existing directory paths +Shop.resolve_locator = function(locator) { + var resolved = null + if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { + resolved = fd.realpath(locator) + if (resolved) return resolved + } + return locator +} + function package_cache_path(pkg) { return global_shop_path + '/cache/' + replace(replace(pkg, '/', '_'), '@', '_') @@ -297,7 +308,8 @@ Shop.load_lock = function() { // Save lock.toml configuration (to global shop) Shop.save_lock = function(lock) { var path = global_shop_path + '/lock.toml' - fd.slurpwrite(path, stone(blob(toml.encode(lock)))); + fd.slurpwrite(path, stone(blob(toml.encode(lock)))) + _lock = lock } @@ -1317,8 +1329,6 @@ Shop.use = function use(path, package_context) { return use_cache[info.cache_key] } -Shop.resolve_locator = resolve_locator - // Resolve a use() module path to a filesystem path without compiling. // Returns the absolute path string, or null if not found. Shop.resolve_use_path = function(path, ctx) { @@ -1335,14 +1345,13 @@ Shop.resolve_program = function(prog, package_context) { var info = resolve_path(prog + '.ce', package_context) if (info) return info - // Auto-install: if the path matches a recognized remote locator, try fetching + // Find best matching package from lock or infer from path var lock = Shop.load_lock() var best_pkg = null var best_remainder = null - var pkg_info = null var parts = array(prog, '/') - var i = 0 var candidate = null + var pkg_info = null var _auto = null arrfor(array(lock), function(pkg_name) { if (starts_with(prog, pkg_name + '/')) { @@ -1353,8 +1362,7 @@ Shop.resolve_program = function(prog, package_context) { } }) - // If not in lock, check if this looks like a fetchable package - // For gitea-style URLs, the package root is host/owner/repo (3 components) + // If not in lock, try gitea-style 3-component package (host/owner/repo) if (!best_pkg && length(parts) > 3) { candidate = text(array(parts, 0, 3), '/') pkg_info = Shop.resolve_package_info(candidate) @@ -1364,78 +1372,19 @@ Shop.resolve_program = function(prog, package_context) { } } - if (best_pkg && best_remainder) { - log.console('fetching ' + best_pkg + '...') - _auto = function() { - // Install the package itself first - Shop.update(best_pkg) - Shop.fetch(best_pkg) - Shop.extract(best_pkg) - // Install dependencies iteratively (each dep must be extracted before reading its deps) - var all_deps = {} - var queue = [best_pkg] - var qi = 0 - var current = null - var direct_deps = null - var dep_locator = null - var dep_dir = null - var build_mod = null - var target = null - var _build_c = null - var _read_deps = null - while (qi < length(queue)) { - current = queue[qi] - qi = qi + 1 - _read_deps = function() { - direct_deps = pkg_tools.dependencies(current) - } disruption { - direct_deps = null - } - _read_deps() - if (direct_deps) { - arrfor(array(direct_deps), function(alias) { - dep_locator = direct_deps[alias] - if (!all_deps[dep_locator]) { - all_deps[dep_locator] = true - dep_dir = pkg_tools.get_dir(dep_locator) - if (!fd.is_dir(dep_dir)) { - log.console(' installing dependency: ' + dep_locator) - Shop.update(dep_locator) - Shop.fetch(dep_locator) - Shop.extract(dep_locator) - } - push(queue, dep_locator) - } - }) - } - } - // Build scripts for all packages - Shop.build_package_scripts(best_pkg) - arrfor(array(all_deps), function(dep) { - Shop.build_package_scripts(dep) - }) - // Build C modules - build_mod = use_cache['core/build'] - if (build_mod) { - _build_c = function() { - target = build_mod.detect_host_target() - arrfor(array(all_deps), function(dep) { - build_mod.build_dynamic(dep, target, 'release') - }) - build_mod.build_dynamic(best_pkg, target, 'release') - } disruption {} - _build_c() - } - } disruption { - return null - } - _auto() - // Retry resolution - info = resolve_path(prog + '.ce', package_context) - if (info) return info - } + if (!best_pkg || !best_remainder) return null - return null + // Auto-install the package and all its dependencies + log.console('fetching ' + best_pkg + '...') + _auto = function() { + Shop.sync_with_deps(best_pkg) + } disruption { + return null + } + _auto() + + info = resolve_path(prog + '.ce', package_context) + return info } // Resolve a use() module path to {resolved_path, package, type} without compiling. @@ -1713,6 +1662,82 @@ Shop.update = function(pkg) { return new_entry } +// Sync a package: ensure it's in lock, fetched, extracted, and compiled +// opts.refresh - check remote for updates even if lock entry exists +// opts.no_build - skip C module build step +// opts.target - explicit build target (auto-detected if not provided) +// opts.buildtype - 'release'|'debug'|'minsize' (default 'release') +Shop.sync = function(pkg, opts) { + var lock = Shop.load_lock() + var info = Shop.resolve_package_info(pkg) + var build_mod = null + var target = null + var _build_c = null + + // Step 1: Ensure lock entry (update if refresh or not in lock) + if ((opts && opts.refresh) || !lock[pkg]) + Shop.update(pkg) + + // Step 2: Fetch zip (no-op for local packages) + if (info && info != 'local') + Shop.fetch(pkg) + + // Step 3: Extract to packages dir + Shop.extract(pkg) + + // Step 4: Compile scripts + Shop.build_package_scripts(pkg) + + // Step 5: Build C modules + if (!opts || !opts.no_build) { + build_mod = use_cache['core/build'] + if (build_mod) { + target = (opts && opts.target) ? opts.target : build_mod.detect_host_target() + _build_c = function() { + build_mod.build_dynamic(pkg, target, (opts && opts.buildtype) ? opts.buildtype : 'release') + } disruption { + // Not all packages have C code + } + _build_c() + } + } +} + +// Sync a package and all its dependencies (BFS) +Shop.sync_with_deps = function(pkg, opts) { + var visited = {} + var queue = [pkg] + var qi = 0 + var current = null + var deps = null + var dep_locator = null + var _read_deps = null + + while (qi < length(queue)) { + current = queue[qi] + qi = qi + 1 + if (visited[current]) continue + visited[current] = true + + Shop.sync(current, opts) + + _read_deps = function() { + deps = pkg_tools.dependencies(current) + } disruption { + deps = null + } + _read_deps() + + if (deps) { + arrfor(array(deps), function(alias) { + dep_locator = deps[alias] + if (!visited[dep_locator]) + push(queue, dep_locator) + }) + } + } +} + function install_zip(zip_blob, target_dir) { var zip = miniz.read(zip_blob) if (!zip) { print("Failed to read zip archive"); disrupt } @@ -1779,34 +1804,6 @@ Shop.remove = function(pkg) { return true } -Shop.get = function(pkg) { - var lock = Shop.load_lock() - var info = null - var commit = null - - if (!lock[pkg]) { - info = Shop.resolve_package_info(pkg) - if (!info) { - print("Invalid package: " + pkg); disrupt - } - - commit = null - if (info != 'local') { - commit = fetch_remote_hash(pkg) - if (!commit) { - print("Could not resolve commit for " + pkg); disrupt - } - } - - lock[pkg] = { - type: info, - commit: commit, - updated: time.number() - } - Shop.save_lock(lock) - } -} - // Compile a module // List all files in a package @@ -1883,6 +1880,7 @@ Shop.build_package_scripts = function(package) ok = ok + 1 } disruption { push(errors, script) + log.console(" compile error: " + package + '/' + script) } _try() }) diff --git a/link.ce b/link.ce index 2c552a5e..234c0c65 100644 --- a/link.ce +++ b/link.ce @@ -28,7 +28,6 @@ var target = null var start_idx = 0 var arg1 = null var arg2 = null -var cwd = null var toml_path = null var content = null var _restore = null @@ -123,30 +122,14 @@ if (cmd == 'list') { target = arg2 // Resolve target if it's a local path - if (target == '.' || fd.is_dir(target)) { - target = fd.realpath(target) - } else if (starts_with(target, './') || starts_with(target, '../')) { - // Relative path that doesn't exist yet - try to resolve anyway - cwd = fd.realpath('.') - if (starts_with(target, './')) { - target = cwd + text(target, 1) - } else { - // For ../ paths, let fd.realpath handle it if possible - target = fd.realpath(target) || target - } - } - // Otherwise target is a package name (e.g., github.com/prosperon) + target = shop.resolve_locator(target) } else { // One argument: assume it's a local path, infer package name from cell.toml target = arg1 // Resolve path - if (target == '.' || fd.is_dir(target)) { - target = fd.realpath(target) - } else if (starts_with(target, './') || starts_with(target, '../')) { - target = fd.realpath(target) || target - } + target = shop.resolve_locator(target) // Must be a local path with cell.toml toml_path = target + '/cell.toml' diff --git a/list.ce b/list.ce index 262f873a..283c6e82 100644 --- a/list.ce +++ b/list.ce @@ -8,11 +8,9 @@ var shop = use('internal/shop') var pkg = use('package') var link = use('link') -var fd = use('fd') var mode = 'local' var target_pkg = null -var resolved = null var i = 0 var deps = null var packages = null @@ -37,13 +35,7 @@ if (args && length(args) > 0) { mode = 'package' target_pkg = args[0] - // Resolve local paths - if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { - resolved = fd.realpath(target_pkg) - if (resolved) { - target_pkg = resolved - } - } + target_pkg = shop.resolve_locator(target_pkg) } } diff --git a/remove.ce b/remove.ce index 09f6d76d..a7f31e9b 100644 --- a/remove.ce +++ b/remove.ce @@ -17,7 +17,6 @@ var target_pkg = null var prune = false var dry_run = false var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--prune') { @@ -43,13 +42,7 @@ if (!target_pkg) { $stop() } -// Resolve relative paths to absolute paths -if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { - resolved = fd.realpath(target_pkg) - if (resolved) { - target_pkg = resolved - } -} +target_pkg = shop.resolve_locator(target_pkg) var packages_to_remove = [target_pkg] diff --git a/resolve.ce b/resolve.ce index e67dba7f..c69d787b 100644 --- a/resolve.ce +++ b/resolve.ce @@ -21,7 +21,6 @@ var target_triple = null var show_locked = false var refresh_first = false var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--target' || args[i] == '-t') { @@ -56,12 +55,7 @@ if (!target_locator) { } // Resolve local paths -if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { - resolved = fd.realpath(target_locator) - if (resolved) { - target_locator = resolved - } -} +target_locator = shop.resolve_locator(target_locator) // Check if it's a valid package var pkg_dir = null diff --git a/update.ce b/update.ce index 3c40e2f7..db653ac7 100644 --- a/update.ce +++ b/update.ce @@ -1,30 +1,28 @@ // cell update [] - Update packages from remote sources // // Usage: -// cell update Update all packages in shop +// cell update Update all packages // cell update . Update current directory package // cell update Update a specific package // // Options: -// --build Run build after updating -// --target Target platform for build (requires --build) +// --target Target platform for build // --follow-links Update link targets instead of origins // --git Run git pull on local packages before updating var shop = use('internal/shop') -var build = use('build') var fd = use('fd') var os = use('os') +var link = use('link') var target_pkg = null -var run_build = false var target_triple = null var follow_links = false var git_pull = false var i = 0 -var resolved = null +var updated = 0 +var packages = null -// Parse arguments for (i = 0; i < length(args); i++) { if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell update [] [options]") @@ -32,13 +30,10 @@ for (i = 0; i < length(args); i++) { log.console("Update packages from remote sources.") log.console("") log.console("Options:") - log.console(" --build Run build after updating") - log.console(" --target Target platform for build (requires --build)") + log.console(" --target Target platform for build") log.console(" --follow-links Update link targets instead of origins") log.console(" --git Run git pull on local packages") $stop() - } else if (args[i] == '--build') { - run_build = true } else if (args[i] == '--target' || args[i] == '-t') { if (i + 1 < length(args)) { target_triple = args[++i] @@ -52,33 +47,22 @@ for (i = 0; i < length(args); i++) { git_pull = true } else if (!starts_with(args[i], '-')) { target_pkg = args[i] - // Resolve relative paths to absolute paths - if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { - resolved = fd.realpath(target_pkg) - if (resolved) { - target_pkg = resolved - } - } } } -// Default target if building -if (run_build && !target_triple) { - target_triple = build.detect_host_target() -} +if (target_pkg) + target_pkg = shop.resolve_locator(target_pkg) -var link = use('link') - -function update_and_fetch(pkg) { +function update_one(pkg) { + var effective_pkg = pkg + var link_target = null var lock = shop.load_lock() var old_entry = lock[pkg] var old_commit = old_entry ? old_entry.commit : null - var effective_pkg = pkg - var link_target = null + var info = shop.resolve_package_info(pkg) var new_entry = null var old_str = null - // Handle follow-links option if (follow_links) { link_target = link.get_target(pkg) if (link_target) { @@ -87,80 +71,47 @@ function update_and_fetch(pkg) { } } + // For local packages with --git, pull first + if (git_pull && info == 'local' && fd.is_dir(effective_pkg + '/.git')) { + log.console(" " + effective_pkg + " (git pull)") + os.system('git -C "' + effective_pkg + '" pull') + } + + // Check for update (sets lock entry if changed) new_entry = shop.update(effective_pkg) - if (new_entry) { - if (new_entry.commit) { - old_str = old_commit ? text(old_commit, 0, 8) : "(new)" - log.console(" " + effective_pkg + " " + old_str + " -> " + text(new_entry.commit, 0, 8)) - shop.fetch(effective_pkg) - } else { - // Local package - run git pull if requested - if (git_pull && fd.is_dir(effective_pkg + '/.git')) { - log.console(" " + effective_pkg + " (git pull)") - os.system('git -C "' + effective_pkg + '" pull') - } else { - log.console(" " + effective_pkg + " (local)") - } - } - shop.extract(effective_pkg) - shop.build_package_scripts(effective_pkg) - return effective_pkg + if (new_entry && new_entry.commit) { + old_str = old_commit ? text(old_commit, 0, 8) : "(new)" + log.console(" " + effective_pkg + " " + old_str + " -> " + text(new_entry.commit, 0, 8)) } - return null + + // Sync: fetch, extract, build + shop.sync(effective_pkg, {target: target_triple}) + + return new_entry } -var updated_packages = [] - -var updated = null -var packages = null -var pkg_count = 0 -var pkg = null if (target_pkg) { - updated = update_and_fetch(target_pkg) - if (updated) { - push(updated_packages, updated) + if (update_one(target_pkg)) { log.console("Updated " + target_pkg + ".") } else { log.console(target_pkg + " is up to date.") } } else { packages = shop.list_packages() - pkg_count = length(packages) - log.console("Checking for updates (" + text(pkg_count) + " package" + (pkg_count == 1 ? "" : "s") + ")...") + log.console("Checking for updates (" + text(length(packages)) + " packages)...") - for (i = 0; i < length(packages); i++) { - pkg = packages[i] - if (pkg == 'core') continue + arrfor(packages, function(pkg) { + if (pkg == 'core') return + if (update_one(pkg)) + updated = updated + 1 + }) - updated = update_and_fetch(pkg) - if (updated) { - push(updated_packages, updated) - } - } - - if (length(updated_packages) > 0) { - log.console("Updated " + text(length(updated_packages)) + " package" + (length(updated_packages) == 1 ? "" : "s") + ".") + if (updated > 0) { + log.console("Updated " + text(updated) + " package(s).") } else { log.console("All packages are up to date.") } } -// Run build if requested -if (run_build && length(updated_packages) > 0) { - log.console("") - log.console("Building updated packages...") - - arrfor(updated_packages, function(pkg) { - var _build = function() { - var lib = build.build_dynamic(pkg, target_triple, 'release') - if (lib) - log.console(" Built: " + lib) - } disruption { - log.error(" Failed to build " + pkg) - } - _build() - }) -} - $stop() diff --git a/verify.ce b/verify.ce index cda42bba..5e9d2e30 100644 --- a/verify.ce +++ b/verify.ce @@ -21,7 +21,6 @@ var scope = null var deep = false var target_triple = null var i = 0 -var resolved = null for (i = 0; i < length(args); i++) { if (args[i] == '--deep') { @@ -206,13 +205,7 @@ if (scope == 'shop') { // Single package locator = scope - // Resolve local paths - if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { - resolved = fd.realpath(locator) - if (resolved) { - locator = resolved - } - } + locator = shop.resolve_locator(locator) if (deep) { // Gather all dependencies From 35d0890242429c864d261fcd8b9ad5cf42dbab87 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 15:00:08 -0600 Subject: [PATCH 4/5] improved fetch --- add.ce | 204 +++++++++++++++++++++++++++-------------------------- audit.ce | 23 +++--- build.ce | 97 +++++++++++++------------ clean.ce | 67 +++++++++--------- clone.ce | 49 +++++++------ fetch.ce | 55 ++++++++------- graph.ce | 63 +++++++++-------- install.ce | 116 +++++++++++++++--------------- link.ce | 33 +++++---- list.ce | 45 ++++++------ remove.ce | 45 ++++++------ resolve.ce | 73 ++++++++++--------- update.ce | 41 ++++++----- verify.ce | 55 ++++++++------- 14 files changed, 504 insertions(+), 462 deletions(-) diff --git a/add.ce b/add.ce index ba2ce584..1278f0f8 100644 --- a/add.ce +++ b/add.ce @@ -21,111 +21,115 @@ var added = 0 var failed = 0 var _add_dep = null var _install = null +var i = 0 -array(args, function(arg) { - if (arg == '--help' || arg == '-h') { +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell add [alias]") + log.console("") + log.console("Add a dependency to the current package.") + log.console("") + log.console("Examples:") + log.console(" cell add gitea.pockle.world/john/prosperon") + log.console(" cell add gitea.pockle.world/john/cell-image image") + log.console(" cell add ../local-package") + log.console(" cell add -r ../packages") + return + } else if (args[i] == '-r') { + recursive = true + } else if (!starts_with(args[i], '-')) { + if (!locator) { + locator = args[i] + } else if (!alias) { + alias = args[i] + } + } + } + + if (!locator && !recursive) { log.console("Usage: cell add [alias]") - log.console("") - log.console("Add a dependency to the current package.") - log.console("") - log.console("Examples:") - log.console(" cell add gitea.pockle.world/john/prosperon") - log.console(" cell add gitea.pockle.world/john/cell-image image") - log.console(" cell add ../local-package") - log.console(" cell add -r ../packages") - $stop() - } else if (arg == '-r') { - recursive = true - } else if (!starts_with(arg, '-')) { - if (!locator) { - locator = arg - } else if (!alias) { - alias = arg + return + } + + if (locator) + locator = shop.resolve_locator(locator) + + // Generate default alias from locator + if (!alias && locator) { + parts = array(locator, '/') + alias = parts[length(parts) - 1] + if (search(alias, '@') != null) + alias = array(alias, '@')[0] + } + + // Check we're in a package directory + if (!fd.is_file(cwd + '/cell.toml')) { + log.error("Not in a package directory (no cell.toml found)") + return + } + + // Recursive mode + if (recursive) { + if (!locator) locator = '.' + locator = shop.resolve_locator(locator) + if (!fd.is_dir(locator)) { + log.error(`${locator} is not a directory`) + return } - } -}) - -if (!locator && !recursive) { - log.console("Usage: cell add [alias]") - $stop() -} - -if (locator) - locator = shop.resolve_locator(locator) - -// Generate default alias from locator -if (!alias && locator) { - parts = array(locator, '/') - alias = parts[length(parts) - 1] - if (search(alias, '@') != null) - alias = array(alias, '@')[0] -} - -// Check we're in a package directory -if (!fd.is_file(cwd + '/cell.toml')) { - log.error("Not in a package directory (no cell.toml found)") - $stop() -} - -// Recursive mode -if (recursive) { - if (!locator) locator = '.' - locator = shop.resolve_locator(locator) - if (!fd.is_dir(locator)) { - log.error(`${locator} is not a directory`) - $stop() - } - locators = filter(pkg.find_packages(locator), function(p) { - return p != cwd - }) - if (length(locators) == 0) { - log.console("No packages found in " + locator) - $stop() - } - log.console(`Found ${text(length(locators))} package(s) in ${locator}`) - - added = 0 - failed = 0 - arrfor(locators, function(loc) { - var loc_parts = array(loc, '/') - var loc_alias = loc_parts[length(loc_parts) - 1] - log.console(" Adding " + loc + " as '" + loc_alias + "'...") - var _add = function() { - pkg.add_dependency(null, loc, loc_alias) - shop.sync(loc) - added = added + 1 - } disruption { - log.console(` Warning: Failed to add ${loc}`) - failed = failed + 1 + locators = filter(pkg.find_packages(locator), function(p) { + return p != cwd + }) + if (length(locators) == 0) { + log.console("No packages found in " + locator) + return } - _add() - }) + log.console(`Found ${text(length(locators))} package(s) in ${locator}`) - log.console("Added " + text(added) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) - $stop() + added = 0 + failed = 0 + arrfor(locators, function(loc) { + var loc_parts = array(loc, '/') + var loc_alias = loc_parts[length(loc_parts) - 1] + log.console(" Adding " + loc + " as '" + loc_alias + "'...") + var _add = function() { + pkg.add_dependency(null, loc, loc_alias) + shop.sync(loc) + added = added + 1 + } disruption { + log.console(` Warning: Failed to add ${loc}`) + failed = failed + 1 + } + _add() + }) + + log.console("Added " + text(added) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) + return + } + + // Single package add + log.console("Adding " + locator + " as '" + alias + "'...") + + _add_dep = function() { + pkg.add_dependency(null, locator, alias) + log.console(" Added to cell.toml") + } disruption { + log.error("Failed to update cell.toml") + return + } + _add_dep() + + _install = function() { + shop.sync_with_deps(locator) + log.console(" Installed to shop") + } disruption { + log.error("Failed to install") + return + } + _install() + + log.console("Added " + alias + " (" + locator + ")") } - -// Single package add -log.console("Adding " + locator + " as '" + alias + "'...") - -_add_dep = function() { - pkg.add_dependency(null, locator, alias) - log.console(" Added to cell.toml") -} disruption { - log.error("Failed to update cell.toml") - $stop() -} -_add_dep() - -_install = function() { - shop.sync_with_deps(locator) - log.console(" Installed to shop") -} disruption { - log.error("Failed to install") - $stop() -} -_install() - -log.console("Added " + alias + " (" + locator + ")") +run() $stop() diff --git a/audit.ce b/audit.ce index 948f76db..33a5d9f3 100644 --- a/audit.ce +++ b/audit.ce @@ -14,17 +14,18 @@ var pkg = use('package') var target_package = null var i = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '--help' || args[i] == '-h') { - log.console("Usage: cell audit []") - log.console("") - log.console("Test-compile all .ce and .cm scripts in package(s).") - log.console("Reports all errors without stopping at the first failure.") - $stop() - } else if (!starts_with(args[i], '-')) { - target_package = args[i] +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell audit []") + log.console("") + log.console("Test-compile all .ce and .cm scripts in package(s).") + log.console("Reports all errors without stopping at the first failure.") + return + } else if (!starts_with(args[i], '-')) { + target_package = args[i] + } } -} // Resolve local paths if (target_package) { @@ -68,5 +69,7 @@ if (length(all_failures) > 0) { } log.console("Audit complete: " + text(total_ok) + "/" + text(total_scripts) + " scripts compiled" + (total_errors > 0 ? ", " + text(total_errors) + " failed" : "")) +} +run() $stop() diff --git a/build.ce b/build.ce index fd025aa3..3f7aebc4 100644 --- a/build.ce +++ b/build.ce @@ -27,51 +27,52 @@ var results = null var success = 0 var failed = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '-t' || args[i] == '--target') { - if (i + 1 < length(args)) { - target = args[++i] - } else { - log.error('-t requires a target') - $stop() - } - } else if (args[i] == '-p' || args[i] == '--package') { - // Legacy support for -p flag - if (i + 1 < length(args)) { - target_package = args[++i] - } else { - log.error('-p requires a package name') - $stop() - } - } else if (args[i] == '-b' || args[i] == '--buildtype') { - if (i + 1 < length(args)) { - buildtype = args[++i] - if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') { - log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize') - $stop() +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '-t' || args[i] == '--target') { + if (i + 1 < length(args)) { + target = args[++i] + } else { + log.error('-t requires a target') + return } - } else { - log.error('-b requires a buildtype (release, debug, minsize)') - $stop() + } else if (args[i] == '-p' || args[i] == '--package') { + // Legacy support for -p flag + if (i + 1 < length(args)) { + target_package = args[++i] + } else { + log.error('-p requires a package name') + return + } + } else if (args[i] == '-b' || args[i] == '--buildtype') { + if (i + 1 < length(args)) { + buildtype = args[++i] + if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') { + log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize') + return + } + } else { + log.error('-b requires a buildtype (release, debug, minsize)') + return + } + } else if (args[i] == '--force') { + force_rebuild = true + } else if (args[i] == '--verbose' || args[i] == '-v') { + verbose = true + } else if (args[i] == '--dry-run') { + dry_run = true + } else if (args[i] == '--list-targets') { + log.console('Available targets:') + targets = build.list_targets() + for (t = 0; t < length(targets); t++) { + log.console(' ' + targets[t]) + } + return + } else if (!starts_with(args[i], '-') && !target_package) { + // Positional argument - treat as package locator + target_package = args[i] } - } else if (args[i] == '--force') { - force_rebuild = true - } else if (args[i] == '--verbose' || args[i] == '-v') { - verbose = true - } else if (args[i] == '--dry-run') { - dry_run = true - } else if (args[i] == '--list-targets') { - log.console('Available targets:') - targets = build.list_targets() - for (t = 0; t < length(targets); t++) { - log.console(' ' + targets[t]) - } - $stop() - } else if (!starts_with(args[i], '-') && !target_package) { - // Positional argument - treat as package locator - target_package = args[i] } -} if (target_package) target_package = shop.resolve_locator(target_package) @@ -82,11 +83,11 @@ if (!target) { if (target) log.console('Target: ' + target) } -if (target && !build.has_target(target)) { - log.error('Invalid target: ' + target) - log.console('Available targets: ' + text(build.list_targets(), ', ')) - $stop() -} + if (target && !build.has_target(target)) { + log.error('Invalid target: ' + target) + log.console('Available targets: ' + text(build.list_targets(), ', ')) + return + } var packages = shop.list_packages() arrfor(packages, function(package) { @@ -123,5 +124,7 @@ if (target_package) { log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`) } +} +run() $stop() diff --git a/clean.ce b/clean.ce index 9b5cceed..83b7c260 100644 --- a/clean.ce +++ b/clean.ce @@ -26,39 +26,40 @@ var dry_run = false var i = 0 var deps = null -for (i = 0; i < length(args); 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 [] [options]") - log.console("") - log.console("Remove cached material to force refetch/rebuild.") - log.console("") - log.console("Scopes:") - log.console(" 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 (!starts_with(args[i], '-')) { - scope = args[i] +var run = function() { + for (i = 0; i < length(args); 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 [] [options]") + log.console("") + log.console("Remove cached material to force refetch/rebuild.") + log.console("") + log.console("Scopes:") + log.console(" 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") + return + } else if (!starts_with(args[i], '-')) { + scope = args[i] + } } -} // Default to --build if nothing specified if (!clean_build && !clean_fetch) { @@ -190,5 +191,7 @@ if (dry_run) { log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.") } } +} +run() $stop() diff --git a/clone.ce b/clone.ce index 366e5407..b5211e63 100644 --- a/clone.ce +++ b/clone.ce @@ -7,11 +7,12 @@ var fd = use('fd') var http = use('http') var miniz = use('miniz') -if (length(args) < 2) { - log.console("Usage: cell clone ") - log.console("Clones a cell package to a local path and links it.") - $stop() -} +var run = function() { + if (length(args) < 2) { + log.console("Usage: cell clone ") + log.console("Clones a cell package to a local path and links it.") + return + } var origin = args[0] var target_path = args[1] @@ -20,34 +21,34 @@ var target_path = args[1] target_path = shop.resolve_locator(target_path) // Check if target already exists -if (fd.is_dir(target_path)) { - log.console("Error: " + target_path + " already exists") - $stop() -} + if (fd.is_dir(target_path)) { + log.console("Error: " + target_path + " already exists") + return + } log.console("Cloning " + origin + " to " + target_path + "...") // Get the latest commit var info = shop.resolve_package_info(origin) -if (!info || info == 'local') { - log.console("Error: " + origin + " is not a remote package") - $stop() -} + if (!info || info == 'local') { + log.console("Error: " + origin + " is not a remote package") + return + } // Update to get the commit hash var update_result = shop.update(origin) -if (!update_result) { - log.console("Error: Could not fetch " + origin) - $stop() -} + if (!update_result) { + log.console("Error: Could not fetch " + origin) + return + } // Fetch and extract to the target path var lock = shop.load_lock() var entry = lock[origin] -if (!entry || !entry.commit) { - log.console("Error: No commit found for " + origin) - $stop() -} + if (!entry || !entry.commit) { + log.console("Error: No commit found for " + origin) + return + } var download_url = shop.get_download_url(origin, entry.commit) log.console("Downloading from " + download_url) @@ -69,7 +70,7 @@ var _clone = function() { zip = miniz.read(zip_blob) if (!zip) { log.console("Error: Failed to read zip archive") - $stop() + return } // Create target directory @@ -102,6 +103,8 @@ var _clone = function() { } disruption { log.console("Error during clone") } -_clone() + _clone() +} +run() $stop() diff --git a/fetch.ce b/fetch.ce index f2b5c45a..89f653b7 100644 --- a/fetch.ce +++ b/fetch.ce @@ -14,33 +14,36 @@ var i = 0 var packages = null var count = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '--help' || args[i] == '-h') { - log.console("Usage: cell fetch [package]") - log.console("Sync packages from remote sources.") - log.console("") - log.console("Arguments:") - log.console(" package Optional package to sync. If omitted, syncs all.") - $stop() - } else if (!starts_with(args[i], '-')) { - target_pkg = args[i] +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell fetch [package]") + log.console("Sync packages from remote sources.") + log.console("") + log.console("Arguments:") + log.console(" package Optional package to sync. If omitted, syncs all.") + return + } else if (!starts_with(args[i], '-')) { + target_pkg = args[i] + } + } + + if (target_pkg) { + target_pkg = shop.resolve_locator(target_pkg) + log.console("Syncing " + target_pkg + "...") + shop.sync(target_pkg) + log.console("Done.") + } else { + packages = shop.list_packages() + count = 0 + arrfor(packages, function(pkg) { + if (pkg == 'core') return + shop.sync(pkg) + count = count + 1 + }) + log.console("Synced " + text(count) + " package(s).") } } - -if (target_pkg) { - target_pkg = shop.resolve_locator(target_pkg) - log.console("Syncing " + target_pkg + "...") - shop.sync(target_pkg) - log.console("Done.") -} else { - packages = shop.list_packages() - count = 0 - arrfor(packages, function(pkg) { - if (pkg == 'core') return - shop.sync(pkg) - count = count + 1 - }) - log.console("Synced " + text(count) + " package(s).") -} +run() $stop() diff --git a/graph.ce b/graph.ce index e8eb2cea..7bc87971 100644 --- a/graph.ce +++ b/graph.ce @@ -23,39 +23,40 @@ var show_locked = false var show_world = false var i = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '--format' || args[i] == '-f') { - if (i + 1 < length(args)) { - format = args[++i] - if (format != 'tree' && format != 'dot' && format != 'json') { - log.error('Invalid format: ' + format + '. Must be tree, dot, or json') - $stop() +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--format' || args[i] == '-f') { + if (i + 1 < length(args)) { + format = args[++i] + if (format != 'tree' && format != 'dot' && format != 'json') { + log.error('Invalid format: ' + format + '. Must be tree, dot, or json') + return + } + } else { + log.error('--format requires a format type') + return } - } 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 [] [options]") + log.console("") + log.console("Emit the dependency graph.") + log.console("") + log.console("Options:") + log.console(" --format 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") + return + } else if (!starts_with(args[i], '-')) { + target_locator = args[i] } - } 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 [] [options]") - log.console("") - log.console("Emit the dependency graph.") - log.console("") - log.console("Options:") - log.console(" --format 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 (!starts_with(args[i], '-')) { - target_locator = args[i] } -} var links = show_locked ? {} : link.load() @@ -236,5 +237,7 @@ if (format == 'tree') { log.console(json.encode(output)) } +} +run() $stop() diff --git a/install.ce b/install.ce index 41deb8df..2e76fc0b 100644 --- a/install.ce +++ b/install.ce @@ -24,37 +24,38 @@ var lock = null var installed = 0 var failed = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '--target' || args[i] == '-t') { - if (i + 1 < length(args)) { - target_triple = args[++i] - } else { - log.error('--target requires a triple') - $stop() +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--target' || args[i] == '-t') { + if (i + 1 < length(args)) { + target_triple = args[++i] + } else { + log.error('--target requires a triple') + return + } + } else if (args[i] == '--dry-run') { + dry_run = true + } else if (args[i] == '-r') { + recursive = true + } else if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell install [options]") + log.console("") + log.console("Install a package and its dependencies.") + log.console("") + log.console("Options:") + log.console(" --target Build for target platform") + log.console(" --dry-run Show what would be installed") + log.console(" -r Recursively find and install all packages in directory") + return + } else if (!starts_with(args[i], '-')) { + locator = args[i] } - } else if (args[i] == '--dry-run') { - dry_run = true - } else if (args[i] == '-r') { - recursive = true - } else if (args[i] == '--help' || args[i] == '-h') { - log.console("Usage: cell install [options]") - log.console("") - log.console("Install a package and its dependencies.") - log.console("") - log.console("Options:") - log.console(" --target Build for target platform") - log.console(" --dry-run Show what would be installed") - log.console(" -r Recursively find and install all packages in directory") - $stop() - } else if (!starts_with(args[i], '-')) { - locator = args[i] } -} -if (!locator && !recursive) { - log.console("Usage: cell install [options]") - $stop() -} + if (!locator && !recursive) { + log.console("Usage: cell install [options]") + return + } if (locator) locator = shop.resolve_locator(locator) @@ -65,14 +66,14 @@ if (recursive) { locator = shop.resolve_locator(locator) if (!fd.is_dir(locator)) { log.error(`${locator} is not a directory`) - $stop() + return } locators = filter(pkg.find_packages(locator), function(p) { return p != cwd }) if (length(locators) == 0) { log.console("No packages found in " + locator) - $stop() + return } log.console(`Found ${text(length(locators))} package(s) in ${locator}`) @@ -82,35 +83,36 @@ if (recursive) { lock = shop.load_lock() log.console(" " + loc + (lock[loc] ? " (already installed)" : "")) }) - $stop() + } else { + installed = 0 + failed = 0 + arrfor(locators, function(loc) { + log.console(" Installing " + loc + "...") + var _inst = function() { + shop.sync(loc, {target: target_triple}) + installed = installed + 1 + } disruption { + failed = failed + 1 + log.console(` Warning: Failed to install ${loc}`) + } + _inst() + }) + + log.console("Installed " + text(installed) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) + } + return +} + + // Single package install with dependencies + if (dry_run) { + log.console("Would install: " + locator + " (and dependencies)") + return } - installed = 0 - failed = 0 - arrfor(locators, function(loc) { - log.console(" Installing " + loc + "...") - var _inst = function() { - shop.sync(loc, {target: target_triple}) - installed = installed + 1 - } disruption { - failed = failed + 1 - log.console(` Warning: Failed to install ${loc}`) - } - _inst() - }) - - log.console("Installed " + text(installed) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : "")) - $stop() + log.console("Installing " + locator + "...") + shop.sync_with_deps(locator, {refresh: true, target: target_triple}) + log.console("Done.") } - -// Single package install with dependencies -if (dry_run) { - log.console("Would install: " + locator + " (and dependencies)") - $stop() -} - -log.console("Installing " + locator + "...") -shop.sync_with_deps(locator, {refresh: true, target: target_triple}) -log.console("Done.") +run() $stop() diff --git a/link.ce b/link.ce index 234c0c65..0f766e0e 100644 --- a/link.ce +++ b/link.ce @@ -34,17 +34,18 @@ var _restore = null var _read_toml = null var _add_link = null -if (length(args) < 1) { - log.console("Usage: link [args] or link [package] ") - log.console("Commands:") - log.console(" list List all active links") - log.console(" sync Ensure all symlinks are in place") - log.console(" delete Remove a link and restore original") - log.console(" clear Remove all links") - log.console(" Link the package in to that path") - log.console(" Link to (path or package)") - $stop() -} +var run = function() { + if (length(args) < 1) { + log.console("Usage: link [args] or link [package] ") + log.console("Commands:") + log.console(" list List all active links") + log.console(" sync Ensure all symlinks are in place") + log.console(" delete Remove a link and restore original") + log.console(" clear Remove all links") + log.console(" Link the package in to that path") + log.console(" Link to (path or package)") + return + } cmd = args[0] @@ -71,7 +72,7 @@ if (cmd == 'list') { } else if (cmd == 'delete' || cmd == 'rm') { if (length(args) < 2) { log.console("Usage: link delete ") - $stop() + return } pkg = args[1] @@ -113,7 +114,7 @@ if (cmd == 'list') { if (!arg1) { log.console("Error: target or package required") - $stop() + return } if (arg2) { @@ -136,7 +137,7 @@ if (cmd == 'list') { if (!fd.is_file(toml_path)) { log.console("Error: No cell.toml found at " + target) log.console("For linking to another package, use: link ") - $stop() + return } // Read package name from cell.toml @@ -159,7 +160,7 @@ if (cmd == 'list') { if (starts_with(target, '/')) { if (!fd.is_file(target + '/cell.toml')) { log.console("Error: " + target + " is not a valid package (no cell.toml)") - $stop() + return } } @@ -172,5 +173,7 @@ if (cmd == 'list') { } _add_link() } +} +run() $stop() diff --git a/list.ce b/list.ce index 283c6e82..362b95b2 100644 --- a/list.ce +++ b/list.ce @@ -18,31 +18,32 @@ var local_pkgs = null var linked_pkgs = null var remote_pkgs = null -if (args && length(args) > 0) { - if (args[0] == 'shop') { - mode = 'shop' - } else if (args[0] == '--help' || args[0] == '-h') { - log.console("Usage: cell list []") - 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(" List dependency tree for a package") - $stop() - } else { - mode = 'package' - target_pkg = args[0] +var run = function() { + if (args && length(args) > 0) { + if (args[0] == 'shop') { + mode = 'shop' + } else if (args[0] == '--help' || args[0] == '-h') { + log.console("Usage: cell list []") + 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(" List dependency tree for a package") + return + } else { + mode = 'package' + target_pkg = args[0] - target_pkg = shop.resolve_locator(target_pkg) + target_pkg = shop.resolve_locator(target_pkg) + } } -} -var links = link.load() -var lock = shop.load_lock() + var links = link.load() + var lock = shop.load_lock() -function print_deps(ctx, raw_indent) { + function print_deps(ctx, raw_indent) { var aliases = null var indent = raw_indent || "" deps = null @@ -173,5 +174,7 @@ if (mode == 'local') { log.console("Total: " + text(length(packages)) + " package(s)") } } +} +run() $stop() diff --git a/remove.ce b/remove.ce index a7f31e9b..30d55391 100644 --- a/remove.ce +++ b/remove.ce @@ -18,29 +18,30 @@ var prune = false var dry_run = false var i = 0 -for (i = 0; i < length(args); 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 [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 (!starts_with(args[i], '-')) { - target_pkg = args[i] +var run = function() { + for (i = 0; i < length(args); 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 [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") + return + } else if (!starts_with(args[i], '-')) { + target_pkg = args[i] + } } -} -if (!target_pkg) { - log.console("Usage: cell remove [options]") - $stop() -} + if (!target_pkg) { + log.console("Usage: cell remove [options]") + return + } target_pkg = shop.resolve_locator(target_pkg) @@ -100,5 +101,7 @@ if (dry_run) { log.console("Removed " + text(length(packages_to_remove)) + " package(s).") } +} +run() $stop() diff --git a/resolve.ce b/resolve.ce index c69d787b..e2e94a22 100644 --- a/resolve.ce +++ b/resolve.ce @@ -22,32 +22,33 @@ var show_locked = false var refresh_first = false var i = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '--target' || args[i] == '-t') { - if (i + 1 < length(args)) { - target_triple = args[++i] - } else { - log.error('--target requires a triple') - $stop() +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--target' || args[i] == '-t') { + if (i + 1 < length(args)) { + target_triple = args[++i] + } else { + log.error('--target requires a triple') + return + } + } 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 [] [options]") + log.console("") + log.console("Print the fully resolved dependency closure.") + log.console("") + log.console("Options:") + log.console(" --target Annotate builds for target platform") + log.console(" --locked Show lock state without applying links") + log.console(" --refresh Refresh floating refs before printing") + return + } else if (!starts_with(args[i], '-')) { + target_locator = args[i] } - } 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 [] [options]") - log.console("") - log.console("Print the fully resolved dependency closure.") - log.console("") - log.console("Options:") - log.console(" --target 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 (!starts_with(args[i], '-')) { - target_locator = args[i] } -} // Default to current directory if (!target_locator) { @@ -57,16 +58,16 @@ if (!target_locator) { // Resolve local paths target_locator = shop.resolve_locator(target_locator) -// Check if it's a valid package -var pkg_dir = null -if (!fd.is_file(target_locator + '/cell.toml')) { - // Try to find it in the shop - 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() + // Check if it's a valid package + var pkg_dir = null + if (!fd.is_file(target_locator + '/cell.toml')) { + // Try to find it in the shop + pkg_dir = shop.get_package_dir(target_locator) + if (!fd.is_file(pkg_dir + '/cell.toml')) { + log.error("Not a valid package: " + target_locator) + return + } } -} // Detect target if not specified if (!target_triple) { @@ -210,7 +211,9 @@ for (i = 0; i < length(sorted); i++) { } } -log.console("") -log.console("Total: " + text(length(sorted)) + " package(s)") + log.console("") + log.console("Total: " + text(length(sorted)) + " package(s)") +} +run() $stop() diff --git a/update.ce b/update.ce index db653ac7..fc8f5325 100644 --- a/update.ce +++ b/update.ce @@ -23,25 +23,26 @@ var i = 0 var updated = 0 var packages = null -for (i = 0; i < length(args); i++) { - if (args[i] == '--help' || args[i] == '-h') { - log.console("Usage: cell update [] [options]") - log.console("") - log.console("Update packages from remote sources.") - log.console("") - log.console("Options:") - log.console(" --target Target platform for build") - log.console(" --follow-links Update link targets instead of origins") - log.console(" --git Run git pull on local packages") - $stop() - } else if (args[i] == '--target' || args[i] == '-t') { - if (i + 1 < length(args)) { - target_triple = args[++i] - } else { - log.error('--target requires a triple') - $stop() - } - } else if (args[i] == '--follow-links') { +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell update [] [options]") + log.console("") + log.console("Update packages from remote sources.") + log.console("") + log.console("Options:") + log.console(" --target Target platform for build") + log.console(" --follow-links Update link targets instead of origins") + log.console(" --git Run git pull on local packages") + return + } else if (args[i] == '--target' || args[i] == '-t') { + if (i + 1 < length(args)) { + target_triple = args[++i] + } else { + log.error('--target requires a triple') + return + } + } else if (args[i] == '--follow-links') { follow_links = true } else if (args[i] == '--git') { git_pull = true @@ -113,5 +114,7 @@ if (target_pkg) { log.console("All packages are up to date.") } } +} +run() $stop() diff --git a/verify.ce b/verify.ce index 5e9d2e30..e08bfe3b 100644 --- a/verify.ce +++ b/verify.ce @@ -22,34 +22,35 @@ var deep = false var target_triple = null var i = 0 -for (i = 0; i < length(args); i++) { - if (args[i] == '--deep') { - deep = true - } else if (args[i] == '--target' || args[i] == '-t') { - if (i + 1 < length(args)) { - target_triple = args[++i] - } else { - log.error('--target requires a triple') - $stop() +var run = function() { + for (i = 0; i < length(args); i++) { + if (args[i] == '--deep') { + deep = true + } else if (args[i] == '--target' || args[i] == '-t') { + if (i + 1 < length(args)) { + target_triple = args[++i] + } else { + log.error('--target requires a triple') + return + } + } else if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell verify [] [options]") + log.console("") + log.console("Verify integrity and consistency.") + log.console("") + log.console("Scopes:") + log.console(" 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 Verify builds for specific target") + return + } else if (!starts_with(args[i], '-')) { + scope = args[i] } - } else if (args[i] == '--help' || args[i] == '-h') { - log.console("Usage: cell verify [] [options]") - log.console("") - log.console("Verify integrity and consistency.") - log.console("") - log.console("Scopes:") - log.console(" 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 Verify builds for specific target") - $stop() - } else if (!starts_with(args[i], '-')) { - scope = args[i] } -} // Default to current directory if (!scope) { @@ -250,5 +251,7 @@ if (length(errors) > 0) { } else { log.console("Verification PASSED: " + text(checked) + " package(s) checked, " + text(length(warnings)) + " warning(s)") } +} +run() $stop() From 148cf12787ec0bd495105c98a7361146d091ff43 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 20 Feb 2026 15:03:58 -0600 Subject: [PATCH 5/5] fixed logging and remove --- docs/cli.md | 2 ++ docs/logging.md | 25 ++++++++++++++++++------- internal/engine.cm | 24 +++++++++++++++++++++++- log.ce | 32 ++++++++++++++++++++------------ remove.ce | 2 ++ 5 files changed, 65 insertions(+), 20 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 907b1d6d..af10caf8 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -317,11 +317,13 @@ Options: - `--format=pretty|bare|json` — output format (default: `pretty` for console, `json` for file) - `--channels=ch1,ch2` — channels to subscribe (default: `console,error,system`). Use `'*'` for all channels (quote to prevent shell glob expansion). - `--exclude=ch1,ch2` — channels to exclude (useful with `'*'`) +- `--stack=ch1,ch2` — channels that capture a full stack trace (default: `error`) ```bash pit log add terminal console --format=bare --channels=console pit log add errors file .cell/logs/errors.jsonl --channels=error pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console +pit log add debug console --channels=error,debug --stack=error,debug ``` ### pit log remove diff --git a/docs/logging.md b/docs/logging.md index 2aea9e42..b7b0d838 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -29,14 +29,16 @@ Non-text values are JSON-encoded automatically. ## Default Behavior -With no configuration, a default sink routes `console`, `error`, and `system` to the terminal in pretty format: +With no configuration, a default sink routes `console`, `error`, and `system` to the terminal in pretty format. The `error` channel includes a stack trace by default: ``` -[a3f12] [console] main.ce:5 server started on port 8080 -[a3f12] [error] main.ce:12 connection refused +[a3f12] [console] server started on port 8080 +[a3f12] [error] connection refused + at handle_request (server.ce:42:3) + at main (main.ce:5:1) ``` -The format is `[actor_id] [channel] file:line message`. +The format is `[actor_id] [channel] message`. Error stack traces are always on unless you explicitly configure a sink without them. ## Configuration @@ -114,14 +116,22 @@ File sinks write one JSON-encoded record per line. Console sinks format the reco ## Stack Traces -Add a `stack` field to a sink to capture a full call stack for specific channels. The value is an array of channel names. +The `error` channel captures stack traces by default. To enable stack traces for other channels, add a `stack` field to a sink — an array of channel names that should include a call stack. + +Via the CLI: + +```bash +pit log add terminal console --channels=console,error,debug --stack=error,debug +``` + +Or in `log.toml`: ```toml [sink.terminal] type = "console" format = "bare" -channels = ["console", "error"] -stack = ["error"] +channels = ["console", "error", "debug"] +stack = ["error", "debug"] ``` Only channels listed in `stack` get stack traces. Other channels on the same sink print without one: @@ -150,6 +160,7 @@ The `pit log` command manages sinks and reads log files. See [CLI — pit log](/ pit log list # show sinks pit log add terminal console --format=bare --channels=console pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console +pit log add debug console --channels=error,debug --stack=error,debug pit log remove terminal pit log read dump --lines=20 --channel=error pit log tail dump diff --git a/internal/engine.cm b/internal/engine.cm index eaf62fb7..2b77b6c5 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -506,20 +506,37 @@ var REPLYTIMEOUT = 60 // seconds before replies are ignored // --- Logging system (bootstrap phase) --- // Early log: prints to console before toml/time/json are loaded. // Upgraded to full sink-based system after config loads (see load_log_config below). +// The bootstrap log forwards to _log_full once the full system is ready, so that +// modules loaded early (like shop.cm) get full logging even though they captured +// the bootstrap function reference. var log_config = null var channel_sinks = {} var wildcard_sinks = [] var warned_channels = {} var stack_channels = {} +var _log_full = null var log_quiet_channels = { shop: true } function log(name, args) { + if (_log_full) return _log_full(name, args) if (log_quiet_channels[name]) return var msg = args[0] + var stk = null + var i = 0 + var fr = null if (msg == null) msg = "" os.print(`[${text(_cell.id, 0, 5)}] [${name}]: ${msg}\n`) + if (name == "error") { + stk = os.stack(2) + if (stk && length(stk) > 0) { + for (i = 0; i < length(stk); i = i + 1) { + fr = stk[i] + os.print(` at ${fr.fn} (${fr.file}:${text(fr.line)}:${text(fr.col)})\n`) + } + } + } } function actor_die(err) @@ -648,6 +665,7 @@ function build_sink_routing() { var names = array(log_config.sink) arrfor(names, function(name) { var sink = log_config.sink[name] + if (!sink || !is_object(sink)) return sink._name = name if (!is_array(sink.channels)) sink.channels = [] if (is_text(sink.exclude)) sink.exclude = [sink.exclude] @@ -677,7 +695,7 @@ function load_log_config() { log_config = toml.decode(text(fd.slurp(log_path))) } } - if (!log_config || !log_config.sink) { + if (!log_config || !log_config.sink || length(array(log_config.sink)) == 0) { log_config = { sink: { terminal: { @@ -787,6 +805,10 @@ log = function(name, args) { // Wire C-level JS_Log through the ƿit log system actor_mod.set_log(log) +// Let the bootstrap log forward to the full system — modules loaded early +// (before the full log was ready) captured the bootstrap function reference. +_log_full = log + var pronto = use_core('pronto') var fallback = pronto.fallback var parallel = pronto.parallel diff --git a/log.ce b/log.ce index 8428a7a5..7caac4f3 100644 --- a/log.ce +++ b/log.ce @@ -1,12 +1,15 @@ // cell log - Manage and read log sinks // // Usage: -// cell log list List configured sinks -// cell log add console [opts] Add a console sink +// cell log list List configured sinks +// cell log add console [opts] Add a console sink // cell log add file [opts] Add a file sink -// cell log remove Remove a sink -// cell log read [opts] Read from a file sink -// cell log tail [--lines=N] Follow a file sink +// cell log remove Remove a sink +// cell log read [opts] Read from a file sink +// cell log tail [--lines=N] Follow a file sink +// +// The --stack option controls which channels capture a stack trace. +// Default: --stack=error (errors always show a stack trace). var toml = use('toml') var fd = use('fd') @@ -53,6 +56,7 @@ function print_help() { log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)") log.console(" --channels=ch1,ch2 Channels to subscribe (default: console,error,system)") log.console(" --exclude=ch1,ch2 Channels to exclude (for wildcard sinks)") + log.console(" --stack=ch1,ch2 Channels that capture a stack trace (default: error)") log.console("") log.console("Options for read:") log.console(" --lines=N Show last N lines (default: all)") @@ -80,21 +84,22 @@ function format_entry(entry) { function do_list() { var config = load_config() var names = null - if (!config || !config.sink) { + names = (config && config.sink) ? array(config.sink) : [] + if (length(names) == 0) { log.console("No log sinks configured.") - log.console("Default: console pretty for console/error/system") + log.console("Default: console pretty for console/error/system (stack traces on error)") return } - names = array(config.sink) arrfor(names, function(n) { var s = config.sink[n] var ch = is_array(s.channels) ? text(s.channels, ', ') : '(none)' var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : "" + var stk = is_array(s.stack) ? " stack=" + text(s.stack, ',') : "" var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty') if (s.type == 'file') - log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex) + log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex + stk) else - log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex) + log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex + stk) }) } @@ -105,6 +110,7 @@ function do_add() { var format = null var channels = ["console", "error", "system"] var exclude = null + var stack_chs = ["error"] var config = null var val = null var i = 0 @@ -138,13 +144,15 @@ function do_add() { if (val) { channels = array(val, ','); continue } val = parse_opt(args[i], 'exclude') if (val) { exclude = array(val, ','); continue } + val = parse_opt(args[i], 'stack') + if (val) { stack_chs = array(val, ','); continue } } config = load_config() if (!config) config = {} if (!config.sink) config.sink = {} - config.sink[name] = {type: sink_type, format: format, channels: channels} + config.sink[name] = {type: sink_type, format: format, channels: channels, stack: stack_chs} if (path) config.sink[name].path = path if (exclude) config.sink[name].exclude = exclude @@ -165,7 +173,7 @@ function do_remove() { log.error("Sink not found: " + name) return } - config.sink[name] = null + delete config.sink[name] save_config(config) log.console("Removed sink: " + name) } diff --git a/remove.ce b/remove.ce index 09f6d76d..c156da71 100644 --- a/remove.ce +++ b/remove.ce @@ -33,6 +33,7 @@ for (i = 0; i < length(args); i++) { log.console(" --prune Also remove packages no longer needed by any root") log.console(" --dry-run Show what would be removed") $stop() + disrupt } else if (!starts_with(args[i], '-')) { target_pkg = args[i] } @@ -41,6 +42,7 @@ for (i = 0; i < length(args); i++) { if (!target_pkg) { log.console("Usage: cell remove [options]") $stop() + disrupt } // Resolve relative paths to absolute paths