var shop = use('shop') var http = use('http') var miniz = use('miniz') var io = use('io') var crypto = use('crypto') var text = use('text') var toml = use('toml') var time = use('time') var uses = {} uses.download = function() { var mods = shop.load_config().dependencies var cache_dir = '.cell/cache' var modules_dir = '.cell/modules' var lock_path = '.cell/lock.toml' // Ensure directories exist if (!io.exists(cache_dir)) io.mkdir(cache_dir) if (!io.exists(modules_dir)) io.mkdir(modules_dir) // Load or create lock file var lock = {} if (io.exists(lock_path)) { var lock_content = io.slurp(lock_path) lock = toml.decode(lock_content) } if (!lock.modules) lock.modules = {} for (var mod in mods) { var cache_path = cache_dir + '/' + mod + '.zip' var module_path = modules_dir + '/' + mod var zip var need_download = false var remote_commit = null // Check remote commit if this is a git repository var api_url = shop.get_api_url(mods[mod]) if (api_url) { log.console(`${mod}: checking remote commit...`) try { var api_response = http.fetch(api_url) remote_commit = shop.extract_commit_hash(mods[mod], text(api_response)) if (remote_commit) { log.console(`${mod}: remote commit = ${remote_commit}`) } } catch (e) { log.console(`${mod}: failed to check remote commit`) } } // Check if module exists in lock file if (!lock.modules[mod] || !lock.modules[mod].hash) { log.console(`${mod}: not in lock file, will download`) need_download = true } else if (!io.exists(cache_path)) { log.console(`${mod}: cache missing, will download`) need_download = true } else if (remote_commit && (!lock.modules[mod].commit || lock.modules[mod].commit != remote_commit)) { log.console(`${mod}: remote has new commit`) log.console(` local: ${lock.modules[mod].commit || 'unknown'}`) log.console(` remote: ${remote_commit}`) need_download = true } if (!need_download) { // Verify cached file hash log.console(`${mod}: verifying cached version`) zip = io.slurpbytes(cache_path) var hash = crypto.hash(zip) var hash_b32 = text(hash, "t") if (hash_b32 != lock.modules[mod].hash) { log.console(`${mod}: hash mismatch, will redownload`) log.console(` expected: ${lock.modules[mod].hash}`) log.console(` actual: ${hash_b32}`) need_download = true } else { log.console(`${mod}: hash verified`) } } if (need_download) { // Download the module log.console(`downloading ${mod} at ${mods[mod]}`) log.console(shop.get_download_url(mods[mod])) zip = http.fetch(shop.get_download_url(mods[mod])) io.slurpwrite(cache_path, zip) log.console(`${mod}: downloaded ${zip.length} bytes`) // Calculate and store hash var hash = crypto.hash(zip) var hash_b32 = text(hash, "t") lock.modules[mod] = { hash: hash_b32, url: mods[mod], downloaded: time.text() } // Store commit hash if available if (remote_commit) { lock.modules[mod].commit = remote_commit } log.console(`${mod}: hash = ${hash_b32}`) // Save updated lock file io.slurpwrite(lock_path, toml.encode(lock)) } // Extract the module var reader = miniz.read(zip) var count = reader.count() log.console(`extracting ${mod} (${count} files)...`) // Remove existing module directory if it exists (for clean updates) if (io.exists(module_path)) { log.console(`${mod}: removing old version...`) io.rmdir(module_path) } // Create module directory io.mkdir(module_path) // Extract each file for (var i = 0; i < count; i++) { if (reader.is_directory(i)) continue var filename = reader.get_filename(i) // Strip the module name prefix if present var prefix = mod + '/' if (filename.indexOf(prefix) == 0) filename = filename.substring(prefix.length) // Skip if filename is empty after stripping if (!filename) continue var filepath = module_path + '/' + filename // Create subdirectories if needed var parts = filename.split('/') if (parts.length > 1) { var dir = module_path for (var j = 0; j < parts.length - 1; j++) { dir = dir + '/' + parts[j] if (!io.exists(dir)) io.mkdir(dir) } } // Extract and write file var data = reader.slurp(reader.get_filename(i)) io.slurpwrite(filepath, data) } log.console(`${mod}: extracted to ${module_path}`) } } if (uses[arg[0]]) uses[arg[0]]() else console.log(`Command ${arg[0]} not understood.`)