Merge branch 'audit_dups'

This commit is contained in:
2026-02-20 15:07:58 -06:00
29 changed files with 1073 additions and 1398 deletions

271
add.ce
View File

@@ -9,188 +9,127 @@
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
var i = 0
array(args, function(arg) {
if (arg == '--help' || arg == '-h') {
log.console("Usage: cell add <locator> [alias]")
log.console("")
log.console("Add a dependency to the current package.")
log.console("")
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon")
log.console(" cell add gitea.pockle.world/john/cell-image image")
log.console(" cell add ../local-package")
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
}
}
})
if (!locator && !recursive) {
log.console("Usage: cell add <locator> [alias]")
$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
}
}
// 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) {
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()
}
// 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) {
locator = '.'
}
resolved = fd.realpath(locator)
if (!resolved || !fd.is_dir(resolved)) {
log.error(`${locator} is not a directory`)
$stop()
}
locators = find_packages(resolved)
if (length(locators) == 0) {
log.console("No packages found in " + resolved)
$stop()
}
log.console(`Found ${text(length(locators))} package(s) in ${resolved}`)
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
var run = function() {
for (i = 0; i < length(args); i++) {
if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell add <locator> [alias]")
log.console("")
log.console("Add a dependency to the current package.")
log.console("")
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon")
log.console(" cell add gitea.pockle.world/john/cell-image image")
log.console(" cell add ../local-package")
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]
}
_build_c()
added++
} disruption {
log.console(` Warning: Failed to add ${loc}`)
failed++
}
_add()
})
summary = "Added " + text(added) + " package(s)."
if (failed > 0) {
summary += " Failed: " + text(failed) + "."
}
log.console(summary)
$stop()
}
log.console("Adding " + locator + " as '" + alias + "'...")
// Add to local project's cell.toml
var _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 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')
if (!locator && !recursive) {
log.console("Usage: cell add <locator> [alias]")
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
}
locators = filter(pkg.find_packages(locator), function(p) {
return p != cwd
})
if (length(locators) == 0) {
log.console("No packages found in " + locator)
return
}
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
}
_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 {
// Not all packages have C code
log.error("Failed to update cell.toml")
return
}
_build_c()
_add_dep()
log.console(" Installed to shop")
} disruption {
log.error("Failed to install")
$stop()
_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 + ")")
}
_install()
log.console("Added " + alias + " (" + locator + ")")
run()
$stop()

View File

