From c4691acdca0c59b02dbec0acf88703e530535e41 Mon Sep 17 00:00:00 2001 From: Cdm2883 Date: Mon, 20 Jan 2025 02:20:19 +0800 Subject: [PATCH] feat: basic project structure --- gradle/libs.versions.toml | 4 + script-compat/todo | 1 + script-definition/build.gradle.kts | 0 script-example/single-script-example.cjs | 16 ++++ script-example/single-script-example.js | 17 ++++ script-example/ts-projectization/package.json | 9 ++ script-example/ts-projectization/src/index.ts | 1 + script-loader/build.gradle.kts | 7 +- .../vip/cdms/allaymc/nodejs/NodePlugin.kt | 6 ++ ...iptExtension.kt => NodePluginExtension.kt} | 6 +- .../cdms/allaymc/nodejs/NodePluginLoader.kt | 58 +++++++++++++ .../cdms/allaymc/nodejs/NodePluginSource.kt | 12 +++ .../vip/cdms/allaymc/nodejs/NodeRuntime.kt | 8 ++ .../cdms/allaymc/nodejs/NodeScriptLoader.kt | 30 ------- .../cdms/allaymc/nodejs/NodeScriptSource.kt | 10 --- .../cdms/allaymc/nodejs/utils/JsDocParser.kt | 83 +++++++++++++++++++ .../cdms/allaymc/nodejs/utils/JsonUtils.kt | 9 ++ .../cdms/allaymc/nodejs/utils/NpmRunner.kt | 2 + .../cdms/allaymc/nodejs/utils/PackageJson.kt | 34 ++++++++ .../cdms/allaymc/nodejs/utils/ScriptHeader.kt | 37 +++++++++ .../src/main/resources/extension.json | 2 +- settings.gradle.kts | 1 + 22 files changed, 308 insertions(+), 45 deletions(-) create mode 100644 script-compat/todo create mode 100644 script-definition/build.gradle.kts create mode 100644 script-example/single-script-example.cjs create mode 100644 script-example/single-script-example.js create mode 100644 script-example/ts-projectization/package.json create mode 100644 script-example/ts-projectization/src/index.ts create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePlugin.kt rename script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/{NodeScriptExtension.kt => NodePluginExtension.kt} (57%) create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginLoader.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginSource.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeRuntime.kt delete mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptLoader.kt delete mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptSource.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsDocParser.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsonUtils.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/NpmRunner.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/PackageJson.kt create mode 100644 script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/ScriptHeader.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e4e43d1..4683738 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ allaymc-nodejs-runtime = "0.0.1-alpha" allaymc-server = "0.1.3" kotlin = "2.1.0" +kotlinx-serialization = "1.8.0" gradleup-shadow = "8.3.0" kotlinx-coroutines = "1.10.1" @@ -19,8 +20,11 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } + caoccao-javet = { group = "com.caoccao.javet", name = "javet", version.ref = "caoccao-javet" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } gradleup-shadow = { id = "com.gradleup.shadow", version.ref = "gradleup-shadow" } diff --git a/script-compat/todo b/script-compat/todo new file mode 100644 index 0000000..c35720a --- /dev/null +++ b/script-compat/todo @@ -0,0 +1 @@ +compat another server's js engine (e.g. lse) \ No newline at end of file diff --git a/script-definition/build.gradle.kts b/script-definition/build.gradle.kts new file mode 100644 index 0000000..e69de29 diff --git a/script-example/single-script-example.cjs b/script-example/single-script-example.cjs new file mode 100644 index 0000000..f3a6e8d --- /dev/null +++ b/script-example/single-script-example.cjs @@ -0,0 +1,16 @@ +/** + * Script description. + * ```json + * // this will be mixed in package.json + * { + * "dependencies": [], // npm + * "allayDependencies": [], // plugins + * } + * ``` + * @name Plugin Name + * @version 0.0.1-alpha + * @author Author Name, Another Author + * @see https://example.com/ + */ + +console.log("Hello world from Node.js!") diff --git a/script-example/single-script-example.js b/script-example/single-script-example.js new file mode 100644 index 0000000..8310dd2 --- /dev/null +++ b/script-example/single-script-example.js @@ -0,0 +1,17 @@ +/** + * Script description. + * ```json + * // this will be mixed in package.json + * { + * "dependencies": [], // npm + * "allayDependencies": [], // plugins + * } + * ``` + * @name Plugin Name + * @module custom-module-name + * @version 0.0.1-alpha + * @author Author Name, Another Author + * @see https://example.com/ + */ + +console.log("Hello world from Node.js!") diff --git a/script-example/ts-projectization/package.json b/script-example/ts-projectization/package.json new file mode 100644 index 0000000..ef3523b --- /dev/null +++ b/script-example/ts-projectization/package.json @@ -0,0 +1,9 @@ +{ + "name": "nodejs-plugin-exmaple", + "version": "0.0.1-alpha", + "description": "A example script plugin with allaymc-nodejs-runtime.", + "main": "dist/index.js", + "scripts": { + }, + "author": "Cdm2883" +} \ No newline at end of file diff --git a/script-example/ts-projectization/src/index.ts b/script-example/ts-projectization/src/index.ts new file mode 100644 index 0000000..a611c0b --- /dev/null +++ b/script-example/ts-projectization/src/index.ts @@ -0,0 +1 @@ +// TODO ts example diff --git a/script-loader/build.gradle.kts b/script-loader/build.gradle.kts index 2852e9d..aced8b2 100644 --- a/script-loader/build.gradle.kts +++ b/script-loader/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlinx.serialization) alias(libs.plugins.gradleup.shadow) } @@ -15,11 +16,15 @@ dependencies { libs.kotlin.reflect.get(), libs.kotlinx.coroutines.core.get(), ) - sharedLibs.forEach { compileOnly(it) } +// sharedLibs.forEach { compileOnly(it) } + sharedLibs.forEach { implementation(it) } val excludeSharedLibs = Action { sharedLibs.forEach { exclude(group = it.group, module = it.name) } } + implementation(libs.kotlinx.serialization.json, excludeSharedLibs) + implementation(libs.caoccao.javet) + // TODO: load platform libs dynamically implementation(libs.caoccao.javet.get().run { "$module-node-windows-x86_64:$version" }) } diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePlugin.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePlugin.kt new file mode 100644 index 0000000..3473ee6 --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePlugin.kt @@ -0,0 +1,6 @@ +package vip.cdms.allaymc.nodejs + +import org.allaymc.api.plugin.Plugin + +class NodePlugin(val loader: NodePluginLoader) : Plugin() { +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptExtension.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginExtension.kt similarity index 57% rename from script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptExtension.kt rename to script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginExtension.kt index eed3261..79877b5 100644 --- a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptExtension.kt +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginExtension.kt @@ -4,9 +4,9 @@ import org.allaymc.server.extension.Extension import org.allaymc.server.plugin.AllayPluginManager @Suppress("unused") -class NodeScriptExtension : Extension() { +class NodePluginExtension : Extension() { override fun main(args: Array?) { - AllayPluginManager.registerSource(NodeScriptSource) - AllayPluginManager.registerLoaderFactory(NodeScriptLoader.Factory) + AllayPluginManager.registerSource(NodePluginSource) + AllayPluginManager.registerLoaderFactory(NodePluginLoader.Factory) } } diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginLoader.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginLoader.kt new file mode 100644 index 0000000..ad88d6e --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginLoader.kt @@ -0,0 +1,58 @@ +package vip.cdms.allaymc.nodejs + +import org.allaymc.api.plugin.PluginContainer +import org.allaymc.api.plugin.PluginDependency +import org.allaymc.api.plugin.PluginDescriptor +import org.allaymc.api.plugin.PluginLoader +import vip.cdms.allaymc.nodejs.utils.JsDoc +import vip.cdms.allaymc.nodejs.utils.PackageJson +import vip.cdms.allaymc.nodejs.utils.parseScriptHeader +import vip.cdms.allaymc.nodejs.utils.plus +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.name +import kotlin.io.path.readText + +class NodePluginLoader( + val path: Path, + val jsDoc: JsDoc, + val packageJson: PackageJson, +) : PluginLoader { + override fun getPluginPath() = path + + private val pluginDescriptor by lazy { + object : PluginDescriptor { + override fun getName() = jsDoc.properties["name"] ?: packageJson.name ?: path.name + override fun getEntrance() = packageJson.main ?: path.name + override fun getDescription() = jsDoc.content ?: packageJson.description ?: "" + override fun getVersion() = jsDoc.properties["version"] ?: packageJson.version ?: "0.0.1-alpha" + override fun getAuthors() = jsDoc.properties["author"]?.split(",")?.map(String::trim) + ?: packageJson.getAuthors() ?: mutableListOf("Anonymous") + override fun getDependencies() = packageJson.allayDependencies?.map { PluginDependency(it.key, it.value, false) } + override fun getWebsite() = jsDoc.properties["see"] ?: packageJson.homepage ?: "" + } + } + override fun loadDescriptor() = pluginDescriptor + + override fun loadPlugin(): PluginContainer { + TODO("Not yet implemented") + } + + object Factory : PluginLoader.Factory { + override fun canLoad(path: Path) = path.startsWith(NodePluginSource.ScriptDirectory.toPath()) && + (Files.isDirectory(path) && Files.exists(path.resolve("package.json")) + || path.toString().matches(Regex(".*\\.(js|cjs|mjs)\$"))) + override fun create(path: Path): NodePluginLoader { + val (header, packageJson) = if (path.toFile().isFile) { + val header = parseScriptHeader(path.toFile().readText()) + header to header.packageJson + } else { + val packageJson = PackageJson.parseFrom(path.resolve("package.json").readText()) + val main = packageJson.main ?: error("Unknown plugin start point") + val header = parseScriptHeader(path.resolve(main).readText()) + header to PackageJson(packageJson.raw + header.packageJson.raw) + } + return NodePluginLoader(path, header.descriptor, packageJson) + } + } +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginSource.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginSource.kt new file mode 100644 index 0000000..2d584ea --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodePluginSource.kt @@ -0,0 +1,12 @@ +package vip.cdms.allaymc.nodejs + +import org.allaymc.api.plugin.PluginSource +import java.io.File + +object NodePluginSource : PluginSource { + val ScriptDirectory = File("node-plugins").apply { mkdirs() } + + override fun find() = ScriptDirectory + .listFiles()?.mapTo(mutableSetOf()) { it.toPath() } + ?: mutableSetOf() +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeRuntime.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeRuntime.kt new file mode 100644 index 0000000..4fc30b9 --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeRuntime.kt @@ -0,0 +1,8 @@ +package vip.cdms.allaymc.nodejs + +import com.caoccao.javet.interop.NodeRuntime +import com.caoccao.javet.interop.V8Host + +object NodeRuntime { + val Instance = V8Host.getNodeInstance().createV8Runtime() +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptLoader.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptLoader.kt deleted file mode 100644 index ac43fd0..0000000 --- a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptLoader.kt +++ /dev/null @@ -1,30 +0,0 @@ -package vip.cdms.allaymc.nodejs - -import org.allaymc.api.plugin.PluginContainer -import org.allaymc.api.plugin.PluginDescriptor -import org.allaymc.api.plugin.PluginLoader -import java.nio.file.Path - -class NodeScriptLoader : PluginLoader { - override fun getPluginPath(): Path { - TODO("Not yet implemented") - } - - override fun loadDescriptor(): PluginDescriptor { - TODO("Not yet implemented") - } - - override fun loadPlugin(): PluginContainer { - TODO("Not yet implemented") - } - - object Factory : PluginLoader.Factory { - override fun canLoad(pluginPath: Path?): Boolean { - TODO("Not yet implemented") - } - - override fun create(pluginPath: Path?): PluginLoader { - TODO("Not yet implemented") - } - } -} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptSource.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptSource.kt deleted file mode 100644 index 412abc0..0000000 --- a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/NodeScriptSource.kt +++ /dev/null @@ -1,10 +0,0 @@ -package vip.cdms.allaymc.nodejs - -import org.allaymc.api.plugin.PluginSource -import java.nio.file.Path - -object NodeScriptSource : PluginSource { - override fun find(): MutableSet { - TODO("Not yet implemented") - } -} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsDocParser.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsDocParser.kt new file mode 100644 index 0000000..67b2141 --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsDocParser.kt @@ -0,0 +1,83 @@ +package vip.cdms.allaymc.nodejs.utils + +data class JsDoc( + val content: String?, + val properties: Map, +) + +fun parseJsDoc(doc: String): JsDoc { + val content = StringBuilder() + val properties = mutableMapOf() + + var begin = false + var isLineBegin = true + var isTrimStarting = false + var isContent = true + var isPropertyKey = false + val propertyKey = StringBuilder() + val propertyValue = StringBuilder() + + var droppedFirstEmptyProperty = false + fun saveProperty() { + if (!droppedFirstEmptyProperty) return Unit.also { droppedFirstEmptyProperty = true } + properties += propertyKey.toString() to propertyValue.toString() + propertyKey.clear() + propertyValue.clear() + } + + var i = 0 + while (i < doc.length) { + val c = doc[i] + + if (!begin && c == '/' && doc[i + 1] == '*' && doc[i + 2] == '*') { + begin = true + i += 3 + continue + } + if (!begin) { + i++ + continue + } + + if (c == '\n' && isContent && content.isNotBlank()) content.append(c) + if (c == '\n' || isLineBegin && c == ' ') { + isLineBegin = true + i++ + continue + } else if (isLineBegin && c == '*' && doc[i + 1] == '/') { + break + } else if (isLineBegin && c == '*') { + isLineBegin = false + isTrimStarting = true + i++ + continue + } else if (isTrimStarting && c == ' ') { + i++ + continue + } else if (isTrimStarting && c == '@') { + saveProperty() + isTrimStarting = false + isContent = false + isPropertyKey = true + i++ + continue + } else { + isLineBegin = false + isTrimStarting = false + } + + if (isPropertyKey && c == ' ') { + isPropertyKey = false + } else if (isPropertyKey) { + propertyKey.append(c) + } else if (!isContent) { + propertyValue.append(c) + } else if (content.isNotBlank() || c != ' ') { + content.append(c) + } + i++ + } + + saveProperty() + return JsDoc(content.toString().takeIf { it.isNotBlank() }?.trimEnd(), properties) +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsonUtils.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsonUtils.kt new file mode 100644 index 0000000..ed4381d --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/JsonUtils.kt @@ -0,0 +1,9 @@ +package vip.cdms.allaymc.nodejs.utils + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject + +operator fun JsonObject?.plus(other: JsonObject?): JsonObject = buildJsonObject { + this@plus?.forEach { (key, value) -> put(key, value) } + other?.forEach { (key, value) -> put(key, value) } +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/NpmRunner.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/NpmRunner.kt new file mode 100644 index 0000000..2ddb7fc --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/NpmRunner.kt @@ -0,0 +1,2 @@ +package vip.cdms.allaymc.nodejs.utils + diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/PackageJson.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/PackageJson.kt new file mode 100644 index 0000000..c5fcc50 --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/PackageJson.kt @@ -0,0 +1,34 @@ +package vip.cdms.allaymc.nodejs.utils + +import kotlinx.serialization.json.* + +@Suppress("OPT_IN_USAGE") +private val PackageJsonParser = Json { + allowComments = true + allowTrailingComma = true +} + +class PackageJson(val raw: JsonObject) { + val name get() = raw["name"]?.jsonPrimitive?.content + val version get() = raw["version"]?.jsonPrimitive?.content + val description get() = raw["description"]?.jsonPrimitive?.content + val main get() = raw["main"]?.jsonPrimitive?.content + +// val dependencies get() = raw["dependencies"]?.jsonObject?.toMap()?.mapValues { it.value.jsonPrimitive.content } + val allayDependencies get() = raw["allayDependencies"]?.jsonObject?.toMap()?.mapValues { it.value.jsonPrimitive.content } + + val author = raw["author"] + fun getAuthors(author: JsonElement? = this.author): MutableList? = when (author) { + is JsonPrimitive -> mutableListOf(author.content) + is JsonArray -> author.flatMap { getAuthors(it) ?: listOf() }.toMutableList() + is JsonObject -> author["name"]?.let { mutableListOf(it.jsonPrimitive.content) } + else -> null + } + + val homepage get() = raw["homepage"]?.jsonPrimitive?.content + + companion object { + fun parseFrom(json: String) = + PackageJson(PackageJsonParser.parseToJsonElement(json.takeIf { it.isNotBlank() } ?: "{}").jsonObject) + } +} diff --git a/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/ScriptHeader.kt b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/ScriptHeader.kt new file mode 100644 index 0000000..c991127 --- /dev/null +++ b/script-loader/src/main/kotlin/vip/cdms/allaymc/nodejs/utils/ScriptHeader.kt @@ -0,0 +1,37 @@ +package vip.cdms.allaymc.nodejs.utils + +data class SingleScriptHeader( + val descriptor: JsDoc, + val packageJson: PackageJson, +) + +fun parseScriptHeader(code: String): SingleScriptHeader { + val doc = parseJsDoc(code) + val content = StringBuilder() + val packageJson = StringBuilder() + + var isContent = true + + var i = 0 + if (doc.content != null) while (i < doc.content.length) { + val c = doc.content[i] + + fun substringOrNull(range: IntRange) = runCatching { doc.content.substring(range) }.getOrNull() + if (c == '\n' && substringOrNull(i+1..i+7) == "```json") { + isContent = false + i += 8 + } else if (c == '\n' && !isContent && substringOrNull(i+1..i+3) == "```") { + isContent = true + i += 4 + } else if (isContent) { + content.append(c) + } else { + packageJson.append(c) + } + + i++ + } + + val json = PackageJson.parseFrom(packageJson.toString()) + return SingleScriptHeader(doc.copy(content = content.toString().takeIf { it.isNotBlank() }), json) +} diff --git a/script-loader/src/main/resources/extension.json b/script-loader/src/main/resources/extension.json index b9bb178..dd67bf8 100644 --- a/script-loader/src/main/resources/extension.json +++ b/script-loader/src/main/resources/extension.json @@ -1,3 +1,3 @@ { - "entrance": "vip.cdms.allaymc.nodejs.NodeScriptExtension" + "entrance": "vip.cdms.allaymc.nodejs.NodePluginExtension" } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 829df79..435b7fa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,3 +20,4 @@ dependencyResolutionManagement { } include(":script-loader") +include(":script-definition")