From 4cd6fd6e0beeb8eeba8951d0035725155c998ae6 Mon Sep 17 00:00:00 2001 From: vocksel Date: Wed, 20 Nov 2024 17:46:58 -0800 Subject: [PATCH 1/6] Plugin publishing workflow (#285) This PR introduces some automation to publish Flipbook builds to the [dev plugin](https://create.roblox.com/store/asset/88523969718241/flipbook-dev) one merge to `main`. Now all our recent changes will be testable in Studio without any extra work on our part Later on I plan to introduce automation to publish to the production plugin, but for now I just want to setup nightlies Resolves #284 --- .github/workflows/release.yml | 17 ++ .gitignore | 3 + .lune/build.luau | 12 -- .lune/lib/compile.luau | 12 ++ .lune/lib/run.luau | 13 +- .lune/open-cloud/luau_execution_task.py | 272 ++++++++++++++++++++++++ .lune/open-cloud/upload_and_run_task.py | 69 ++++++ .lune/publish-plugin.luau | 56 +++++ .lune/tasks/publish-plugin-asset.luau | 40 ++++ project.luau | 8 + 10 files changed, 484 insertions(+), 18 deletions(-) create mode 100644 .lune/open-cloud/luau_execution_task.py create mode 100644 .lune/open-cloud/upload_and_run_task.py create mode 100644 .lune/publish-plugin.luau create mode 100644 .lune/tasks/publish-plugin-asset.luau create mode 100644 project.luau diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8398432a..a2f08b50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,3 +32,20 @@ jobs: files: ${{ env.MODEL_FILE }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-plugin-nightly: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + + - uses: Roblox/setup-foreman@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install packages + run: lune run wally-install + + - name: Publish to Creator Store + run: lune run publish-plugin -- --apiKey ${{ secrets.ROBLOX_API_KEY }} diff --git a/.gitignore b/.gitignore index e714241d..e6d8884c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,8 @@ DevPackages Packages wally.lock +# Python +__pycache__ + # Other /*.log diff --git a/.lune/build.luau b/.lune/build.luau index cd917de7..2f827397 100644 --- a/.lune/build.luau +++ b/.lune/build.luau @@ -15,22 +15,10 @@ assert(target == "dev" or target == "prod", `bad value for target (must be one o local output = if args.output then args.output else `{getPluginsPath(process.os)}/{constants.PLUGIN_FILENAME}` assert(typeof(output) == "string", `bad value for output (string expected, got {typeof(output)})`) -local PRUNED_FILES = { - "*.spec.luau", - "*.story.luau", - "*.storybook.luau", -} - local function build() clean() compile(target) - if target == "prod" then - for _, pattern in PRUNED_FILES do - run("find", { constants.BUILD_PATH, "-type", "f", "-name", pattern, "-delete" }) - end - end - run("rojo", { "build", "-o", output }) end diff --git a/.lune/lib/compile.luau b/.lune/lib/compile.luau index 1b6db7f6..40577edd 100644 --- a/.lune/lib/compile.luau +++ b/.lune/lib/compile.luau @@ -4,6 +4,12 @@ local run = require("./run") type Target = "prod" | "dev" +local PRUNED_FILES = { + "*.spec.luau", + "*.story.luau", + "*.storybook.luau", +} + local function compile(target: Target) fs.writeDir(constants.BUILD_PATH) @@ -27,6 +33,12 @@ local function compile(target: Target) `{constants.BUILD_PATH}/Example`, }) end + + if target == "prod" then + for _, pattern in PRUNED_FILES do + run("find", { constants.BUILD_PATH, "-type", "f", "-name", pattern, "-delete" }) + end + end end return compile diff --git a/.lune/lib/run.luau b/.lune/lib/run.luau index 357c3e79..6d2dc3b6 100644 --- a/.lune/lib/run.luau +++ b/.lune/lib/run.luau @@ -18,15 +18,16 @@ local function run(program: string, params: { string }, options: Options?) env = if options then options.env else nil, }) - if result.code > 0 then - process.exit(result.code) + local out + if result.ok then + out = result.stdout + else + out = result.stderr end - local output = if result.ok then result.stdout else result.stderr + out = out:gsub("\n$", "") - output = output:gsub("\n$", "") - - return output + return out, result.ok end return run diff --git a/.lune/open-cloud/luau_execution_task.py b/.lune/open-cloud/luau_execution_task.py new file mode 100644 index 00000000..31acc64f --- /dev/null +++ b/.lune/open-cloud/luau_execution_task.py @@ -0,0 +1,272 @@ +# upstream: https://raw.githubusercontent.com/Roblox/place-ci-cd-demo/refs/heads/production/scripts/python/luau_execution_task.py + +import argparse +import logging +import urllib.request +import urllib.error +import base64 +import sys +import json +import time +import hashlib +import os + + +def parseArgs(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "-k", + "--api-key", + help="Path to a file containing your OpenCloud API key. You can also use the environment variable RBLX_OC_API_KEY to specify the API key. This option takes precedence over the environment variable.", + metavar="", + ) + parser.add_argument( + "-u", + "--universe", + "-e", + "--experience", + required=True, + help="ID of the experience (a.k.a. universe) containing the place you want to execute the task against.", + metavar="", + type=int, + ) + parser.add_argument( + "-p", + "--place", + required=True, + help="ID of the place you want to execute the task against.", + metavar="", + type=int, + ) + parser.add_argument( + "-v", + "--place-version", + help="Version of the place you want to execute the task against. If not given, the latest version of the place will be used.", + metavar="", + type=int, + ) + parser.add_argument( + "-f", + "--script-file", + required=True, + help="Path to a file containing your Luau script.", + metavar="", + ) + parser.add_argument( + "-c", + "--continuous", + help="If specified, this script will run in a loop and automatically create a new task after the previous task has finished, but only if the script file is updated. If the script file has not been updated, this script will wait for it to be updated before submitting a new task.", + action="store_true", + ) + parser.add_argument( + "-o", + "--output", + help="Path to a file to write the task's output to. If not given, output is written to stdout.", + metavar="", + ) + parser.add_argument( + "-l", + "--log-output", + help="Path to a file to write the task's logs to. If not given, logs are written to stderr.", + metavar="", + ) + + return parser.parse_args() + + +def makeRequest(url, headers, body=None): + data = None + if body is not None: + data = body.encode("utf8") + request = urllib.request.Request( + url, data=data, headers=headers, method="GET" if body is None else "POST" + ) + max_attempts = 3 + for i in range(max_attempts): + try: + return urllib.request.urlopen(request) + except Exception as e: + if "certificate verify failed" in str(e): + logging.error( + f"{str(e)} - you may need to install python certificates, see https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error" + ) + sys.exit(1) + if i == max_attempts - 1: + raise e + else: + logging.info(f"Retrying error: {str(e)}") + time.sleep(1) + + +def readFileExitOnFailure(path, file_description): + try: + with open(path, "r") as f: + return f.read() + except FileNotFoundError: + logging.error(f"{file_description.capitalize()} file not found: {path}") + except IsADirectoryError: + logging.error(f"Invalid {file_description} file: {path} is a directory") + except PermissionError: + logging.error(f"Permission denied to read {file_description} file: {path}") + sys.exit(1) + + +def loadAPIKey(api_key_arg): + source = "" + if api_key_arg: + api_key_arg = api_key_arg.strip() + source = f"file {api_key_arg}" + key = readFileExitOnFailure(api_key_arg, "API key").strip() + else: + if "RBLX_OC_API_KEY" not in os.environ: + logging.error( + "API key needed. Either provide the --api-key option or set the RBLX_OC_API_KEY environment variable." + ) + sys.exit(1) + source = "environment variable RBLX_OC_API_KEY" + key = os.environ["RBLX_OC_API_KEY"].strip() + + try: + base64.b64decode(key, validate=True) + return key + except Exception as e: + logging.error( + f"API key appears invalid (not valid base64, loaded from {source}): {str(e)}" + ) + sys.exit(1) + + +def createTask(api_key, script, universe_id, place_id, place_version): + headers = {"Content-Type": "application/json", "x-api-key": api_key} + data = {"script": script} + url = f"https://apis.roblox.com/cloud/v2/universes/{universe_id}/places/{place_id}/" + if place_version: + url += f"versions/{place_version}/" + url += "luau-execution-session-tasks" + + try: + response = makeRequest(url, headers=headers, body=json.dumps(data)) + except urllib.error.HTTPError as e: + logging.error(f"Create task request failed, response body:\n{e.fp.read()}") + sys.exit(1) + + task = json.loads(response.read()) + return task + + +def pollForTaskCompletion(api_key, path): + headers = {"x-api-key": api_key} + url = f"https://apis.roblox.com/cloud/v2/{path}" + + logging.info("Waiting for task to finish...") + + while True: + try: + response = makeRequest(url, headers=headers) + except urllib.error.HTTPError as e: + logging.error(f"Get task request failed, response body:\n{e.fp.read()}") + sys.exit(1) + + task = json.loads(response.read()) + if task["state"] != "PROCESSING": + sys.stderr.write("\n") + sys.stderr.flush() + return task + else: + sys.stderr.write(".") + sys.stderr.flush() + time.sleep(3) + + +def getTaskLogs(api_key, task_path): + headers = {"x-api-key": api_key} + url = f"https://apis.roblox.com/cloud/v2/{task_path}/logs" + + try: + response = makeRequest(url, headers=headers) + except urllib.error.HTTPError as e: + logging.error(f"Get task logs request failed, response body:\n{e.fp.read()}") + sys.exit(1) + + logs = json.loads(response.read()) + messages = logs["luauExecutionSessionTaskLogs"][0]["messages"] + return "".join([m + "\n" for m in messages]) + + +def handleLogs(task, log_output_file_path, api_key): + logs = getTaskLogs(api_key, task["path"]) + if logs: + if log_output_file_path: + with open(log_output_file_path, "w") as f: + f.write(logs) + logging.info(f"Task logs written to {log_output_file_path}") + else: + logging.info(f"Task logs:\n{logs.strip()}") + else: + logging.info("The task did not produce any logs") + + +def handleSuccess(task, output_path): + output = task["output"] + if output["results"]: + if output_path: + with open(output_path, "w") as f: + f.write(json.dumps(output["results"])) + logging.info(f"Task results written to {output_path}") + else: + logging.info("Task output:") + print(json.dumps(output["results"])) + else: + logging.info("The task did not return any results") + + +def handleFailure(task): + logging.error(f'Task failed, error:\n{json.dumps(task["error"])}') + + +if __name__ == "__main__": + logging.basicConfig( + format="[%(asctime)s] [%(name)s] [%(levelname)s]: %(message)s", + level=logging.INFO, + ) + + args = parseArgs() + + api_key = loadAPIKey(args.api_key) + + waiting_msg_printed = False + prev_script_hash = None + while True: + script = readFileExitOnFailure(args.script_file, "script") + script_hash = hashlib.sha256(script.encode("utf8")).hexdigest() + + if prev_script_hash is not None and script_hash == prev_script_hash: + if not waiting_msg_printed: + logging.info("Waiting for changes to script file...") + waiting_msg_printed = True + time.sleep(1) + continue + + if prev_script_hash is not None: + logging.info("Detected change to script file, submitting new task") + + prev_script_hash = script_hash + waiting_msg_printed = False + + task = createTask( + api_key, script, args.universe, args.place, args.place_version + ) + logging.info(f"Task created, path: {task['path']}") + + task = pollForTaskCompletion(api_key, task["path"]) + logging.info(f'Task is now in {task["state"]} state') + + handleLogs(task, args.log_output, api_key) + if task["state"] == "COMPLETE": + handleSuccess(task, args.output) + else: + handleFailure(task) + + if not args.continuous: + break diff --git a/.lune/open-cloud/upload_and_run_task.py b/.lune/open-cloud/upload_and_run_task.py new file mode 100644 index 00000000..264ceb1a --- /dev/null +++ b/.lune/open-cloud/upload_and_run_task.py @@ -0,0 +1,69 @@ +# upstream: https://github.com/Roblox/place-ci-cd-demo/blob/production/scripts/python/upload_and_run_task.py +import os +import sys +import urllib.request +import json + +from luau_execution_task import createTask, pollForTaskCompletion, getTaskLogs + +ROBLOX_API_KEY = os.environ["ROBLOX_API_KEY"] +ROBLOX_UNIVERSE_ID = os.environ["ROBLOX_UNIVERSE_ID"] +ROBLOX_PLACE_ID = os.environ["ROBLOX_PLACE_ID"] + + +def read_file(file_path): + with open(file_path, "rb") as file: + return file.read() + + +def upload_place(binary_path, universe_id, place_id, do_publish=False): + print("Uploading place to Roblox") + version_type = "Published" if do_publish else "Saved" + request_headers = { + "x-api-key": ROBLOX_API_KEY, + "Content-Type": "application/xml", + "Accept": "application/json", + } + + url = f"https://apis.roblox.com/universes/v1/{universe_id}/places/{place_id}/versions?versionType={version_type}" + + buffer = read_file(binary_path) + req = urllib.request.Request( + url, data=buffer, headers=request_headers, method="POST" + ) + + with urllib.request.urlopen(req) as response: + data = json.loads(response.read().decode("utf-8")) + place_version = data.get("versionNumber") + + return place_version + + +def run_luau_task(universe_id, place_id, place_version, script_file): + print("Executing Luau task") + script_contents = read_file(script_file).decode("utf8") + + task = createTask( + ROBLOX_API_KEY, script_contents, universe_id, place_id, place_version + ) + task = pollForTaskCompletion(ROBLOX_API_KEY, task["path"]) + logs = getTaskLogs(ROBLOX_API_KEY, task["path"]) + + print(logs) + + if task["state"] == "COMPLETE": + print("Luau task completed successfully") + exit(0) + else: + print("Luau task failed", file=sys.stderr) + exit(1) + + +if __name__ == "__main__": + universe_id = ROBLOX_UNIVERSE_ID + place_id = ROBLOX_PLACE_ID + binary_file = sys.argv[1] + script_file = sys.argv[2] + + place_version = upload_place(binary_file, universe_id, place_id) + run_luau_task(universe_id, place_id, place_version, script_file) diff --git a/.lune/publish-plugin.luau b/.lune/publish-plugin.luau new file mode 100644 index 00000000..004c13fd --- /dev/null +++ b/.lune/publish-plugin.luau @@ -0,0 +1,56 @@ +local process = require("@lune/process") + +local parseArgs = require("./lib/parseArgs") +local run = require("./lib/run") +local clean = require("./lib/clean") +local compile = require("./lib/compile") +local project = require("../project") + +local args = parseArgs(process.args) + +local target = if args.target then args.target else "dev" +assert(target == "dev" or target == "prod", `bad value for target (must be one of "dev" or "prod", got "{target}")`) + +local apiKey = assert(args.apiKey, "--apiKey must be supplied with a valid Open Cloud API key") +assert(typeof(apiKey) == "string", `bad value for apiKey (string expected, got {typeof(apiKey)})`) + +clean() +compile("prod") + +run("rojo", { "build", "tests.project.json", "-o", "tests.rbxl" }) + +local publishPluginAssetTask = "build/publish-plugin-asset.luau" + +do -- mini darklua for swapping out globals in the task with real values + run("cp", { ".lune/tasks/publish-plugin-asset.luau", publishPluginAssetTask }) + + local GLOBAL_SUBSTITUTIONS = { + ROBLOX_CREATOR_ID = project.ROBLOX_ASSET_CREATOR_ID, + ROBLOX_ASSET_ID = if target == "dev" + then project.ROBLOX_PLUGIN_ASSET_ID_DEV + else project.ROBLOX_PLUGIN_ASSET_ID_PROD, + } + + for global, value in GLOBAL_SUBSTITUTIONS do + run("sed", { "-i", "-e", `s/_G.{global}/{value}/g`, publishPluginAssetTask }) + end +end + +local output, success = run("python3", { + ".lune/open-cloud/upload_and_run_task.py", + "tests.rbxl", + publishPluginAssetTask, +}, { + env = { + ROBLOX_UNIVERSE_ID = project.ROBLOX_UNIVERSE_ID, + ROBLOX_PLACE_ID = project.ROBLOX_PLACE_ID, + ROBLOX_API_KEY = apiKey, + }, +}) + +run("rm", { "tests.rbxl" }) + +if not success then + print(output) + process.exit(1) +end diff --git a/.lune/tasks/publish-plugin-asset.luau b/.lune/tasks/publish-plugin-asset.luau new file mode 100644 index 00000000..3d59313f --- /dev/null +++ b/.lune/tasks/publish-plugin-asset.luau @@ -0,0 +1,40 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local AssetService = game:GetService("AssetService") + +local CREATOR_ID: number = _G.ROBLOX_CREATOR_ID +assert(typeof(CREATOR_ID) == "number", "_G.ROBLOX_CREATOR_ID must be supplied") + +local ASSET_ID: number? = _G.ROBLOX_ASSET_ID + +local root = ReplicatedStorage:FindFirstChild("flipbook") +assert(root, "no plugin found for upload") + +local assetId: number? +local versionNumber = 1 + +if ASSET_ID == nil then + print("Creating plugin asset for the first time") + local _ + _, assetId = AssetService:CreateAssetAsync(root, Enum.AssetType.Plugin, { + Name = "Simple Plugin", + CreatorId = CREATOR_ID, + CreatorType = Enum.CreatorType.User, + }) +else + print(`Updating asset with ID {ASSET_ID}`) + assetId = ASSET_ID + local _ + _, versionNumber = AssetService:CreateAssetVersionAsync(root, Enum.AssetType.Plugin, assetId, { + CreatorId = CREATOR_ID, + CreatorType = Enum.CreatorType.User, + }) + print("Created new version", versionNumber) +end + +print("Plugin uploaded successfully! View it on the Creator Store:") +print(`https://create.roblox.com/store/asset/{assetId}`) + +return { + assetId = tostring(assetId), -- we make it a string because the Luau exec API serializes integers as floating points + versionNumber = tostring(versionNumber), +} diff --git a/project.luau b/project.luau new file mode 100644 index 00000000..6db7fd69 --- /dev/null +++ b/project.luau @@ -0,0 +1,8 @@ +return { + ROBLOX_UNIVERSE_ID = "6599100156", + ROBLOX_PLACE_ID = "123506190725771", + + ROBLOX_ASSET_CREATOR_ID = "1343930", + ROBLOX_PLUGIN_ASSET_ID_PROD = "8517129161", + ROBLOX_PLUGIN_ASSET_ID_DEV = "88523969718241", +} From 49d6eb5c6c148c606d1ea4eab774b2ac78ef6d79 Mon Sep 17 00:00:00 2001 From: vocksel Date: Thu, 21 Nov 2024 06:35:50 -0800 Subject: [PATCH 2/6] Auto-publish to production (#286) --- .github/workflows/release.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2f08b50..e00f99e8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,12 @@ on: pull_request: release: types: [published] + push: + branches: + - main jobs: - publish: + publish-github-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -33,6 +36,24 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + publish-plugin: + runs-on: ubuntu-latest + if: github.event.release + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + + - uses: Roblox/setup-foreman@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install packages + run: lune run wally-install + + - name: Publish to Creator Store + if: ${{ github.event.release }} + run: lune run publish-plugin -- --target prod --apiKey ${{ secrets.ROBLOX_API_KEY }} + publish-plugin-nightly: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' @@ -48,4 +69,4 @@ jobs: run: lune run wally-install - name: Publish to Creator Store - run: lune run publish-plugin -- --apiKey ${{ secrets.ROBLOX_API_KEY }} + run: lune run publish-plugin -- --target dev --apiKey ${{ secrets.ROBLOX_API_KEY }} From a4bbd294d4b9a6d90887e0d6f418272d42a1b253 Mon Sep 17 00:00:00 2001 From: vocksel Date: Thu, 21 Nov 2024 10:43:27 -0800 Subject: [PATCH 3/6] Bump version in wally.toml (#288) --- wally.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wally.toml b/wally.toml index 8262045e..e95907af 100644 --- a/wally.toml +++ b/wally.toml @@ -1,6 +1,6 @@ [package] name = "flipbook-labs/flipbook" -version = "1.5.1" +version = "2.0.0" license = "MIT" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" From 399841072ed9afdf8dcab520fdc5efc990a9738a Mon Sep 17 00:00:00 2001 From: vocksel Date: Thu, 21 Nov 2024 10:48:48 -0800 Subject: [PATCH 4/6] Inject build information when compiling (#287) # Problem It can be useful to know the version, channel, and commit hash for a given build of Flipbook, especially to help when debugging. We only include the version right now, the other two would be useful # Solution Created a new BuildInfo component which gets mounted on AboutView. It leverages some new globals that get injected during the darklua build step. I've also removed the inclusion of `wally.toml` in the Flipbook plugin build since the only info we were using was the version, and that's now supplied via `_G.BUILD_VERSION` # Checklist - [x] Ran `lune run test` locally before merging --- .darklua.json | 17 ++++++++++++++- .lune/lib/compile.luau | 17 +++++++++++++++ build.project.json | 3 --- default.project.json | 3 --- src/About/AboutView.luau | 10 ++++++--- src/About/BuildInfo.luau | 45 ++++++++++++++++++++++++++++++++++++++++ src/constants.luau | 5 ----- src/init.server.luau | 5 ----- 8 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 src/About/BuildInfo.luau diff --git a/.darklua.json b/.darklua.json index 183d337f..8d77ddb4 100644 --- a/.darklua.json +++ b/.darklua.json @@ -15,6 +15,21 @@ "indexing_style": "property" } }, + { + "rule": "inject_global_value", + "identifier": "BUILD_VERSION", + "env": "BUILD_VERSION" + }, + { + "rule": "inject_global_value", + "identifier": "BUILD_CHANNEL", + "env": "BUILD_CHANNEL" + }, + { + "rule": "inject_global_value", + "identifier": "BUILD_HASH", + "env": "BUILD_HASH" + }, "compute_expression", "remove_unused_if_branch", "remove_unused_while", @@ -22,4 +37,4 @@ "remove_nil_declaration", "remove_empty_do" ] -} \ No newline at end of file +} diff --git a/.lune/lib/compile.luau b/.lune/lib/compile.luau index 40577edd..148ac351 100644 --- a/.lune/lib/compile.luau +++ b/.lune/lib/compile.luau @@ -1,7 +1,10 @@ local constants = require("./constants") local fs = require("@lune/fs") +local serde = require("@lune/serde") local run = require("./run") +local wallyToml = serde.decode("toml", fs.readFile("wally.toml")) + type Target = "prod" | "dev" local PRUNED_FILES = { @@ -13,6 +16,14 @@ local PRUNED_FILES = { local function compile(target: Target) fs.writeDir(constants.BUILD_PATH) + local commitHash = run("git", { "rev-parse", "--short", "HEAD" }) + + local env = { + BUILD_VERSION = wallyToml.package.version, + BUILD_CHANNEL = if target == "prod" then "production" else "development", + BUILD_HASH = commitHash, + } + run("rojo", { "sourcemap", constants.ROJO_BUILD_PROJECT, @@ -20,10 +31,14 @@ local function compile(target: Target) constants.DARKLUA_SOURCEMAP_PATH, }) + print("substituting globals", env) + run("darklua", { "process", constants.SOURCE_PATH, constants.BUILD_PATH, + }, { + env = env, }) if target == "dev" then @@ -31,6 +46,8 @@ local function compile(target: Target) "process", "example", `{constants.BUILD_PATH}/Example`, + }, { + env = env, }) end diff --git a/build.project.json b/build.project.json index 42b0edf5..d723f103 100644 --- a/build.project.json +++ b/build.project.json @@ -7,9 +7,6 @@ "Example": { "$path": "example" }, - "wally.toml": { - "$path": "wally.toml" - }, "$path": "src" } } diff --git a/default.project.json b/default.project.json index d10e409e..dd08eca6 100644 --- a/default.project.json +++ b/default.project.json @@ -4,9 +4,6 @@ "Packages": { "$path": "Packages" }, - "wally.toml": { - "$path": "wally.toml" - }, "$path": "build" } } diff --git a/src/About/AboutView.luau b/src/About/AboutView.luau index fec7ba36..79c49bcf 100644 --- a/src/About/AboutView.luau +++ b/src/About/AboutView.luau @@ -1,11 +1,11 @@ local React = require("@pkg/React") local RobloxProfile = require("@root/About/RobloxProfile") +local BuildInfo = require("@root/About/BuildInfo") local Sprite = require("@root/Common/Sprite") local assets = require("@root/assets") local nextLayoutOrder = require("@root/Common/nextLayoutOrder") local useTheme = require("@root/Common/useTheme") -local wally = require(script.Parent.Parent["wally.toml"]) local useMemo = React.useMemo @@ -58,7 +58,7 @@ local function AboutView() Font = theme.font, LayoutOrder = nextLayoutOrder(), Size = UDim2.fromScale(0, 0), - Text = `flipbook v{wally.package.version}`, + Text = `flipbook {if _G.BUILD_VERSION then _G.BUILD_VERSION else "unreleased"}`, TextColor3 = theme.text, TextSize = theme.headerTextSize, TextWrapped = true, @@ -130,6 +130,10 @@ local function AboutView() }, authors), }), + BuildInfo = React.createElement(BuildInfo, { + layoutOrder = nextLayoutOrder(), + }), + Copy = React.createElement("TextLabel", { AutomaticSize = Enum.AutomaticSize.XY, BackgroundTransparency = 1, @@ -137,7 +141,7 @@ local function AboutView() LayoutOrder = nextLayoutOrder(), Size = UDim2.fromScale(0, 0), Text = `Copyright © 2021—{currentYear} flipbook-labs`, - TextColor3 = theme.text, + TextColor3 = theme.textSubtitle, TextSize = theme.textSize, TextWrapped = true, }), diff --git a/src/About/BuildInfo.luau b/src/About/BuildInfo.luau new file mode 100644 index 00000000..6da02163 --- /dev/null +++ b/src/About/BuildInfo.luau @@ -0,0 +1,45 @@ +local React = require("@pkg/React") + +local nextLayoutOrder = require("@root/Common/nextLayoutOrder") +local useTheme = require("@root/Common/useTheme") + +local BUILD_INFO = { + { label = "Version", value = _G.BUILD_VERSION }, + { label = "Channel", value = _G.BUILD_CHANNEL }, + { label = "Hash", value = _G.BUILD_HASH }, +} + +export type Props = { + layoutOrder: number?, +} + +local function BuildInfo(props: Props) + local theme = useTheme() + + local children: { [string]: React.Node } = {} + for _, info in BUILD_INFO do + children[info.label] = React.createElement("TextLabel", { + LayoutOrder = nextLayoutOrder(), + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + Font = theme.font, + Text = `{info.label}: {info.value}`, + TextColor3 = theme.textSubtitle, + TextSize = theme.textSize, + }) + end + + return React.createElement("Frame", { + LayoutOrder = props.layoutOrder, + AutomaticSize = Enum.AutomaticSize.XY, + BackgroundTransparency = 1, + }, { + Layout = React.createElement("UIListLayout", { + SortOrder = Enum.SortOrder.LayoutOrder, + HorizontalAlignment = Enum.HorizontalAlignment.Center, + Padding = theme.paddingSmall, + }), + }, children) +end + +return BuildInfo diff --git a/src/constants.luau b/src/constants.luau index 325e7474..78fe8291 100644 --- a/src/constants.luau +++ b/src/constants.luau @@ -10,11 +10,6 @@ return { CONTROLS_MIN_HEIGHT = 100, -- px CONTROLS_MAX_HEIGHT = 400, -- px - -- Enabling dev mode will add flipbook's storybook to the list of available - -- storybooks to make localy testing easier. It also adds a [DEV] tag to the - -- plugin - IS_DEV_MODE = false, - SPRING_CONFIG = { clamp = true, mass = 0.6, diff --git a/src/init.server.luau b/src/init.server.luau index 3a775379..3cbe3155 100644 --- a/src/init.server.luau +++ b/src/init.server.luau @@ -10,16 +10,11 @@ local ReactRoblox = require("@pkg/ReactRoblox") local ContextProviders = require("@root/Common/ContextProviders") local PluginApp = require("@root/Plugin/PluginApp") -local constants = require("@root/constants") local createToggleButton = require("@root/Plugin/createToggleButton") local createWidget = require("@root/Plugin/createWidget") local PLUGIN_NAME = "flipbook" -if constants.IS_DEV_MODE then - PLUGIN_NAME = "flipbook [DEV]" -end - local toolbar = plugin:CreateToolbar(PLUGIN_NAME) local widget = createWidget(plugin, PLUGIN_NAME) local root = ReactRoblox.createRoot(widget) From a8118ab01f2bee2d865c6ff324bc034ae88ac848 Mon Sep 17 00:00:00 2001 From: vocksel Date: Thu, 21 Nov 2024 11:05:39 -0800 Subject: [PATCH 5/6] Fix docs deployment (#289) --- .github/workflows/docs.yml | 5 +++-- .github/workflows/release.yml | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4102a53f..13b7881e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,6 +34,7 @@ jobs: path: docs/build deploy-docs: + if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' needs: build-docs permissions: pages: write @@ -42,7 +43,7 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest - if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' steps: - - uses: actions/deploy-pages@v4 + - name: Deploy to GitHub Pages id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e00f99e8..d0e076cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,8 @@ on: jobs: publish-github-release: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v3 From 64c2e6b35b9414e0a7c9f74d19c6780a6fedfb83 Mon Sep 17 00:00:00 2001 From: vocksel Date: Sun, 24 Nov 2024 09:12:27 -0800 Subject: [PATCH 6/6] Run publish-plugin on PR (#294) Deployment steps are reserved for specific events, but it's helpful to include as many steps in a workflow as we can in CI to catch issues early. --- .github/workflows/release.yml | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0e076cc..6a1bb21f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,6 @@ jobs: publish-plugin: runs-on: ubuntu-latest - if: github.event.release timeout-minutes: 10 steps: - uses: actions/checkout@v3 @@ -52,23 +51,10 @@ jobs: - name: Install packages run: lune run wally-install - - name: Publish to Creator Store - if: ${{ github.event.release }} - run: lune run publish-plugin -- --target prod --apiKey ${{ secrets.ROBLOX_API_KEY }} - - publish-plugin-nightly: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - - - uses: Roblox/setup-foreman@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Install packages - run: lune run wally-install - - - name: Publish to Creator Store + - name: Publish nightly build to Creator Store + if: github.ref == 'refs/heads/main' run: lune run publish-plugin -- --target dev --apiKey ${{ secrets.ROBLOX_API_KEY }} + + - name: Publish release to Creator Store + if: github.event.release + run: lune run publish-plugin -- --target prod --apiKey ${{ secrets.ROBLOX_API_KEY }}