@@ -10,32 +10,26 @@
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') {
log.console("Usage: cell audit [<locator>]")
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 [<locator>]")
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) {
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
@@ -75,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()

112
build.ce
View File

@@ -22,67 +22,60 @@ 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
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]
}
}
// 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) {
@@ -90,17 +83,16 @@ 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()
log.console('Preparing packages...')
arrfor(packages, function(package) {
if (package == 'core') return
shop.extract(package)
shop.sync(package, {no_build: true})
})
var _build = null
@@ -132,5 +124,7 @@ if (target_package) {
log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`)
}
}
run()
$stop()

View File

@@ -118,19 +118,7 @@ function get_build_dir() {
return shop.get_build_dir()
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
if (!fd.stat(current).isDirectory) fd.mkdir(current)
}
}
Build.ensure_dir = ensure_dir
Build.ensure_dir = fd.ensure_dir
// ============================================================================
// Dependency scanning helpers
@@ -267,7 +255,7 @@ Build.compile_file = function(pkg, file, target, opts) {
var fail_path = cache_path(quick_content, SALT_FAIL)
var build_dir = get_build_dir()
ensure_dir(build_dir)
fd.ensure_dir(build_dir)
// Check for cached failure (skip files that previously failed to compile)
if (fd.is_file(fail_path)) {
@@ -429,7 +417,7 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
all_objects = array(all_objects, _extra)
var link_content = compute_link_content(all_objects, resolved_ldflags, target_ldflags, {target: _target, cc: cc})
var build_dir = get_build_dir()
ensure_dir(build_dir)
fd.ensure_dir(build_dir)
var dylib_path = cache_path(link_content, SALT_DYLIB)
var cmd_parts = null
var cmd_str = null
@@ -703,7 +691,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
var src = text(fd.slurp(src_path))
var native_key = native_cache_content(src, _target, san_flags)
var build_dir = get_build_dir()
ensure_dir(build_dir)
fd.ensure_dir(build_dir)
var dylib_path = cache_path(native_key, SALT_NATIVE)
if (fd.is_file(dylib_path))
@@ -775,7 +763,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
var src = text(fd.slurp(src_path))
var native_key = native_cache_content(src, _target, san_flags)
var build_dir = get_build_dir()
ensure_dir(build_dir)
fd.ensure_dir(build_dir)
var dylib_path = cache_path(native_key, SALT_NATIVE)
if (fd.is_file(dylib_path))

View File

@@ -24,42 +24,42 @@ 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++) {
if (args[i] == '--build') {
clean_build = true
} else if (args[i] == '--fetch') {
clean_fetch = true
} else if (args[i] == '--all') {
clean_build = true
clean_fetch = true
} else if (args[i] == '--deep') {
deep = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell clean [<scope>] [options]")
log.console("")
log.console("Remove cached material to force refetch/rebuild.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Clean specific package")
log.console(" shop Clean entire shop")
log.console(" world Clean all world packages")
log.console("")
log.console("Options:")
log.console(" --build Remove build outputs only (default)")
log.console(" --fetch Remove fetched sources only")
log.console(" --all Remove both build outputs and fetched sources")
log.console(" --deep Apply to full dependency closure")
log.console(" --dry-run Show what would be deleted")
$stop()
} else if (!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 [<scope>] [options]")
log.console("")
log.console("Remove cached material to force refetch/rebuild.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Clean specific package")
log.console(" shop Clean entire shop")
log.console(" world Clean all world packages")
log.console("")
log.console("Options:")
log.console(" --build Remove build outputs only (default)")
log.console(" --fetch Remove fetched sources only")
log.console(" --all Remove both build outputs and fetched sources")
log.console(" --deep Apply to full dependency closure")
log.console(" --dry-run Show what would be deleted")
return
} else if (!starts_with(args[i], '-')) {
scope = args[i]
}
}
}
// Default to --build if nothing specified
if (!clean_build && !clean_fetch) {
@@ -76,12 +76,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 = []
@@ -196,5 +191,7 @@ if (dry_run) {
log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.")
}
}
}
run()
$stop()

113
clone.ce
View File

@@ -5,115 +5,56 @@ var shop = use('internal/shop')
var link = use('link')
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 <origin> <path>")
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 <origin> <path>")
log.console("Clones a cell package to a local path and links it.")
return
}
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)) {
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)
var zip_blob = null
var zip = null
var count = 0
var i = 0
var filename = null
var first_slash = null
var rel_path = null
var full_path = null
var dir_path = null
var _clone = function() {
zip_blob = http.fetch(download_url)
// Extract zip to target path
zip = miniz.read(zip_blob)
if (!zip) {
log.console("Error: Failed to read zip archive")
$stop()
}
// Create target directory
fd.mkdir(target_path)
count = zip.count()
for (i = 0; i < count; i++) {
if (zip.is_directory(i)) continue
filename = zip.get_filename(i)
first_slash = search(filename, '/')
if (first_slash == null) continue
if (first_slash + 1 >= length(filename)) continue
rel_path = text(filename, first_slash + 1)
full_path = target_path + '/' + rel_path
dir_path = fd.dirname(full_path)
// Ensure directory exists
if (!fd.is_dir(dir_path)) {
fd.mkdir(dir_path)
}
fd.slurpwrite(full_path, zip.slurp(filename))
}
var zip_blob = http.fetch(download_url)
shop.install_zip(zip_blob, target_path)
log.console("Extracted to " + target_path)
@@ -123,6 +64,8 @@ var _clone = function() {
} disruption {
log.console("Error during clone")
}
_clone()
_clone()
}
run()
$stop()

49
diff.ce
View File

@@ -8,6 +8,7 @@ var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var time = use('time')
var testlib = use('internal/testlib')
var _args = args == null ? [] : args
@@ -27,10 +28,7 @@ if (length(_args) > 0) {
target_test = _args[0]
}
function is_valid_package(dir) {
var _dir = dir == null ? '.' : dir
return fd.is_file(_dir + '/cell.toml')
}
var is_valid_package = testlib.is_valid_package
if (!is_valid_package('.')) {
log.console('No cell.toml found in current directory')
@@ -63,47 +61,8 @@ function collect_tests(specific_test) {
return test_files
}
// Deep comparison of two values
function values_equal(a, b) {
var i = 0
var ka = null
var kb = null
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
i = 0
while (i < length(a)) {
if (!values_equal(a[i], b[i])) return false
i = i + 1
}
return true
}
if (is_object(a) && is_object(b)) {
ka = array(a)
kb = array(b)
if (length(ka) != length(kb)) return false
i = 0
while (i < length(ka)) {
if (!values_equal(a[ka[i]], b[ka[i]])) return false
i = i + 1
}
return true
}
return false
}
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
}
var values_equal = testlib.values_equal
var describe = testlib.describe
// Run a single test file through both paths
function diff_test_file(file_path) {

View File

@@ -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

View File

@@ -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

20
fd.cm
View File

@@ -97,4 +97,24 @@ fd.globfs = function(globs, dir) {
return results
}
fd.ensure_dir = function ensure_dir(path) {
if (fd.is_dir(path)) return true
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.is_dir(current))
fd.mkdir(current)
}
return true
}
fd.safe_package_path = function safe_package_path(pkg) {
if (pkg && starts_with(pkg, '/'))
return replace(replace(pkg, '/', '_'), '@', '_')
return replace(pkg, '@', '_')
}
return fd

113
fetch.ce
View File

@@ -1,90 +1,49 @@
// 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 <package> - Fetch a specific package
// cell fetch - Sync all packages
// cell fetch <package> - 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("")
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.")
$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).")
}
}
var all_packages = shop.list_packages()
var lock = shop.load_lock()
var packages_to_fetch = []
if (target_pkg) {
// Fetch specific package
if (find(all_packages, target_pkg) == null) {
log.error("Package not found: " + target_pkg)
$stop()
}
push(packages_to_fetch, target_pkg)
} else {
// Fetch all packages
packages_to_fetch = all_packages
}
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, ", "))
run()
$stop()

46
fuzz.ce
View File

@@ -12,6 +12,7 @@
var fd = use('fd')
var time = use('time')
var json = use('json')
var testlib = use('internal/testlib')
var os_ref = use('internal/os')
var analyze = os_ref.analyze
@@ -54,48 +55,9 @@ if (!run_ast_noopt_fn) {
// Ensure failures directory exists
var failures_dir = "tests/fuzz_failures"
function ensure_dir(path) {
if (fd.is_dir(path)) return
var parts = array(path, '/')
var current = ''
var j = 0
while (j < length(parts)) {
if (parts[j] != '') {
current = current + parts[j] + '/'
if (!fd.is_dir(current)) {
fd.mkdir(current)
}
}
j = j + 1
}
}
// Deep comparison
function values_equal(a, b) {
var j = 0
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
j = 0
while (j < length(a)) {
if (!values_equal(a[j], b[j])) return false
j = j + 1
}
return true
}
return false
}
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
return "<other>"
}
var ensure_dir = fd.ensure_dir
var values_equal = testlib.values_equal
var describe = testlib.describe
// Run a single fuzz iteration
function run_fuzz(seed_val) {

View File

@@ -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,41 +22,41 @@ 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') {
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 [<locator>] [options]")
log.console("")
log.console("Emit the dependency graph.")
log.console("")
log.console("Options:")
log.console(" --format <fmt> Output format: tree (default), dot, json")
log.console(" --resolved Show resolved view with links applied (default)")
log.console(" --locked Show lock view without links")
log.console(" --world Graph all packages in shop")
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 [<locator>] [options]")
log.console("")
log.console("Emit the dependency graph.")
log.console("")
log.console("Options:")
log.console(" --format <fmt> Output format: tree (default), dot, json")
log.console(" --resolved Show resolved view with links applied (default)")
log.console(" --locked Show lock view without links")
log.console(" --world Graph all packages in shop")
$stop()
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
}
}
var links = show_locked ? {} : link.load()
@@ -127,13 +126,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)
}
@@ -244,5 +237,7 @@ if (format == 'tree') {
log.console(json.encode(output))
}
}
run()
$stop()

View File

@@ -1,4 +1,4 @@
// cell install <locator> - Install a package to the shop
// cell install <locator> - Install a package and its dependencies
//
// Usage:
// cell install <locator> Install a package and its dependencies
@@ -6,266 +6,113 @@
//
// Options:
// --target <triple> 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 <locator> [options]")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
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') {
if (i + 1 < length(args)) {
target_triple = args[++i]
} else {
log.error('--target requires a triple')
$stop()
}
} else if (args[i] == '--refresh') {
refresh = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '-r') {
recursive = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Install a package and its dependencies to the shop.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
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 <locator>")
$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
}
}
// 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 install each
if (recursive) {
if (!locator) {
locator = '.'
}
resolved = fd.realpath(locator)
if (!resolved || !fd.is_dir(resolved)) {
log.error(`${locator} is not a directory`)
$stop()
}
locators = find_packages(resolved)
if (length(locators) == 0) {
log.console("No packages found in " + resolved)
$stop()
}
log.console(`Found ${text(length(locators))} package(s) in ${resolved}`)
}
// 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) {
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
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
}
_build_c()
push(packages_to_install, loc)
} disruption {
push(skipped_packages, loc)
log.console(` Warning: Failed to install ${loc}`)
} 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 <locator> [options]")
log.console("")
log.console("Install a package and its dependencies.")
log.console("")
log.console("Options:")
log.console(" --target <triple> 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]
}
_inst()
})
summary = "Installed " + text(length(packages_to_install)) + " package(s)."
if (length(skipped_packages) > 0) {
summary += " Failed: " + text(length(skipped_packages)) + "."
}
log.console(summary)
$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)
if (!locator && !recursive) {
log.console("Usage: cell install <locator> [options]")
return
}
push(packages_to_install, pkg_locator)
if (locator)
locator = shop.resolve_locator(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}`)
}
// Recursive mode: find all packages in directory and install each
if (recursive) {
if (!locator) locator = '.'
locator = shop.resolve_locator(locator)
if (!fd.is_dir(locator)) {
log.error(`${locator} is not a directory`)
return
}
_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)" : ""))
locators = filter(pkg.find_packages(locator), function(p) {
return p != cwd
})
if (length(skipped_packages) > 0) {
log.console("")
log.console("Would skip (missing local paths):")
arrfor(skipped_packages, function(p) {
log.console(" " + p)
if (length(locators) == 0) {
log.console("No packages found in " + locator)
return
}
log.console(`Found ${text(length(locators))} package(s) in ${locator}`)
if (dry_run) {
log.console("Would install:")
arrfor(locators, function(loc) {
lock = shop.load_lock()
log.console(" " + loc + (lock[loc] ? " (already installed)" : ""))
})
} 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) + "." : ""))
}
$stop()
return
}
// 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
// Single package install with dependencies
if (dry_run) {
log.console("Would install: " + locator + " (and dependencies)")
return
}
_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("Installing " + locator + "...")
shop.sync_with_deps(locator, {refresh: true, target: target_triple})
log.console("Done.")
}
log.console(summary)
run()
$stop()

