clean up cmd line
This commit is contained in:
257
verify.ce
Normal file
257
verify.ce
Normal file
@@ -0,0 +1,257 @@
|
||||
// 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 < 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 [<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 (!args[i].startsWith('-')) {
|
||||
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 (link_target.startsWith('/')) {
|
||||
// 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 = link_target.startsWith('/') ? 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 (target.startsWith('/')) {
|
||||
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 == '.' || locator.startsWith('./') || locator.startsWith('../') || 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)
|
||||
for (var dep of all_deps) {
|
||||
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()
|
||||
|
||||
for (var p of packages_to_verify) {
|
||||
if (p == 'core') continue
|
||||
verify_package(p)
|
||||
}
|
||||
|
||||
// Print results
|
||||
if (warnings.length > 0) {
|
||||
log.console("Warnings:")
|
||||
for (var w of warnings) {
|
||||
log.console(" " + w)
|
||||
}
|
||||
log.console("")
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
log.console("Errors:")
|
||||
for (var e of errors) {
|
||||
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()
|
||||
Reference in New Issue
Block a user