add checking for new mod versions

This commit is contained in:
2025-06-02 12:12:05 -05:00
parent f70f65d1c3
commit e689679aac
9 changed files with 236 additions and 80 deletions

View File

@@ -1,3 +1,2 @@
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master"
extramath = "https://gitea.pockle.world/john/extramath@master"

View File

@@ -1,5 +1,6 @@
[modules]
[modules.extramath]
hash = "4244JXYZT7IMYQFYXOSPRK7VFCH4FBYQCQ5FCKYXMGA4QMN6RMPA===="
hash = "MCLZT3JABTAENS4WVXKGWJ7JPBLZER4YQ5VN2PE7ZD2Z4WYGTIMA===="
url = "https://gitea.pockle.world/john/extramath@master"
downloaded = "Monday June 2 10:41:16.23 AM -5 2025 AD"
downloaded = "Monday June 2 12:07:20.42 PM -5 2025 AD"
commit = "84d81a19a8455bcf8dc494739e9e6d545df6ff2c"

View File

@@ -12,34 +12,9 @@ if (!io.exists('.cell/build')) {
log.console("Cleaning build artifacts...")
// Recursively delete directory contents
function remove_dir(path) {
var files = io.enumerate(path, false) // non-recursive first
// Delete all files and subdirectories
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (io.is_directory(file)) {
remove_dir(file) // Recurse into subdirectory
} else {
try {
io.rm(file)
} catch (e) {
log.error("Failed to remove " + file + ": " + e)
}
}
}
// Now remove the empty directory
try {
io.rm(path)
} catch (e) {
log.error("Failed to remove directory " + path + ": " + e)
}
}
// Remove the build directory
try {
io.rmdir('.cell/build')
remove_dir('.cell/build')
log.console("Build directory removed")
} catch (e) {

View File

@@ -250,6 +250,7 @@ if (io.exists(configPath)) {
}
globalThis.json = use('json')
globalThis.text = use('text')
var time = use('time')
var blob = use('blob')

View File

@@ -1,5 +1,32 @@
var io = this
function remove_dir(path) {
var files = io.enumerate(path, false) // non-recursive first
// Delete all files and subdirectories
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (io.is_directory(file)) {
remove_dir(file) // Recurse into subdirectory
} else {
try {
io.rm(file)
} catch (e) {
log.error("Failed to remove " + file + ": " + e)
}
}
}
// Now remove the empty directory
try {
io.rm(path)
} catch (e) {
log.error("Failed to remove directory " + path + ": " + e)
}
}
io.rmdir = remove_dir
io.rm[cell.DOC] = `Remove the file or empty directory at the given path.
:param path: The file or empty directory to remove. Must be empty if a directory.

View File

@@ -36,6 +36,22 @@ uses.download = function()
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) {
@@ -44,6 +60,11 @@ uses.download = function()
} 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) {
@@ -79,6 +100,12 @@ uses.download = function()
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
@@ -91,9 +118,14 @@ uses.download = function()
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
if (!io.exists(module_path))
io.mkdir(module_path)
io.mkdir(module_path)
// Extract each file
for (var i = 0; i < count; i++) {

View File

@@ -109,6 +109,74 @@ Shop.add_dependency = function(alias, locator) {
return true
}
// Get the API URL for checking remote git commits
Shop.get_api_url = function(locator) {
var parsed = Shop.parse_locator(locator)
if (!parsed) return null
// Handle different git hosting patterns
if (locator.startsWith('https://')) {
// Remove https:// prefix for parsing
var cleanLocator = locator.substring(8)
var hostAndPath = cleanLocator.split('@')[0]
var parts = hostAndPath.split('/')
// Gitea pattern: gitea.pockle.world/user/repo@branch
if (hostAndPath.includes('gitea.')) {
var host = parts[0]
var user = parts[1]
var repo = parts[2]
return 'https://' + host + '/api/v1/repos/' + user + '/' + repo + '/branches/' + parsed.version
}
// GitHub pattern: github.com/user/repo@tag or @branch
if (hostAndPath.includes('github.com')) {
var user = parts[1]
var repo = parts[2]
// Try branch first, then tag
return 'https://api.github.com/repos/' + user + '/' + repo + '/branches/' + parsed.version
}
// GitLab pattern: gitlab.com/user/repo@tag
if (hostAndPath.includes('gitlab.')) {
var user = parts[1]
var repo = parts[2]
var projectId = encodeURIComponent(user + '/' + repo)
return 'https://' + parts[0] + '/api/v4/projects/' + projectId + '/repository/branches/' + parsed.version
}
}
// Fallback - return null if no API pattern matches
return null
}
// Extract commit hash from API response
Shop.extract_commit_hash = function(locator, response) {
if (!response) return null
var data
try {
data = json.decode(response)
} catch (e) {
log.console("Failed to parse API response: " + e)
return null
}
// Handle different git hosting response formats
if (locator.includes('gitea.')) {
// Gitea: response.commit.id
return data.commit && data.commit.id
} else if (locator.includes('github.com')) {
// GitHub: response.commit.sha
return data.commit && data.commit.sha
} else if (locator.includes('gitlab.')) {
// GitLab: response.commit.id
return data.commit && data.commit.id
}
return null
}
// Get the module directory for a given alias
Shop.get_module_dir = function(alias) {
var config = Shop.load_config()

View File

@@ -1,51 +1,118 @@
// cell update <alias> - Update a dependency to a new version
// cell update [alias] - Check for updates and optionally install them
var io = use('io')
var shop = use('shop')
var http = use('http')
var toml = use('toml')
var json = use('json')
var os = use('os')
if (args.length < 1) {
log.console("Usage: cell update <alias> [new-version]")
log.console("Example: cell update jj_mod v0.7.0")
$_.stop()
return
}
var alias = args[0]
if (!io.exists('.cell/shop.toml')) {
log.error("No shop.toml found. Run 'cell init' first.")
if (!io.exists('.cell/cell.toml')) {
log.error("No cell.toml found. Run 'cell init' first.")
$_.stop()
return
}
var config = shop.load_config()
if (!config || !config.dependencies || !config.dependencies[alias]) {
log.error("Dependency '" + alias + "' not found")
if (!config || !config.dependencies) {
log.console("No dependencies to update")
$_.stop()
return
}
var current_version = config.dependencies[alias]
log.console("Current version: " + current_version)
// Load lock file
var lock_path = '.cell/lock.toml'
var lock = {}
if (io.exists(lock_path)) {
var lock_content = io.slurp(lock_path)
lock = toml.decode(lock_content)
}
if (!lock.modules) lock.modules = {}
if (args.length > 1) {
// Update to specific version
var new_version = args[1]
// Parse the current locator to keep the host/path
var parsed = shop.parse_locator(current_version)
if (parsed) {
var new_locator = parsed.path + '@' + new_version
config.dependencies[alias] = new_locator
shop.save_config(config)
log.console("Updated " + alias + " to " + new_locator)
log.console("Run 'cell get " + new_locator + "' to fetch the new version")
var updates_available = []
// Check specific dependency or all
var deps_to_check = {}
if (args.length > 0) {
var alias = args[0]
if (!config.dependencies[alias]) {
log.error("Dependency '" + alias + "' not found")
$_.stop()
return
}
deps_to_check[alias] = config.dependencies[alias]
} else {
// TODO: Check for latest version
log.console("TODO: Check for latest version of " + alias)
log.console("For now, specify version: cell update " + alias + " <version>")
deps_to_check = config.dependencies
}
// Check each dependency for updates
for (var alias in deps_to_check) {
var locator = deps_to_check[alias]
log.console("Checking " + alias + " (" + locator + ")...")
// Get API URL to check commits
var api_url = shop.get_api_url(locator)
if (!api_url) {
log.console(" Cannot check updates (no API support)")
continue
}
try {
log.console(api_url)
var api_response = http.fetch(api_url)
var remote_commit = shop.extract_commit_hash(locator, text(api_response))
if (!remote_commit) {
log.console(" Failed to get remote commit")
continue
}
var local_commit = lock.modules[alias] && lock.modules[alias].commit
if (!local_commit) {
log.console(" No local commit tracked")
updates_available.push({
alias: alias,
locator: locator,
local_commit: null,
remote_commit: remote_commit
})
} else if (local_commit !== remote_commit) {
log.console(" Update available!")
log.console(" Local: " + local_commit.substring(0, 8))
log.console(" Remote: " + remote_commit.substring(0, 8))
updates_available.push({
alias: alias,
locator: locator,
local_commit: local_commit,
remote_commit: remote_commit
})
} else {
log.console(" Up to date (" + local_commit.substring(0, 8) + ")")
}
} catch (e) {
log.console(" Failed to check: " + e)
}
}
if (updates_available.length === 0) {
log.console("\nAll dependencies are up to date!")
$_.stop()
return
}
log.console("\n" + updates_available.length + " update(s) available:")
for (var i = 0; i < updates_available.length; i++) {
var update = updates_available[i]
log.console(" - " + update.alias)
}
// If specific dependency was requested, auto-install
if (args.length > 0 && updates_available.length > 0) {
log.console("\nDownloading update...")
os.system("cell mod download")
} else if (updates_available.length > 0) {
log.console("\nRun 'cell mod download' to install updates")
}
$_.stop()

View File

@@ -1,25 +1,11 @@
var http = use('http')
var text = use('text')
// Test with a simpler endpoint first
log.console("Testing httpbin.org chunked response...")
try {
var b = http.fetch("https://httpbin.org/stream/3")
log.console(b.length)
var text1 = text(b)
log.console("httpbin response length:", text1.length)
log.console("httpbin response:", text1)
} catch (e) {
log.console("httpbin error:", e)
}
log.console("\nTesting dictionary.ink...")
try {
var b2 = http.fetch("https://dictionary.ink/find?word=theological")
var b2 = http.fetch("https://gitea.pockle.world/api/v1/repos/john/prosperon/branches/master")
log.console(b2.length)
var text2 = text(b2)
log.console("dictionary response length:", text2.length)
log.console("dictionary first 500 chars:", text2.substring(0, 500))
log.console(text(b2))
} catch (e) {
log.console("dictionary error:", e)
}