View File

@@ -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
@@ -1402,27 +1424,38 @@ 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(_ => {
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
@@ -1442,18 +1475,25 @@ $_.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.sync(_deps[_di])
} disruption {
log.error('failed to install dependency: ' + _deps[_di])
disrupt
}
_auto_install()
}
_di = _di + 1
}
@@ -1461,10 +1501,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

View File

@@ -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),

View File

@@ -40,20 +40,6 @@ function put_into_cache(content, obj)
fd.slurpwrite(path, obj)
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
}
}
function hash_path(content, salt)
{
var s = salt || 'mach'
@@ -88,11 +74,6 @@ Shop.get_core_dir = function() {
return get_packages_dir() + '/' + core_package
}
// Get the links file path (in the global shop)
function get_links_path() {
return global_shop_path + '/link.toml'
}
// Get the reports directory (in the global shop)
Shop.get_reports_dir = function() {
return global_shop_path + '/reports'
@@ -131,15 +112,12 @@ function split_explicit_package_import(path)
mod_path = text(array(parts, i), '/')
if (!mod_path || length(mod_path) == 0) continue
candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate)
candidate_dir = get_packages_dir() + '/' + fd.safe_package_path(pkg_candidate)
if (fd.is_file(candidate_dir + '/cell.toml'))
return {package: pkg_candidate, path: mod_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 +136,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 +156,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)
@@ -256,15 +240,15 @@ function get_canonical_package(alias, package_context) {
return null
}
// return the safe path for the package
// guaranteed to be validated
function safe_package_path(pkg)
{
// For absolute paths, replace / with _ to create a valid directory name
// Also replace @ with _
if (pkg && starts_with(pkg, '/'))
return replace(replace(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)
@@ -294,7 +278,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
}
@@ -344,9 +329,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
}
@@ -442,7 +429,7 @@ function try_native_mod_dylib(pkg, stem) {
var build_mod = use_cache['core/build']
if (!build_mod) return null
var src_path = get_packages_dir() + '/' + safe_package_path(pkg) + '/' + stem
var src_path = get_packages_dir() + '/' + fd.safe_package_path(pkg) + '/' + stem
if (!fd.is_file(src_path)) return null
var src = text(fd.slurp(src_path))
@@ -660,7 +647,7 @@ Shop.all_script_paths = function() {
packages = array(packages, ['core'])
}
for (i = 0; i < length(packages); i++) {
pkg_dir = starts_with(packages[i], '/') ? packages[i] : get_packages_dir() + '/' + safe_package_path(packages[i])
pkg_dir = starts_with(packages[i], '/') ? packages[i] : get_packages_dir() + '/' + fd.safe_package_path(packages[i])
scripts = get_package_scripts(packages[i])
for (j = 0; j < length(scripts); j++) {
result[] = {
@@ -705,7 +692,7 @@ function resolve_mod_fn(path, pkg) {
// Compute _pkg_dir and _stem early so all paths can use them
if (pkg) {
_pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
_pkg_dir = get_packages_dir() + '/' + fd.safe_package_path(pkg)
if (starts_with(path, _pkg_dir + '/')) {
_stem = text(path, length(_pkg_dir) + 1)
}
@@ -767,7 +754,7 @@ function resolve_mod_fn(path, pkg) {
mcode_json = shop_json.encode(optimized)
// Cache mcode (architecture-independent) in content-addressed store
ensure_dir(global_shop_path + '/build')
fd.ensure_dir(global_shop_path + '/build')
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
// Cache mach blob
@@ -797,13 +784,18 @@ 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)
explicit = null
}
if (explicit) {
explicit_path = get_packages_dir() + '/' + safe_package_path(explicit.package) + '/' + explicit.path
explicit_path = get_packages_dir() + '/' + fd.safe_package_path(explicit.package) + '/' + explicit.path
if (fd.is_file(explicit_path))
return {path: explicit_path, scope: SCOPE_PACKAGE, pkg: explicit.package}
}
@@ -819,7 +811,7 @@ function resolve_path(path, ctx)
if (starts_with(ctx, '/'))
ctx_dir = ctx
else
ctx_dir = get_packages_dir() + '/' + safe_package_path(ctx)
ctx_dir = get_packages_dir() + '/' + fd.safe_package_path(ctx)
ctx_path = ctx_dir + '/' + path
if (fd.is_file(ctx_path)) {
@@ -833,15 +825,34 @@ function resolve_path(path, ctx)
alias = pkg_tools.split_alias(ctx, path)
if (alias) {
alias_path = get_packages_dir() + '/' + safe_package_path(alias.package) + '/' + alias.path
alias_path = get_packages_dir() + '/' + fd.safe_package_path(alias.package) + '/' + alias.path
if (fd.is_file(alias_path))
return {path: alias_path, scope: SCOPE_PACKAGE, pkg: ctx}
}
package_path = get_packages_dir() + '/' + safe_package_path(path)
package_path = get_packages_dir() + '/' + fd.safe_package_path(path)
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() + '/' + fd.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))
@@ -1254,7 +1265,7 @@ function get_module(path, package_context) {
if (!info) {
log.shop(`Module '${path}' could not be found in package '${package_context}'`)
_ctx_dir = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + safe_package_path(package_context)) : null
_ctx_dir = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + fd.safe_package_path(package_context)) : null
if (_ctx_dir) {
if (fd.is_file(_ctx_dir + '/' + path + '.c') || fd.is_file(_ctx_dir + '/' + path + '.cpp'))
log.shop(`C source exists at ${_ctx_dir}/${path}.c but was not compiled - run 'cell build'`)
@@ -1290,7 +1301,7 @@ Shop.use = function use(path, package_context) {
var _alias2 = null
if (!info) {
log.shop(`Module '${path}' could not be found in package '${package_context}'`)
_ctx_dir2 = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + safe_package_path(package_context)) : null
_ctx_dir2 = package_context ? (starts_with(package_context, '/') ? package_context : get_packages_dir() + '/' + fd.safe_package_path(package_context)) : null
if (_ctx_dir2) {
if (fd.is_file(_ctx_dir2 + '/' + path + '.c') || fd.is_file(_ctx_dir2 + '/' + path + '.cpp'))
log.shop(`C source exists at ${_ctx_dir2}/${path}.c but was not compiled - run 'cell build'`)
@@ -1307,8 +1318,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) {
@@ -1317,6 +1326,56 @@ 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
// Find best matching package from lock or infer from path
var lock = Shop.load_lock()
var best_pkg = null
var best_remainder = null
var parts = array(prog, '/')
var candidate = null
var pkg_info = 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, 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)
if (pkg_info && pkg_info != 'local') {
best_pkg = candidate
best_remainder = text(array(parts, 3), '/')
}
}
if (!best_pkg || !best_remainder) 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.
// type is 'script', 'native', or null. Checks .cm files, C symbols, and aliases.
Shop.resolve_import_info = function(path, ctx) {
@@ -1346,7 +1405,7 @@ function get_cache_path(pkg, commit) {
function get_package_abs_dir(package)
{
return get_packages_dir() + '/' + safe_package_path(package)
return get_packages_dir() + '/' + fd.safe_package_path(package)
}
// Fetch the latest commit hash from remote for a package
@@ -1370,7 +1429,7 @@ function fetch_remote_hash(pkg) {
// Returns the zip blob or null on failure
function download_zip(pkg, commit_hash) {
var cache_path = get_cache_path(pkg, commit_hash)
ensure_dir(global_shop_path + '/cache')
fd.ensure_dir(global_shop_path + '/cache')
var download_url = Shop.get_download_url(pkg, commit_hash)
if (!download_url) {
@@ -1429,7 +1488,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')
@@ -1537,17 +1596,23 @@ 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)
log.console(`checking ${pkg}`)
if (!info) {
log.error("Not a valid package locator: " + pkg)
return null
}
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
@@ -1563,8 +1628,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)
@@ -1586,6 +1651,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 }
@@ -1593,8 +1734,8 @@ 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)
ensure_dir(target_dir)
log.shop("Extracting to " + target_dir)
fd.ensure_dir(target_dir)
var count = zip.count()
var created_dirs = {}
@@ -1617,7 +1758,7 @@ function install_zip(zip_blob, target_dir) {
dir_path = fd.dirname(full_path)
if (!created_dirs[dir_path]) {
ensure_dir(dir_path)
fd.ensure_dir(dir_path)
created_dirs[dir_path] = true
}
file_data = zip.slurp(filename)
@@ -1638,7 +1779,7 @@ Shop.remove = function(pkg) {
}
// Remove package symlink/directory
var pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
var pkg_dir = get_packages_dir() + '/' + fd.safe_package_path(pkg)
if (fd.is_link(pkg_dir)) {
fd.unlink(pkg_dir)
} else if (fd.is_dir(pkg_dir)) {
@@ -1652,34 +1793,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
@@ -1756,6 +1869,7 @@ Shop.build_package_scripts = function(package)
ok = ok + 1
} disruption {
push(errors, script)
log.console(" compile error: " + package + '/' + script)
}
_try()
})
@@ -1776,7 +1890,8 @@ Shop.get_lib_dir = function() {
return global_shop_path + '/lib'
}
Shop.ensure_dir = ensure_dir
Shop.ensure_dir = fd.ensure_dir
Shop.install_zip = install_zip
Shop.ensure_package_dylibs = ensure_package_dylibs
Shop.get_local_dir = function() {
@@ -1790,7 +1905,7 @@ Shop.get_build_dir = function() {
// Get the absolute path for a package
Shop.get_package_dir = function(pkg) {
return get_packages_dir() + '/' + safe_package_path(pkg)
return get_packages_dir() + '/' + fd.safe_package_path(pkg)
}
// Generate C symbol name for a file within a package
@@ -1811,7 +1926,7 @@ Shop.c_symbol_prefix = function(pkg) {
// Get the library directory name for a package
Shop.lib_name_for_package = function(pkg) {
return safe_package_path(pkg)
return fd.safe_package_path(pkg)
}
// Load a module explicitly as mach bytecode, bypassing dylib resolution.
@@ -1865,7 +1980,7 @@ Shop.load_as_mach = function(path, pkg) {
optimized = _streamline_mod(ir)
mcode_json = shop_json.encode(optimized)
cached_mcode_path = hash_path(content_key, 'mcode')
ensure_dir(global_shop_path + '/build')
fd.ensure_dir(global_shop_path + '/build')
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
compiled = mach_compile_mcode_bin(file_path, mcode_json)
put_into_cache(content_key, compiled)
@@ -1902,7 +2017,7 @@ Shop.load_as_dylib = function(path, pkg) {
}
if (!real_pkg) return null
pkg_dir = get_packages_dir() + '/' + safe_package_path(real_pkg)
pkg_dir = get_packages_dir() + '/' + fd.safe_package_path(real_pkg)
if (!starts_with(file_path, pkg_dir + '/')) return null
stem = text(file_path, length(pkg_dir) + 1)
result = try_native_mod_dylib(real_pkg, stem)

View File

@@ -33,26 +33,54 @@ function get_pkg_dir(package_name) {
return shop.get_package_dir(package_name)
}
// Ensure directory exists
function ensure_dir(path) {
if (fd.is_dir(path)) return true
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
// Deep comparison of two values (handles arrays and objects)
function values_equal(a, b) {
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.is_dir(current)) {
fd.mkdir(current)
var ka = null
var kb = null
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
i = 0
while (i < length(a)) {
if (!values_equal(a[i], b[i])) return false
i = i + 1
}
return true
}
return true
if (is_object(a) && is_object(b)) {
ka = array(a)
kb = array(b)
if (length(ka) != length(kb)) return false
i = 0
while (i < length(ka)) {
if (!values_equal(a[ka[i]], b[ka[i]])) return false
i = i + 1
}
return true
}
return false
}
// Describe a value for error messages
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
}
return {
is_valid_package: is_valid_package,
get_current_package_name: get_current_package_name,
get_pkg_dir: get_pkg_dir,
ensure_dir: ensure_dir
ensure_dir: fd.ensure_dir,
values_equal: values_equal,
describe: describe
}

54
link.ce
View File

@@ -28,24 +28,24 @@ 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
var _read_toml = null
var _add_link = null
if (length(args) < 1) {
log.console("Usage: link <command> [args] or link [package] <target>")
log.console("Commands:")
log.console(" list List all active links")
log.console(" sync Ensure all symlinks are in place")
log.console(" delete <package> Remove a link and restore original")
log.console(" clear Remove all links")
log.console(" <path> Link the package in <path> to that path")
log.console(" <package> <target> Link <package> to <target> (path or package)")
$stop()
}
var run = function() {
if (length(args) < 1) {
log.console("Usage: link <command> [args] or link [package] <target>")
log.console("Commands:")
log.console(" list List all active links")
log.console(" sync Ensure all symlinks are in place")
log.console(" delete <package> Remove a link and restore original")
log.console(" clear Remove all links")
log.console(" <path> Link the package in <path> to that path")
log.console(" <package> <target> Link <package> to <target> (path or package)")
return
}
cmd = args[0]
@@ -72,7 +72,7 @@ if (cmd == 'list') {
} else if (cmd == 'delete' || cmd == 'rm') {
if (length(args) < 2) {
log.console("Usage: link delete <package>")
$stop()
return
}
pkg = args[1]
@@ -114,7 +114,7 @@ if (cmd == 'list') {
if (!arg1) {
log.console("Error: target or package required")
$stop()
return
}
if (arg2) {
@@ -123,37 +123,21 @@ 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'
if (!fd.is_file(toml_path)) {
log.console("Error: No cell.toml found at " + target)
log.console("For linking to another package, use: link <package> <target>")
$stop()
return
}
// Read package name from cell.toml
@@ -176,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
}
}
@@ -189,5 +173,7 @@ if (cmd == 'list') {
}
_add_link()
}
}
run()
$stop()

28
link.cm
View File

@@ -20,30 +20,8 @@ function get_packages_dir() {
return global_shop_path + '/packages'
}
// return the safe path for the package
function safe_package_path(pkg) {
// For absolute paths, replace / with _ to create a valid directory name
if (pkg && starts_with(pkg, '/'))
return replace(replace(pkg, '/', '_'), '@', '_')
return replace(pkg, '@', '_')
}
function get_package_abs_dir(package) {
return get_packages_dir() + '/' + safe_package_path(package)
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
}
return get_packages_dir() + '/' + fd.safe_package_path(package)
}
// Resolve a link target to its actual path
@@ -54,7 +32,7 @@ function resolve_link_target(target) {
return target
}
// Target is another package - resolve to its directory
return get_packages_dir() + '/' + safe_package_path(target)
return get_packages_dir() + '/' + fd.safe_package_path(target)
}
var Link = {}
@@ -194,7 +172,7 @@ Link.sync_one = function(canonical, target, shop) {
// Ensure parent directories exist
var parent = fd.dirname(target_dir)
ensure_dir(parent)
fd.ensure_dir(parent)
// Check current state
var current_link = null

51
list.ce
View File

@@ -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
@@ -20,37 +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 [<scope>]")
log.console("")
log.console("List packages and dependencies.")
log.console("")
log.console("Scopes:")
log.console(" (none) List dependencies of current package")
log.console(" shop List all packages in shop with status")
log.console(" <locator> List dependency tree for a package")
$stop()
} else {
mode = 'package'
target_pkg = args[0]
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 [<scope>]")
log.console("")
log.console("List packages and dependencies.")
log.console("")
log.console("Scopes:")
log.console(" (none) List dependencies of current package")
log.console(" shop List all packages in shop with status")
log.console(" <locator> List dependency tree for a package")
return
} else {
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)
}
}
}
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
@@ -181,5 +174,7 @@ if (mode == 'local') {
log.console("Total: " + text(length(packages)) + " package(s)")
}
}
}
run()
$stop()

32
log.ce
View File

@@ -1,12 +1,15 @@
// cell log - Manage and read log sinks
//
// Usage:
// cell log list List configured sinks
// cell log add <name> console [opts] Add a console sink
// cell log list List configured sinks
// cell log add <name> console [opts] Add a console sink
// cell log add <name> file <path> [opts] Add a file sink
// cell log remove <name> Remove a sink
// cell log read <sink> [opts] Read from a file sink
// cell log tail <sink> [--lines=N] Follow a file sink
// cell log remove <name> Remove a sink
// cell log read <sink> [opts] Read from a file sink
// cell log tail <sink> [--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)
}

View File

@@ -6,16 +6,6 @@ var link = use('link')
var global_shop_path = runtime.shop_path
// Convert package name to a safe directory name
// For absolute paths (local packages), replace / with _
// For remote packages, keep slashes as they use nested directories
function safe_package_path(pkg) {
if (!pkg) return pkg
if (starts_with(pkg, '/'))
return replace(replace(pkg, '/', '_'), '@', '_')
return replace(pkg, '@', '_')
}
function get_path(name)
{
// If name is null, return the current project directory
@@ -33,11 +23,11 @@ function get_path(name)
if (starts_with(link_target, '/'))
return link_target
// Otherwise it's another package name, resolve that
return global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_')
return global_shop_path + '/packages/' + fd.safe_package_path(link_target)
}
// Remote packages use nested directories, so don't transform slashes
return global_shop_path + '/packages/' + replace(name, '@', '_')
return global_shop_path + '/packages/' + fd.safe_package_path(name)
}
var config_cache = {}
@@ -202,6 +192,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 []

