Merge branch 'audit_dups'
This commit is contained in:
271
add.ce
271
add.ce
@@ -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()
|
||||
|
||||
32
audit.ce
32
audit.ce
@@ -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
112
build.ce
@@ -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()
|
||||
|
||||
22
build.cm
22
build.cm
@@ -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))
|
||||
|
||||
75
clean.ce
75
clean.ce
@@ -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
113
clone.ce
@@ -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
49
diff.ce
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
20
fd.cm
@@ -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
113
fetch.ce
@@ -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
46
fuzz.ce
@@ -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) {
|
||||
|
||||
73
graph.ce
73
graph.ce
@@ -15,7 +15,6 @@
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var link = use('link')
|
||||
var fd = use('fd')
|
||||
var json = use('json')
|
||||
|
||||
var target_locator = null
|
||||
@@ -23,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()
|
||||
|
||||
309
install.ce
309
install.ce
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
295
internal/shop.cm
295
internal/shop.cm
@@ -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)
|
||||
|
||||
@@ -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
54
link.ce
@@ -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
28
link.cm
@@ -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
51
list.ce
@@ -8,11 +8,9 @@
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var link = use('link')
|
||||
var fd = use('fd')
|
||||
|
||||
var mode = 'local'
|
||||
var target_pkg = null
|
||||
var resolved = null
|
||||
var i = 0
|
||||
var deps = null
|
||||
var packages = null
|
||||
@@ -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
32
log.ce
@@ -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)
|
||||
}
|
||||
|
||||
36
package.cm
36
package.cm
@@ -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
168
remove.ce
@@ -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()
|
||||
|
||||
81
resolve.ce
81
resolve.ce
@@ -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()
|
||||
|
||||
@@ -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
75
test.ce
@@ -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
158
update.ce
@@ -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()
|
||||
|
||||
64
verify.ce
64
verify.ce
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user