diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index d7bfcf7f..917d309e 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -34,6 +34,17 @@ jobs: zip -r ../ScriptableMC-JavaScript-Lib.zip ./* cd ../ cp ./ScriptableMC-JavaScript-Lib.zip ../../ScriptableMC-Engine-JS/src/main/resources/libraries.zip + - name: Export lib-smc with gradle + run: ./gradlew :ScriptableMC-Tools-TS:generateLibSMC + - name: Compile lib-smc, create archive, and publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd ./ScriptableMC-Tools-TS/lib-smc/ + npm install + npm run compile + npm publish ./js + zip -r ./Lib-SMC.zip ./* - name: Build All Plugins with Gradle run: ./gradlew :shadowJarAll - name: Copy artifacts @@ -45,6 +56,7 @@ jobs: cp ./build/ScriptableMC-Engine-PY-Bundled.jar ./artifacts/ cp ./ScriptableMC-Tools-TS/lib/ScriptableMC-TypeScript-Lib.zip ./artifacts/ cp ./ScriptableMC-Tools-TS/lib/ScriptableMC-JavaScript-Lib.zip ./artifacts/ + cp ./ScriptableMC-Tools-TS/lib-smc/Lib-SMC.zip ./artifacts/ - uses: actions/upload-artifact@v1 with: name: ScriptableMC-Engine-DEV diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6dbf2a2..20a98845 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,18 @@ jobs: zip -r ../ScriptableMC-JavaScript-Lib.zip ./* cd ../ cp ./ScriptableMC-JavaScript-Lib.zip ../../ScriptableMC-Engine-JS/src/main/resources/libraries.zip + - name: Export lib-smc with gradle + run: ./gradlew :ScriptableMC-Tools-TS:generateLibSMCRelease + - name: Compile lib-smc, create archive, and publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + cd ./ScriptableMC-Tools-TS/lib-smc/ + npm install + npm run compile + npm publish ./js --access public + zip -r ./Lib-SMC.zip ./* - name: Build JS Engine with Gradle run: ./gradlew :shadowJarAll - name: Upload JavaScript Engine Plugin Jar @@ -49,13 +61,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - args: 'ScriptableMC-Engine-JS-Bundled/build/libs/ScriptableMC-Engine-JS-Bundled.jar' + args: 'ScriptableMC-Engine-JS/Bundled/build/libs/ScriptableMC-Engine-JS-Bundled.jar' - name: Upload Bundled Python Engine Plugin Jar uses: skx/github-action-publish-binaries@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - args: 'ScriptableMC-Engine-PY-Bundled/build/libs/ScriptableMC-Engine-PY-Bundled.jar' + args: 'ScriptableMC-Engine-PY/Bundled/build/libs/ScriptableMC-Engine-PY-Bundled.jar' - name: Upload TypeScript Libraries uses: skx/github-action-publish-binaries@master env: diff --git a/.gitignore b/.gitignore index 929cb5f1..e19ebaef 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ node_modules/ .rpt2_cache/ libraries/tools/kotlin-test-nodejs-runner/lib/ local.properties -lib \ No newline at end of file +lib +lib-smc \ No newline at end of file diff --git a/README.md b/README.md index edd5de50..69f12399 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/astorks/ScriptableMC-Engine?label=ScriptableMC&style=for-the-badge)](https://github.com/astorks/ScriptableMC-Engine/releases/latest) [![SpigotMC Resource](https://img.shields.io/badge/SpigotMC-Resource-yellow?style=for-the-badge)](https://www.spigotmc.org/resources/scriptablemc-engine.74690/) +![License](https://img.shields.io/github/license/astorks/ScriptableMC-Engine?style=for-the-badge) Run JavaScript/TypeScript plugins for Minecraft 1.15 using the GraalJS script engine.
Supported Minecraft Versions: Bukkit/Spigot/Paper 1.12, 1.13, 1.14, **1.15**
@@ -15,25 +16,28 @@ Most linux servers already use OpenJDK, however if you're running windows or wan GraalVM-CE is a free open source java runtime created by Oracle that is pre-packaged with the GraalJS Engine allowing all javascript engine features including AOT compilation of javascript. -## Installing The Plugin -- Ensure your server is running OpenJDK 8+ or GraalVM -- [Download the latest ScriptableMC plugin](https://github.com/astorks/ScriptableMC-Engine/releases/latest) and place it in your plugins folder. -- Take a look at [ScriptableMC-TypeScript](https://github.com/astorks/ScriptableMC-TypeScript) for a full typescript plugin example. +## Installing The JavaScript Engine Plugin +##### If your server is running on a Standard JDK +- Download the latest [`ScriptableMC-Engine-JS-Bundled.jar`](https://github.com/astorks/ScriptableMC-Engine/releases/latest/download/ScriptableMC-Engine-JS-Bundled.jar) and place it in your plugins folder. +##### If your server is running on a GraalVM JDK +- Download the latest [`ScriptableMC-Engine-JS.jar`](https://github.com/astorks/ScriptableMC-Engine/releases/latest/download/ScriptableMC-Engine-JS.jar) and place it in your plugins folder. + +Take a look at [ScriptableMC-TypeScript](https://github.com/astorks/ScriptableMC-TypeScript) for a full typescript plugin example. You can take the typescript example and compile it, then directly modify the javascript if you don't want to use typescript. ## Commands and Permissions ##### ScritableMC Base Command | Command | Alias | Description | Permission | | ------------- | ------------- |-----------------------------------------------------| ------------------------ | -| `/scriptablemc menu` | `/smc m` | Opens an inventory menu that allows you to control the scriptable engine. | `scriptablemc.menu` | -| `/scriptablemc info` | `/smc i` | Prints plugin version and GraalVM/GraalJS versions if available. | `scriptablemc.info` | -| `/scriptablemc reload` | `/smc rl` | Fully reloads all script engines. | `scriptablemc.reload` | +| `/scriptablemc menu` | `/smc m` | Opens an inventory menu that allows you to control the script engines. | `scriptablemc.menu` | +| `/scriptablemc info` | `/smc i` | Prints plugin version and all loaded script engine versions if available. | `scriptablemc.info` | +| `/scriptablemc reload` | `/smc rl` | Fully reloads all script engines and all script files. | `scriptablemc.reload` | | `/scriptablemc version` | `/smc v` | Check the github releases for any updates. | `scriptablemc.version` | -##### ScritableMC JavaScript Sub Command +##### ScritableMC JavaScript Engine Commands | Command | Alias | Description | Permission | | ------------- | ------------- |-----------------------------------------------------| ------------------------ | -| `/scriptablemc reload` | `/smc rl /jsrl` | Fully reloads the script engine and all script files. | `scriptablemc.reload` | +| `/scriptablemc javascript reload` | `/smc rl /jsrl` | Fully reloads the javascript engine and all script files. | `scriptablemc.js.reload` | | `/scriptablemc javascript execute ` | `/smc js ex ` | Executes javascript source and prints the return value. | `scriptablemc.js.execute` | | `/scriptablemc javascript execute stash` | `/smc js ex stash` | Executes javascript source stored in your stash. | `scriptablemc.js.execute` | | `/scriptablemc javascript file ` | `/smc js f ` | Executes javascript file from the scripts folder. | `scriptablemc.js.execute.file` | @@ -41,6 +45,17 @@ You can take the typescript example and compile it, then directly modify the jav | `/scriptablemc javascript stash ` | `/smc js st ` | Adds a line to your javascript stash. | `scriptablemc.js.execute` | | `/scriptablemc javascript stash clear` | `/smc js st clear` | Clears your javascript stash. | `scriptablemc.js.execute` | +##### ScritableMC Python Engine Commands +| Command | Alias | Description | Permission | +| ------------- | ------------- |-----------------------------------------------------| ------------------------ | +| `/scriptablemc python reload` | `/smc rl /pyrl` | Fully reloads the python script engine and all script files. | `scriptablemc.python.reload` | +| `/scriptablemc python execute ` | `/smc py ex /pyex` | Executes python source and prints the return value. | `scriptablemc.python.execute` | +| `/scriptablemc python execute stash` | `/smc py ex stash` | Executes python source stored in your stash. | `scriptablemc.python.execute` | +| `/scriptablemc python file ` | `/smc py f ` | Executes python file from the scripts folder. | `scriptablemc.python.execute.file` | +| `/scriptablemc python stash` | `/smc py st` | Prints all stored python lines in your stash. | `scriptablemc.python.execute` | +| `/scriptablemc python stash ` | `/smc py st ` | Adds a line to your python stash. | `scriptablemc.python.execute` | +| `/scriptablemc python stash clear` | `/smc py st clear` | Clears your python stash. | `scriptablemc.python.execute` | + ### `/smc javascript execute` Command Example > `/jsex return 1 + 1`
> ![2](https://i.imgur.com/1MkiDxW.png) @@ -54,4 +69,5 @@ https://github.com/MinusKube/SmartInvs - Inventory helper library
https://github.com/lucko/helper - MinecraftVersions helper library
https://github.com/jkcclemens/khttp - HTTP helper library
https://github.com/aikar/commands - Built-in commands and auto-completion
+https://github.com/apache/commons-io - FileUtils helper library
https://github.com/graalvm/graaljs - JavaScript engine diff --git a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/ScriptEngineConfig.kt b/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/ScriptEngineConfig.kt new file mode 100644 index 00000000..c3fad357 --- /dev/null +++ b/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/ScriptEngineConfig.kt @@ -0,0 +1,70 @@ +package com.pixlfox.scriptablemc + +import org.bukkit.configuration.file.FileConfiguration + +abstract class ScriptEngineConfig(private val config: FileConfiguration) { + val rootScriptsFolder: String + get() = readConfigString("root_scripts_folder", "./scripts") + + abstract val mainScriptFiles: List + abstract val executeCommandTemplate: String + abstract val scriptMimeType: String + + val autoEnablePlugins: Boolean + get() = readConfigBoolean("auto_enable_plugins", true) + + val debug: Boolean + get() = readConfigBoolean("debug", false) + + val extractLibs: Boolean + get() = readConfigBoolean("extract_libs", true) + + val debugger: ScriptEngineDebuggerConfig + get() = ScriptEngineDebuggerConfig(this) + + @JvmOverloads + fun readConfigString(path: String, def: String = ""): String { + val input = config.getString(path, def).orEmpty() + val regex = Regex("\\\$\\{(.*)}") + + return regex.replace(input) { + val configValue = readConfigString(it.groups[1]!!.value) + configValue + } + } + + @JvmOverloads + fun readConfigStringList(path: String, def: List = listOf()): List { + val inputList = config.getStringList(path) + val regex = Regex("\\\$\\{(.*)}") + + if(inputList.isEmpty()) { + inputList.addAll(def) + } + + for((index, input) in inputList.withIndex()) { + inputList[index] = regex.replace(input) { + val configValue = readConfigString(it.groups[1]!!.value) + configValue + } + } + + return inputList + } + + @JvmOverloads + fun readConfigBoolean(path: String, def: Boolean = false): Boolean { + return readConfigString(path, def.toString()).equals("true", true) + } +} + +class ScriptEngineDebuggerConfig(private val config: ScriptEngineConfig) { + val enabled: Boolean + get() = config.readConfigBoolean("debugger.enabled", false) + + val address: String + get() = config.readConfigString("debugger.address", "127.0.0.1:9229") + + val waitAttached: Boolean + get() = config.readConfigBoolean("debugger.wait_attached", true) +} \ No newline at end of file diff --git a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginContext.kt b/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginContext.kt index 47220277..1e0ff017 100644 --- a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginContext.kt +++ b/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginContext.kt @@ -29,6 +29,9 @@ abstract class ScriptablePluginContext: Listener { abstract val inventoryManager: InventoryManager abstract val pluginVersion: Version + var isEnabled: Boolean = false + internal set + val server: Server get() = Bukkit.getServer() diff --git a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginEngine.kt b/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginEngine.kt index 9796e1be..9c8ea70e 100644 --- a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginEngine.kt +++ b/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/core/ScriptablePluginEngine.kt @@ -1,14 +1,20 @@ package com.pixlfox.scriptablemc.core +import com.pixlfox.scriptablemc.ScriptEngineConfig import com.pixlfox.scriptablemc.ScriptEngineMain +import com.smc.exceptions.ScriptNotFoundException import com.smc.version.Version import fr.minuskube.inv.InventoryManager +import org.bukkit.Bukkit import org.bukkit.command.CommandSender import org.graalvm.polyglot.* import java.io.File +import java.util.* @Suppress("MemberVisibilityCanBePrivate", "unused") abstract class ScriptablePluginEngine { + abstract val languageName: String + abstract val languageFileExtension: String abstract val bootstrapPlugin: ScriptEngineMain abstract val debugEnabled: Boolean @@ -18,16 +24,44 @@ abstract class ScriptablePluginEngine { abstract val graalContext: Context abstract val globalBindings: Value + abstract val config: ScriptEngineConfig + val pluginVersion: Version get() = bootstrapPlugin.pluginVersion - abstract fun start() - abstract fun close() + var enabledAllPlugins: Boolean = false + internal set + + val startupErrors: MutableList = mutableListOf() + + open fun start() { + inventoryManager.init() + globalBindings.putMember("engine", this) + + loadAllHelperClasses() + + for(mainScriptFile in config.mainScriptFiles) { + loadMainScript(mainScriptFile) + } + + if(!enabledAllPlugins && config.autoEnablePlugins) { + enableAllPlugins() + } + } + + open fun close() { + for(scriptablePlugin in scriptablePlugins) { + scriptablePlugin.disable() + } + scriptablePlugins.clear() + + graalContext.close(true) + } - fun loadAllHelperClasses() { + open fun loadAllHelperClasses() { for(helperClass in preLoadClasses) { try { - javaClass.classLoader.loadClass(helperClass.replace("*", "")) + javaClass.classLoader.loadClass(if(helperClass.startsWith("*")) helperClass.substring(1) else helperClass) } catch (e: Exception) { if(!helperClass.startsWith("*")) { @@ -38,15 +72,109 @@ abstract class ScriptablePluginEngine { } } - abstract fun evalFile(filePath: String): Value - abstract fun evalFile(scriptFile: File): Value - abstract fun eval(source: String): Value - abstract fun evalCommandSender(source: String, sender: CommandSender): Value - abstract fun eval(source: Source): Value + open fun enableAllPlugins() { + for (pluginContext in scriptablePlugins.filter { !it.isEnabled }) { + enablePlugin(pluginContext) + } + enabledAllPlugins = true + } + + open fun disableAllPlugins() { + for (pluginContext in scriptablePlugins.filter { it.isEnabled }) { + disablePlugin(pluginContext) + } + enabledAllPlugins = false + } + + open fun enablePlugin(pluginContext: ScriptablePluginContext) { + if(!pluginContext.isEnabled) { + pluginContext.enable() + } + else { + bootstrapPlugin.logger.warning("Trying to enable an already-enabled scriptable plugin context.") + } + } + + open fun disablePlugin(pluginContext: ScriptablePluginContext) { + if(pluginContext.isEnabled) { + pluginContext.disable() + } + else { + bootstrapPlugin.logger.warning("Trying to disabled an already-disabled scriptable plugin context.") + } + } + + open fun eval(source: Source): Value { + return graalContext.eval(source) + } + + open fun evalCommandSender(source: String, sender: CommandSender): Value { + val tempScriptFile = File("${config.rootScriptsFolder}/${UUID.randomUUID()}.$languageFileExtension") + try { + tempScriptFile.writeText(config.executeCommandTemplate.replace("%SOURCE%", source)) + + var evalReturn = evalFile(tempScriptFile) + + if(evalReturn.canInstantiate()) { + evalReturn = evalReturn.newInstance() + } + + if(evalReturn.hasMember("execute") && evalReturn.canInvokeMember("execute")) { + evalReturn.putMember("sender", sender) + evalReturn.putMember("server", Bukkit.getServer()) + evalReturn.putMember("servicesManager", Bukkit.getServicesManager()) + return evalReturn.invokeMember("execute", sender, Bukkit.getServer(), Bukkit.getServicesManager()) + } + + return evalReturn + } + finally { + tempScriptFile.delete() + } + } + + open fun evalFile(filePath: String): Value { + val scriptFile = File("${config.rootScriptsFolder}/$filePath") + + return if(scriptFile.exists()) { + eval( + Source.newBuilder(languageName, scriptFile) + .name(scriptFile.name) + .mimeType(config.scriptMimeType) + .interactive(false) + .build() + ) + } else { + throw ScriptNotFoundException(scriptFile) + } + } + + open fun evalFile(scriptFile: File): Value { + return if(scriptFile.exists()) { + eval( + Source.newBuilder(languageName, scriptFile) + .name(scriptFile.name) + .mimeType(config.scriptMimeType) + .interactive(false) + .build() + ) + } else { + throw ScriptNotFoundException(scriptFile) + } + } + + open fun eval(source: String): Value { + return graalContext.eval( + Source.newBuilder(languageName, source,"${UUID.randomUUID()}.$languageFileExtension") + .mimeType(config.scriptMimeType) + .interactive(false) + .cached(false) + .build() + ) + } + + abstract fun loadMainScript(path: String) abstract fun loadPlugin(scriptableClass: Value): ScriptablePluginContext - abstract fun enableAllPlugins() - abstract fun enablePlugin(pluginContext: ScriptablePluginContext) - abstract fun disablePlugin(pluginContext: ScriptablePluginContext) companion object { val preLoadClasses: Array = arrayOf( diff --git a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/exceptions/ScriptNotFoundException.kt b/ScriptableMC-Engine-Core/src/main/kotlin/com/smc/exceptions/ScriptNotFoundException.kt similarity index 50% rename from ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/exceptions/ScriptNotFoundException.kt rename to ScriptableMC-Engine-Core/src/main/kotlin/com/smc/exceptions/ScriptNotFoundException.kt index b49568bb..9487a94e 100644 --- a/ScriptableMC-Engine-Core/src/main/kotlin/com/pixlfox/scriptablemc/exceptions/ScriptNotFoundException.kt +++ b/ScriptableMC-Engine-Core/src/main/kotlin/com/smc/exceptions/ScriptNotFoundException.kt @@ -1,5 +1,5 @@ -package com.pixlfox.scriptablemc.exceptions +package com.smc.exceptions import java.io.File -class ScriptNotFoundException(scriptFile: File) : Exception("Unable to load script: ${scriptFile.path}.") \ No newline at end of file +class ScriptNotFoundException(scriptFile: File) : Exception("Unable to load script: ${scriptFile.absolutePath}.") \ No newline at end of file diff --git a/ScriptableMC-Engine-JS-Bundled/build.gradle.kts b/ScriptableMC-Engine-JS/Bundled/build.gradle.kts similarity index 100% rename from ScriptableMC-Engine-JS-Bundled/build.gradle.kts rename to ScriptableMC-Engine-JS/Bundled/build.gradle.kts diff --git a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptConfig.kt b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptConfig.kt index 7abdadc3..d4b2f1bb 100644 --- a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptConfig.kt +++ b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptConfig.kt @@ -2,27 +2,27 @@ package com.pixlfox.scriptablemc import org.bukkit.configuration.file.FileConfiguration -class SMCJavaScriptConfig(private val config: FileConfiguration) { - val rootScriptsFolder: String - get() = config.getString("root_scripts_folder", "./scripts").orEmpty() +class SMCJavaScriptConfig(config: FileConfiguration) : ScriptEngineConfig(config) { + override val mainScriptFiles: List + get() = readConfigStringList("main_script_files", listOf("\${root_scripts_folder}/main.js")) - val debug: Boolean - get() = config.getBoolean("debug", false) + override val executeCommandTemplate: String + get() = readConfigString("execute_command_template", "import * as lib from './lib/global.js';\n" + + "new (class EvalCommandSenderContext {\n" + + " execute(sender, server, servicesManager) {\n" + + " %SOURCE%\n" + + " }\n" + + "})()") - val extractLibs: Boolean - get() = config.getBoolean("extract_libs", true) + override val scriptMimeType: String + get() = readConfigString("script_mime_type", "application/javascript+module") - val debugger: SMCJavaScriptDebuggerConfig - get() = SMCJavaScriptDebuggerConfig(config) -} + val commonJsModulesEnabled: Boolean + get() = readConfigBoolean("common_js.modules_enabled", true) -class SMCJavaScriptDebuggerConfig(private val config: FileConfiguration) { - val enabled: Boolean - get() = config.getBoolean("debugger.enabled", false) + val commonJsModulesPath: String + get() = readConfigString("common_js.modules_path", "\${root_scripts_folder}/node_modules") - val address: String - get() = config.getString("debugger.address", "127.0.0.1:9229").orEmpty() - - val waitAttached: Boolean - get() = config.getBoolean("debugger.wait_attached", true) + val commonJsGlobalsFile: String + get() = readConfigString("common_js.globals_file", "globals.js") } \ No newline at end of file diff --git a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptEngineMain.kt b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptEngineMain.kt index 8fb0c127..4521c402 100644 --- a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptEngineMain.kt +++ b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/SMCJavaScriptEngineMain.kt @@ -53,47 +53,49 @@ class SMCJavaScriptEngineMain : ScriptEngineMain() { try { scriptEngine = JavaScriptPluginEngine(this, SMCJavaScriptConfig(config)) scriptEngine!!.start() - logger.info("JavaScript engine started.") - sender?.sendMessage("$chatMessagePrefix ${ChatColor.GREEN}JavaScript engine started.") - } catch (e: IllegalStateException) { - if (e.message?.contains("Make sure the truffle-api.jar is on the classpath.", true) == true) { - logger.warning("JavaScript engine failed to start.") - e.printStackTrace() - logger.severe("Unable to find truffle-api.jar. This shouldn't happen since it's now packaged inside the plugin, you might want to check for an updated ScriptableMC Engine release.") - logger.severe("ScriptableMC Engine Version: $pluginVersion") - logger.severe("ScriptableMC-Engine Download Page: https://github.com/astorks/ScriptableMC-Engine/releases/latest") - - sender?.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}JavaScript engine failed to start. Check the server console.") - } else { - logger.warning("JavaScript engine failed to start.") - e.printStackTrace() + if(scriptEngine!!.startupErrors.any()) { + for(error in scriptEngine!!.startupErrors) { + error.printStackTrace() + if(sender != null) { + sender.sendMessage("$chatMessagePrefix ${ChatColor.RED}$error") + for (stackTrace in error.stackTrace.filter { it.fileName?.endsWith(".js") == true }) { + sender.sendMessage("${ChatColor.RED}$stackTrace") + } + } + } - sender?.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}JavaScript engine failed to start. Check the server console.") + logger.warning("JavaScript engine started with errors.") + sender?.sendMessage("$chatMessagePrefix ${ChatColor.YELLOW}JavaScript engine started with errors.") } - } catch (e: Exception) { - logger.warning("JavaScript engine failed to start.") - e.printStackTrace() - + else { + logger.info("JavaScript engine started.") + sender?.sendMessage("$chatMessagePrefix ${ChatColor.GREEN}JavaScript engine started.") + } + } + catch (error: Exception) { + error.printStackTrace() if(sender != null) { - sender.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}$e") - for (stackTrace in e.stackTrace) { - sender.sendMessage("${ChatColor.DARK_RED}$stackTrace") + sender.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}$error") + for (stackTrace in error.stackTrace) { + sender.sendMessage("${ChatColor.RED}$stackTrace") } } + + logger.severe("JavaScript engine failed to start.") + sender?.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}JavaScript engine failed to start.") } } } override fun reloadScriptEngine(sender: CommandSender?) { unloadScriptEngine(sender) + saveDefaultConfig() reloadConfig() loadScriptEngine(sender) } companion object { - private var inst: SMCJavaScriptEngineMain? = null - var instance: SMCJavaScriptEngineMain - internal set(value) { inst = value } - get() { return inst!! } + var instance: SMCJavaScriptEngineMain? = null + private set } } \ No newline at end of file diff --git a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginContext.kt b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginContext.kt index 8d5cd585..84988681 100644 --- a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginContext.kt +++ b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginContext.kt @@ -28,6 +28,7 @@ class JavaScriptPluginContext(override val engine: ScriptablePluginEngine, overr } pluginInstance.invokeMember("onEnable") + isEnabled = true } override fun disable() { @@ -43,6 +44,7 @@ class JavaScriptPluginContext(override val engine: ScriptablePluginEngine, overr for(command in commands) { unregisterCommand(command) } + isEnabled = false } companion object { diff --git a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginEngine.kt b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginEngine.kt index 99e1ddeb..3baf2458 100644 --- a/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginEngine.kt +++ b/ScriptableMC-Engine-JS/src/main/kotlin/com/pixlfox/scriptablemc/core/JavaScriptPluginEngine.kt @@ -2,24 +2,22 @@ package com.pixlfox.scriptablemc.core import com.pixlfox.scriptablemc.SMCJavaScriptConfig import com.pixlfox.scriptablemc.ScriptEngineMain -import com.pixlfox.scriptablemc.exceptions.ScriptNotFoundException +import com.smc.exceptions.ScriptNotFoundException import com.pixlfox.scriptablemc.utils.UnzipUtility import fr.minuskube.inv.InventoryManager -import org.bukkit.Bukkit -import org.bukkit.command.CommandSender import org.graalvm.polyglot.* import java.io.File -import java.util.* @Suppress("MemberVisibilityCanBePrivate", "unused") -class JavaScriptPluginEngine(override val bootstrapPlugin: ScriptEngineMain, private val config: SMCJavaScriptConfig): ScriptablePluginEngine() { +class JavaScriptPluginEngine(override val bootstrapPlugin: ScriptEngineMain, override val config: SMCJavaScriptConfig): ScriptablePluginEngine() { + override val languageName: String = "js" + override val languageFileExtension: String = "js" override val debugEnabled: Boolean = config.debug override val graalContext: Context override val globalBindings: Value override val scriptablePlugins: MutableList = mutableListOf() override val inventoryManager: InventoryManager = InventoryManager(bootstrapPlugin) - private var enabledAllPlugins: Boolean = false init { if(config.extractLibs) { @@ -34,7 +32,7 @@ class JavaScriptPluginEngine(override val bootstrapPlugin: ScriptEngineMain, pri } var contextBuilder = Context - .newBuilder("js") + .newBuilder(languageName) .allowAllAccess(true) .allowExperimentalOptions(true) .allowHostAccess(HostAccess.ALL) @@ -43,17 +41,41 @@ class JavaScriptPluginEngine(override val bootstrapPlugin: ScriptEngineMain, pri .allowCreateThread(true) .option("js.ecmascript-version", "2020") - contextBuilder = contextBuilder - .option("js.commonjs-require", "true") + if(config.commonJsModulesEnabled) { + if(config.debug) { + bootstrapPlugin.logger.info("Enabling CommonJS support...") + } - if(File("${config.rootScriptsFolder}/node_modules/").exists()) { - contextBuilder = contextBuilder - .option("js.commonjs-require-cwd", "${config.rootScriptsFolder}/node_modules") - } + if (!File(config.commonJsModulesPath).exists()) { + if(config.debug) { + bootstrapPlugin.logger.info("CommonJS creating modules folder: ${config.commonJsModulesPath}") + } + File(config.commonJsModulesPath).mkdirs() + } + else { + if(config.debug) { + bootstrapPlugin.logger.info("CommonJS using modules folder: ${config.commonJsModulesPath}") + } + } - if(File("${config.rootScriptsFolder}/__globals__.js").exists()) { contextBuilder = contextBuilder - .option("js.commonjs-global-properties", "${config.rootScriptsFolder}/__globals__.js") + .option("js.commonjs-require", "true") + .option("js.commonjs-require-cwd", config.commonJsModulesPath) + + if (File(File(config.commonJsModulesPath), config.commonJsGlobalsFile).exists()) { + if(config.debug) { + bootstrapPlugin.logger.info("CommonJS using globals: ${File(File(config.commonJsModulesPath), config.commonJsGlobalsFile).path}") + } + contextBuilder = contextBuilder + .option("js.commonjs-global-properties", config.commonJsGlobalsFile) + } + else { + if(config.debug) { + bootstrapPlugin.logger.warning("CommonJS unable to read globals: ${File(File(config.commonJsModulesPath), config.commonJsGlobalsFile).path}") + } + } + + bootstrapPlugin.logger.info("CommonJS support enabled.") } if(config.debugger.enabled) { @@ -67,120 +89,49 @@ class JavaScriptPluginEngine(override val bootstrapPlugin: ScriptEngineMain, pri graalContext = contextBuilder.build() - globalBindings = graalContext.getBindings("js") + globalBindings = graalContext.getBindings(languageName) } - override fun start() { - instance = this - inventoryManager.init() - globalBindings.putMember("engine", this) - - loadAllHelperClasses() - - val mainScriptFile = File("${config.rootScriptsFolder}/main.js") - if(!mainScriptFile.parentFile.exists()) { - mainScriptFile.parentFile.mkdirs() - } - - if(mainScriptFile.exists()) { - val mainReturn = eval( - Source.newBuilder("js", mainScriptFile) - .name("main.js") - .mimeType("application/javascript+module") - .interactive(false) - .build() - ) - - // Load all plugin types returned as an array - if(mainReturn.hasArrayElements()) { - for (i in 0 until mainReturn.arraySize) { - this.loadPlugin(mainReturn.getArrayElement(i)) - } + override fun loadMainScript(path: String) { + try { + val mainScriptFile = File(path) + if(!mainScriptFile.parentFile.exists()) { + mainScriptFile.parentFile.mkdirs() + } - // Enable all plugins if not already enabled - if(!enabledAllPlugins) { - enableAllPlugins() + if(mainScriptFile.exists()) { + val mainReturn = eval( + Source.newBuilder(languageName, mainScriptFile) + .name(mainScriptFile.name) + .mimeType(config.scriptMimeType) + .interactive(false) + .build() + ) + + // Load all plugin types returned as an array + if(mainReturn.hasArrayElements()) { + for (i in 0 until mainReturn.arraySize) { + this.loadPlugin(mainReturn.getArrayElement(i)) + } } } + else { + throw ScriptNotFoundException(mainScriptFile) + } } - else { - throw ScriptNotFoundException(mainScriptFile) - } - } - - override fun close() { - instance = null - for(scriptablePlugin in scriptablePlugins) { - scriptablePlugin.disable() - } - scriptablePlugins.clear() - - graalContext.close(true) - } - - override fun evalFile(filePath: String): Value { - val scriptFile = File("${config.rootScriptsFolder}/$filePath") - - return if(scriptFile.exists()) { - eval( - Source.newBuilder("js", scriptFile) - .name(scriptFile.name) - .mimeType("application/javascript+module") - .interactive(false) - .build() - ) - } else { - throw ScriptNotFoundException(scriptFile) + catch(ex: Exception) { + startupErrors.add(ex) } } - override fun evalFile(scriptFile: File): Value { - return if(scriptFile.exists()) { - eval( - Source.newBuilder("js", scriptFile) - .name(scriptFile.name) - .mimeType("application/javascript+module") - .interactive(false) - .build() - ) - } else { - throw ScriptNotFoundException(scriptFile) - } - } - - override fun eval(source: String): Value { - return graalContext.eval( - Source.newBuilder("js", source,"${UUID.randomUUID()}.js") - .mimeType("application/javascript+module") - .interactive(false) - .cached(false) - .build() - ) - } - - override fun evalCommandSender(source: String, sender: CommandSender): Value { - val tempScriptFile = File("${config.rootScriptsFolder}/${UUID.randomUUID()}.js") - try { - tempScriptFile.writeText("import * as lib from './lib/global.js';\n" + - "new (class EvalCommandSenderContext {\n" + - " run(sender, server, servicesManager) {\n" + - " $source\n" + - " }\n" + - "})()\n") - val evalCommandSenderContext = evalFile(tempScriptFile) - - evalCommandSenderContext.putMember("sender", sender) - evalCommandSenderContext.putMember("server", Bukkit.getServer()) - evalCommandSenderContext.putMember("servicesManager", Bukkit.getServicesManager()) - return evalCommandSenderContext.invokeMember("run", sender, Bukkit.getServer(), Bukkit.getServicesManager()) - } - finally { - tempScriptFile.delete() - } + override fun start() { + instance = this + super.start() } - override fun eval(source: Source): Value { - return graalContext.eval(source) + override fun close() { + instance = null + super.close() } override fun loadPlugin(scriptableClass: Value): ScriptablePluginContext { @@ -193,25 +144,8 @@ class JavaScriptPluginEngine(override val bootstrapPlugin: ScriptEngineMain, pri return pluginContext } - override fun enableAllPlugins() { - for (pluginContext in scriptablePlugins) { - pluginContext.enable() - } - enabledAllPlugins = true - } - - override fun enablePlugin(pluginContext: ScriptablePluginContext) { - pluginContext.enable() - } - - override fun disablePlugin(pluginContext: ScriptablePluginContext) { - pluginContext.disable() - } - companion object { - private var inst: JavaScriptPluginEngine? = null - var instance: JavaScriptPluginEngine? - internal set(value) { inst = value } - get() { return inst } + var instance: JavaScriptPluginEngine? = null + private set } } \ No newline at end of file diff --git a/ScriptableMC-Engine-JS/src/main/resources/config.yml b/ScriptableMC-Engine-JS/src/main/resources/config.yml index ebd32987..995f98ac 100644 --- a/ScriptableMC-Engine-JS/src/main/resources/config.yml +++ b/ScriptableMC-Engine-JS/src/main/resources/config.yml @@ -3,6 +3,24 @@ # Default: ./scripts root_scripts_folder: './scripts' +# Script MIME type +# ES2020 Module: application/javascript+module +# Pure JavaScript: application/javascript +# Default: application/javascript+module +script_mime_type: 'application/javascript+module' + +# Main script files +# The main entry point javascript files +# Default: ${root_scripts_folder}/main.js +main_script_files: + - '${root_scripts_folder}/main.js' + +# Auto-Enable all scriptable plugins after executing all main scripts +# If set to false you will have to manually enable all plugins +# via engine.enableAllPlugins() after you have loaded all plugins +# Default: true +auto_enable_plugins: true + # Prints some additional debugging messages # Default: false debug: false @@ -18,6 +36,48 @@ version_check: true # Default: true extract_libs: true +# Execute command template +# This is the code that will be executed with /jsex and /smc javascript execute +# If a class is returned it will be instantiated and continued on as an object +# If object is returned and has a method execute script engine will call +# returnedObject.execute(sender: CommandSender, server: BukkitServer, servicesManager) +# as well as set returnedObject.sender, returnedObject.server, and returnedObject.servicesManager +# you can also add additional methods, vars to the template source below +# Otherwise the script engine will return last execute code +# %SOURCE% will be replaced with with the command source. +execute_command_template: " +import * as lib from './lib/global.js'; + +new (class EvalCommandSenderContext { + execute(sender, server, servicesManager) { + %SOURCE% + } +})()" + +common_js: + # Enable CommonJS `require()` support + # Allows the use of CommonJS NPM modules + # Default: true + enabled: true + + # CommonJS modules path + # The default path to load CommonJS modules from. + # Default: ${root_scripts_folder}/node_modules + modules_path: '${root_scripts_folder}/node_modules' + + # CommonJS globals file name + # The CommonJS globals file used to define global CommonJS values + # Relative to the `common_js.modules_path` folder + # Default: globals.js + globals_file: 'globals.js' + +# Chrome debugger settings +# **WARNING** THE DEBUGGER SHOULD NEVER BE ENABLED ON A PUBLIC SERVER +# IT IS INTENDED ONLY FOR DEBUGGING THE SCRIPT ENGINE AND CAN BREAK/FREEZE YOUR WHOLE SERVER +# Default: enabled: false, address: 127.0.0.1:9229, wait_attached: true +# If enabled: true and wait_attached: true the whole minecraft server will freeze until the chrome debugger is attached +# this is useful for debugging the script engine startup +# To enable remote debugging set address: 0.0.0.0:9229 debugger: enabled: false address: 127.0.0.1:9229 diff --git a/ScriptableMC-Engine-JS/src/main/resources/plugin.yml b/ScriptableMC-Engine-JS/src/main/resources/plugin.yml index a019cdc1..40b1e83a 100644 --- a/ScriptableMC-Engine-JS/src/main/resources/plugin.yml +++ b/ScriptableMC-Engine-JS/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ScriptableMC-Engine-JS description: 'Run JavaScript/TypeScript plugins for Bukkit/Spigot/Paper Minecraft 1.15' -version: '1.2.0' +version: '1.2.1' api-version: '1.13' author: AStorks main: com.pixlfox.scriptablemc.SMCJavaScriptEngineMain diff --git a/ScriptableMC-Engine-PY-Bundled/build.gradle.kts b/ScriptableMC-Engine-PY/Bundled/build.gradle.kts similarity index 100% rename from ScriptableMC-Engine-PY-Bundled/build.gradle.kts rename to ScriptableMC-Engine-PY/Bundled/build.gradle.kts diff --git a/ScriptableMC-Engine-PY-Bundled/libraries/graalpython.jar b/ScriptableMC-Engine-PY/Bundled/libraries/graalpython.jar similarity index 100% rename from ScriptableMC-Engine-PY-Bundled/libraries/graalpython.jar rename to ScriptableMC-Engine-PY/Bundled/libraries/graalpython.jar diff --git a/ScriptableMC-Engine-PY-Bundled/libraries/sulong-api.jar b/ScriptableMC-Engine-PY/Bundled/libraries/sulong-api.jar similarity index 100% rename from ScriptableMC-Engine-PY-Bundled/libraries/sulong-api.jar rename to ScriptableMC-Engine-PY/Bundled/libraries/sulong-api.jar diff --git a/ScriptableMC-Engine-PY-Bundled/libraries/sulong.jar b/ScriptableMC-Engine-PY/Bundled/libraries/sulong.jar similarity index 100% rename from ScriptableMC-Engine-PY-Bundled/libraries/sulong.jar rename to ScriptableMC-Engine-PY/Bundled/libraries/sulong.jar diff --git a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonConfig.kt b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonConfig.kt index 4ce0675d..a2d45f0d 100644 --- a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonConfig.kt +++ b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonConfig.kt @@ -2,27 +2,13 @@ package com.pixlfox.scriptablemc import org.bukkit.configuration.file.FileConfiguration -class SMCPythonConfig(private val config: FileConfiguration) { - val rootScriptFolder: String - get() = config.getString("root_scripts_folder", "./scripts").orEmpty() +class SMCPythonConfig(config: FileConfiguration) : ScriptEngineConfig(config) { + override val mainScriptFiles: List + get() = readConfigStringList("main_script_files", listOf("\${root_scripts_folder}/main.py")) - val debug: Boolean - get() = config.getBoolean("debug", false) + override val executeCommandTemplate: String + get() = readConfigString("execute_command_template", "") - val extractLibs: Boolean - get() = config.getBoolean("extract_libs", true) - - val debugger: SMCPythonDebuggerConfig - get() = SMCPythonDebuggerConfig(config) -} - -class SMCPythonDebuggerConfig(private val config: FileConfiguration) { - val enabled: Boolean - get() = config.getBoolean("debugger.enabled", false) - - val address: String - get() = config.getString("debugger.address", "127.0.0.1:9229").orEmpty() - - val waitAttached: Boolean - get() = config.getBoolean("debugger.wait_attached", true) + override val scriptMimeType: String + get() = readConfigString("script_mime_type", "text/x-python") } \ No newline at end of file diff --git a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonEngineMain.kt b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonEngineMain.kt index 3de2c363..a06bf421 100644 --- a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonEngineMain.kt +++ b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/SMCPythonEngineMain.kt @@ -53,47 +53,50 @@ class SMCPythonEngineMain : ScriptEngineMain() { try { scriptEngine = PythonPluginEngine(this, SMCPythonConfig(config)) scriptEngine!!.start() - logger.info("Python engine started.") - sender?.sendMessage("$chatMessagePrefix ${ChatColor.GREEN}Python engine started.") - } catch (e: IllegalStateException) { - if (e.message?.contains("Make sure the truffle-api.jar is on the classpath.", true) == true) { - logger.warning("Python engine failed to start.") - e.printStackTrace() - logger.severe("Unable to find truffle-api.jar. This shouldn't happen since it's now packaged inside the plugin, you might want to check for an updated ScriptableMC Engine release.") - logger.severe("ScriptableMC Engine Version: $pluginVersion") - logger.severe("ScriptableMC-Engine Download Page: https://github.com/astorks/ScriptableMC-Engine/releases/latest") - - sender?.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}Python engine failed to start. Check the server console.") - } else { - logger.warning("Scriptable plugin engine failed to start.") - e.printStackTrace() - - sender?.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}Python engine failed to start. Check the server console.") - } - } catch (e: Exception) { - logger.warning("Python engine failed to start.") - e.printStackTrace() + if(scriptEngine!!.startupErrors.any()) { + for(error in scriptEngine!!.startupErrors) { + error.printStackTrace() + if(sender != null) { + sender.sendMessage("$chatMessagePrefix ${ChatColor.RED}$error") + for (stackTrace in error.stackTrace.filter { it.fileName?.endsWith(".py") == true }) { + sender.sendMessage("${ChatColor.RED}$stackTrace") + } + } + } + + logger.warning("Python engine started with errors.") + sender?.sendMessage("$chatMessagePrefix ${ChatColor.YELLOW}Python engine started with errors.") + } + else { + logger.info("Python engine started.") + sender?.sendMessage("$chatMessagePrefix ${ChatColor.GREEN}Python engine started.") + } + } + catch (error: Exception) { + error.printStackTrace() if(sender != null) { - sender.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}$e") - for (stackTrace in e.stackTrace) { - sender.sendMessage("${ChatColor.DARK_RED}$stackTrace") + sender.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}$error") + for (stackTrace in error.stackTrace) { + sender.sendMessage("${ChatColor.RED}$stackTrace") } } + + logger.severe("Python engine failed to start.") + sender?.sendMessage("$chatMessagePrefix ${ChatColor.DARK_RED}Python engine failed to start.") } } } override fun reloadScriptEngine(sender: CommandSender?) { unloadScriptEngine(sender) + saveDefaultConfig() reloadConfig() loadScriptEngine(sender) } companion object { - private var inst: SMCPythonEngineMain? = null - var instance: SMCPythonEngineMain - internal set(value) { inst = value } - get() { return inst!! } + var instance: SMCPythonEngineMain? = null + private set } } \ No newline at end of file diff --git a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginContext.kt b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginContext.kt index f82452e8..8b9e1b87 100644 --- a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginContext.kt +++ b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginContext.kt @@ -28,6 +28,7 @@ class PythonPluginContext(override val engine: ScriptablePluginEngine, override } pluginInstance.invokeMember("onEnable") + isEnabled = true } override fun disable() { @@ -43,6 +44,7 @@ class PythonPluginContext(override val engine: ScriptablePluginEngine, override for(command in commands) { unregisterCommand(command) } + isEnabled = false } companion object { diff --git a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginEngine.kt b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginEngine.kt index 00e42d5d..22f77fa2 100644 --- a/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginEngine.kt +++ b/ScriptableMC-Engine-PY/src/main/kotlin/com/pixlfox/scriptablemc/core/PythonPluginEngine.kt @@ -2,27 +2,26 @@ package com.pixlfox.scriptablemc.core import com.pixlfox.scriptablemc.SMCPythonConfig import com.pixlfox.scriptablemc.ScriptEngineMain -import com.pixlfox.scriptablemc.exceptions.ScriptNotFoundException +import com.smc.exceptions.ScriptNotFoundException import com.pixlfox.scriptablemc.utils.UnzipUtility import fr.minuskube.inv.InventoryManager -import org.bukkit.command.CommandSender import org.graalvm.polyglot.* import java.io.File -import java.util.* @Suppress("MemberVisibilityCanBePrivate", "unused") -class PythonPluginEngine(override val bootstrapPlugin: ScriptEngineMain, private val config: SMCPythonConfig): ScriptablePluginEngine() { +class PythonPluginEngine(override val bootstrapPlugin: ScriptEngineMain, override val config: SMCPythonConfig): ScriptablePluginEngine() { + override val languageName: String = "python" + override val languageFileExtension: String = "py" override val graalContext: Context override val debugEnabled: Boolean = config.debug override val globalBindings: Value override val scriptablePlugins: MutableList = mutableListOf() override val inventoryManager: InventoryManager = InventoryManager(bootstrapPlugin) - private var enabledAllPlugins: Boolean = false init { if(config.extractLibs) { val librariesResource = bootstrapPlugin.getResource("lib-py.zip") - val libFolder = File("${config.rootScriptFolder}/lib-py") + val libFolder = File("${config.rootScriptsFolder}/lib-py") if (librariesResource != null && !libFolder.exists()) { if(debugEnabled) { bootstrapPlugin.logger.info("Extracting python libraries from ScriptableMC-Engine-PY resources to ${libFolder.path}...") @@ -32,14 +31,14 @@ class PythonPluginEngine(override val bootstrapPlugin: ScriptEngineMain, private } var contextBuilder = Context - .newBuilder("python") + .newBuilder(languageName) .allowAllAccess(true) .allowExperimentalOptions(true) .allowHostAccess(HostAccess.ALL) .allowHostClassLoading(true) .allowIO(true) - .option("python.CoreHome", "${config.rootScriptFolder}/lib-py/lib-graalpython/") - .option("python.StdLibHome", "${config.rootScriptFolder}/lib-py/lib-python/3/") + .option("python.CoreHome", "${config.rootScriptsFolder}/lib-py/lib-graalpython/") + .option("python.StdLibHome", "${config.rootScriptsFolder}/lib-py/lib-python/3/") if(config.debugger.enabled) { contextBuilder = contextBuilder @@ -51,108 +50,53 @@ class PythonPluginEngine(override val bootstrapPlugin: ScriptEngineMain, private } graalContext = contextBuilder.build() - globalBindings = graalContext.getBindings("python") + globalBindings = graalContext.getBindings(languageName) } - override fun start() { - instance = this - inventoryManager.init() - globalBindings.putMember("engine", this) - - loadAllHelperClasses() - - val mainScriptFile = File("${config.rootScriptFolder}/main.py") - if(!mainScriptFile.parentFile.exists()) { - mainScriptFile.parentFile.mkdirs() - } - - if(mainScriptFile.exists()) { - val mainReturn = eval( - Source.newBuilder("python", mainScriptFile) - .name("main.py") - .interactive(false) - .build() - ) - - // Load all plugin types returned as an array - if(mainReturn.hasArrayElements()) { - for (i in 0 until mainReturn.arraySize) { - this.loadPlugin(mainReturn.getArrayElement(i)) - } + override fun loadMainScript(path: String) { + try { + val mainScriptFile = File(path) + if(!mainScriptFile.parentFile.exists()) { + mainScriptFile.parentFile.mkdirs() + } - // Enable all plugins if not already enabled - if(!enabledAllPlugins) { - enableAllPlugins() + if(mainScriptFile.exists()) { + val mainReturn = eval( + Source.newBuilder(languageName, mainScriptFile) + .name(mainScriptFile.name) + .interactive(false) + .build() + ) + + // Load all plugin types returned as an array + if(mainReturn.hasArrayElements()) { + for (i in 0 until mainReturn.arraySize) { + this.loadPlugin(mainReturn.getArrayElement(i)) + } + + // Enable all plugins if not already enabled + if(!enabledAllPlugins) { + enableAllPlugins() + } } } + else { + throw ScriptNotFoundException(mainScriptFile) + } } - else { - throw ScriptNotFoundException(mainScriptFile) - } - } - - override fun close() { - instance = null - for(scriptablePlugin in scriptablePlugins) { - scriptablePlugin.disable() - } - scriptablePlugins.clear() - - graalContext.close(true) - } - - override fun evalFile(filePath: String): Value { - val scriptFile = File("${config.rootScriptFolder}/$filePath") - - return if(scriptFile.exists()) { - eval( - Source.newBuilder("python", scriptFile) - .name(scriptFile.name) - .interactive(false) - .build() - ) - } else { - throw ScriptNotFoundException(scriptFile) - } - } - - override fun evalFile(scriptFile: File): Value { - return if(scriptFile.exists()) { - eval( - Source.newBuilder("python", scriptFile) - .name(scriptFile.name) - .interactive(false) - .build() - ) - } else { - throw ScriptNotFoundException(scriptFile) + catch(ex: Exception) { + startupErrors.add(ex) } } - override fun eval(source: String): Value { - return graalContext.eval( - Source.newBuilder("python", source,"${UUID.randomUUID()}.py") - .interactive(false) - .cached(false) - .build() - ) - } - - override fun evalCommandSender(source: String, sender: CommandSender): Value { - val tempScriptFile = File("${config.rootScriptFolder}/${UUID.randomUUID()}/__init__.py") - try { - tempScriptFile.parentFile.mkdirs() - tempScriptFile.writeText(source) - return evalFile(tempScriptFile) - } - finally { - tempScriptFile.delete() - tempScriptFile.parentFile.delete() - } + override fun start() { + instance = this + super.start() } - override fun eval(source: Source): Value { - return graalContext.eval(source) + override fun close() { + instance = null + super.close() } override fun loadPlugin(scriptableClass: Value): ScriptablePluginContext { @@ -165,25 +109,8 @@ class PythonPluginEngine(override val bootstrapPlugin: ScriptEngineMain, private return pluginContext } - override fun enableAllPlugins() { - for (pluginContext in scriptablePlugins) { - pluginContext.enable() - } - enabledAllPlugins = true - } - - override fun enablePlugin(pluginContext: ScriptablePluginContext) { - pluginContext.enable() - } - - override fun disablePlugin(pluginContext: ScriptablePluginContext) { - pluginContext.disable() - } - companion object { - private var inst: PythonPluginEngine? = null - var instance: PythonPluginEngine? - internal set(value) { inst = value } - get() { return inst } + var instance: PythonPluginEngine? = null + private set } } \ No newline at end of file diff --git a/ScriptableMC-Engine-PY/src/main/resources/config.yml b/ScriptableMC-Engine-PY/src/main/resources/config.yml index 12ec47ab..bce27697 100644 --- a/ScriptableMC-Engine-PY/src/main/resources/config.yml +++ b/ScriptableMC-Engine-PY/src/main/resources/config.yml @@ -3,6 +3,24 @@ # Default: ./scripts root_scripts_folder: './scripts' +# Script MIME type +# .py text/x-python +# .pyc application/x-python-code +# Default: text/x-python +script_mime_type: 'text/x-python' + +# Main script files +# The main entry point python script files +# Default: ${root_scripts_folder}/main.py +main_script_files: + - '${root_scripts_folder}/main.py' + +# Auto-Enable all scriptable plugins after loading main scripts +# If set to false you will have to manually enable all plugins +# via engine.enableAllPlugins() after you have loaded all plugins +# Default: true +auto_enable_plugins: true + # Prints some additional debugging messages # Default: false debug: false @@ -18,6 +36,24 @@ version_check: true # Default: true extract_libs: true +# Execute command template +# This is the code that will be executed with /pyex and /smc python execute +# If a class is returned it will be instantiated and continued on as an object +# If object is returned and has a method execute script engine will call +# returnedObject.execute(sender: CommandSender, server: BukkitServer, servicesManager) +# as well as set returnedObject.sender, returnedObject.server, and returnedObject.servicesManager +# you can also add additional methods, vars to the template source below +# Otherwise the script engine will return last execute code +# %SOURCE% will be replaced with with the command source. +execute_command_template: "%SOURCE%" + +# Chrome debugger settings +# **WARNING** THE DEBUGGER SHOULD NEVER BE ENABLED ON A PUBLIC SERVER +# IT IS INTENDED ONLY FOR DEBUGGING THE SCRIPT ENGINE AND CAN BREAK/FREEZE YOUR WHOLE SERVER +# Default: enabled: false, address: 127.0.0.1:9229, wait_attached: true +# If enabled: true and wait_attached: true the whole minecraft server will freeze until the chrome debugger is attached +# this is useful for debugging the script engine startup +# To enable remote debugging set address: 0.0.0.0:9229 debugger: enabled: false address: 127.0.0.1:9229 diff --git a/ScriptableMC-Engine-PY/src/main/resources/plugin.yml b/ScriptableMC-Engine-PY/src/main/resources/plugin.yml index 7480341d..59101626 100644 --- a/ScriptableMC-Engine-PY/src/main/resources/plugin.yml +++ b/ScriptableMC-Engine-PY/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ScriptableMC-Engine-PY description: 'Run Python plugins for Bukkit/Spigot/Paper Minecraft 1.15' -version: '1.2.0' +version: '1.2.1' api-version: '1.13' author: AStorks main: com.pixlfox.scriptablemc.SMCPythonEngineMain diff --git a/ScriptableMC-Tools-TS/build.gradle.kts b/ScriptableMC-Tools-TS/build.gradle.kts index 132204bd..5e3f17a3 100644 --- a/ScriptableMC-Tools-TS/build.gradle.kts +++ b/ScriptableMC-Tools-TS/build.gradle.kts @@ -57,5 +57,20 @@ tasks.register("exportTypeScriptLibraries") { group = "libraries" main = "com.pixlfox.scriptablemc.TypescriptLibraryExporter" classpath = sourceSets["main"].runtimeClasspath - //classpath = sourceSets["main"].java.srcDirs +} + +tasks.register("generateLibSMC") { + dependsOn(":ScriptableMC-Tools-TS:classes") + group = "libraries" + main = "com.pixlfox.scriptablemc.TypescriptLibraryExporter" + args = mutableListOf("--lib-smc") + classpath = sourceSets["main"].runtimeClasspath +} + +tasks.register("generateLibSMCRelease") { + dependsOn(":ScriptableMC-Tools-TS:classes") + group = "libraries" + main = "com.pixlfox.scriptablemc.TypescriptLibraryExporter" + args = mutableListOf("--lib-smc", "--release") + classpath = sourceSets["main"].runtimeClasspath } \ No newline at end of file diff --git a/ScriptableMC-Tools-TS/src/main/kotlin/com/pixlfox/scriptablemc/TypescriptLibraryExporter.kt b/ScriptableMC-Tools-TS/src/main/kotlin/com/pixlfox/scriptablemc/TypescriptLibraryExporter.kt index b7604754..f6d05e41 100644 --- a/ScriptableMC-Tools-TS/src/main/kotlin/com/pixlfox/scriptablemc/TypescriptLibraryExporter.kt +++ b/ScriptableMC-Tools-TS/src/main/kotlin/com/pixlfox/scriptablemc/TypescriptLibraryExporter.kt @@ -5,16 +5,18 @@ import com.beust.klaxon.JsonObject import com.beust.klaxon.Parser import com.thoughtworks.paranamer.BytecodeReadingParanamer import com.thoughtworks.paranamer.Paranamer +import org.bukkit.plugin.PluginDescriptionFile import java.io.File import java.lang.reflect.* @Suppress("MemberVisibilityCanBePrivate", "UnstableApiUsage", "unused") -class TypescriptLibraryExporter { +class TypescriptLibraryExporter(args: Array = arrayOf()) { private var basePath: String = "./lib" private val classList = mutableListOf>() private var allowedPackagesRegex: Regex = Regex("(org\\.bukkit|com\\.pixlfox|com\\.smc|fr\\.minuskube\\.inv|com\\.google|java\\.sql|java\\.io|java\\.nio|khttp|org\\.apache\\.commons\\.io)(.*)?") private val paranamer: Paranamer = BytecodeReadingParanamer() + private val isRelease: Boolean = args.contains("--release") private fun safeName(name: String): String = when { name.equals("function", true) -> "_function" @@ -349,6 +351,18 @@ class TypescriptLibraryExporter { return this } + fun exportIndexLibrary(): TypescriptLibraryExporter { + val file = File("$basePath/ts/index.ts") + if(!file.exists()) { + file.parentFile.mkdirs() + file.createNewFile() + file.writeText(generateTypescriptGlobalExports()) + println("Exported index.ts.") + } + + return this + } + fun copyStaticSources(): TypescriptLibraryExporter { File("./src/main/ts/").copyRecursively(File("$basePath/ts/"), true) @@ -376,7 +390,8 @@ class TypescriptLibraryExporter { " \"version\": \"1.0.0\",\n" + " \"description\": \"Typescript plugin example and libraries for Minecraft 1.15\",\n" + " \"scripts\": {\n" + - " \"compile\": \"npx tsc\"\n" + + " \"compile\": \"npx tsc\",\n" + + " \"compile_commonjs\": \"npx tsc -m commonjs --outDir ./commonjs\"\n" + " },\n" + " \"author\": \"Ashton Storks\",\n" + " \"license\": \"ISC\",\n" + @@ -393,6 +408,66 @@ class TypescriptLibraryExporter { return this } + fun exportCommonJSProjectFiles(): TypescriptLibraryExporter { + File("$basePath/tsconfig.json").writeText("{\n" + + " \"compilerOptions\": {\n" + + " \"target\": \"es2019\",\n" + + " \"module\": \"commonjs\",\n" + + " \"sourceMap\": false,\n" + + " \"allowJs\": true,\n" + + " \"outDir\": \"js\",\n" + + " \"rootDir\": \"ts\",\n" + + " \"declaration\": true\n" + + " },\n" + + " \"include\": [\n" + + " \"ts/**/*\"\n" + + " ]\n" + + "}") + + val pluginDescription = PluginDescriptionFile(File("../ScriptableMC-Engine-JS/src/main/resources/plugin.yml").inputStream()) + val githubSha = System.getenv().getOrElse("GITHUB_SHA") { "" } + val githubTag = System.getenv().getOrElse("GITHUB_REF") { "" } + + val isReleaseTag = githubTag.startsWith("refs/tags/v") && isRelease + + val version = when { + isReleaseTag -> githubTag.substring(11) + isRelease && githubSha.isNullOrEmpty() -> pluginDescription.version + else -> "${pluginDescription.version}-dev-$githubSha" + } + + if(isRelease) { + File("$basePath/.npmrc").writeText("//registry.npmjs.org/:_authToken=\${NPM_TOKEN}") + } + else { + File("$basePath/.npmrc").writeText("//npm.pkg.github.com/:_authToken=\${GITHUB_TOKEN}\n" + + "registry=https://npm.pkg.github.com/astorks") + } + + File("$basePath/package.json").writeText("{\n" + + " \"name\": \"@astorks/lib-smc\",\n" + + " \"repository\": \"git@github.com:astorks/ScriptableMC-Engine.git\",\n" + + " \"version\": \"$version\",\n" + + " \"description\": \"JavaScript CommonJS libraries for ScriptableMC\",\n" + + " \"scripts\": {\n" + + " \"compile\": \"npx tsc\",\n" + + " \"postcompile\": \"cp ./package.json ./js/package.json && cp ./.npmrc ./js/.npmrc\"\n" + + " },\n" + + " \"author\": \"Ashton Storks\",\n" + + " \"license\": \"ISC\",\n" + + " \"bugs\": {\n" + + " \"url\": \"https://github.com/astorks/ScriptableMC-TypeScript/issues\"\n" + + " },\n" + + " \"homepage\": \"https://github.com/astorks/ScriptableMC-TypeScript#readme\",\n" + + " \"dependencies\": {},\n" + + " \"devDependencies\": {\n" + + " \"typescript\": \"^3.7.5\"\n" + + " }\n" + + "}") + + return this + } + @Suppress("UNCHECKED_CAST") private fun generateTypescriptSource(_class: Class<*>): String { var source = "declare var Java: any;\n" @@ -404,6 +479,7 @@ class TypescriptLibraryExporter { return source } + private fun generateTypescriptImports(_class: Class<*>): String { var tsImportsSource = "" @@ -666,14 +742,27 @@ class TypescriptLibraryExporter { companion object { @JvmStatic fun main(args: Array) { - TypescriptLibraryExporter() - .addHelperClasses() - .addBukkitClasses() - .clean() - .exportLibraries() - .exportGlobalLibrary() - .copyStaticSources() - .exportProjectFiles() + if(args.contains("--lib-smc")) { + TypescriptLibraryExporter(args) + .basePath("./lib-smc") + .addHelperClasses() + .addBukkitClasses() + .clean() + .exportLibraries() + .exportIndexLibrary() + .copyStaticSources() + .exportCommonJSProjectFiles() + } + else { + TypescriptLibraryExporter(args) + .addHelperClasses() + .addBukkitClasses() + .clean() + .exportLibraries() + .exportGlobalLibrary() + .copyStaticSources() + .exportProjectFiles() + } } } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 8d35fd86..e1c0ce13 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { group = "com.pixlfox.scriptablemc" - version = "1.2.0" + version = "1.2.1" repositories { mavenCentral() @@ -47,9 +47,9 @@ tasks.register("shadowJarAll") { group = "shadow" dependsOn(":ScriptableMC-Engine-JS:shadowJar") - dependsOn(":ScriptableMC-Engine-JS-Bundled:shadowJar") + dependsOn(":ScriptableMC-Engine-JS:Bundled:shadowJar") dependsOn(":ScriptableMC-Engine-PY:shadowJar") - dependsOn(":ScriptableMC-Engine-PY-Bundled:shadowJar") + dependsOn(":ScriptableMC-Engine-PY:Bundled:shadowJar") doFirst { if(!file("./build").exists()) file("./build").mkdirs() @@ -61,9 +61,9 @@ tasks.register("shadowJarAll") { doLast { file("./ScriptableMC-Engine-JS/build/libs/ScriptableMC-Engine-JS.jar").copyTo(file("./build/ScriptableMC-Engine-JS.jar"), overwrite = true) - file("./ScriptableMC-Engine-JS-Bundled/build/libs/ScriptableMC-Engine-JS-Bundled.jar").copyTo(file("./build/ScriptableMC-Engine-JS-Bundled.jar"), overwrite = true) + file("./ScriptableMC-Engine-JS/Bundled/build/libs/ScriptableMC-Engine-JS-Bundled.jar").copyTo(file("./build/ScriptableMC-Engine-JS-Bundled.jar"), overwrite = true) file("./ScriptableMC-Engine-PY/build/libs/ScriptableMC-Engine-PY.jar").copyTo(file("./build/ScriptableMC-Engine-PY.jar"), overwrite = true) - file("./ScriptableMC-Engine-PY-Bundled/build/libs/ScriptableMC-Engine-PY-Bundled.jar").copyTo(file("./build/ScriptableMC-Engine-PY-Bundled.jar"), overwrite = true) + file("./ScriptableMC-Engine-PY/Bundled/build/libs/ScriptableMC-Engine-PY-Bundled.jar").copyTo(file("./build/ScriptableMC-Engine-PY-Bundled.jar"), overwrite = true) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index de616a9d..193fbe93 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ rootProject.name = "ScriptableMC-Engine" include("ScriptableMC-Engine-Core") include("ScriptableMC-Engine-JS") -include("ScriptableMC-Engine-JS-Bundled") +include("ScriptableMC-Engine-JS:Bundled") include("ScriptableMC-Engine-PY") -include("ScriptableMC-Engine-PY-Bundled") +include("ScriptableMC-Engine-PY:Bundled") include("ScriptableMC-Tools-TS") \ No newline at end of file