168
remove.ce
View File

@@ -17,95 +17,91 @@ 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') {
prune = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
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 <locator> [options]")
log.console("")
log.console("Remove a package from the shop.")
log.console("")
log.console("Options:")
log.console(" --prune Also remove packages no longer needed by any root")
log.console(" --dry-run Show what would be removed")
return
} else if (!starts_with(args[i], '-')) {
target_pkg = args[i]
}
}
if (!target_pkg) {
log.console("Usage: cell remove <locator> [options]")
log.console("")
log.console("Remove a package from the shop.")
log.console("")
log.console("Options:")
log.console(" --prune Also remove packages no longer needed by any root")
log.console(" --dry-run Show what would be removed")
$stop()
} else if (!starts_with(args[i], '-')) {
target_pkg = args[i]
return
}
target_pkg = shop.resolve_locator(target_pkg)
var packages_to_remove = [target_pkg]
var lock = null
var all_packages = null
var needed = null
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
lock = shop.load_lock()
all_packages = shop.list_packages()
// Build set of all needed packages (excluding target)
needed = {}
arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return
// Mark this package and its deps as needed
needed[p] = true
var _gather = function() {
var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) {
needed[dep] = true
})
} disruption {
// Skip if can't read deps
}
_gather()
})
// Find packages that are NOT needed
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
}
})
}
if (dry_run) {
log.console("Would remove:")
arrfor(packages_to_remove, function(p) {
log.console(" " + p)
})
} else {
arrfor(packages_to_remove, function(p) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
// Remove from shop
shop.remove(p)
})
log.console("Removed " + text(length(packages_to_remove)) + " package(s).")
}
}
if (!target_pkg) {
log.console("Usage: cell remove <locator> [options]")
$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
}
}
var packages_to_remove = [target_pkg]
var lock = null
var all_packages = null
var needed = null
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
lock = shop.load_lock()
all_packages = shop.list_packages()
// Build set of all needed packages (excluding target)
needed = {}
arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return
// Mark this package and its deps as needed
needed[p] = true
var _gather = function() {
var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) {
needed[dep] = true
})
} disruption {
// Skip if can't read deps
}
_gather()
})
// Find packages that are NOT needed
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
}
})
}
if (dry_run) {
log.console("Would remove:")
arrfor(packages_to_remove, function(p) {
log.console(" " + p)
})
} else {
arrfor(packages_to_remove, function(p) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
// Remove from shop
shop.remove(p)
})
log.console("Removed " + text(length(packages_to_remove)) + " package(s).")
}
run()
$stop()

