diff --git a/Makefile b/Makefile index d6c44f10..36580b46 100755 --- a/Makefile +++ b/Makefile @@ -3,54 +3,72 @@ # # If cell doesn't exist yet, use 'make bootstrap' first (requires meson) # or manually build with meson once. +# +# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core -cell: libcell_runtime.dylib cell_main core.qop - cat cell_main > cell - cat core.qop >> cell +CELL_SHOP = $(HOME)/.cell +CELL_CORE = $(CELL_SHOP)/core + +cell: libcell_runtime.dylib cell_main + cp cell_main cell chmod +x cell cp cell /opt/homebrew/bin/cell cp libcell_runtime.dylib /opt/homebrew/lib/ # Build the shared runtime library (everything except main.c) # Uses existing cell to run build -d -libcell_runtime.dylib: .cell/build/dynamic +libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic cell build -d - cp .cell/build/dynamic/libcell_runtime.dylib . + cp $(CELL_SHOP)/build/dynamic/libcell_runtime.dylib . # Build the thin main wrapper that links to libcell_runtime cell_main: source/main.c libcell_runtime.dylib cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib -# Create core.qop from scripts folder -core.qop: scripts/*.cm scripts/*.c - cell qopconv -d scripts . core.qop +# Install core: symlink this directory to ~/.cell/core +install: $(CELL_SHOP) + @echo "Linking cell core to $(CELL_CORE)" + rm -rf $(CELL_CORE) + ln -s $(PWD) $(CELL_CORE) + @echo "Core installed." + +# Create the cell shop directories +$(CELL_SHOP): + mkdir -p $(CELL_SHOP) + mkdir -p $(CELL_SHOP)/packages + mkdir -p $(CELL_SHOP)/cache + mkdir -p $(CELL_SHOP)/build + +$(CELL_CORE): + ln -s $(PWD) $(CELL_CORE) # Static build: creates a fully static cell binary (for distribution) static: cell build - cp .cell/build/static/cell . - cat core.qop >> cell + cp $(CELL_SHOP)/build/static/cell . # Bootstrap: build cell from scratch using meson (only needed once) -bootstrap: +# Also installs core scripts to ~/.cell/core +bootstrap: install meson setup build_bootstrap -Dbuildtype=release meson compile -C build_bootstrap cp build_bootstrap/cell . cp build_bootstrap/libcell_runtime.dylib . - @echo "Bootstrap complete. Now run 'make' to rebuild with cell itself." + @echo "Bootstrap complete. Cell shop initialized at $(CELL_SHOP)" + @echo "Now run 'make' to rebuild with cell itself." # Clean build artifacts clean: - rm -rf .cell/build build_bootstrap - rm -f cell cell_main libcell_runtime.dylib core.qop + rm -rf $(CELL_SHOP)/build build_bootstrap + rm -f cell cell_main libcell_runtime.dylib # Ensure dynamic build directory exists -.cell/build/dynamic: - mkdir -p .cell/build/dynamic +$(CELL_SHOP)/build/dynamic: $(CELL_SHOP) + mkdir -p $(CELL_SHOP)/build/dynamic # Legacy meson target meson: meson setup build_dbg -Dbuildtype=debugoptimized meson install -C build_dbg -.PHONY: cell static bootstrap clean meson +.PHONY: cell static bootstrap clean meson install diff --git a/.cell/cell.toml b/cell.toml similarity index 100% rename from .cell/cell.toml rename to cell.toml diff --git a/scripts/build.ce b/scripts/build.ce index d08726e5..7ada730d 100644 --- a/scripts/build.ce +++ b/scripts/build.ce @@ -89,13 +89,20 @@ function find_cell_dir() { if (config && config.dependencies && config.dependencies.cell) { var pkg = config.dependencies.cell var parsed = shop.parse_package(pkg) - var pkg_dir = '.cell/modules/' + parsed.path + var pkg_dir = shop.get_shop_path() + '/modules/' + parsed.path if (fd.is_file(pkg_dir + '/source/cell.c')) { log.console("Using cell from dependency: " + pkg_dir) return pkg_dir } } + // Check Shop Core Dir + var core_dir = shop.get_core_dir() + if (fd.is_file(core_dir + '/source/cell.c')) { + log.console("Using cell from core: " + core_dir) + return core_dir + } + // Fallback: try ~/work/cell var home_cell = os.getenv('HOME') + '/work/cell' if (fd.is_file(home_cell + '/source/cell.c')) { @@ -139,7 +146,7 @@ if (dynamic_mode) { log.console("Found " + text(c_files.length) + " C files to compile") // Get build directory -var build_dir = dynamic_mode ? '.cell/build/dynamic' : build.get_build_dir(target) +var build_dir = dynamic_mode ? shop.get_shop_path() + '/build/dynamic' : build.get_build_dir(target) build.ensure_dir(build_dir) // Load cell config for platform-specific flags @@ -159,8 +166,9 @@ if (dynamic_mode) { var compile_options = { target: target, cflags: cflags, - includes: [], - defines: {} + includes: [cell_dir, cell_dir + '/source'], // Add cell root and source for includes + defines: {}, + module_dir: cell_dir // Ensure we run compilation from cell dir context } // Add target-specific defines @@ -171,13 +179,33 @@ if (target == 'playdate') { // Compile all C files var objects = [] for (var i = 0; i < c_files.length; i++) { - var src = cell_dir + '/' + c_files[i] - var obj = build_dir + '/' + c_files[i] + '.o' + var src = c_files[i] // Relative to module_dir because we set it above? + // No, c_files list was created with full paths (or relative to cell_dir?) + // build.list_files returns relative paths if used with prefix. + // We collected them as 'source/file.c'. + // But compile_file expects src_path. If we use module_dir, src_path should be relative to it. + + // Adjusted: + var src_rel = c_files[i] + var obj_base = shop.get_global_build_dir() + '/cell' // Build cell into its own area? + // Wait, existing logic was: + // var build_dir = dynamic_mode ? shop.get_shop_path() + '/build/dynamic' : build.get_build_dir(target) + + // Let's stick to existing obj path logic but ensure src is handled right. + // construct object path + var obj = build_dir + (dynamic_mode ? '/' : '/static/') + src_rel + '.o' // Separate static build + + // The 'src' var in loop was: var src = cell_dir + '/' + c_files[i] + // But if we pass module_dir = cell_dir, we should pass src relative to it? + // build.cm says: if (src_path.startsWith(module_dir + '/')) src_file = src_path.substring... + + // So we can pass absolute path, it should handle it. + var src_abs = cell_dir + '/' + src_rel // Check if recompilation needed var needs_compile = true if (fd.is_file(obj)) { - var src_stat = fd.stat(src) + var src_stat = fd.stat(src_abs) var obj_stat = fd.stat(obj) if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) { needs_compile = false @@ -185,7 +213,9 @@ for (var i = 0; i < c_files.length; i++) { } if (needs_compile) { - var result = build.compile_file(src, obj, compile_options) + // Ensure specific file includes if needed? + // The global includes should cover it. + var result = build.compile_file(src_abs, obj, compile_options) if (!result) { log.error("Build failed") $_.stop() @@ -200,7 +230,7 @@ if (!dynamic_mode) { for (var p = 0; p < packages.length; p++) { var pkg = packages[p] var parsed = shop.parse_package(pkg) - var pkg_dir = '.cell/modules/' + parsed.path + var pkg_dir = shop.get_shop_path() + '/modules/' + parsed.path if (!fd.is_dir(pkg_dir)) continue @@ -224,7 +254,10 @@ if (!dynamic_mode) { for (var f = 0; f < pkg_c_files.length; f++) { var src = pkg_dir + '/' + pkg_c_files[f] - var obj = build_dir + '/packages/' + parsed.path + '/' + pkg_c_files[f] + '.o' + + // Use shop to determine build directory for this package, instead of manual concat + var pkg_build_base = shop.get_build_dir(parsed.path) + var obj = pkg_build_base + '/' + pkg_c_files[f] + '.o' var safe_name = pkg_c_files[f].substring(0, pkg_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_') var use_name = use_prefix + safe_name + '_use' @@ -392,7 +425,7 @@ if (dynamic_mode) { for (var p = 0; p < packages.length; p++) { var pkg = packages[p] var parsed = shop.parse_package(pkg) - var pkg_dir = '.cell/modules/' + parsed.path + var pkg_dir = shop.get_shop_path() + '/modules/' + parsed.path var pkg_scripts = shop.list_modules(parsed.path) for (var i = 0; i < pkg_scripts.length; i++) { var pack_name = parsed.path + '/' + pkg_scripts[i] diff --git a/scripts/build.cm b/scripts/build.cm index cc60463e..0900faf2 100644 --- a/scripts/build.cm +++ b/scripts/build.cm @@ -8,6 +8,7 @@ var os_mod = use('os') var Build = {} // Embedded cross-compilation toolchain configurations +// Note: build.cm uses shop to resolve paths Build.toolchains = { playdate: { binaries: { @@ -384,6 +385,10 @@ function ensure_dir(path) { if (fd.stat(path).isDirectory) return true var parts = path.split('/') var current = '' + // Handle absolute paths (leading /) + if (path.startsWith('/')) { + current = '/' + } for (var i = 0; i < parts.length; i++) { if (parts[i] == '') continue current += parts[i] + '/' @@ -530,9 +535,12 @@ Build.select_c_files = function(files, target) { } // Get build directory for a target +// Uses shop module to get the global shop path Build.get_build_dir = function(target) { - if (!target) return '.cell/build/static' - return '.cell/build/' + target + var shop = use('shop') + var shop_path = shop.get_shop_path() + if (!target) return shop_path + '/build/static' + return shop_path + '/build/' + target } // Compile a single C file @@ -580,10 +588,14 @@ Build.compile_file = function(src_path, obj_path, options) { // Adjust source path relative to module dir var src_file = src_path + // If src_path is absolute, checking if it is inside module_dir if (src_path.startsWith(module_dir + '/')) { src_file = src_path.substring(module_dir.length + 1) } else if (!src_path.startsWith('/')) { src_file = '"$HERE/' + src_path + '"' + } else { + // It's absolute and outside module dir, use as is (or relative to root?) + // If we use absolute path, it should be fine } // Adjust output path to be absolute/relative to HERE @@ -592,6 +604,12 @@ Build.compile_file = function(src_path, obj_path, options) { out_file = '"$HERE/' + out_file + '"' } + // If we're changing CWD to module_dir, we need to make sure out_file (if absolute) is still valid? + // Yes absolute paths work anywhere. + // Issue reported was: /Users/john/work/accio//Users/john/.cell/build/... + // This happens if we constructed a path that was "module_dir + '/' + absolute_path" somewhere. + // Likely in build.ce or shop.cm (build_package). + var cc_cmd = cc + ' -c' + compile_flags + ' ' + src_file + ' -o ' + out_file full_cmd = 'HERE=$(pwd); cd ' + module_dir + ' && ' + cc_cmd } else { @@ -703,8 +721,9 @@ Build.get_flags = function(config, platform, key) { } // Load config from a directory +// Config is now at /cell.toml (package root), not /.cell/cell.toml Build.load_config = function(dir) { - var path = dir + '/.cell/cell.toml' + var path = dir + '/cell.toml' if (!fd.is_file(path)) return {} var content = fd.slurp(path) if (!content || !content.length) return {} diff --git a/scripts/clean.ce b/scripts/clean.ce index 7fd525ae..eda8c397 100644 --- a/scripts/clean.ce +++ b/scripts/clean.ce @@ -1,9 +1,12 @@ -// cell clean - Remove build artifacts from modules/ +// cell clean - Remove build artifacts from global shop var fd = use('fd') +var shop = use('shop') -if (!fd.is_dir('.cell/build')) { - log.console("No build directory found") +var build_dir = shop.get_shop_path() + '/build' + +if (!fd.is_dir(build_dir)) { + log.console("No build directory found at " + build_dir) $_.stop() return } @@ -12,8 +15,8 @@ log.console("Cleaning build artifacts...") // Remove the build directory try { - fd.rm('.cell/build') - log.console("Build directory removed") + fd.rm(build_dir) + log.console("Build directory removed: " + build_dir) } catch (e) { log.error(e) } diff --git a/scripts/config.ce b/scripts/config.ce index 9e2ba5df..2c9c5ec4 100644 --- a/scripts/config.ce +++ b/scripts/config.ce @@ -106,7 +106,7 @@ if (args.length == 0) { var config = shop.load_config() if (!config) { - log.error("Failed to load .cell/cell.toml") + log.error("Failed to load cell.toml") $_.stop() return } diff --git a/scripts/engine.cm b/scripts/engine.cm index 3bcd51fa..8d1ccb96 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -37,22 +37,36 @@ globalThis.clone = function(obj) { } } -var qop = use_embed('qop') -var core_qop = qop.open(hidden.core_qop_blob) var utf8 = use_embed('utf8') var js = use_embed('js') +var fd = use_embed('fd') + +// Get the core path from C runtime +var core_path = hidden.core_path +if (!core_path) { + throw new Error('Core path not set - cell shop not properly initialized') +} var use_cache = {} +// Load a core module from the file system function use_core(path) { var cache_path = `2::${path}`; if (use_cache[cache_path]) return use_cache[cache_path]; var sym = use_embed(path) - var script = core_qop.read(path + MOD_EXT); - if (script) { - script = utf8.decode(script) + // Core scripts are now in .cell/core/scripts + var file_path = core_path + '/scripts/' + path + MOD_EXT + + // Fallback check for root (optional, for backward compat if needed, but not strictly necessary if we are consistent) + if (!fd.is_file(file_path)) { + file_path = core_path + '/' + path + MOD_EXT + } + + if (fd.is_file(file_path)) { + var script_blob = fd.slurp(file_path) + var script = utf8.decode(script_blob) var mod = `(function setup_${path}_module($_){${script}})` var fn = js.eval(path, mod) var result = fn.call(sym); @@ -194,7 +208,6 @@ function create_actor(desc = {id:guid()}) { var $_ = create_actor() var shop = use('shop') -os.core_qop = core_qop os.use_cache = use_cache shop.set_os(os, $_) @@ -205,7 +218,7 @@ var config = shop.load_config() // Get package name from a resolved path function get_package_from_path(path) { if (!path) return null - var modules_prefix = '.cell/modules/' + var modules_prefix = shop.get_shop_path() + '/modules/' if (path.startsWith(modules_prefix)) { var rest = path.substring(modules_prefix.length) var slash_idx = rest.indexOf('/') @@ -630,30 +643,26 @@ function report_to_overling(msg) sys_msg(overling, {kind:'underling', message:msg, from: $_}) } -// Check for embedded entry point configuration -var entry_config = null -var entry_data = core_qop.read('__entry__.json') -if (entry_data) { - try { - entry_config = json.decode(text(utf8.decode(entry_data))) - } catch(e) {} -} - -// Determine the program to run -// Priority: 1. command line arg, 2. embedded entry point, 3. error +// Determine the program to run from command line var program = cell.args.program -if (!program && entry_config && entry_config.entry) { - program = entry_config.entry - cell.args.program = program -} -// Store static_only mode for shop.cm to use -if (entry_config && entry_config.static_only) { - cell.static_only = true -} - -if (!program) +if (!program) { + log.error('No program specified. Usage: cell [args...]') os.exit(1) +} + +// Find the package containing the program +// The program path is resolved relative to current directory +var package_dir = shop.set_current_package(program) +if (package_dir) { + // Reload config from the package + config = shop.load_config() + if (config) { + config.system = config.system || {} + config.system.__proto__ = default_config + cell.config = config + } +} function handle_actor_disconnect(id) { var greeter = greeters[id] diff --git a/scripts/fd.c b/scripts/fd.c index fda54344..f6e2a4dd 100644 --- a/scripts/fd.c +++ b/scripts/fd.c @@ -665,14 +665,14 @@ JSC_CCALL(fd_realpath, DWORD len = GetFullPathNameA(path, PATH_MAX, resolved, NULL); JS_FreeCString(js, path); if (len == 0 || len >= PATH_MAX) { - return JS_ThrowInternalError(js, "realpath failed: %s", strerror(errno)); + return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno)); } return JS_NewString(js, resolved); #else char *resolved = realpath(path, NULL); JS_FreeCString(js, path); if (!resolved) { - return JS_ThrowInternalError(js, "realpath failed: %s", strerror(errno)); + return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno)); } JSValue result = JS_NewString(js, resolved); free(resolved); diff --git a/scripts/install.ce b/scripts/install.ce new file mode 100644 index 00000000..8cfba93e --- /dev/null +++ b/scripts/install.ce @@ -0,0 +1,30 @@ +// cell install - Install a package to the shop +// Does not modify the current project's cell.toml + +var shop = use('shop') + +if (args.length < 1) { + log.console("Usage: cell install ") + $_.stop() + return +} + +var locator = args[0] + +log.console("Installing " + locator + "...") +if (!shop.update(locator)) { + log.console("Failed to install " + locator) + $_.exit(1) +} + +var deps = shop.list_packages(locator) +for (var dep of deps) { + log.console("Installing dependency " + dep) + shop.update(dep) + shop.build_package(dep) +} + +shop.build_package(locator) +log.console("Installed " + locator) + +$_.stop() diff --git a/scripts/list.ce b/scripts/list.ce index 1ce1ca67..28dfb3ac 100644 --- a/scripts/list.ce +++ b/scripts/list.ce @@ -11,6 +11,8 @@ var target_pkg = null if (args && args.length > 0) { if (args[0] == 'all') { mode = 'all' + } else if (args[0] == 'shop') { + mode = 'shop' } else if (args[0] == 'package') { if (args.length < 2) { log.console("Usage: cell list package ") @@ -24,6 +26,7 @@ if (args && args.length > 0) { log.console(" cell list : list local packages") log.console(" cell list all : list all recursive packages") log.console(" cell list package : list dependencies of ") + log.console(" cell list shop : list all packages in shop") $_.stop() return } @@ -51,6 +54,21 @@ if (mode == 'local') { log.console(" " + all[i]) } if (all.length == 0) log.console(" (none)") +} else if (mode == 'shop') { + log.console("Shop Packages:") + var all = shop.list_shop_packages() + // Sort by package name or something + + if (all.length == 0) { + log.console(" (none)") + } else { + for (var i = 0; i < all.length; i++) { + var item = all[i] + var name = item.package || "unknown" + var ver = item.commit || item.type || "unknown" + log.console(" " + name + " [" + ver + "]") + } + } } function print_deps(ctx) { diff --git a/scripts/qop.c b/scripts/qop.c index 7ecad38e..788a559e 100644 --- a/scripts/qop.c +++ b/scripts/qop.c @@ -1,3 +1,4 @@ +#define QOP_IMPLEMENTATION #include "qop.h" #include "cell.h" @@ -77,16 +78,6 @@ static void write_64(unsigned long long v, FILE *fh) { fwrite(b, 8, 1, fh); } -static unsigned long long qop_hash(const char *key) { - unsigned long long h = 525201411107845655ull; - for (;*key;++key) { - h ^= (unsigned char)*key; - h *= 0x5bd1e9955bd1e995ull; - h ^= h >> 47; - } - return h; -} - static qop_desc *js2qop(JSContext *js, JSValue v) { return JS_GetOpaque(v, js_qop_archive_class_id); } diff --git a/scripts/shop.cm b/scripts/shop.cm index e6055f2f..1cab4259 100644 --- a/scripts/shop.cm +++ b/scripts/shop.cm @@ -9,8 +9,6 @@ var crypto = use('crypto') var utf8 = use('utf8') var blob = use('blob') var build_utils = use('build') -var qop -var core_qop // a package string is what is used to import a module, like prosperon/sprite // in prosperon/sprite, sprite is the module, and prosperon is the package (usually, an alias) @@ -18,6 +16,13 @@ var core_qop var Shop = {} +// Global shop path - this is where all packages, builds, and cache are stored +// Located at ~/.cell on the user's machine +var global_shop_path = null + +// Current package context - the package we're running from (resolved from program path) +var current_package_path = null + var SCOPE_LOCAL = 0 var SCOPE_PACKAGE = 1 var SCOPE_CORE = 2 @@ -31,22 +36,207 @@ var os var use_cache var platform var $_ + +// Initialize the global shop path +function init_global_shop() { + if (global_shop_path) return global_shop_path + + var home = os.getenv('HOME') + if (!home) { + // Try Windows-style + home = os.getenv('USERPROFILE') + } + if (!home) { + throw new Error('Could not determine home directory. Set HOME environment variable.') + } + + global_shop_path = home + '/.cell' + + // Verify the shop exists + if (!fd.is_dir(global_shop_path)) { + throw new Error('Cell shop not found at ' + global_shop_path + '. Run "cell install" to set up.') + } + + return global_shop_path +} + +// Get the global shop path +Shop.get_shop_path = function() { + return init_global_shop() +} + +// Find the package directory containing a file by looking for cell.toml +// Returns the absolute path to the package directory, or null if not in a package +Shop.find_package_dir = function(file_path) { + if (!file_path) return null + + // Walk up directories looking for cell.toml + var dir = file_path + if (fd.is_file(dir)) { + // if it's a file, start from parent + var last_slash = dir.lastIndexOf('/') + if (last_slash > 0) dir = dir.substring(0, last_slash) + } + + while (dir && dir.length > 0) { + var toml_path = dir + '/cell.toml' + if (fd.is_file(toml_path)) { + return fd.realpath(dir) + } + var last_slash = dir.lastIndexOf('/') + if (last_slash <= 0) break + dir = dir.substring(0, last_slash) + } + + // Check current directory as fallback + if (fd.is_file('cell.toml')) { + return fd.realpath('.') + } + + return null +} + +// Link a local package into the shop +function ensure_package_link(abs_path) { + if (!abs_path || !abs_path.startsWith('/')) return false + + var packages_dir = get_modules_dir() + var target_link = packages_dir + abs_path + + // If link already exists and points to correct place, we are good + if (fd.is_link(target_link)) { + var points_to = fd.readlink(target_link) + if (points_to == abs_path) { + update_local_lock(abs_path) + return true + } + // Incorrect link, remove it + fd.unlink(target_link) + } else if (fd.is_dir(target_link)) { + // If it's a real directory, that's weird for a local package mirror, but let's assume it might be a copy? + // User wants symlinks. + // If it's not a link, maybe we should leave it or warn? + // Use user instructions: "shop should ... ensure it's there ... symlink local dirs" + // safely assuming we can replace if it's not the right thing might be dangerous if user put real files there. + // For now, if it's a dir, check if it's the package itself? + // simpler: proceed to link logic + } + + // Create parent dirs + var parent = target_link.substring(0, target_link.lastIndexOf('/')) + ensure_dir(parent) + + try { + fd.symlink(abs_path, target_link) + // log.console("Linked " + abs_path + " -> " + target_link) + update_local_lock(abs_path) + return true + } catch (e) { + log.error("Failed to link package " + abs_path + ": " + e) + return false + } +} + +function update_local_lock(abs_path) { + var lock = Shop.load_lock() + var name = abs_path.split('/').pop() + + // Check if we can find a better name from cell.toml + var toml_path = abs_path + '/cell.toml' + if (fd.is_file(toml_path)) { + // We could parse it, but for now directory name is usually the package alias + } + + lock[name] = { + package: abs_path, + type: 'local', + updated: time.number() + } + Shop.save_lock(lock) +} + +Shop.ensure_package_link = ensure_package_link + +// Set the current package context from a program path +Shop.set_current_package = function(program_path) { + current_package_path = Shop.find_package_dir(program_path) + + if (current_package_path && current_package_path.startsWith('/')) { + // It's a local package, ensure it is linked in the shop + ensure_package_link(current_package_path) + } + + return current_package_path +} + +// Get the current package path +Shop.get_current_package = function() { + return current_package_path +} + Shop.set_os = function(o, $guy) { os = o $_ = $guy - qop = os.load_internal('js_qop_use') - core_qop = os.core_qop use_cache = os.use_cache platform = os.platform() if (platform == 'macOS') dylib_ext = '.dylib' else if (platform == 'Windows') dylib_ext = '.dll' + + // Initialize the global shop + init_global_shop() } var config = null -var shop_path = '.cell/cell.toml' +// Get the config path for a package +// If package_path is null, uses current_package_path +function get_config_path(package_path) { + if (package_path) { + return package_path + '/cell.toml' + } + if (current_package_path) { + return current_package_path + '/cell.toml' + } + // Fallback to current directory + return 'cell.toml' +} + +// Get the lock file path (in the global shop) +function get_lock_path() { + return global_shop_path + '/lock.toml' +} + +// Get the packages directory (in the global shop) +function get_modules_dir() { + return global_shop_path + '/packages' +} + +// Get the cache directory (in the global shop) +function get_cache_dir() { + return global_shop_path + '/cache' +} + +// Get the build directory (in the global shop) +function get_global_build_dir() { + return global_shop_path + '/build' +} + +// Get the core directory (in the global shop) +Shop.get_core_dir = function() { + return global_shop_path + '/core' +} + +// Get the links file path (in the global shop) +function get_links_path() { + return global_shop_path + '/link.toml' +} + +// Get the reports directory (in the global shop) +Shop.get_reports_dir = function() { + return global_shop_path + '/reports' +} var open_dl = {} @@ -81,57 +271,55 @@ Shop.file_info = function(file) { name_without_ext = file.substring(0, file.length - ACTOR_EXT.length) } - // Check if file is in a package - if (file.startsWith('.cell/modules/')) { - var rest = file.substring('.cell/modules/'.length) - - // Get all packages and find which one matches this path - var packages = Shop.list_packages() - var matched_pkg = null - var matched_path = null - - for (var i = 0; i < packages.length; i++) { - var pkg = packages[i] - var parsed = Shop.parse_package(pkg) - var pkg_path = parsed.path + // Check if file is in a package (in global shop packages dir) + var packages_prefix = get_modules_dir() + '/' + if (file.startsWith(packages_prefix)) { + var rest = file.substring(packages_prefix.length) - if (rest.startsWith(pkg_path + '/')) { - // Found matching package - use longest match - if (!matched_pkg || pkg_path.length > matched_path.length) { - matched_pkg = pkg - matched_path = pkg_path - } + // Get all packages and find which one matches this path + // With the new structure, everything in packages/ is a package + // gitea.pockle.world/user/repo/file.cm + + var parts = rest.split('/') + // Heuristic: packages form is host/user/repo + if (parts.length >= 3) { + var pkg_path = parts.slice(0, 3).join('/') + info.package = pkg_path + if (rest.length > pkg_path.length) { + info.name = rest.substring(pkg_path.length + 1) + } else { + info.name = "" // root? + } + } else { + // fallback + info.package = rest } - } - - if (matched_path) { - info.package = matched_path - var import_part = rest.substring(matched_path.length + 1) - // Strip extension from import name - if (info.is_module && import_part.endsWith(MOD_EXT)) { - import_part = import_part.substring(0, import_part.length - MOD_EXT.length) - } else if (info.is_actor && import_part.endsWith(ACTOR_EXT)) { - import_part = import_part.substring(0, import_part.length - ACTOR_EXT.length) + + if (info.name) { + if (info.is_module && info.name.endsWith(MOD_EXT)) + info.name = info.name.substring(0, info.name.length - MOD_EXT.length) + if (info.is_actor && info.name.endsWith(ACTOR_EXT)) + info.name = info.name.substring(0, info.name.length - ACTOR_EXT.length) } - info.name = import_part + } else { - // Fallback: use first path component as package - var slash_idx = rest.indexOf('/') - if (slash_idx > 0) { - info.package = rest.substring(0, slash_idx) - var import_part = rest.substring(slash_idx + 1) - if (info.is_module && import_part.endsWith(MOD_EXT)) { - import_part = import_part.substring(0, import_part.length - MOD_EXT.length) - } else if (info.is_actor && import_part.endsWith(ACTOR_EXT)) { - import_part = import_part.substring(0, import_part.length - ACTOR_EXT.length) + // Check if it's in any other package via cell.toml lookup + var pkg_dir = Shop.find_package_dir(file) + if (pkg_dir) { + info.package = pkg_dir + if (file.startsWith(pkg_dir + '/')) { + var rel = file.substring(pkg_dir.length + 1) + info.name = rel + if (info.is_module && info.name.endsWith(MOD_EXT)) + info.name = info.name.substring(0, info.name.length - MOD_EXT.length) + if (info.is_actor && info.name.endsWith(ACTOR_EXT)) + info.name = info.name.substring(0, info.name.length - ACTOR_EXT.length) + } + } else { + info.package = 'local' // Should we keep 'local' for truly orphan files? + info.name = name_without_ext } - info.name = import_part - } } - } else { - info.package = 'local' - info.name = name_without_ext - } return info } @@ -185,7 +373,7 @@ function get_import_dl(name) { var pkg = get_import_package(name) if (!pkg) return null if (open_dl[pkg]) return open_dl[pkg] - var dlpath = `.cell/modules/${pkg}/${pkg}${dylib_ext}` + var dlpath = get_modules_dir() + '/' + pkg + '/' + pkg + dylib_ext var dl = os.dylib_open(dlpath) if (dl) { open_dl[pkg] = dl @@ -209,13 +397,16 @@ var ensure_dir = build_utils.ensure_dir Shop.load_config = function(module) { var content + var config_path if (!module) { - if (!fd.is_file(shop_path)) + config_path = get_config_path() + if (!fd.is_file(config_path)) return null - content = fd.slurp(shop_path) + content = fd.slurp(config_path) } else { - var module_path = `.cell/modules/${module}/.cell/cell.toml` - if (!fd.stat(module_path).isFile) + // Module config is at //cell.toml + var module_path = get_modules_dir() + '/' + module + '/cell.toml' + if (!fd.is_file(module_path)) return null content = fd.slurp(module_path) @@ -256,12 +447,13 @@ Shop.load_config = function(module) { // Save cell.toml configuration Shop.save_config = function(config) { - fd.slurpwrite(shop_path, utf8.encode(toml.encode(config))); + var config_path = get_config_path() + fd.slurpwrite(config_path, utf8.encode(toml.encode(config))); } -// Load lock.toml configuration +// Load lock.toml configuration (from global shop) Shop.load_lock = function() { - var path = '.cell/lock.toml' + var path = get_lock_path() if (!fd.is_file(path)) return {} @@ -298,15 +490,17 @@ Shop.load_lock = function() { return lock } -// Save lock.toml configuration +// Save lock.toml configuration (to global shop) Shop.save_lock = function(lock) { - fd.slurpwrite('.cell/lock.toml', utf8.encode(toml.encode(lock))); + var path = get_lock_path() + ensure_dir(global_shop_path) + fd.slurpwrite(path, utf8.encode(toml.encode(lock))); } var link_cache = null Shop.load_links = function() { if (link_cache) return link_cache - var path = '.cell/link.toml' + var path = get_links_path() if (!fd.is_file(path)) { link_cache = {} return link_cache @@ -326,7 +520,9 @@ Shop.load_links = function() { Shop.save_links = function(links) { link_cache = links var cfg = { links: links } - fd.slurpwrite('.cell/link.toml', utf8.encode(toml.encode(cfg))) + var path = get_links_path() + ensure_dir(global_shop_path) + fd.slurpwrite(path, utf8.encode(toml.encode(cfg))) } Shop.add_link = function(canonical, target) { @@ -396,19 +592,20 @@ Shop.resolve_package_info = function(pkg) { var path = pkg if (path.includes('@')) path = path.split('@')[0] - if (path.startsWith('/')) path = path.substring(1) var links = Shop.load_links() if (links[path]) { - return { type: 'local', path: links[path] } + return { type: 'package', path: links[path] } } if (pkg.startsWith('/')) { - return { type: 'local', path: pkg } + return { type: 'package', path: pkg } } if (pkg.includes('gitea.')) { return { type: 'gitea' } } + + // If it looks like a path but isn't absolute, might be relative in future, but for now strict return { type: 'unknown' } } @@ -513,7 +710,7 @@ Shop.get_module_dir = function(alias) { var parsed = Shop.parse_package(pkg) if (!parsed) return null - return '.cell/modules/' + parsed.path + return get_modules_dir() + '/' + parsed.path } function lock_package(loc) @@ -526,7 +723,7 @@ Shop.check_cache = function(pkg) { var parsed = Shop.parse_package(pkg) if (!parsed) return null - var cache_path = `.cell/cache/${parsed.path}.zip` + var cache_path = get_cache_dir() + '/' + parsed.path + '.zip' if (fd.is_file(cache_path)) { log.console("Found cached zip: " + cache_path) return true @@ -545,11 +742,12 @@ var script_forms = [] // core: // : (package is the full canonical package name) // local: -// package: (fallback when pkg isn't provided but path is under .cell/modules) +// package: (fallback when pkg isn't provided but path is under modules) function make_compile_name(path, rel_path, pkg, scope) { if (scope == SCOPE_CORE) return 'core:' + rel_path if (pkg) return pkg + ':' + rel_path - if (path && path.startsWith('.cell/modules/')) return 'package:' + rel_path + var modules_dir = get_modules_dir() + if (path && path.startsWith(modules_dir + '/')) return 'package:' + rel_path return 'local:' + rel_path } @@ -581,15 +779,39 @@ function get_flags(config, platform, key) { Shop.get_flags = get_flags -function get_build_dir(pkg = 'local') { - return '.cell/build/' + pkg +function get_build_dir(pkg) { + if (!pkg) pkg = current_package_path + if (!pkg) return get_global_build_dir() + '/local' // Fallback for non-package scripts + + // If pkg is absolute path, mirror it + if (pkg.startsWith('/')) { + // Accio folder should be .cell/packages/Users/... + // But build should be .cell/build/packages/Users/... + return get_global_build_dir() + '/packages' + pkg + } + + // Otherwise it's a relative package path (from packages dir) + return get_global_build_dir() + '/packages/' + pkg } Shop.get_build_dir = get_build_dir function get_rel_path(path, pkg) { - if (!pkg) return path - var prefix = '.cell/modules/' + pkg + '/' + if (!pkg) { + // For local files, strip the current package path prefix if present + if (current_package_path && path.startsWith(current_package_path + '/')) { + return path.substring(current_package_path.length + 1) + } + return path + } + + var prefix + if (pkg.startsWith('/')) { + prefix = pkg + '/' + } else { + prefix = get_modules_dir() + '/' + pkg + '/' + } + if (path.startsWith(prefix)) { return path.substring(prefix.length) } @@ -600,7 +822,19 @@ function resolve_mod_fn(path, pkg) { if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`) var rel_path = get_rel_path(path, pkg) - var build_dir = get_build_dir(pkg) + + // Determine build directory based on context + var build_dir + if (pkg) { + build_dir = get_build_dir(pkg) + } else if (current_package_path) { + // Local file in current package - use package path (strip leading /) + var pkg_id = current_package_path.substring(1) // Remove leading / + build_dir = get_global_build_dir() + '/' + pkg_id + } else { + build_dir = get_build_dir('local') + } + var cache_path = build_dir + '/' + rel_path + '.o' if (fd.is_file(cache_path) && fd.stat(path).mtime <= fd.stat(cache_path).mtime) { @@ -622,15 +856,48 @@ function resolve_mod_fn(path, pkg) return js.eval_compile(fn) } +// Resolve and cache a core module +function resolve_core_mod_fn(core_path, rel_path) { + var build_dir = get_global_build_dir() + '/core' + var cache_path = build_dir + '/' + rel_path + '.o' + + if (fd.is_file(cache_path) && fd.stat(core_path).mtime <= fd.stat(cache_path).mtime) { + var obj = fd.slurp(cache_path) + var fn = js.compile_unblob(obj) + return js.eval_compile(fn) + } + + var ext = core_path.substring(core_path.lastIndexOf('.')) + var form = script_forms[ext] + if (!form) throw new Error(`No script form for extension ${ext}`) + + var compile_name = make_compile_name(core_path, rel_path, null, SCOPE_CORE) + var script = form(null, text(fd.slurp(core_path))) + var fn = js.compile(compile_name, script) + ensure_dir(cache_path.substring(0, cache_path.lastIndexOf('/'))) + fd.slurpwrite(cache_path, js.compile_blob(fn)) + return js.eval_compile(fn) +} + function resolve_locator(path, ext, ctx) { // In static_only mode, only look in the embedded pack var static_only = cell.static_only if (!static_only) { + // First, check if file exists in current package directory + if (current_package_path) { + var pkg_local_path = current_package_path + '/' + path + ext + if (fd.is_file(pkg_local_path)) { + var fn = resolve_mod_fn(pkg_local_path, null) + return {path: pkg_local_path, scope: SCOPE_LOCAL, symbol:fn} + } + } + + // Check CWD for local file var local_path if (ctx) - local_path = `.cell/modules/${ctx}/${path}${ext}` + local_path = get_modules_dir() + '/' + ctx + '/' + path + ext else local_path = path + ext @@ -639,38 +906,37 @@ function resolve_locator(path, ext, ctx) return {path: local_path, scope: SCOPE_LOCAL, symbol:fn} } + // Check installed packages var canonical_pkg = get_normalized_package(path, ctx) var pkg_path = get_path_in_package(path, ctx) - var mod_path = `.cell/modules/${pkg_path}${ext}` + var mod_path = get_modules_dir() + '/' + pkg_path + ext if (fd.is_file(mod_path)) { var fn = resolve_mod_fn(mod_path, canonical_pkg) return {path: mod_path, scope: SCOPE_PACKAGE, symbol:fn} } } - // Check embedded pack (core_qop) - // For packages, try the full package path first + // Check core directory for core modules + var core_dir = Shop.get_core_dir() + + // For packages, try the full package path first in core if (ctx) { var pkg_rel = ctx + '/' + path + ext - var pkg_core = core_qop.read(pkg_rel) - if (pkg_core != null) { - var form = script_forms[ext] - if (!form) throw new Error(`No script form for extension ${ext}`) - var script = form(null, text(pkg_core), ctx) - var compile_name = make_compile_name(pkg_rel, pkg_rel, ctx, SCOPE_CORE) - var fn = js.compile(compile_name, script) - return {path: pkg_rel, scope: SCOPE_CORE, symbol:js.eval_compile(fn)}; + var pkg_core_path = core_dir + '/' + pkg_rel + if (fd.is_file(pkg_core_path)) { + var fn = resolve_core_mod_fn(pkg_core_path, pkg_rel) + return {path: pkg_rel, scope: SCOPE_CORE, symbol:fn}; } } - var core = core_qop.read(path + ext) - if (core != null) { - var form = script_forms[ext] - if (!form) throw new Error(`No script form for extension ${ext}`) - var script = form(null,text(core)) - var compile_name = make_compile_name(path + ext, path + ext, null, SCOPE_CORE) - var fn = js.compile(compile_name, script) - return {path: path + ext, scope: SCOPE_CORE, symbol:js.eval_compile(fn)}; + // Check core directory for the module + // Core scripts are now in .cell/core/scripts + var core_dir = Shop.get_core_dir() + var core_file_path = core_dir + '/scripts/' + path + ext + if (path == 'text') log.console("Checking core mod: " + core_file_path + " exists: " + fd.is_file(core_file_path)) + if (fd.is_file(core_file_path)) { + var fn = resolve_core_mod_fn(core_file_path, path + ext) + return {path: path + ext, scope: SCOPE_CORE, symbol:fn}; } return null; @@ -891,7 +1157,7 @@ function get_cache_path(pkg, commit) { if (!parsed) return null var slug = parsed.path.split('/').join('_') - return `.cell/cache/${slug}_${commit}.zip` + return get_cache_dir() + '/' + slug + '_' + commit + '.zip' } function rm_recursive(path) { @@ -995,7 +1261,7 @@ Shop.update = function(pkg) { var lock = Shop.load_lock() var parsed = Shop.parse_package(pkg) var info = Shop.resolve_package_info(pkg) - var target_dir = '.cell/modules/' + parsed.path + var target_dir = get_modules_dir() + '/' + parsed.path var result = info.type == 'local' ? update_local(pkg, info, target_dir) @@ -1186,7 +1452,7 @@ Shop.remove = function(alias) { var locator = config.dependencies[alias] var parsed = Shop.parse_package(locator) - var target_dir = '.cell/modules/' + parsed.path + var target_dir = get_modules_dir() + '/' + parsed.path // Remove from config delete config.dependencies[alias] @@ -1239,8 +1505,8 @@ Shop.remove_replacement = function(alias) { // List all files in a package Shop.list_files = function(pkg) { var dir - if (!pkg) dir = '.' - else dir = '.cell/modules/' + pkg + if (!pkg) dir = current_package_path || '.' + else dir = get_modules_dir() + '/' + pkg var files = [] @@ -1328,7 +1594,7 @@ Shop.build_package = function(package) var build_dir = get_build_dir(package) ensure_dir(build_dir) - var module_dir = package ? '.cell/modules/' + package : '.' + var module_dir = package ? get_modules_dir() + '/' + package : (current_package_path || '.') log.console(`Building package ${package ? package : 'local'} to ${build_dir}`) @@ -1349,8 +1615,15 @@ Shop.build_package = function(package) for (var i=0; i 0) { - var key = line.substring(0, eq_index).trim() + var key_part = line.substring(0, eq_index).trim() var value = line.substring(eq_index + 1).trim() + // Handle quoted keys in key-value pairs too if needed? + // For now assuming simple keys or quoted keys + var key = parse_key(key_part) + // Parse value if (value.startsWith('"') && value.endsWith('"')) { // String - unescape quotes @@ -59,6 +64,37 @@ function parse_toml(text) { return result } +function parse_key(str) { + if (str.startsWith('"') && str.endsWith('"')) { + return str.slice(1, -1).replace(/\\"/g, '"') + } + return str +} + +// Split a key path by dots, respecting quotes +function parse_key_path(str) { + var parts = [] + var current = '' + var in_quote = false + + for (var i = 0; i < str.length; i++) { + var c = str[i] + if (c == '"' && (i==0 || str[i-1] != '\\')) { + in_quote = !in_quote + // We don't verify if it's strictly correct TOML quote usage, just rudimentary + } else if (c == '.' && !in_quote) { + parts.push(parse_key(current.trim())) + current = '' + continue + } + current += c + } + if (current.trim().length > 0) + parts.push(parse_key(current.trim())) + + return parts +} + function parse_array(str) { // Remove brackets str = str.slice(1, -1).trim() @@ -121,13 +157,20 @@ function encode_toml(obj) { return String(value) } + function quote_key(k) { + if (k.includes('.') || k.includes('"') || k.includes(' ')) { + return '"' + k.replace(/"/g, '\\"') + '"' + } + return k + } + // First pass: encode top-level simple values var keys = Object.keys(obj) for (var i = 0; i < keys.length; i++) { var key = keys[i] var value = obj[key] if (value == null || typeof value != 'object' || Array.isArray(value)) { - result.push(key + ' = ' + encode_value(value)) + result.push(quote_key(key) + ' = ' + encode_value(value)) } } @@ -141,7 +184,10 @@ function encode_toml(obj) { if (value != null && typeof value == 'object' && !Array.isArray(value)) { // Nested object - create section - var section_path = path ? path + '.' + key : key + // We MUST quote the key segment if it has dots, otherwise it becomes a nested table path + var quoted = quote_key(key) + var section_path = path ? path + '.' + quoted : quoted + result.push('[' + section_path + ']') // First encode direct properties of this section @@ -150,7 +196,7 @@ function encode_toml(obj) { var sk = section_keys[j] var sv = value[sk] if (sv == null || typeof sv != 'object' || Array.isArray(sv)) { - result.push(sk + ' = ' + encode_value(sv)) + result.push(quote_key(sk) + ' = ' + encode_value(sv)) } } diff --git a/scripts/update.ce b/scripts/update.ce index 086d2c14..aeb80e36 100644 --- a/scripts/update.ce +++ b/scripts/update.ce @@ -4,11 +4,14 @@ var shop = use('shop') var alias = args.length > 0 ? args[0] : null -var packages = shop.list_packages() +var packages = shop.list_shop_packages() -log.console("Checking for updates...") +log.console("Checking for updates (" + packages.length + " packages)...") -for (var pack of packages) { +for (var info of packages) { + var pack = info.package + if (!pack || pack == 'core') continue + log.console("Updating " + pack) shop.update(pack) shop.build_package(pack) diff --git a/scripts/upgrade.ce b/scripts/upgrade.ce new file mode 100644 index 00000000..721d534c --- /dev/null +++ b/scripts/upgrade.ce @@ -0,0 +1,37 @@ +var shop = use('shop') +var fd = use('fd') + +var cmd = args.length > 0 ? args[0] : null + +if (cmd == 'link') { + if (args.length < 2) { + log.console("Usage: cell upgrade link ") + return + } + var target = args[1] + if (shop.link_core(target)) { + log.console("Linked core -> " + fd.realpath(target)) + } else { + log.console("Failed to link core to " + target) + } +} else if (cmd == 'unlink') { + if (shop.unlink_core()) { + log.console("Unlinked core.") + } else { + log.console("Core was not linked.") + } + log.console("Fetching latest core...") + shop.upgrade_core() +} else { + // cell upgrade (no args) + if (shop.is_core_linked()) { + var core_dir = shop.get_core_dir() + log.console("Core is linked to " + fd.readlink(core_dir)) + log.console("Unlink first to upgrade standard core.") + } else { + log.console("Fetching latest core...") + shop.upgrade_core() + } +} + +$_.stop() \ No newline at end of file diff --git a/scripts/version.ce b/scripts/version.ce new file mode 100644 index 00000000..0060daec --- /dev/null +++ b/scripts/version.ce @@ -0,0 +1,3 @@ +var shop = use('shop') +log.console("0.1.0") +$_.stop() \ No newline at end of file diff --git a/source/cell.c b/source/cell.c index 28cc81ec..0112c816 100644 --- a/source/cell.c +++ b/source/cell.c @@ -11,103 +11,100 @@ #include "cell.h" #include "cell_internal.h" -#define QOP_IMPLEMENTATION -#include "qop.h" - -#define ENGINE "engine.cm" +#define ENGINE "scripts/engine.cm" +#define CELL_SHOP_DIR ".cell" +#define CELL_CORE_DIR "core" #include #include +#include -static qop_desc qop_core; -static qop_file *qop_hashmap = NULL; cell_rt *root_cell = NULL; +static char *core_path = NULL; -int get_executable_path(char *buffer, unsigned int buffer_size) { -#if defined(__linux__) - ssize_t len = readlink("/proc/self/exe", buffer, buffer_size - 1); - if (len == -1) { - return 0; +// Get the home directory +static const char* get_home_dir(void) { + const char *home = getenv("HOME"); + if (!home) { + home = getenv("USERPROFILE"); // Windows fallback } - buffer[len] = '\0'; - return len; -#elif defined(__APPLE__) - if (_NSGetExecutablePath(buffer, &buffer_size) == 0) { - return buffer_size; - } -#elif defined(_WIN32) - return GetModuleFileName(NULL, buffer, buffer_size); -#endif - - return 0; + return home; } -int prosperon_mount_core(void) +// Find and verify the cell shop at ~/.cell +int find_cell_shop(void) { - char exe_path[PATH_MAX]; - int exe_path_len = get_executable_path(exe_path, sizeof(exe_path)); - if (exe_path_len == 0) { - printf("ERROR: Could not get executable path\n"); + const char *home = get_home_dir(); + if (!home) { + printf("ERROR: Could not determine home directory. Set HOME environment variable.\n"); return 0; } - // Load the entire executable into memory - FILE *fh = fopen(exe_path, "rb"); - if (!fh) { - printf("ERROR: Could not open executable\n"); + // Build path to ~/.cell/core + size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR "/" CELL_CORE_DIR) + 1; + core_path = malloc(path_len); + if (!core_path) { + printf("ERROR: Could not allocate memory for core path\n"); return 0; } + snprintf(core_path, path_len, "%s/" CELL_SHOP_DIR "/" CELL_CORE_DIR, home); - fseek(fh, 0, SEEK_END); - long file_size = ftell(fh); - fseek(fh, 0, SEEK_SET); - - unsigned char *buf = malloc(file_size); - if (!buf) { - printf("ERROR: Could not allocate memory for executable\n"); - fclose(fh); - return 0; - } - - if (fread(buf, 1, file_size, fh) != (size_t)file_size) { - printf("ERROR: Could not read executable\n"); - free(buf); - fclose(fh); - return 0; - } - - fclose(fh); - - // Open the QOP archive from the in-memory data - int archive_size = qop_open_data(buf, file_size, &qop_core); - if (archive_size == 0) { - printf("ERROR: Could not open QOP archive\n"); - free(buf); - return 0; - } - - // Read the archive index - qop_hashmap = malloc(qop_core.hashmap_size); - if (!qop_hashmap) { - printf("ERROR: Could not allocate memory for QOP hashmap\n"); - qop_close(&qop_core); - free(buf); - return 0; - } - - int index_len = qop_read_index(&qop_core, qop_hashmap); - if (index_len == 0) { - printf("ERROR: Could not read QOP index\n"); - free(qop_hashmap); - qop_hashmap = NULL; - qop_close(&qop_core); - free(buf); + // Check if the core directory exists + struct stat st; + if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) { + printf("ERROR: Cell shop not found at %s/" CELL_SHOP_DIR "\n", home); + printf("Run 'cell install' to set up the cell environment.\n"); + free(core_path); + core_path = NULL; return 0; } return 1; } +// Load a file from the core directory +static char* load_core_file(const char *filename, size_t *out_size) { + if (!core_path) return NULL; + + size_t path_len = strlen(core_path) + 1 + strlen(filename) + 1; + char *full_path = malloc(path_len); + if (!full_path) return NULL; + + snprintf(full_path, path_len, "%s/%s", core_path, filename); + + FILE *fh = fopen(full_path, "rb"); + free(full_path); + + if (!fh) return NULL; + + fseek(fh, 0, SEEK_END); + long file_size = ftell(fh); + fseek(fh, 0, SEEK_SET); + + char *data = malloc(file_size + 1); + if (!data) { + fclose(fh); + return NULL; + } + + if (fread(data, 1, file_size, fh) != (size_t)file_size) { + free(data); + fclose(fh); + return NULL; + } + + fclose(fh); + data[file_size] = 0; + + if (out_size) *out_size = file_size; + return data; +} + +// Get the core path for use by scripts +const char* cell_get_core_path(void) { + return core_path; +} + void actor_disrupt(cell_rt *crt) { crt->disrupt = 1; @@ -163,39 +160,28 @@ void script_startup(cell_rt *prt) crt->init_wota = NULL; } + // Store the core path for scripts to use JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell"); JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden"); - size_t archive_size = qop_core.data_size - qop_core.files_offset; - JSValue blob = js_new_blob_stoned_copy(js, qop_core.data + qop_core.files_offset, archive_size); - JS_SetPropertyStr(js, hidden, "core_qop_blob", blob); + if (core_path) { + JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path)); + } JS_FreeValue(js, hidden); JS_FreeValue(js, js_cell); JS_FreeValue(js, globalThis); - // Find and load engine.cm from QOP archive - qop_file *engine_file = qop_find(&qop_core, ENGINE); - if (!engine_file) { - printf("ERROR: Could not find file %s in QOP archive!\n", ENGINE); - return; - } - - char *data = malloc(engine_file->size + 1); + // Load engine.cm from the core directory + size_t engine_size; + char *data = load_core_file(ENGINE, &engine_size); if (!data) { - printf("ERROR: Could not allocate memory for %s!\n", ENGINE); + printf("ERROR: Could not load %s from %s!\n", ENGINE, core_path); return; } - int bytes_read = qop_read(&qop_core, engine_file, (unsigned char *)data); - if (bytes_read != (int)engine_file->size) { - printf("ERROR: Could not read file %s from QOP archive!\n", ENGINE); - free(data); - return; - } - data[engine_file->size] = 0; - crt->state = ACTOR_RUNNING; - JSValue v = JS_Eval(js, data, (size_t)engine_file->size, ENGINE, 0); + JSValue v = JS_Eval(js, data, engine_size, ENGINE, 0); + free(data); uncaught_exception(js, v); crt->state = ACTOR_IDLE; set_actor_state(crt); @@ -223,10 +209,9 @@ int cell_init(int argc, char **argv) { int script_start = 1; - /* Load QOP package attached to executable - this is now mandatory! */ - int mounted = prosperon_mount_core(); - if (!mounted) { - printf("ERROR: Could not load core QOP package.\n"); + /* Find the cell shop at ~/.cell */ + int found = find_cell_shop(); + if (!found) { return 1; }