fix links, fix hot reload
This commit is contained in:
219
link.cm
Normal file
219
link.cm
Normal file
@@ -0,0 +1,219 @@
|
||||
// Link management module for cell packages
|
||||
// Handles creating, removing, and syncing symlinks for local development
|
||||
|
||||
var toml = use('toml')
|
||||
var fd = use('fd')
|
||||
var utf8 = use('utf8')
|
||||
var os = use('os')
|
||||
|
||||
var global_shop_path = os.global_shop_path
|
||||
|
||||
// Get the links file path (in the global shop)
|
||||
function get_links_path() {
|
||||
return global_shop_path + '/link.toml'
|
||||
}
|
||||
|
||||
// Get the packages directory (in the global shop)
|
||||
function get_packages_dir() {
|
||||
return global_shop_path + '/packages'
|
||||
}
|
||||
|
||||
// return the safe path for the package
|
||||
function safe_package_path(pkg) {
|
||||
return pkg.replaceAll('@', '_')
|
||||
}
|
||||
|
||||
function get_package_abs_dir(package) {
|
||||
return get_packages_dir() + '/' + package
|
||||
}
|
||||
|
||||
function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
var parts = path.split('/')
|
||||
var current = path.startsWith('/') ? '/' : ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) {
|
||||
fd.mkdir(current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve a link target to its actual path
|
||||
// If target is a local path (starts with /), return it directly
|
||||
// If target is a package name, return the package directory
|
||||
function resolve_link_target(target) {
|
||||
if (target.startsWith('/')) {
|
||||
return target
|
||||
}
|
||||
// Target is another package - resolve to its directory
|
||||
return get_packages_dir() + '/' + safe_package_path(target)
|
||||
}
|
||||
|
||||
var Link = {}
|
||||
|
||||
var link_cache = null
|
||||
|
||||
Link.load = function() {
|
||||
if (link_cache) return link_cache
|
||||
var path = get_links_path()
|
||||
if (!fd.is_file(path)) {
|
||||
link_cache = {}
|
||||
return link_cache
|
||||
}
|
||||
|
||||
try {
|
||||
var content = text(fd.slurp(path))
|
||||
var cfg = toml.decode(content)
|
||||
link_cache = cfg.links || {}
|
||||
} catch (e) {
|
||||
log.console("Warning: Failed to load link.toml: " + e)
|
||||
link_cache = {}
|
||||
}
|
||||
return link_cache
|
||||
}
|
||||
|
||||
Link.save = function(links) {
|
||||
link_cache = links
|
||||
var cfg = { links: links }
|
||||
var path = get_links_path()
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(cfg)))
|
||||
}
|
||||
|
||||
Link.add = function(canonical, target, shop) {
|
||||
// Validate canonical package exists in shop
|
||||
var lock = shop.load_lock()
|
||||
if (!lock[canonical]) {
|
||||
throw new Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
}
|
||||
|
||||
// Validate target is a valid package
|
||||
if (target.startsWith('/')) {
|
||||
// Local path - must have cell.toml
|
||||
if (!fd.is_file(target + '/cell.toml')) {
|
||||
throw new Error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
}
|
||||
} else {
|
||||
// Remote package target - ensure it's installed
|
||||
shop.get(target)
|
||||
}
|
||||
|
||||
var links = Link.load()
|
||||
links[canonical] = target
|
||||
Link.save(links)
|
||||
|
||||
// Create the symlink immediately
|
||||
Link.sync_one(canonical, target, shop)
|
||||
|
||||
log.console("Linked " + canonical + " -> " + target)
|
||||
return true
|
||||
}
|
||||
|
||||
Link.remove = function(canonical) {
|
||||
var links = Link.load()
|
||||
if (!links[canonical]) return false
|
||||
|
||||
// Remove the symlink if it exists
|
||||
var target_dir = get_package_abs_dir(canonical)
|
||||
if (fd.is_link(target_dir)) {
|
||||
fd.unlink(target_dir)
|
||||
log.console("Removed symlink at " + target_dir)
|
||||
}
|
||||
|
||||
delete links[canonical]
|
||||
Link.save(links)
|
||||
log.console("Unlinked " + canonical)
|
||||
return true
|
||||
}
|
||||
|
||||
Link.clear = function() {
|
||||
// Remove all symlinks first
|
||||
var links = Link.load()
|
||||
for (var canonical in links) {
|
||||
var target_dir = get_package_abs_dir(canonical)
|
||||
if (fd.is_link(target_dir)) {
|
||||
fd.unlink(target_dir)
|
||||
}
|
||||
}
|
||||
|
||||
Link.save({})
|
||||
log.console("Cleared all links")
|
||||
return true
|
||||
}
|
||||
|
||||
// Sync a single link - ensure the symlink is in place
|
||||
Link.sync_one = function(canonical, target, shop) {
|
||||
var target_dir = get_package_abs_dir(canonical)
|
||||
var link_target = resolve_link_target(target)
|
||||
|
||||
// Ensure parent directories exist
|
||||
var parent = target_dir.substring(0, target_dir.lastIndexOf('/'))
|
||||
ensure_dir(parent)
|
||||
|
||||
// Check current state
|
||||
var current_link = null
|
||||
if (fd.is_link(target_dir)) {
|
||||
current_link = fd.readlink(target_dir)
|
||||
}
|
||||
|
||||
// If already correctly linked, nothing to do
|
||||
if (current_link == link_target) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove existing file/dir/link
|
||||
if (fd.is_link(target_dir)) {
|
||||
fd.unlink(target_dir)
|
||||
} else if (fd.is_dir(target_dir)) {
|
||||
fd.rmdir(target_dir, 1)
|
||||
}
|
||||
|
||||
// Create symlink
|
||||
fd.symlink(link_target, target_dir)
|
||||
return true
|
||||
}
|
||||
|
||||
// Sync all links - ensure all symlinks are in place
|
||||
Link.sync_all = function(shop) {
|
||||
var links = Link.load()
|
||||
var count = 0
|
||||
var errors = []
|
||||
|
||||
for (var canonical in links) {
|
||||
var target = links[canonical]
|
||||
try {
|
||||
// Validate target exists
|
||||
var link_target = resolve_link_target(target)
|
||||
if (!fd.is_dir(link_target)) {
|
||||
errors.push(canonical + ': target ' + link_target + ' does not exist')
|
||||
continue
|
||||
}
|
||||
if (!fd.is_file(link_target + '/cell.toml')) {
|
||||
errors.push(canonical + ': target ' + link_target + ' is not a valid package')
|
||||
continue
|
||||
}
|
||||
|
||||
Link.sync_one(canonical, target, shop)
|
||||
count++
|
||||
} catch (e) {
|
||||
errors.push(canonical + ': ' + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
return { synced: count, errors: errors }
|
||||
}
|
||||
|
||||
// Check if a package is currently linked
|
||||
Link.is_linked = function(canonical) {
|
||||
var links = Link.load()
|
||||
return canonical in links
|
||||
}
|
||||
|
||||
// Get the link target for a package (or null if not linked)
|
||||
Link.get_target = function(canonical) {
|
||||
var links = Link.load()
|
||||
return links[canonical] || null
|
||||
}
|
||||
|
||||
return Link
|
||||
Reference in New Issue
Block a user