From 9bbb1edd79757baa3b15de8b3c36a130380c431e Mon Sep 17 00:00:00 2001 From: Micah Date: Sat, 14 Feb 2026 11:10:36 -0800 Subject: [PATCH] Upload plugin as part of release workflow (#1227) --- .github/workflows/release.yml | 7 +++ .gitmodules | 3 ++ .lune/.config.luau | 8 ++++ .lune/opencloud-execute | 1 + .lune/scripts/plugin-upload.luau | 51 +++++++++++++++++++++ .lune/upload-plugin.luau | 78 ++++++++++++++++++++++++++++++++ build.rs | 1 + plugin.project.json | 3 ++ plugin/UploadDetails.json | 7 +++ rokit.toml | 1 + src/cli/plugin.rs | 2 +- 11 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 .lune/.config.luau create mode 160000 .lune/opencloud-execute create mode 100644 .lune/scripts/plugin-upload.luau create mode 100644 .lune/upload-plugin.luau create mode 100644 plugin/UploadDetails.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 581be871..f0a6e9d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,6 +44,13 @@ jobs: with: name: Rojo.rbxm path: Rojo.rbxm + + - name: Upload Plugin to Roblox + env: + RBX_API_KEY: ${{ secrets.PLUGIN_UPLOAD_TOKEN }} + RBX_UNIVERSE_ID: ${{ vars.PLUGIN_CI_PLACE_ID }} + RBX_PLACE_ID: ${{ vars.PLUGIN_CI_UNIVERSE_ID }} + run: lune run upload-plugin Rojo.rbxm build: needs: ["create-release"] diff --git a/.gitmodules b/.gitmodules index 90ebae43..59bf5d32 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "plugin/Packages/msgpack-luau"] path = plugin/Packages/msgpack-luau url = https://github.com/cipharius/msgpack-luau/ +[submodule ".lune/opencloud-execute"] + path = .lune/opencloud-execute + url = https://github.com/Dekkonot/opencloud-luau-execute-lune.git diff --git a/.lune/.config.luau b/.lune/.config.luau new file mode 100644 index 00000000..43665fdb --- /dev/null +++ b/.lune/.config.luau @@ -0,0 +1,8 @@ +return { + luau = { + languagemode = "strict", + aliases = { + lune = "~/.lune/.typedefs/0.10.4/", + }, + }, +} diff --git a/.lune/opencloud-execute b/.lune/opencloud-execute new file mode 160000 index 00000000..8ae86dd3 --- /dev/null +++ b/.lune/opencloud-execute @@ -0,0 +1 @@ +Subproject commit 8ae86dd3ad35eb66b0a4edda7e8771a5986cd0c6 diff --git a/.lune/scripts/plugin-upload.luau b/.lune/scripts/plugin-upload.luau new file mode 100644 index 00000000..c5708796 --- /dev/null +++ b/.lune/scripts/plugin-upload.luau @@ -0,0 +1,51 @@ +local args: any = ... +assert(args, "no arguments passed to script") + +local input: buffer = args.BinaryInput + +local AssetService = game:GetService("AssetService") +local SerializationService = game:GetService("SerializationService") +local EncodingService = game:GetService("EncodingService") + +local input_hash: buffer = EncodingService:ComputeBufferHash(input, Enum.HashAlgorithm.Sha256) +local hex_hash: { string } = table.create(buffer.len(input_hash)) +for i = 0, buffer.len(input_hash) - 1 do + table.insert(hex_hash, string.format("%02x", buffer.readu8(input_hash, i))) +end + +print(`Deserializing plugin file (size: {buffer.len(input)} bytes, hash: {table.concat(hex_hash, "")})`) +local plugin = SerializationService:DeserializeInstancesAsync(input)[1] + +local UploadDetails = require(plugin.UploadDetails) :: any +local PLUGIN_ID = UploadDetails.assetId +local PLUGIN_NAME = UploadDetails.name +local PLUGIN_DESCRIPTION = UploadDetails.description +local PLUGIN_CREATOR_ID = UploadDetails.creatorId +local PLUGIN_CREATOR_TYPE = UploadDetails.creatorType + +assert(typeof(PLUGIN_ID) == "number", "UploadDetails did not contain a number field 'assetId'") +assert(typeof(PLUGIN_NAME) == "string", "UploadDetails did not contain a string field 'name'") +assert(typeof(PLUGIN_DESCRIPTION) == "string", "UploadDetails did not contain a string field 'description'") +assert(typeof(PLUGIN_CREATOR_ID) == "number", "UploadDetails did not contain a number field 'creatorId'") +assert(typeof(PLUGIN_CREATOR_TYPE) == "string", "UploadDetails did not contain a string field 'creatorType'") +assert( + Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE) ~= nil, + "UploadDetails field 'creatorType' was not a valid member of Enum.AssetCreatorType" +) + +print(`Uploading to {PLUGIN_ID}`) +print(`Plugin Name: {PLUGIN_NAME}`) +print(`Plugin Description: {PLUGIN_DESCRIPTION}`) + +local result, version_or_err = AssetService:CreateAssetVersionAsync(plugin, Enum.AssetType.Plugin, PLUGIN_ID, { + ["Name"] = PLUGIN_NAME, + ["Description"] = PLUGIN_DESCRIPTION, + ["CreatorId"] = PLUGIN_CREATOR_ID, + ["CreatorType"] = Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE), +}) + +if result ~= Enum.CreateAssetResult.Success then + error(`Plugin failed to upload because: {result.Name} - {version_or_err}`) +end + +print(`Plugin uploaded successfully. New version is {version_or_err}.`) diff --git a/.lune/upload-plugin.luau b/.lune/upload-plugin.luau new file mode 100644 index 00000000..e71acf6c --- /dev/null +++ b/.lune/upload-plugin.luau @@ -0,0 +1,78 @@ +local fs = require("@lune/fs") +local process = require("@lune/process") +local stdio = require("@lune/stdio") + +local luau_execute = require("./opencloud-execute") + +local UNIVERSE_ID = process.env["RBX_UNIVERSE_ID"] +local PLACE_ID = process.env["RBX_PLACE_ID"] + +local version_string = fs.readFile("plugin/Version.txt") +local versions = { string.match(version_string, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") } +if versions[4] ~= "" then + print("This release is a pre-release. Skipping uploading plugin.") + process.exit(0) +end + +local plugin_path = process.args[1] +assert( + typeof(plugin_path) == "string", + "no plugin path provided, expected usage is `lune run upload-plugin [PATH TO RBXM]`." +) + +-- For local testing +if process.env["CI"] ~= "true" then + local rojo = process.exec("rojo", { "build", "plugin.project.json", "--output", plugin_path }) + if not rojo.ok then + stdio.ewrite("plugin upload failed because: could not build plugin.rbxm\n\n") + stdio.ewrite(rojo.stderr) + stdio.ewrite("\n") + process.exit(1) + end +else + assert(fs.isFile(plugin_path), `Plugin file did not exist at {plugin_path}`) +end +local plugin_content = fs.readFile(plugin_path) + +local engine_script = fs.readFile(".lune/scripts/plugin-upload.luau") + +print("Creating task to upload plugin") +local task = luau_execute.create_task_latest(UNIVERSE_ID, PLACE_ID, engine_script, 300, false, plugin_content) + +print("Waiting for task to finish") +local success = luau_execute.await_finish(task) +if not success then + local error = luau_execute.get_error(task) + assert(error, "could not fetch error from task") + stdio.ewrite("plugin upload failed because: task did not finish successfully\n\n") + stdio.ewrite(error.code) + stdio.ewrite("\n") + stdio.ewrite(error.message) + stdio.ewrite("\n") + process.exit(1) +end + +print("Output from task:\n") +for _, log in luau_execute.get_structured_logs(task) do + if log.messageType == "ERROR" then + stdio.write(stdio.color("red")) + stdio.write(log.message) + stdio.write("\n") + stdio.write(stdio.color("reset")) + elseif log.messageType == "INFO" then + stdio.write(stdio.color("cyan")) + stdio.write(log.message) + stdio.write("\n") + stdio.write(stdio.color("reset")) + elseif log.messageType == "WARNING" then + stdio.write(stdio.color("yellow")) + stdio.write(log.message) + stdio.write("\n") + stdio.write(stdio.color("reset")) + else + stdio.write(stdio.color("reset")) + stdio.write(log.message) + stdio.write("\n") + stdio.write(stdio.color("reset")) + end +end diff --git a/build.rs b/build.rs index 0f52585b..d0457fb1 100644 --- a/build.rs +++ b/build.rs @@ -75,6 +75,7 @@ fn main() -> Result<(), anyhow::Error> { "src" => snapshot_from_fs_path(&plugin_dir.join("src"))?, "Packages" => snapshot_from_fs_path(&plugin_dir.join("Packages"))?, "Version.txt" => snapshot_from_fs_path(&plugin_dir.join("Version.txt"))?, + "UploadDetails.json" => snapshot_from_fs_path(&plugin_dir.join("UploadDetails.json"))?, }), }); diff --git a/plugin.project.json b/plugin.project.json index 83265250..1742547b 100644 --- a/plugin.project.json +++ b/plugin.project.json @@ -22,6 +22,9 @@ }, "Version": { "$path": "plugin/Version.txt" + }, + "UploadDetails": { + "$path": "plugin/UploadDetails.json" } } } diff --git a/plugin/UploadDetails.json b/plugin/UploadDetails.json new file mode 100644 index 00000000..2396598f --- /dev/null +++ b/plugin/UploadDetails.json @@ -0,0 +1,7 @@ +{ + "assetId": 13916111004, + "name": "Rojo", + "description": "The plugin portion of Rojo, a tool to enable professional tooling for Roblox developers.", + "creatorId": 32644114, + "creatorType": "Group" +} \ No newline at end of file diff --git a/rokit.toml b/rokit.toml index 6fbd8746..67dafa12 100644 --- a/rokit.toml +++ b/rokit.toml @@ -3,3 +3,4 @@ rojo = "rojo-rbx/rojo@7.5.1" selene = "Kampfkarren/selene@0.29.0" stylua = "JohnnyMorganz/stylua@2.1.0" run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0" +lune = "lune-org/lune@0.10.4" diff --git a/src/cli/plugin.rs b/src/cli/plugin.rs index 03177654..af7025ca 100644 --- a/src/cli/plugin.rs +++ b/src/cli/plugin.rs @@ -98,5 +98,5 @@ fn uninstall_plugin() -> anyhow::Result<()> { #[test] fn plugin_initialize() { - assert!(initialize_plugin().is_ok()) + let _ = initialize_plugin().unwrap(); }