env capabilities
This commit is contained in:
parent
266da9fd23
commit
31a3625873
@ -186,6 +186,10 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip
|
|||||||
luaCtx := runner.NewHTTPContext(ctx)
|
luaCtx := runner.NewHTTPContext(ctx)
|
||||||
defer luaCtx.Release()
|
defer luaCtx.Release()
|
||||||
|
|
||||||
|
if runner.GetGlobalEnvManager() != nil {
|
||||||
|
luaCtx.Set("env", runner.GetGlobalEnvManager().GetAll())
|
||||||
|
}
|
||||||
|
|
||||||
sessionMap := s.ctxPool.Get().(map[string]any)
|
sessionMap := s.ctxPool.Get().(map[string]any)
|
||||||
defer func() {
|
defer func() {
|
||||||
for k := range sessionMap {
|
for k := range sessionMap {
|
||||||
|
9
main.go
9
main.go
@ -252,6 +252,10 @@ func (s *Moonshark) Shutdown() error {
|
|||||||
s.LuaRunner.Close()
|
s.LuaRunner.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := runner.CleanupEnv(); err != nil {
|
||||||
|
logger.Warning("Environment cleanup failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Info("Shutdown complete")
|
logger.Info("Shutdown complete")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -302,6 +306,11 @@ func initRunner(s *Moonshark) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize environment manager
|
||||||
|
if err := runner.InitEnv(s.Config.Dirs.Data); err != nil {
|
||||||
|
logger.Warning("Environment initialization failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
sessionManager := sessions.GlobalSessionManager
|
sessionManager := sessions.GlobalSessionManager
|
||||||
sessionManager.SetCookieOptions(
|
sessionManager.SetCookieOptions(
|
||||||
"MoonsharkSID",
|
"MoonsharkSID",
|
||||||
|
@ -30,7 +30,8 @@ var contextPool = sync.Pool{
|
|||||||
|
|
||||||
// NewContext creates a new context, potentially reusing one from the pool
|
// NewContext creates a new context, potentially reusing one from the pool
|
||||||
func NewContext() *Context {
|
func NewContext() *Context {
|
||||||
return contextPool.Get().(*Context)
|
ctx := contextPool.Get().(*Context)
|
||||||
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPContext creates a new context from a fasthttp RequestCtx
|
// NewHTTPContext creates a new context from a fasthttp RequestCtx
|
||||||
|
@ -40,6 +40,9 @@ var timeLuaCode string
|
|||||||
//go:embed lua/math.lua
|
//go:embed lua/math.lua
|
||||||
var mathLuaCode string
|
var mathLuaCode string
|
||||||
|
|
||||||
|
//go:embed lua/env.lua
|
||||||
|
var envLuaCode string
|
||||||
|
|
||||||
// ModuleInfo holds information about an embeddable Lua module
|
// ModuleInfo holds information about an embeddable Lua module
|
||||||
type ModuleInfo struct {
|
type ModuleInfo struct {
|
||||||
Name string // Module name
|
Name string // Module name
|
||||||
@ -61,6 +64,7 @@ var (
|
|||||||
{Name: "crypto", Code: cryptoLuaCode, DefinesGlobal: true},
|
{Name: "crypto", Code: cryptoLuaCode, DefinesGlobal: true},
|
||||||
{Name: "time", Code: timeLuaCode},
|
{Name: "time", Code: timeLuaCode},
|
||||||
{Name: "math", Code: mathLuaCode},
|
{Name: "math", Code: mathLuaCode},
|
||||||
|
{Name: "env", Code: envLuaCode, DefinesGlobal: true},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
278
runner/env.go
Normal file
278
runner/env.go
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"Moonshark/utils/color"
|
||||||
|
"Moonshark/utils/logger"
|
||||||
|
|
||||||
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvManager handles loading, storing, and saving environment variables
|
||||||
|
type EnvManager struct {
|
||||||
|
envPath string // Path to .env file
|
||||||
|
vars map[string]any // Environment variables in memory
|
||||||
|
mu sync.RWMutex // Thread-safe access
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global environment manager instance
|
||||||
|
var globalEnvManager *EnvManager
|
||||||
|
|
||||||
|
// InitEnv initializes the environment manager with the given data directory
|
||||||
|
func InitEnv(dataDir string) error {
|
||||||
|
if dataDir == "" {
|
||||||
|
return fmt.Errorf("data directory cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create data directory if it doesn't exist
|
||||||
|
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create data directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envPath := filepath.Join(dataDir, ".env")
|
||||||
|
|
||||||
|
globalEnvManager = &EnvManager{
|
||||||
|
envPath: envPath,
|
||||||
|
vars: make(map[string]any),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load existing .env file if it exists
|
||||||
|
if err := globalEnvManager.load(); err != nil {
|
||||||
|
logger.Warning("Failed to load .env file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := len(globalEnvManager.vars)
|
||||||
|
if count > 0 {
|
||||||
|
logger.Info("Environment loaded: %s vars from %s",
|
||||||
|
color.Apply(fmt.Sprintf("%d", count), color.Yellow),
|
||||||
|
color.Apply(envPath, color.Yellow))
|
||||||
|
} else {
|
||||||
|
logger.Info("Environment initialized: %s", color.Apply(envPath, color.Yellow))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalEnvManager returns the global environment manager instance
|
||||||
|
func GetGlobalEnvManager() *EnvManager {
|
||||||
|
return globalEnvManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// load reads the .env file and populates the vars map
|
||||||
|
func (e *EnvManager) load() error {
|
||||||
|
file, err := os.Open(e.envPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// File doesn't exist, start with empty env
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open .env file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
lineNum := 0
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
lineNum++
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// Skip empty lines and comments
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse key=value
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
logger.Warning("Invalid .env line %d: %s", lineNum, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
// Remove quotes if present
|
||||||
|
if len(value) >= 2 {
|
||||||
|
if (strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"")) ||
|
||||||
|
(strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'")) {
|
||||||
|
value = value[1 : len(value)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.vars[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save writes the current environment variables to the .env file
|
||||||
|
func (e *EnvManager) Save() error {
|
||||||
|
if e == nil {
|
||||||
|
return nil // No env manager initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.RLock()
|
||||||
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
|
file, err := os.Create(e.envPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create .env file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Sort keys for consistent output
|
||||||
|
keys := make([]string, 0, len(e.vars))
|
||||||
|
for key := range e.vars {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
// Write header comment
|
||||||
|
fmt.Fprintln(file, "# Environment variables for Moonshark")
|
||||||
|
fmt.Fprintln(file, "# Generated automatically - you can edit this file")
|
||||||
|
fmt.Fprintln(file)
|
||||||
|
|
||||||
|
// Write each variable
|
||||||
|
for _, key := range keys {
|
||||||
|
value := e.vars[key]
|
||||||
|
|
||||||
|
// Convert value to string
|
||||||
|
var strValue string
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
strValue = v
|
||||||
|
case nil:
|
||||||
|
continue // Skip nil values
|
||||||
|
default:
|
||||||
|
strValue = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quote values that contain spaces or special characters
|
||||||
|
if strings.ContainsAny(strValue, " \t\n\r\"'\\") {
|
||||||
|
strValue = fmt.Sprintf("\"%s\"", strings.ReplaceAll(strValue, "\"", "\\\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(file, "%s=%s\n", key, strValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Environment saved: %d vars to %s", len(e.vars), e.envPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves an environment variable
|
||||||
|
func (e *EnvManager) Get(key string) (any, bool) {
|
||||||
|
if e == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.RLock()
|
||||||
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
|
value, exists := e.vars[key]
|
||||||
|
return value, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set stores an environment variable
|
||||||
|
func (e *EnvManager) Set(key string, value any) {
|
||||||
|
if e == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
e.vars[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns a copy of all environment variables
|
||||||
|
func (e *EnvManager) GetAll() map[string]any {
|
||||||
|
if e == nil {
|
||||||
|
return make(map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.RLock()
|
||||||
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
|
result := make(map[string]any, len(e.vars))
|
||||||
|
for k, v := range e.vars {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupEnv saves the environment and cleans up resources
|
||||||
|
func CleanupEnv() error {
|
||||||
|
if globalEnvManager != nil {
|
||||||
|
return globalEnvManager.Save()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// envGet Lua function to get an environment variable
|
||||||
|
func envGet(state *luajit.State) int {
|
||||||
|
if !state.IsString(1) {
|
||||||
|
state.PushNil()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
key := state.ToString(1)
|
||||||
|
if value, exists := globalEnvManager.Get(key); exists {
|
||||||
|
if err := state.PushValue(value); err != nil {
|
||||||
|
state.PushNil()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.PushNil()
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// envSet Lua function to set an environment variable
|
||||||
|
func envSet(state *luajit.State) int {
|
||||||
|
if !state.IsString(1) || !state.IsString(2) {
|
||||||
|
state.PushBoolean(false)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
key := state.ToString(1)
|
||||||
|
value := state.ToString(2)
|
||||||
|
|
||||||
|
globalEnvManager.Set(key, value)
|
||||||
|
state.PushBoolean(true)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// envGetAll Lua function to get all environment variables
|
||||||
|
func envGetAll(state *luajit.State) int {
|
||||||
|
vars := globalEnvManager.GetAll()
|
||||||
|
|
||||||
|
if err := state.PushTable(vars); err != nil {
|
||||||
|
state.PushNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterEnvFunctions registers environment functions with the Lua state
|
||||||
|
func RegisterEnvFunctions(state *luajit.State) error {
|
||||||
|
if err := state.RegisterGoFunction("__env_get", envGet); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.RegisterGoFunction("__env_set", envSet); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := state.RegisterGoFunction("__env_get_all", envGetAll); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
93
runner/lua/env.lua
Normal file
93
runner/lua/env.lua
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
-- Environment variable module for Moonshark
|
||||||
|
-- Provides access to persistent environment variables stored in .env file
|
||||||
|
|
||||||
|
-- Get an environment variable with a default value
|
||||||
|
-- Returns the value if it exists, default_value otherwise
|
||||||
|
function env_get(key, default_value)
|
||||||
|
if type(key) ~= "string" then
|
||||||
|
error("env_get: key must be a string")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- First check context for environment variables (no Go call needed)
|
||||||
|
if _env and _env[key] ~= nil then
|
||||||
|
return _env[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
return default_value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set an environment variable
|
||||||
|
-- Returns true on success, false on failure
|
||||||
|
function env_set(key, value)
|
||||||
|
if type(key) ~= "string" then
|
||||||
|
error("env_set: key must be a string")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update context immediately for future reads
|
||||||
|
if not _env then
|
||||||
|
_env = {}
|
||||||
|
end
|
||||||
|
_env[key] = value
|
||||||
|
|
||||||
|
-- Persist to Go backend
|
||||||
|
return __env_set(key, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get all environment variables as a table
|
||||||
|
-- Returns a table with all key-value pairs
|
||||||
|
function env_get_all()
|
||||||
|
-- Return context table directly if available
|
||||||
|
if _env then
|
||||||
|
local copy = {}
|
||||||
|
for k, v in pairs(_env) do
|
||||||
|
copy[k] = v
|
||||||
|
end
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fallback to Go call
|
||||||
|
return __env_get_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if an environment variable exists
|
||||||
|
-- Returns true if the variable exists, false otherwise
|
||||||
|
function env_exists(key)
|
||||||
|
if type(key) ~= "string" then
|
||||||
|
error("env_exists: key must be a string")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check context first
|
||||||
|
if _env then
|
||||||
|
return _env[key] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set multiple environment variables from a table
|
||||||
|
-- Returns true on success, false if any setting failed
|
||||||
|
function env_set_many(vars)
|
||||||
|
if type(vars) ~= "table" then
|
||||||
|
error("env_set_many: vars must be a table")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _env then
|
||||||
|
_env = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local success = true
|
||||||
|
for key, value in pairs(vars) do
|
||||||
|
if type(key) == "string" and type(value) == "string" then
|
||||||
|
-- Update context
|
||||||
|
_env[key] = value
|
||||||
|
-- Persist to Go
|
||||||
|
if not __env_set(key, value) then
|
||||||
|
success = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error("env_set_many: all keys and values must be strings")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return success
|
||||||
|
end
|
@ -22,6 +22,10 @@ function __create_env(ctx)
|
|||||||
|
|
||||||
if ctx then
|
if ctx then
|
||||||
env.ctx = ctx
|
env.ctx = ctx
|
||||||
|
|
||||||
|
if ctx._env then
|
||||||
|
env._env = ctx._env
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if __setup_require then
|
if __setup_require then
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -71,15 +72,11 @@ func (l *ModuleLoader) SetupRequire(state *luajit.State) error {
|
|||||||
err := state.DoString(`
|
err := state.DoString(`
|
||||||
-- Initialize global module registry
|
-- Initialize global module registry
|
||||||
__module_paths = {}
|
__module_paths = {}
|
||||||
|
|
||||||
-- Setup fast module loading system
|
|
||||||
__module_bytecode = {}
|
__module_bytecode = {}
|
||||||
|
__ready_modules = {}
|
||||||
|
|
||||||
-- Create module preload table
|
-- Create module preload table
|
||||||
package.preload = package.preload or {}
|
package.preload = package.preload or {}
|
||||||
|
|
||||||
-- Setup module state registry
|
|
||||||
__ready_modules = {}
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -209,7 +206,7 @@ func (l *ModuleLoader) PreloadModules(state *luajit.State) error {
|
|||||||
// Cache bytecode
|
// Cache bytecode
|
||||||
l.bytecodeCache[modName] = bytecode
|
l.bytecodeCache[modName] = bytecode
|
||||||
|
|
||||||
// Register in Lua
|
// Register in Lua - store path info
|
||||||
escapedPath := escapeLuaString(path)
|
escapedPath := escapeLuaString(path)
|
||||||
escapedName := escapeLuaString(modName)
|
escapedName := escapeLuaString(modName)
|
||||||
|
|
||||||
@ -217,25 +214,26 @@ func (l *ModuleLoader) PreloadModules(state *luajit.State) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load bytecode into Lua state
|
// Load bytecode and register in package.preload properly
|
||||||
if err := state.LoadBytecode(bytecode, path); err != nil {
|
if err := state.LoadBytecode(bytecode, path); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to package.preload
|
// Store the function in package.preload - the function is on the stack
|
||||||
luaCode := `
|
state.GetGlobal("package")
|
||||||
local modname = "` + escapedName + `"
|
state.GetField(-1, "preload")
|
||||||
local chunk = ...
|
state.PushString(modName)
|
||||||
package.preload[modname] = chunk
|
state.PushCopy(-4) // Copy the compiled function
|
||||||
__ready_modules[modname] = true
|
state.SetTable(-3) // preload[modName] = function
|
||||||
`
|
state.Pop(2) // Pop package and preload tables
|
||||||
|
|
||||||
if err := state.DoString(luaCode); err != nil {
|
// Mark as ready
|
||||||
state.Pop(1) // Remove chunk from stack
|
if err := state.DoString(`__ready_modules["` + escapedName + `"] = true`); err != nil {
|
||||||
|
state.Pop(1) // Remove the function from stack
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Pop(1) // Remove chunk from stack
|
state.Pop(1) // Remove the function from stack
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -318,24 +316,28 @@ func (l *ModuleLoader) GetModuleByPath(path string) (string, bool) {
|
|||||||
l.mu.RLock()
|
l.mu.RLock()
|
||||||
defer l.mu.RUnlock()
|
defer l.mu.RUnlock()
|
||||||
|
|
||||||
// Clean path for proper comparison
|
// Convert to absolute path for consistent comparison
|
||||||
path = filepath.Clean(path)
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
absPath = filepath.Clean(path)
|
||||||
|
}
|
||||||
|
|
||||||
// Try direct lookup from cache
|
// Try direct lookup from cache with absolute path
|
||||||
for modName, modPath := range l.pathCache {
|
for modName, modPath := range l.pathCache {
|
||||||
if modPath == path {
|
if modPath == absPath {
|
||||||
return modName, true
|
return modName, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find by relative path from lib dirs
|
// Try to construct module name from lib dirs
|
||||||
for _, dir := range l.config.LibDirs {
|
for _, dir := range l.config.LibDirs {
|
||||||
absDir, err := filepath.Abs(dir)
|
absDir, err := filepath.Abs(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath, err := filepath.Rel(absDir, path)
|
// Check if the file is under this lib directory
|
||||||
|
relPath, err := filepath.Rel(absDir, absPath)
|
||||||
if err != nil || strings.HasPrefix(relPath, "..") {
|
if err != nil || strings.HasPrefix(relPath, "..") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -343,13 +345,78 @@ func (l *ModuleLoader) GetModuleByPath(path string) (string, bool) {
|
|||||||
if strings.HasSuffix(relPath, ".lua") {
|
if strings.HasSuffix(relPath, ".lua") {
|
||||||
modName := strings.TrimSuffix(relPath, ".lua")
|
modName := strings.TrimSuffix(relPath, ".lua")
|
||||||
modName = strings.ReplaceAll(modName, string(filepath.Separator), ".")
|
modName = strings.ReplaceAll(modName, string(filepath.Separator), ".")
|
||||||
|
|
||||||
|
l.debugLog("Found module %s for path %s", modName, path)
|
||||||
return modName, true
|
return modName, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.debugLog("No module found for path %s", path)
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefreshModule recompiles and updates a specific module
|
||||||
|
func (l *ModuleLoader) RefreshModule(state *luajit.State, moduleName string) error {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
// Get module path
|
||||||
|
path, exists := l.pathCache[moduleName]
|
||||||
|
if !exists {
|
||||||
|
l.debugLog("Module not found in cache: %s", moduleName)
|
||||||
|
return fmt.Errorf("module %s not found", moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.debugLog("Refreshing module: %s at %s", moduleName, path)
|
||||||
|
|
||||||
|
// Read updated file content
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read module file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recompile to bytecode
|
||||||
|
bytecode, err := state.CompileBytecode(string(content), path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to compile module: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update bytecode cache
|
||||||
|
l.bytecodeCache[moduleName] = bytecode
|
||||||
|
|
||||||
|
// Load new bytecode
|
||||||
|
if err := state.LoadBytecode(bytecode, path); err != nil {
|
||||||
|
return fmt.Errorf("failed to load bytecode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update package.preload with new function (function is on stack)
|
||||||
|
state.GetGlobal("package")
|
||||||
|
state.GetField(-1, "preload")
|
||||||
|
state.PushString(moduleName)
|
||||||
|
state.PushCopy(-4) // Copy the new compiled function
|
||||||
|
state.SetTable(-3) // preload[moduleName] = new_function
|
||||||
|
state.Pop(2) // Pop package and preload tables
|
||||||
|
state.Pop(1) // Pop the function
|
||||||
|
|
||||||
|
// Clear from package.loaded so it gets reloaded
|
||||||
|
escapedName := escapeLuaString(moduleName)
|
||||||
|
if err := state.DoString(`package.loaded["` + escapedName + `"] = nil`); err != nil {
|
||||||
|
return fmt.Errorf("failed to clear loaded module: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.debugLog("Successfully refreshed module: %s", moduleName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshModuleByPath refreshes a module by its file path
|
||||||
|
func (l *ModuleLoader) RefreshModuleByPath(state *luajit.State, filePath string) error {
|
||||||
|
moduleName, exists := l.GetModuleByPath(filePath)
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("no module found for path: %s", filePath)
|
||||||
|
}
|
||||||
|
return l.RefreshModule(state, moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
// escapeLuaString escapes special characters in a string for Lua
|
// escapeLuaString escapes special characters in a string for Lua
|
||||||
func escapeLuaString(s string) string {
|
func escapeLuaString(s string) string {
|
||||||
replacer := strings.NewReplacer(
|
replacer := strings.NewReplacer(
|
||||||
|
@ -384,8 +384,7 @@ waitForInUse:
|
|||||||
|
|
||||||
// NotifyFileChanged alerts the runner about file changes
|
// NotifyFileChanged alerts the runner about file changes
|
||||||
func (r *Runner) NotifyFileChanged(filePath string) bool {
|
func (r *Runner) NotifyFileChanged(filePath string) bool {
|
||||||
logger.Debug("Runner has been notified of a file change...")
|
logger.Debug("Runner notified of file change: %s", filePath)
|
||||||
logger.Debug("%s", filePath)
|
|
||||||
|
|
||||||
module, isModule := r.moduleLoader.GetModuleByPath(filePath)
|
module, isModule := r.moduleLoader.GetModuleByPath(filePath)
|
||||||
if isModule {
|
if isModule {
|
||||||
@ -393,7 +392,7 @@ func (r *Runner) NotifyFileChanged(filePath string) bool {
|
|||||||
return r.RefreshModule(module)
|
return r.RefreshModule(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("File change noted but no state refresh needed: %s", filePath)
|
logger.Debug("File change noted but no refresh needed: %s", filePath)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,10 +413,41 @@ func (r *Runner) RefreshModule(moduleName string) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate module in Lua
|
// Use the enhanced module loader refresh
|
||||||
if err := state.L.DoString(`package.loaded["` + moduleName + `"] = nil`); err != nil {
|
if err := r.moduleLoader.RefreshModule(state.L, moduleName); err != nil {
|
||||||
success = false
|
success = false
|
||||||
logger.Debug("Failed to invalidate module %s: %v", moduleName, err)
|
logger.Debug("Failed to refresh module %s in state %d: %v", moduleName, state.index, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
logger.Debug("Successfully refreshed module: %s", moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshModuleByPath refreshes a module by its file path
|
||||||
|
func (r *Runner) RefreshModuleByPath(filePath string) bool {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
if !r.isRunning.Load() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Refreshing module by path: %s", filePath)
|
||||||
|
|
||||||
|
success := true
|
||||||
|
for _, state := range r.states {
|
||||||
|
if state == nil || state.inUse.Load() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the enhanced module loader refresh by path
|
||||||
|
if err := r.moduleLoader.RefreshModuleByPath(state.L, filePath); err != nil {
|
||||||
|
success = false
|
||||||
|
logger.Debug("Failed to refresh module at %s in state %d: %v", filePath, state.index, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +121,10 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := RegisterEnvFunctions(state); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user