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
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:
100
scripts/build.js
Normal file
100
scripts/build.js
Normal 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()
|
||||
@@ -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
70
scripts/get.js
Normal 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
25
scripts/init.js
Normal 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()
|
||||
94
scripts/module_resolver.js
Normal file
94
scripts/module_resolver.js
Normal 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
69
scripts/patch.js
Normal 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
286
scripts/shop.js
Normal 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
75
scripts/test_modules.js
Normal 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
105
scripts/toml.js
Normal 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
51
scripts/update.js
Normal 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
47
scripts/vendor.js
Normal 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()
|
||||
@@ -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),
|
||||
|
||||
17
test_shop/.cell/modules/jj_mod@v0.6.3/main.js
Normal file
17
test_shop/.cell/modules/jj_mod@v0.6.3/main.js
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
15
test_shop/.cell/modules/jj_mod@v0.6.3/utils.js
Normal file
15
test_shop/.cell/modules/jj_mod@v0.6.3/utils.js
Normal 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
21
test_shop/.cell/shop.toml
Normal 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
15
test_shop/helper.js
Normal 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
41
test_shop/main.js
Normal 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()
|
||||
Reference in New Issue
Block a user