// cell verify [] - Verify integrity and consistency // // Usage: // cell verify Verify current directory package // cell verify . Verify current directory package // cell verify Verify specific package // cell verify shop Verify entire shop // cell verify world Verify all world roots // // Options: // --deep Traverse full dependency closure // --target Verify builds for specific target var shop = use('internal/shop') var pkg = use('package') var link = use('link') var build = use('build') var fd = use('fd') var scope = null var deep = false var target_triple = null for (var i = 0; i < args.length; i++) { if (args[i] == '--deep') { deep = true } else if (args[i] == '--target' || args[i] == '-t') { if (i + 1 < args.length) { target_triple = args[++i] } else { log.error('--target requires a triple') $stop() } } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell verify [] [options]") log.console("") log.console("Verify integrity and consistency.") log.console("") log.console("Scopes:") log.console(" Verify specific package") log.console(" shop Verify entire shop") log.console(" world Verify all world roots") log.console("") log.console("Options:") log.console(" --deep Traverse full dependency closure") log.console(" --target Verify builds for specific target") $stop() } else if (!starts_with(args[i], '-')) { scope = args[i] } } // Default to current directory if (!scope) { scope = '.' } // Detect target if not specified if (!target_triple) { target_triple = build.detect_host_target() } var errors = [] var warnings = [] var checked = 0 function add_error(msg) { errors.push(msg) } function add_warning(msg) { warnings.push(msg) } // Verify a single package function verify_package(locator) { checked++ var lock = shop.load_lock() var lock_entry = lock[locator] var links = link.load() var link_target = links[locator] // Check lock entry exists if (!lock_entry) { add_error(locator + ": not in lock") } // Check package directory exists var pkg_dir = shop.get_package_dir(locator) var dir_exists = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir) if (!dir_exists) { add_error(locator + ": package directory missing at " + pkg_dir) return } // Check cell.toml exists if (!fd.is_file(pkg_dir + '/cell.toml')) { add_error(locator + ": missing cell.toml") return } // For linked packages, verify link target if (link_target) { if (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 && c_files.length > 0) { if (!fd.is_file(lib_path)) { add_warning(locator + ": library not built at " + lib_path) } } } catch (e) { // Skip build check if can't determine C files } } // Check for link cycles function check_link_cycles() { var links = link.load() function follow_chain(origin, visited) { if (visited[origin]) { return origin // cycle detected } visited[origin] = true var target = links[origin] if (target && links[target]) { return follow_chain(target, visited) } return null } for (var origin in links) { var cycle_start = follow_chain(origin, {}) if (cycle_start) { add_error("Link cycle detected starting from: " + origin) } } } // Check for dangling links function check_dangling_links() { var links = link.load() for (var origin in links) { var target = links[origin] if (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) packages_to_verify.push(locator) arrfor(all_deps, function(dep) { packages_to_verify.push(dep) }) } else { packages_to_verify.push(locator) } } log.console("Verifying " + text(packages_to_verify.length) + " package(s)...") log.console("") // Run verification check_link_cycles() check_dangling_links() arrfor(packages_to_verify, function(p) { if (p == 'core') return verify_package(p) }) // Print results if (warnings.length > 0) { log.console("Warnings:") arrfor(warnings, function(w) { log.console(" " + w) }) log.console("") } if (errors.length > 0) { log.console("Errors:") arrfor(errors, function(e) { log.console(" " + e) }) log.console("") log.console("Verification FAILED: " + text(errors.length) + " error(s), " + text(warnings.length) + " warning(s)") // Note: would use process.exit(1) if available } else { log.console("Verification PASSED: " + text(checked) + " package(s) checked, " + text(warnings.length) + " warning(s)") } $stop()