initial modules attempt
Some checks failed
Build and Deploy / build-macos (push) Failing after 7s
Build and Deploy / build-linux (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled

This commit is contained in:
2025-05-29 18:48:19 -05:00
parent f54200a7dd
commit 939269b060
18 changed files with 1114 additions and 12 deletions

100
scripts/build.js Normal file
View File

@@ -0,0 +1,100 @@
// cell build - Compile all modules in modules/ to build/
var io = use('io')
var shop = use('shop')
var js = use('js')
if (!io.exists('.cell/shop.toml')) {
log.error("No shop.toml found. Run 'cell init' first.")
$_.stop()
return
}
var config = shop.load_config()
if (!config || !config.dependencies) {
log.console("No dependencies to build")
$_.stop()
return
}
log.console("Building modules...")
// Process each dependency
for (var alias in config.dependencies) {
var version = config.dependencies[alias]
var parsed = shop.parse_locator(version)
var module_name = alias
if (parsed && parsed.version) {
module_name = alias + '@' + parsed.version
}
var source_dir = '.cell/modules/' + module_name
// Check if replaced with local path
if (config.replace && config.replace[version]) {
source_dir = config.replace[version]
log.console("Using local override for " + alias + ": " + source_dir)
}
if (!io.exists(source_dir)) {
log.console("Skipping " + alias + " - source not found at " + source_dir)
continue
}
var build_dir = '.cell/build/' + module_name
if (!io.exists(build_dir)) {
io.mkdir(build_dir)
}
// Apply patches if any
if (config.patches && config.patches[alias]) {
var patch_file = config.patches[alias]
if (io.exists(patch_file)) {
log.console("TODO: Apply patch " + patch_file + " to " + alias)
}
}
// Find and compile all .js files
var files = io.enumerate(source_dir, true) // recursive
var compiled_count = 0
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (file.endsWith('.js')) {
// Read source
var src_path = file
var src_content = io.slurp(src_path)
// Calculate relative path for output
var rel_path = file.substring(source_dir.length)
if (rel_path.startsWith('/')) rel_path = rel_path.substring(1)
var out_path = build_dir + '/' + rel_path + '.o'
// Ensure output directory exists
var out_dir = out_path.substring(0, out_path.lastIndexOf('/'))
if (!io.exists(out_dir)) {
io.mkdir(out_dir)
}
// Compile
var mod_name = rel_path.replace(/\.js$/, '').replace(/\//g, '_')
var wrapped = '(function ' + mod_name + '_module(arg){' + src_content + ';})'
try {
var compiled = js.compile(src_path, wrapped)
var blob = js.compile_blob(compiled)
io.slurpwrite(out_path, blob)
compiled_count++
} catch (e) {
log.error("Failed to compile " + src_path + ": " + e)
}
}
}
log.console("Built " + alias + ": " + compiled_count + " files compiled")
}
log.console("Build complete!")
$_.stop()

View File

@@ -95,7 +95,17 @@ if (!io.exists('.cell')) {
os.exit(1);
}
// Ensure .cell directory structure exists
if (!io.exists('.cell/modules')) {
io.mkdir('.cell/modules')
}
if (!io.exists('.cell/build')) {
io.mkdir('.cell/build')
}
// Mount directories
io.mount("scripts")
io.mount(".cell/build") // Mount build directory to search for compiled files
var RESPATH = 'scripts/resources.js'
var canonical = io.realdir(RESPATH) + 'resources.js'
@@ -186,16 +196,15 @@ globalThis.use = function use(file, ...args) {
inProgress[path] = true
loadingStack.push(file)
// Determine the compiled file path in .prosperon directory
var compiledPath = path + '.o'
// Determine the compiled file path in .cell/build directory
// Create a path that mirrors the source structure
var relPath = path.replace(/^scripts\//, '')
var compiledPath = '.cell/build/' + relPath + '.o'
// Ensure .prosperon directory exists
if (!io.exists('.prosperon')) {
io.mkdir('.prosperon')
}
// Check if compiled version exists and is newer than source
// Check if we should use a compiled version
var useCompiled = false
// First check if there's a compiled version in .cell/build
if (io.exists(compiledPath)) {
var srcStat = io.stat(path)
var compiledStat = io.stat(compiledPath)
@@ -204,6 +213,24 @@ globalThis.use = function use(file, ...args) {
}
}
// Also check if there's a .o file without full path (for direct lookups in mounted build dir)
var buildOnlyPath = '.cell/build/' + request_name + '.js.o'
if (!useCompiled && io.exists(buildOnlyPath)) {
// Check if it's newer than the source (if we have source)
if (path) {
var srcStat = io.stat(path)
var compiledStat = io.stat(buildOnlyPath)
if (compiledStat.modtime >= srcStat.modtime) {
useCompiled = true
compiledPath = buildOnlyPath
}
} else {
// No source file, just use the compiled version
useCompiled = true
compiledPath = buildOnlyPath
}
}
var fn
var mod_name = path.name()
@@ -218,8 +245,13 @@ globalThis.use = function use(file, ...args) {
var mod_script = `(function setup_${mod_name}_module(arg){${script};})`
fn = js.compile(path, mod_script)
// Save compiled version to .prosperon directory
// Save compiled version to .cell/build directory
var compiled = js.compile_blob(fn)
// Ensure parent directories exist
var compiledDir = compiledPath.substring(0, compiledPath.lastIndexOf('/'))
if (!io.exists(compiledDir)) {
io.mkdir(compiledDir)
}
io.slurpwrite(compiledPath, compiled)
fn = js.eval_compile(fn)
@@ -254,6 +286,14 @@ globalThis.use = function use(file, ...args) {
return ret
}
// Now that 'use' is defined, load and mount shop modules if shop.toml exists
if (io.exists('.cell/shop.toml')) {
var shop = use('shop')
if (shop) {
shop.mount()
}
}
globalThis.json = use('json')
var time = use('time')
@@ -729,7 +769,27 @@ function enet_check()
//enet_check();
// Finally, run the program
// First try to find the program as-is (e.g., "moth.js" or "moth")
var prog = resources.find_script(prosperon.args.program)
// If not found and doesn't have an extension, check if it's a directory
if (!prog && !prosperon.args.program.includes('.')) {
// Check if it's a directory
if (io.exists(prosperon.args.program) && io.is_directory(prosperon.args.program)) {
// Try directory/main.js
var dir_main = prosperon.args.program + '/main.js'
prog = resources.find_script(dir_main)
if (prog) {
// Update the program name to reflect what we're actually running
prosperon.args.program = dir_main
}
}
}
if (!prog) {
throw new Error(`Could not find program: ${prosperon.args.program}`)
}
prog = io.slurp(prog)
var prog_script = `(function ${prosperon.args.program.name()}($_) { ${prog} })`
var val = js.eval(prosperon.args.program, prog_script)($_)

70
scripts/get.js Normal file
View File

@@ -0,0 +1,70 @@
// cell get <locator> - Fetch a module and add it to dependencies
var io = use('io')
var shop = use('shop')
if (args.length < 1) {
log.console("Usage: cell get <locator>")
log.console("Example: cell get git.world/jj/mod@v0.6.3")
$_.stop()
return
}
var locator = args[0]
var parsed = shop.parse_locator(locator)
if (!parsed) {
log.error("Invalid locator format. Expected: host/owner/name@version")
$_.stop()
return
}
// Initialize shop if needed
if (!io.exists('.cell/shop.toml')) {
log.console("No shop.toml found. Initializing...")
shop.init()
}
// Load current config
var config = shop.load_config()
if (!config) {
log.error("Failed to load shop.toml")
$_.stop()
return
}
// Use the module name as the default alias
var alias = parsed.name
if (args.length > 1) {
alias = args[1]
}
// Check if already exists
if (config.dependencies && config.dependencies[alias]) {
log.console("Dependency '" + alias + "' already exists with version: " + config.dependencies[alias])
log.console("Use 'cell update " + alias + "' to change version")
$_.stop()
return
}
// Add to dependencies
log.console("Adding dependency: " + alias + " = " + locator)
shop.add_dependency(alias, locator)
// Create module directory
var module_dir = '.cell/modules/' + alias + '@' + parsed.version
if (!io.exists(module_dir)) {
io.mkdir(module_dir)
}
// TODO: Actually fetch the module from the repository
log.console("Module directory created at: " + module_dir)
log.console("TODO: Implement actual fetching from " + parsed.path)
log.console("")
log.console("For now, manually place module files in: " + module_dir)
log.console("Then run 'cell build' to compile modules")
// Update lock.toml
// TODO: Calculate and store checksums
$_.stop()

25
scripts/init.js Normal file
View File

@@ -0,0 +1,25 @@
// cell init - Initialize a new .cell program shop
var io = use('io')
var shop = use('shop')
// Initialize the .cell directory structure
log.console("Initializing .cell program shop...")
var success = shop.init()
if (success) {
log.console("Created .cell directory structure:")
log.console(" .cell/")
log.console(" ├── shop.toml (manifest)")
log.console(" ├── lock.toml (checksums)")
log.console(" ├── modules/ (vendored source)")
log.console(" ├── build/ (compiled modules)")
log.console(" └── patches/ (patches)")
log.console("")
log.console("Edit .cell/shop.toml to configure your project.")
} else {
log.error("Failed to initialize .cell directory")
}
$_.stop()

View File

@@ -0,0 +1,94 @@
// Module resolver for handling different import styles
// Works with the PhysFS mount system set up by shop.js
var ModuleResolver = {}
// Resolve module imports according to the specification
ModuleResolver.resolve = function(request, from_path) {
// Handle scheme-qualified imports
if (request.includes('://')) {
var parts = request.split('://')
var scheme = parts[0]
var path = parts[1]
// Direct mapping to mount points
return '/' + scheme + '/' + path
}
// Handle relative imports
if (request.startsWith('./') || request.startsWith('../')) {
// Relative imports are resolved from the importing module's directory
if (from_path) {
var dir = from_path.substring(0, from_path.lastIndexOf('/'))
return resolve_relative(dir, request)
}
return request
}
// Handle bare imports
// PhysFS will search through all mounted directories
// The mount order ensures proper precedence
return request
}
// Helper to resolve relative paths
function resolve_relative(base, relative) {
var parts = base.split('/')
var rel_parts = relative.split('/')
for (var i = 0; i < rel_parts.length; i++) {
var part = rel_parts[i]
if (part === '.') {
continue
} else if (part === '..') {
parts.pop()
} else {
parts.push(part)
}
}
return parts.join('/')
}
// Get the shop configuration if available
ModuleResolver.get_shop_config = function() {
try {
var shop = use('shop')
if (shop) {
return shop.load_config()
}
} catch (e) {
// Shop not available yet
}
return null
}
// Check if a bare import should be routed to an alias
ModuleResolver.check_alias = function(request) {
var config = ModuleResolver.get_shop_config()
if (!config) return null
var first_segment = request.split('/')[0]
// Check dependencies
if (config.dependencies && config.dependencies[first_segment]) {
return '/' + request
}
// Check aliases
if (config.aliases && config.aliases[first_segment]) {
var actual = config.aliases[first_segment]
return '/' + actual + request.substring(first_segment.length)
}
// Check for single-alias fallback
if (config.dependencies && Object.keys(config.dependencies).length === 1) {
// If only one dependency and no local file matches, route there
var only_dep = Object.keys(config.dependencies)[0]
return '/' + only_dep + '/' + request
}
return null
}
return ModuleResolver

69
scripts/patch.js Normal file
View File

@@ -0,0 +1,69 @@
// cell patch <module> - Create a patch for a module
var io = use('io')
var shop = use('shop')
if (args.length < 1) {
log.console("Usage: cell patch <module>")
log.console("Example: cell patch jj_mod")
log.console("")
log.console("This creates a patch file in .cell/patches/ that will be")
log.console("applied when building the module.")
$_.stop()
return
}
var module_name = args[0]
if (!io.exists('.cell/shop.toml')) {
log.error("No shop.toml found. Run 'cell init' first.")
$_.stop()
return
}
var config = shop.load_config()
if (!config || !config.dependencies || !config.dependencies[module_name]) {
log.error("Module '" + module_name + "' not found in dependencies")
$_.stop()
return
}
// Ensure patches directory exists
if (!io.exists('.cell/patches')) {
io.mkdir('.cell/patches')
}
var patch_file = '.cell/patches/' + module_name + '-fix.patch'
if (io.exists(patch_file)) {
log.console("Patch already exists: " + patch_file)
log.console("Edit it directly or delete it to create a new one.")
$_.stop()
return
}
// Create patch template
var patch_template = `# Patch for ${module_name}
#
# To create a patch:
# 1. Make a copy of the module: cp -r .cell/modules/${module_name}@* /tmp/${module_name}-orig
# 2. Edit files in .cell/modules/${module_name}@*
# 3. Generate patch: diff -ruN /tmp/${module_name}-orig .cell/modules/${module_name}@* > ${patch_file}
#
# This patch will be automatically applied during 'cell build'
`
io.slurpwrite(patch_file, patch_template)
// Add to shop.toml
if (!config.patches) {
config.patches = {}
}
config.patches[module_name] = patch_file
shop.save_config(config)
log.console("Created patch skeleton: " + patch_file)
log.console("Follow the instructions in the file to create your patch.")
log.console("The patch will be applied automatically during 'cell build'.")
$_.stop()

286
scripts/shop.js Normal file
View File

@@ -0,0 +1,286 @@
// Module shop system for managing dependencies and mods
var io = use('io')
var toml = use('toml')
var json = use('json')
var Shop = {}
// Load shop.toml configuration
Shop.load_config = function() {
var shop_path = '.cell/shop.toml'
if (!io.exists(shop_path)) {
return null
}
var content = io.slurp(shop_path)
return toml.parse(content)
}
// Save shop.toml configuration
Shop.save_config = function(config) {
// Simple TOML writer for our needs
var lines = []
// Top-level strings
if (config.module) lines.push('module = "' + config.module + '"')
if (config.engine) lines.push('engine = "' + config.engine + '"')
if (config.entrypoint) lines.push('entrypoint = "' + config.entrypoint + '"')
// Dependencies section
if (config.dependencies && Object.keys(config.dependencies).length > 0) {
lines.push('')
lines.push('[dependencies]')
for (var key in config.dependencies) {
lines.push(key + ' = "' + config.dependencies[key] + '"')
}
}
// Aliases section
if (config.aliases && Object.keys(config.aliases).length > 0) {
lines.push('')
lines.push('[aliases]')
for (var key in config.aliases) {
lines.push(key + ' = "' + config.aliases[key] + '"')
}
}
// Replace section
if (config.replace && Object.keys(config.replace).length > 0) {
lines.push('')
lines.push('[replace]')
for (var key in config.replace) {
lines.push('"' + key + '" = "' + config.replace[key] + '"')
}
}
// Patches section
if (config.patches && Object.keys(config.patches).length > 0) {
lines.push('')
lines.push('[patches]')
for (var key in config.patches) {
lines.push(key + ' = "' + config.patches[key] + '"')
}
}
// Mods section
if (config.mods && config.mods.enabled && config.mods.enabled.length > 0) {
lines.push('')
lines.push('[mods]')
lines.push('enabled = [')
for (var i = 0; i < config.mods.enabled.length; i++) {
lines.push(' "' + config.mods.enabled[i] + '",')
}
lines.push(']')
}
io.slurpwrite('.cell/shop.toml', lines.join('\n'))
}
// Initialize .cell directory structure
Shop.init = function() {
if (!io.exists('.cell')) {
io.mkdir('.cell')
}
if (!io.exists('.cell/modules')) {
io.mkdir('.cell/modules')
}
if (!io.exists('.cell/build')) {
io.mkdir('.cell/build')
}
if (!io.exists('.cell/patches')) {
io.mkdir('.cell/patches')
}
if (!io.exists('.cell/shop.toml')) {
var default_config = {
module: "my-game",
engine: "mist/prosperon@v0.9.3",
entrypoint: "main.js",
dependencies: {},
aliases: {},
replace: {},
patches: {},
mods: {
enabled: []
}
}
Shop.save_config(default_config)
}
if (!io.exists('.cell/lock.toml')) {
io.slurpwrite('.cell/lock.toml', '# Lock file for module integrity\n')
}
return true
}
// Mount modules according to the specification
Shop.mount = function() {
var config = Shop.load_config()
if (!config) {
log.error("No shop.toml found")
return false
}
// 1. Mount mods first (highest priority, prepend=1)
if (config.mods && config.mods.enabled) {
for (var i = 0; i < config.mods.enabled.length; i++) {
var mod_path = config.mods.enabled[i]
if (io.exists(mod_path)) {
io.mount(mod_path, "/", true) // prepend=true
log.console("Mounted mod: " + mod_path)
}
}
}
// 2. Self is already mounted (project root)
// This happens in prosperon.c
// 3. Mount aliases (dependencies)
if (config.dependencies) {
for (var alias in config.dependencies) {
var version = config.dependencies[alias]
var parsed = Shop.parse_locator(version)
var module_name = alias
if (parsed && parsed.version) {
module_name = alias + '@' + parsed.version
}
// Check if replaced with local path
var mount_path = '.cell/modules/' + module_name
if (config.replace && config.replace[version]) {
mount_path = config.replace[version]
}
// Try compiled version first
var compiled_path = '.cell/build/' + module_name
if (io.exists(compiled_path)) {
io.mount(compiled_path, alias, false) // Mount at alias name
log.console("Mounted compiled: " + alias + " at /" + alias + " from " + compiled_path)
} else if (io.exists(mount_path)) {
io.mount(mount_path, alias, false) // Mount at alias name
log.console("Mounted source: " + alias + " at /" + alias + " from " + mount_path)
}
// Also handle short aliases
if (config.aliases) {
for (var short_alias in config.aliases) {
if (config.aliases[short_alias] === alias) {
if (io.exists(compiled_path)) {
io.mount(compiled_path, short_alias, false)
log.console("Mounted alias: " + short_alias + " -> " + alias)
} else if (io.exists(mount_path)) {
io.mount(mount_path, short_alias, false)
log.console("Mounted alias: " + short_alias + " -> " + alias)
}
}
}
}
}
}
// 4. Mount compiled modules directory
if (io.exists('.cell/build')) {
io.mount('.cell/build', "modules", false)
log.console("Mounted compiled modules at /modules")
}
// 5. Mount source modules directory
if (io.exists('.cell/modules')) {
io.mount('.cell/modules', "modules-src", false)
log.console("Mounted source modules at /modules-src")
}
// 6. Mount core if available
if (io.exists('.cell/modules/core')) {
io.mount('.cell/modules/core', "core", false)
log.console("Mounted core at /core")
}
// 6. Core is already mounted in prosperon.c
return true
}
// Parse module locator (e.g., "git.world/jj/mod@v0.6.3")
Shop.parse_locator = function(locator) {
var parts = locator.split('@')
if (parts.length !== 2) {
return null
}
return {
path: parts[0],
version: parts[1],
name: parts[0].split('/').pop()
}
}
// Add a dependency
Shop.add_dependency = function(alias, locator) {
var config = Shop.load_config()
if (!config) {
log.error("No shop.toml found")
return false
}
if (!config.dependencies) {
config.dependencies = {}
}
config.dependencies[alias] = locator
Shop.save_config(config)
return true
}
// Get the module directory for a given alias
Shop.get_module_dir = function(alias) {
var config = Shop.load_config()
if (!config || !config.dependencies || !config.dependencies[alias]) {
return null
}
var version = config.dependencies[alias]
var module_name = alias + '@' + version.split('@')[1]
// Check if replaced
if (config.replace && config.replace[version]) {
return config.replace[version]
}
return '.cell/modules/' + module_name
}
// Compile a module
Shop.compile_module = function(alias) {
var module_dir = Shop.get_module_dir(alias)
if (!module_dir) {
log.error("Module not found: " + alias)
return false
}
// TODO: Implement actual compilation
// For now, just copy .js files to .cell/build with .o extension
log.console("Would compile module: " + alias + " from " + module_dir)
return true
}
// Build all modules
Shop.build = function() {
var config = Shop.load_config()
if (!config || !config.dependencies) {
return true
}
for (var alias in config.dependencies) {
Shop.compile_module(alias)
}
return true
}
return Shop

75
scripts/test_modules.js Normal file
View File

@@ -0,0 +1,75 @@
// Test script for the module system
var io = use('io')
var shop = use('shop')
log.console("=== Testing Module System ===")
// Test 1: TOML parser
log.console("\n1. Testing TOML parser...")
var toml = use('toml')
var test_toml = `
module = "test"
version = "1.0.0"
[dependencies]
foo = "bar@1.0"
baz = "qux@2.0"
[arrays]
items = ["one", "two", "three"]
`
var parsed = toml.parse(test_toml)
log.console("Parsed module: " + parsed.module)
log.console("Dependencies: " + json.encode(parsed.dependencies))
log.console("✓ TOML parser working")
// Test 2: Shop initialization
log.console("\n2. Testing shop initialization...")
var test_dir = "module_test_" + Date.now()
io.mkdir(test_dir)
var old_cwd = io.basedir()
// Create a test shop
io.writepath(test_dir)
shop.init()
if (io.exists('.cell/shop.toml')) {
log.console("✓ Shop initialized successfully")
} else {
log.console("✗ Shop initialization failed")
}
// Test 3: Module resolution
log.console("\n3. Testing module resolver...")
var resolver = use('module_resolver')
var tests = [
{input: "core://time", expected: "/core/time"},
{input: "mod://utils", expected: "/mod/utils"},
{input: "./helper", expected: "./helper"},
{input: "sprite", expected: "sprite"}
]
for (var i = 0; i < tests.length; i++) {
var test = tests[i]
var result = resolver.resolve(test.input)
if (result === test.expected) {
log.console("✓ " + test.input + " -> " + result)
} else {
log.console("✗ " + test.input + " -> " + result + " (expected " + test.expected + ")")
}
}
// Clean up
io.writepath(old_cwd)
io.rm(test_dir + '/.cell/shop.toml')
io.rm(test_dir + '/.cell/lock.toml')
io.rm(test_dir + '/.cell/patches')
io.rm(test_dir + '/.cell/build')
io.rm(test_dir + '/.cell/modules')
io.rm(test_dir + '/.cell')
io.rm(test_dir)
log.console("\n=== Module System Test Complete ===")
$_.stop()

105
scripts/toml.js Normal file
View File

@@ -0,0 +1,105 @@
// Simple TOML parser for shop.toml
// Supports basic TOML features needed for the module system
function parse_toml(text) {
var lines = text.split('\n')
var result = {}
var current_section = result
var current_section_name = ''
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim()
// Skip empty lines and comments
if (!line || line.startsWith('#')) continue
// Section header
if (line.startsWith('[') && line.endsWith(']')) {
var section_path = line.slice(1, -1).split('.')
current_section = result
current_section_name = section_path.join('.')
for (var j = 0; j < section_path.length; j++) {
var key = section_path[j]
if (!current_section[key]) {
current_section[key] = {}
}
current_section = current_section[key]
}
continue
}
// Key-value pair
var eq_index = line.indexOf('=')
if (eq_index > 0) {
var key = line.substring(0, eq_index).trim()
var value = line.substring(eq_index + 1).trim()
// Parse value
if (value.startsWith('"') && value.endsWith('"')) {
// String
current_section[key] = value.slice(1, -1)
} else if (value.startsWith('[') && value.endsWith(']')) {
// Array
current_section[key] = parse_array(value)
} else if (value === 'true' || value === 'false') {
// Boolean
current_section[key] = value === 'true'
} else if (!isNaN(Number(value))) {
// Number
current_section[key] = Number(value)
} else {
// Unquoted string
current_section[key] = value
}
}
}
return result
}
function parse_array(str) {
// Remove brackets
str = str.slice(1, -1).trim()
if (!str) return []
var items = []
var current = ''
var in_quotes = false
for (var i = 0; i < str.length; i++) {
var char = str[i]
if (char === '"' && (i === 0 || str[i-1] !== '\\')) {
in_quotes = !in_quotes
current += char
} else if (char === ',' && !in_quotes) {
items.push(parse_value(current.trim()))
current = ''
} else {
current += char
}
}
if (current.trim()) {
items.push(parse_value(current.trim()))
}
return items
}
function parse_value(str) {
if (str.startsWith('"') && str.endsWith('"')) {
return str.slice(1, -1)
} else if (str === 'true' || str === 'false') {
return str === 'true'
} else if (!isNaN(Number(str))) {
return Number(str)
} else {
return str
}
}
return {
parse: parse_toml
}

51
scripts/update.js Normal file
View File

@@ -0,0 +1,51 @@
// cell update <alias> - Update a dependency to a new version
var io = use('io')
var shop = use('shop')
if (args.length < 1) {
log.console("Usage: cell update <alias> [new-version]")
log.console("Example: cell update jj_mod v0.7.0")
$_.stop()
return
}
var alias = args[0]
if (!io.exists('.cell/shop.toml')) {
log.error("No shop.toml found. Run 'cell init' first.")
$_.stop()
return
}
var config = shop.load_config()
if (!config || !config.dependencies || !config.dependencies[alias]) {
log.error("Dependency '" + alias + "' not found")
$_.stop()
return
}
var current_version = config.dependencies[alias]
log.console("Current version: " + current_version)
if (args.length > 1) {
// Update to specific version
var new_version = args[1]
// Parse the current locator to keep the host/path
var parsed = shop.parse_locator(current_version)
if (parsed) {
var new_locator = parsed.path + '@' + new_version
config.dependencies[alias] = new_locator
shop.save_config(config)
log.console("Updated " + alias + " to " + new_locator)
log.console("Run 'cell get " + new_locator + "' to fetch the new version")
}
} else {
// TODO: Check for latest version
log.console("TODO: Check for latest version of " + alias)
log.console("For now, specify version: cell update " + alias + " <version>")
}
$_.stop()

47
scripts/vendor.js Normal file
View File

@@ -0,0 +1,47 @@
// cell vendor - Copy all dependencies into modules/ for hermetic builds
var io = use('io')
var shop = use('shop')
if (!io.exists('.cell/shop.toml')) {
log.error("No shop.toml found. Run 'cell init' first.")
$_.stop()
return
}
var config = shop.load_config()
if (!config || !config.dependencies) {
log.console("No dependencies to vendor")
$_.stop()
return
}
log.console("Vendoring dependencies...")
for (var alias in config.dependencies) {
var locator = config.dependencies[alias]
var parsed = shop.parse_locator(locator)
if (!parsed) {
log.error("Invalid locator: " + locator)
continue
}
var module_dir = '.cell/modules/' + alias + '@' + parsed.version
if (config.replace && config.replace[locator]) {
// Already using local path
log.console(alias + " - using local path: " + config.replace[locator])
} else if (!io.exists(module_dir)) {
log.console(alias + " - not found at " + module_dir)
log.console(" Run 'cell get " + locator + "' to fetch it")
} else {
log.console(alias + " - already vendored at " + module_dir)
}
}
log.console("")
log.console("All dependencies are vendored in .cell/modules/")
log.console("This ensures hermetic, reproducible builds.")
$_.stop()

View File

@@ -191,8 +191,19 @@ JSC_SCALL(io_slurpwrite,
END:
)
JSC_SSCALL(io_mount,
if (!PHYSFS_mount(str,str2,0)) ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", str, str2, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
JSC_CCALL(io_mount,
const char *src = JS_ToCString(js, argv[0]);
const char *mountpoint = JS_ToCString(js, argv[1]);
int prepend = 0;
if (argc > 2 && !JS_IsUndefined(argv[2]))
prepend = JS_ToBool(js, argv[2]);
if (!PHYSFS_mount(src, mountpoint, prepend))
ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", src, mountpoint, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
JS_FreeCString(js, src);
JS_FreeCString(js, mountpoint);
)
JSC_SCALL(io_unmount,
@@ -347,7 +358,7 @@ static const JSCFunctionListEntry js_io_funcs[] = {
MIST_FUNC_DEF(io, globfs, 2),
MIST_FUNC_DEF(io, match, 2),
MIST_FUNC_DEF(io, exists, 1),
MIST_FUNC_DEF(io, mount, 2),
MIST_FUNC_DEF(io, mount, 3),
MIST_FUNC_DEF(io,unmount,1),
MIST_FUNC_DEF(io,slurp,1),
MIST_FUNC_DEF(io,slurpbytes,1),

View File

@@ -0,0 +1,17 @@
// Main entry point for jj_mod
var utils = use("./utils")
log.console("jj_mod loaded! Version 0.6.3")
return {
utils: utils,
version: "0.6.3",
create_thing: function(name) {
return {
name: name,
id: utils.random_range(1000, 9999)
}
}
}

View File

@@ -0,0 +1,15 @@
// Example module file for jj_mod
function format_number(n) {
return n.toFixed(2)
}
function random_range(min, max) {
return Math.random() * (max - min) + min
}
return {
format_number: format_number,
random_range: random_range,
PI: 3.14159
}

21
test_shop/.cell/shop.toml Normal file
View File

@@ -0,0 +1,21 @@
module = "test-shop"
engine = "mist/prosperon@v0.9.3"
entrypoint = "main.js"
[dependencies]
jj_mod = "git.world/jj/mod@v0.6.3"
prosperon_extras = "git.world/mist/prosperon-extras@v1.0.0"
[aliases]
mod = "jj_mod"
extras = "prosperon_extras"
[replace]
# For local development
# "git.world/jj/mod@v0.6.3" = "../local-jj-mod"
[patches]
# jj_mod = "patches/jj_mod-fix.patch"
[mods]
enabled = []

15
test_shop/helper.js Normal file
View File

@@ -0,0 +1,15 @@
// Helper module for testing relative imports
function greet(name) {
log.console("Hello, " + name + "!")
}
function calculate(a, b) {
return a + b
}
return {
greet: greet,
calculate: calculate,
version: "1.0.0"
}

41
test_shop/main.js Normal file
View File

@@ -0,0 +1,41 @@
// Example main.js that uses the module system
log.console("=== Module System Test ===")
// Test bare imports
try {
var sprite = use("sprite")
log.console("✓ Loaded sprite from bare import")
} catch (e) {
log.console("✗ Failed to load sprite: " + e)
}
// Test relative imports
try {
var helper = use("./helper")
log.console("✓ Loaded helper from relative import")
helper.greet("Module System")
} catch (e) {
log.console("✗ Failed to load helper: " + e)
}
// Test scheme-qualified imports
try {
var core_time = use("core://time")
log.console("✓ Loaded time from core:// scheme")
} catch (e) {
log.console("✗ Failed to load core://time: " + e)
}
// Test aliased module (if configured in shop.toml)
try {
var mod = use("mod/utils")
log.console("✓ Loaded mod/utils from aliased module")
} catch (e) {
log.console("✗ Failed to load mod/utils: " + e)
}
log.console("")
log.console("Test complete!")
$_.stop()