Skip to content

Commit

Permalink
feat: basic project structure
Browse files Browse the repository at this point in the history
Cdm2883 committed Jan 20, 2025
1 parent 1d5259a commit c4691ac
Showing 22 changed files with 308 additions and 45 deletions.
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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" }
1 change: 1 addition & 0 deletions script-compat/todo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
compat another server's js engine (e.g. lse)
Empty file.
16 changes: 16 additions & 0 deletions script-example/single-script-example.cjs
Original file line number Diff line number Diff line change
@@ -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!")
17 changes: 17 additions & 0 deletions script-example/single-script-example.js
Original file line number Diff line number Diff line change
@@ -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!")
9 changes: 9 additions & 0 deletions script-example/ts-projectization/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
1 change: 1 addition & 0 deletions script-example/ts-projectization/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// TODO ts example
7 changes: 6 additions & 1 deletion script-loader/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<ExternalModuleDependency> {
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" })
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package vip.cdms.allaymc.nodejs

import org.allaymc.api.plugin.Plugin

class NodePlugin(val loader: NodePluginLoader) : Plugin() {
}
Original file line number Diff line number Diff line change
@@ -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<out String>?) {
AllayPluginManager.registerSource(NodeScriptSource)
AllayPluginManager.registerLoaderFactory(NodeScriptLoader.Factory)
AllayPluginManager.registerSource(NodePluginSource)
AllayPluginManager.registerLoaderFactory(NodePluginLoader.Factory)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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<NodeRuntime>()
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package vip.cdms.allaymc.nodejs.utils

data class JsDoc(
val content: String?,
val properties: Map<String, String>,
)

fun parseJsDoc(doc: String): JsDoc {
val content = StringBuilder()
val properties = mutableMapOf<String, String>()

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)
}
Original file line number Diff line number Diff line change
@@ -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) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package vip.cdms.allaymc.nodejs.utils

Original file line number Diff line number Diff line change
@@ -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<String>? = 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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion script-loader/src/main/resources/extension.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"entrance": "vip.cdms.allaymc.nodejs.NodeScriptExtension"
"entrance": "vip.cdms.allaymc.nodejs.NodePluginExtension"
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -20,3 +20,4 @@ dependencyResolutionManagement {
}

include(":script-loader")
include(":script-definition")

0 comments on commit c4691ac

Please sign in to comment.