Files
cell/verify.ce
2026-01-21 00:52:18 -06:00

258 lines
6.8 KiB
Plaintext

// cell verify [<scope>] - Verify integrity and consistency
//
// Usage:
// cell verify Verify current directory package
// cell verify . Verify current directory package
// cell verify <locator> Verify specific package
// cell verify shop Verify entire shop
// cell verify world Verify all world roots
//
// Options:
// --deep Traverse full dependency closure
// --target <triple> Verify builds for specific target
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var build = use('build')
var fd = use('fd')
var scope = null
var deep = false
var target_triple = null
for (var i = 0; i < 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()
}
} 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) {
scope = '.'
}
// Detect target if not specified
if (!target_triple) {
target_triple = build.detect_host_target()
}
var errors = []
var warnings = []
var checked = 0
function add_error(msg) {
push(errors, msg)
}
function add_warning(msg) {
push(warnings, msg)
}
// Verify a single package
function verify_package(locator) {
checked++
var lock = shop.load_lock()
var lock_entry = lock[locator]
var links = link.load()
var link_target = links[locator]
// Check lock entry exists
if (!lock_entry) {
add_error(locator + ": not in lock")
}
// Check package directory exists
var pkg_dir = shop.get_package_dir(locator)
var dir_exists = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
if (!dir_exists) {
add_error(locator + ": package directory missing at " + pkg_dir)
return
}
// Check cell.toml exists
if (!fd.is_file(pkg_dir + '/cell.toml')) {
add_error(locator + ": missing cell.toml")
return
}
// For linked packages, verify link target
if (link_target) {
if (starts_with(link_target, '/')) {
// Local path target
if (!fd.is_dir(link_target)) {
add_error(locator + ": link target does not exist: " + link_target)
} else if (!fd.is_file(link_target + '/cell.toml')) {
add_error(locator + ": link target is not a valid package: " + link_target)
}
} else {
// Package target
var target_dir = shop.get_package_dir(link_target)
if (!fd.is_dir(target_dir) && !fd.is_link(target_dir)) {
add_error(locator + ": link target package not found: " + link_target)
}
}
// Check symlink is correct
if (fd.is_link(pkg_dir)) {
var current_target = fd.readlink(pkg_dir)
var expected_target = starts_with(link_target, '/') ? link_target : shop.get_package_dir(link_target)
if (current_target != expected_target) {
add_warning(locator + ": symlink target mismatch (expected " + expected_target + ", got " + current_target + ")")
}
} else {
add_warning(locator + ": linked but directory is not a symlink")
}
}
// Check build output exists
var lib_dir = shop.get_lib_dir()
var lib_name = shop.lib_name_for_package(locator)
var dylib_ext = '.dylib' // TODO: detect from target
var lib_path = lib_dir + '/' + lib_name + dylib_ext
// Only check for builds if package has C files
try {
var c_files = pkg.get_c_files(locator, target_triple, true)
if (c_files && length(c_files) > 0) {
if (!fd.is_file(lib_path)) {
add_warning(locator + ": library not built at " + lib_path)
}
}
} catch (e) {
// Skip build check if can't determine C files
}
}
// Check for link cycles
function check_link_cycles() {
var links = link.load()
function follow_chain(origin, visited) {
if (visited[origin]) {
return origin // cycle detected
}
visited[origin] = true
var target = links[origin]
if (target && links[target]) {
return follow_chain(target, visited)
}
return null
}
arrfor(links, function(origin) {
var cycle_start = follow_chain(origin, {})
if (cycle_start) {
add_error("Link cycle detected starting from: " + origin)
}
})
}
// Check for dangling links
function check_dangling_links() {
var links = link.load()
arrfor(array(links), function(origin) {
var target = links[origin]
if (starts_with(target, '/')) {
if (!fd.is_dir(target)) {
add_error("Dangling link: " + origin + " -> " + target + " (target does not exist)")
}
}
})
}
// Gather packages to verify
var packages_to_verify = []
if (scope == 'shop') {
packages_to_verify = shop.list_packages()
} else if (scope == 'world') {
// For now, world is the same as shop
// In future, this could be a separate concept
packages_to_verify = shop.list_packages()
} else {
// Single package
var locator = scope
// Resolve local paths
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
if (deep) {
// Gather all dependencies
var all_deps = pkg.gather_dependencies(locator)
push(packages_to_verify, locator)
arrfor(all_deps, function(dep) {
push(packages_to_verify, dep)
})
} else {
push(packages_to_verify, locator)
}
}
log.console("Verifying " + text(length(packages_to_verify)) + " package(s)...")
log.console("")
// Run verification
check_link_cycles()
check_dangling_links()
arrfor(packages_to_verify, function(p) {
if (p == 'core') return
verify_package(p)
})
// Print results
if (length(warnings) > 0) {
log.console("Warnings:")
arrfor(warnings, function(w) {
log.console(" " + w)
})
log.console("")
}
if (length(errors) > 0) {
log.console("Errors:")
arrfor(errors, function(e) {
log.console(" " + e)
})
log.console("")
log.console("Verification FAILED: " + text(length(errors)) + " error(s), " + text(length(warnings)) + " warning(s)")
// Note: would use process.exit(1) if available
} else {
log.console("Verification PASSED: " + text(checked) + " package(s) checked, " + text(length(warnings)) + " warning(s)")
}
$stop()