View File

@@ -21,34 +21,34 @@ 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') {
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 [<locator>] [options]")
log.console("")
log.console("Print the fully resolved dependency closure.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Annotate builds for target platform")
log.console(" --locked Show lock state without applying links")
log.console(" --refresh Refresh floating refs before printing")
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 [<locator>] [options]")
log.console("")
log.console("Print the fully resolved dependency closure.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Annotate builds for target platform")
log.console(" --locked Show lock state without applying links")
log.console(" --refresh Refresh floating refs before printing")
$stop()
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
}
}
// Default to current directory
if (!target_locator) {
@@ -56,23 +56,18 @@ 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
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) {
@@ -216,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()

View File

@@ -8093,6 +8093,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)) {

75
test.ce
View File

@@ -7,6 +7,7 @@ var time = use('time')
var json = use('json')
var blob = use('blob')
var dbg = use('js')
var testlib = use('internal/testlib')
// run gc with dbg.gc()
@@ -29,23 +30,8 @@ def ACTOR_TEST_TIMEOUT = 30000 // 30 seconds timeout for actor tests
var pending_actor_tests = []
var actor_test_results = []
// Check if current directory is a valid cell package
function is_valid_package(dir) {
var _dir = dir == null ? '.' : dir
return fd.is_file(_dir + '/cell.toml')
}
// Get current package name from cell.toml or null
function get_current_package_name() {
if (!is_valid_package('.')) return null
var _load = function() {
var config = pkg.load_config(null)
return config.package || 'local'
} disruption {
return 'local'
}
return _load()
}
var is_valid_package = testlib.is_valid_package
var get_current_package_name = testlib.get_current_package_name
// Parse arguments
// Usage:
@@ -192,32 +178,8 @@ if (diff_mode && !run_ast_noopt_fn) {
return
}
// Diff mode: deep comparison helper
function values_equal(a, b) {
var i = 0
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
i = 0
while (i < length(a)) {
if (!values_equal(a[i], b[i])) return false
i = i + 1
}
return true
}
return false
}
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
return "<other>"
}
var values_equal = testlib.values_equal
var describe = testlib.describe
// Diff mode: run a test function through noopt and compare
var diff_mismatches = 0
@@ -251,31 +213,8 @@ function diff_check(test_name, file_path, opt_fn, noopt_fn) {
}
}
function ensure_dir(path) {
if (fd.is_dir(path)) return true
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.is_dir(current))
fd.mkdir(current)
}
return true
}
// Get the directory for a package
function get_pkg_dir(package_name) {
if (!package_name) {
return fd.realpath('.')
}
if (starts_with(package_name, '/')) {
return package_name
}
return shop.get_package_dir(package_name)
}
var ensure_dir = fd.ensure_dir
var get_pkg_dir = testlib.get_pkg_dir
// Collect .ce actor tests from a package
function collect_actor_tests(package_name, specific_test) {

158
update.ce
View File

@@ -1,84 +1,69 @@
// cell update [<locator>] - 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 <locator> Update a specific package
//
// Options:
// --build Run build after updating
// --target <triple> Target platform for build (requires --build)
// --target <triple> 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('internal/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 [<locator>] [options]")
log.console("")
log.console("Update packages from remote sources.")
log.console("")
log.console("Options:")
log.console(" --build Run build after updating")
log.console(" --target <triple> Target platform for build (requires --build)")
log.console(" --follow-links Update link targets instead of origins")
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]
} 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 [<locator>] [options]")
log.console("")
log.console("Update packages from remote sources.")
log.console("")
log.console("Options:")
log.console(" --target <triple> 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
} 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 +72,49 @@ 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()
})
}
run()
$stop()

View File

@@ -21,36 +21,36 @@ 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') {
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 [<scope>] [options]")
log.console("")
log.console("Verify integrity and consistency.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Verify specific package")
log.console(" shop Verify entire shop")
log.console(" world Verify all world roots")
log.console("")
log.console("Options:")
log.console(" --deep Traverse full dependency closure")
log.console(" --target <triple> Verify builds for specific target")
return
} else if (!starts_with(args[i], '-')) {
scope = args[i]
}
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell verify [<scope>] [options]")
log.console("")
log.console("Verify integrity and consistency.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Verify specific package")
log.console(" shop Verify entire shop")
log.console(" world Verify all world roots")
log.console("")
log.console("Options:")
log.console(" --deep Traverse full dependency closure")
log.console(" --target <triple> Verify builds for specific target")
$stop()
} else if (!starts_with(args[i], '-')) {
scope = args[i]
}
}
// Default to current directory
if (!scope) {
@@ -206,13 +206,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
@@ -257,5 +251,7 @@ if (length(errors) > 0) {
} else {
log.console("Verification PASSED: " + text(checked) + " package(s) checked, " + text(length(warnings)) + " warning(s)")
}
}
run()
$stop()