global cell shops

This commit is contained in:
2025-12-08 13:01:44 -06:00
parent 1bcdab64ff
commit 401f69b503
18 changed files with 849 additions and 301 deletions

View File

@@ -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

View File

@@ -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]

View File

@@ -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 <dir>/cell.toml (package root), not <dir>/.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 {}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 <program.ce> [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]

View File

@@ -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);

30
scripts/install.ce Normal file
View File

@@ -0,0 +1,30 @@
// cell install <locator> - 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 <locator>")
$_.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()

View File

@@ -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 <name>")
@@ -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 <name>: list dependencies of <name>")
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) {

View File

@@ -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);
}

View File

@@ -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)
// 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)
// Get all packages and find which one matches this path
var packages = Shop.list_packages()
var matched_pkg = null
var matched_path = null
// With the new structure, everything in packages/ is a package
// gitea.pockle.world/user/repo/file.cm
for (var i = 0; i < packages.length; i++) {
var pkg = packages[i]
var parsed = Shop.parse_package(pkg)
var pkg_path = parsed.path
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
}
}
}
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)
}
info.name = import_part
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 {
// 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)
}
info.name = import_part
}
info.name = "" // root?
}
} else {
info.package = 'local'
// fallback
info.package = rest
}
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)
}
} else {
// 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
}
}
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 <modules_dir>/<module>/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:<module path>
// <package>:<module path> (package is the full canonical package name)
// local:<module path>
// package:<module path> (fallback when pkg isn't provided but path is under .cell/modules)
// package:<module path> (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<files.length; i++) {
var file = files[i]
var src_path
if (!package) src_path = file
else src_path = '.cell/modules/' + package + '/' + file
if (!package) {
src_path = (current_package_path || '.') + '/' + file
} else if (package.startsWith('/')) {
// Local absolute package
src_path = package + '/' + file
} else {
// Regular package
src_path = get_modules_dir() + '/' + package + '/' + file
}
if (file.endsWith('.cm') || file.endsWith('.ce')) {
// Compile module
@@ -1477,10 +1750,10 @@ Shop.build_package = function(package)
var temp_lib = 'cellmod' + dylib_ext
var objs_str = ''
for (var i=0; i<c_objects.length; i++) {
objs_str += '"$HERE/' + c_objects[i] + '" '
objs_str += '"' + c_objects[i] + '" '
}
var link_cmd = 'HERE=$(pwd); cd ' + module_dir + ' && cc ' + link_flags + ' ' + objs_str + ' -lcell_runtime -lc -lc++ -o ' + temp_lib
var link_cmd = 'cd ' + module_dir + ' && cc ' + link_flags + ' ' + objs_str + ' -lcell_runtime -lc -lc++ -o ' + temp_lib
var link_cmd_hash = get_hash(link_cmd)
// Check if we need to relink
@@ -1596,7 +1869,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
// If we're in a package context, check the package first
if (package_name) {
var pkg_path = '.cell/modules/' + package_name + '/' + module_name + '.cm'
var pkg_path = get_modules_dir() + '/' + package_name + '/' + module_name + '.cm'
if (is_file_fn(pkg_path)) {
return { path: pkg_path, package_name: package_name }
}
@@ -1615,20 +1888,22 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
var parsed = Shop.parse_package(pkg)
var canonical_path = parsed.path
var dep_path = '.cell/modules/' + canonical_path + '/' + sub_module + '.cm'
var dep_path = get_modules_dir() + '/' + canonical_path + '/' + sub_module + '.cm'
if (is_file_fn(dep_path)) {
return { path: dep_path, package_name: pkg_alias }
}
}
// Check local path (relative to project root)
var local_path = module_name + '.cm'
// Check local path (relative to current package)
var local_base = current_package_path || '.'
var local_path = local_base + '/' + module_name + '.cm'
if (is_file_fn(local_path)) {
return { path: local_path, package_name: null }
}
} else {
// Simple module name - check local first, then dependencies
var local_path = module_name + '.cm'
var local_base = current_package_path || '.'
var local_path = local_base + '/' + module_name + '.cm'
if (is_file_fn(local_path)) {
return { path: local_path, package_name: null }
}
@@ -1639,7 +1914,7 @@ Shop.resolve_module = function(module_name, package_name, is_file_fn) {
var parsed = Shop.parse_package(pkg)
var canonical_path = parsed.path
var dep_path = '.cell/modules/' + canonical_path + '/' + module_name + '.cm'
var dep_path = get_modules_dir() + '/' + canonical_path + '/' + module_name + '.cm'
if (is_file_fn(dep_path)) {
return { path: dep_path, package_name: alias }
}
@@ -1664,4 +1939,82 @@ Shop.resolve_alias = function(name, ctx) {
}
Shop.get_canonical_package = get_canonical_package
Shop.upgrade_core = function() {
var pkg = "gitea.pockle.world/john/cell"
var hash = fetch_remote_hash(pkg)
if (!hash) {
// Fallback to master?
log.console("Could not fetch remote hash for core, assuming master.")
hash = "master"
}
var zip = get_or_download_zip(pkg, hash)
if (!zip) return false
var core_dir = Shop.get_core_dir()
install_zip(zip, core_dir)
var lock = Shop.load_lock()
lock['core'] = {
package: pkg,
commit: hash,
updated: time.number()
}
Shop.save_lock(lock)
log.console("Core updated to " + hash)
return true
}
Shop.link_core = function(target) {
if (!fd.is_dir(target)) return false
var core_dir = Shop.get_core_dir()
if (fd.is_link(core_dir)) fd.unlink(core_dir)
else if (fd.is_dir(core_dir)) {
// Safety check?
// For now, we assume user knows what they are doing.
// We can't easily rm recursive if it's a real dir unless we use system rm or implement it.
// shop.cm has rm_recursive? No, I implemented it?
// I saw `function rm_recursive` at line 1141. internal.
rm_recursive(core_dir)
}
fd.symlink(fd.realpath(target), core_dir)
var lock = Shop.load_lock()
lock['core'] = {
package: fd.realpath(target),
type: 'local',
updated: time.number()
}
Shop.save_lock(lock)
return true
}
Shop.unlink_core = function() {
var core_dir = Shop.get_core_dir()
if (fd.is_link(core_dir)) {
fd.unlink(core_dir)
return true
}
return false
}
Shop.is_core_linked = function() {
var core_dir = Shop.get_core_dir()
return fd.is_link(core_dir)
}
Shop.list_shop_packages = function() {
var lock = Shop.load_lock()
var list = []
for (var k in lock) {
if (lock[k]) list.push(lock[k])
}
return list
}
return Shop

View File

@@ -58,8 +58,8 @@ function ensure_dir(path) {
// Collect .ce actor tests from a package
function collect_actor_tests(pkg) {
var prefix = pkg ? `.cell/modules/${pkg}` : "."
var tests_dir = `${prefix}/tests`
var prefix = pkg ? shop.get_shop_path() + '/modules/' + pkg : (shop.get_current_package() || '.')
var tests_dir = prefix + '/tests'
if (!fd.is_dir(tests_dir)) return []
@@ -110,8 +110,8 @@ function spawn_actor_test(test_info) {
}
function run_tests(pkg) {
var prefix = pkg ? `.cell/modules/${pkg}` : "."
var tests_dir = `${prefix}/tests`
var prefix = pkg ? shop.get_shop_path() + '/modules/' + pkg : (shop.get_current_package() || '.')
var tests_dir = prefix + '/tests'
var pkg_result = {
package: pkg || "local",
@@ -448,7 +448,7 @@ if (all_actor_tests.length == 0) {
// Generate Reports function
function generate_reports(totals) {
var timestamp = Math.floor(time.number()).toString()
var report_dir = `.cell/reports/test_${timestamp}`
var report_dir = shop.get_reports_dir() + '/test_' + timestamp
ensure_dir(report_dir)
// Generate test.txt

View File

@@ -1,4 +1,4 @@
// Simple TOML parser for shop.toml
// Simple TOML parser for cell modules
// Supports basic TOML features needed for the module system
function parse_toml(text) {
@@ -16,8 +16,9 @@ function parse_toml(text) {
// Section header
if (line.startsWith('[') && line.endsWith(']')) {
var section_path = line.slice(1, -1).split('.')
var section_path = parse_key_path(line.slice(1, -1))
current_section = result
// Reconstruct name for debugging/legacy (not strictly needed for object construction)
current_section_name = section_path.join('.')
for (var j = 0; j < section_path.length; j++) {
@@ -33,9 +34,13 @@ function parse_toml(text) {
// Key-value pair
var eq_index = line.indexOf('=')
if (eq_index > 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))
}
}

View File

@@ -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 info of packages) {
var pack = info.package
if (!pack || pack == 'core') continue
for (var pack of packages) {
log.console("Updating " + pack)
shop.update(pack)
shop.build_package(pack)

37
scripts/upgrade.ce Normal file
View File

@@ -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 <core_dir>")
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()

3
scripts/version.ce Normal file
View File

@@ -0,0 +1,3 @@
var shop = use('shop')
log.console("0.1.0")
$_.stop()

View File

@@ -11,101 +11,98 @@
#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 <signal.h>
#include <unistd.h>
#include <sys/stat.h>
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);
// 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);
unsigned char *buf = malloc(file_size);
if (!buf) {
printf("ERROR: Could not allocate memory for executable\n");
char *data = malloc(file_size + 1);
if (!data) {
fclose(fh);
return 0;
return NULL;
}
if (fread(buf, 1, file_size, fh) != (size_t)file_size) {
printf("ERROR: Could not read executable\n");
free(buf);
if (fread(data, 1, file_size, fh) != (size_t)file_size) {
free(data);
fclose(fh);
return 0;
return NULL;
}
fclose(fh);
data[file_size] = 0;
// 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;
if (out_size) *out_size = file_size;
return data;
}
// 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);
return 0;
}
return 1;
// Get the core path for use by scripts
const char* cell_get_core_path(void) {
return core_path;
}
void actor_disrupt(cell_rt *crt)
@@ -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;
}