From ad081ea0583e4e44808ca99b28bec5b15d8f1f63 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 3 Jul 2026 21:13:01 +0200 Subject: [PATCH 1/2] Implement CDN uploading --- .github/actions/publish-apps/action.yml | 21 ++++++++ .github/actions/release-apps/action.yml | 10 +++- .github/workflows/main.yml | 17 +++++++ Buildscripts/CDN/upload-app-files.py | 65 +++++++++++++++++++++++++ release.py | 27 ++++++++-- 5 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 .github/actions/publish-apps/action.yml create mode 100644 Buildscripts/CDN/upload-app-files.py diff --git a/.github/actions/publish-apps/action.yml b/.github/actions/publish-apps/action.yml new file mode 100644 index 0000000..e9d7cac --- /dev/null +++ b/.github/actions/publish-apps/action.yml @@ -0,0 +1,21 @@ +name: Publish Apps + +inputs: + sdk_version: + description: The SDK version that determines the path on the CDN + required: true + +runs: + using: 'composite' + steps: + - name: 'Download cdn-files' + uses: actions/download-artifact@v4 + with: + name: 'cdn-files' + path: cdn_files + - name: 'Install boto3' + shell: bash + run: pip install boto3 + - name: 'Upload files' + shell: bash + run: python Buildscripts/CDN/upload-app-files.py cdn_files ${{ inputs.sdk_version }} ${{ env.CDN_ID }} ${{ env.CDN_TOKEN_NAME }} ${{ env.CDN_TOKEN_VALUE }} diff --git a/.github/actions/release-apps/action.yml b/.github/actions/release-apps/action.yml index 9c9b715..0b4b08e 100644 --- a/.github/actions/release-apps/action.yml +++ b/.github/actions/release-apps/action.yml @@ -1,5 +1,10 @@ name: Release Apps +outputs: + sdk_version: + description: 'Common SDK version shared by all bundled apps' + value: ${{ steps.release.outputs.sdk_version }} + runs: using: 'composite' steps: @@ -11,8 +16,11 @@ runs: run: rsync -av downloaded_apps/*/*.app cdn_files/ shell: bash - name: 'Create CDN release files' - run: python release.py cdn_files/ + id: release shell: bash + run: | + python release.py cdn_files/ + echo "sdk_version=$(cat sdk_version.txt)" >> "$GITHUB_OUTPUT" - name: 'Upload Artifact' uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9906cbf..965ca6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,24 @@ jobs: Bundle: runs-on: ubuntu-latest needs: [Build] + outputs: + sdk_version: ${{ steps.release.outputs.sdk_version }} steps: - uses: actions/checkout@v4 - name: "Build" + id: release uses: ./.github/actions/release-apps + PublishApps: + runs-on: ubuntu-latest + needs: [Bundle] + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') + steps: + - uses: actions/checkout@v4 + - name: "Publish Apps" + env: + CDN_ID: ${{ secrets.CDN_ID }} + CDN_TOKEN_NAME: ${{ secrets.CDN_TOKEN_NAME }} + CDN_TOKEN_VALUE: ${{ secrets.CDN_TOKEN_VALUE }} + uses: ./.github/actions/publish-apps + with: + sdk_version: ${{ needs.Bundle.outputs.sdk_version }} diff --git a/Buildscripts/CDN/upload-app-files.py b/Buildscripts/CDN/upload-app-files.py new file mode 100644 index 0000000..2b693e5 --- /dev/null +++ b/Buildscripts/CDN/upload-app-files.py @@ -0,0 +1,65 @@ +import os +import sys +import boto3 + +SHELL_COLOR_RED = "\033[91m" +SHELL_COLOR_ORANGE = "\033[93m" +SHELL_COLOR_RESET = "\033[m" + +def print_warning(message): + print(f"{SHELL_COLOR_ORANGE}WARNING: {message}{SHELL_COLOR_RESET}") + +def print_error(message): + print(f"{SHELL_COLOR_RED}ERROR: {message}{SHELL_COLOR_RESET}") + +def print_help(): + print("Usage: python upload-app-files.py [path] [sdkVersion] [cloudflareAccountId] [cloudflareTokenName] [cloudflareTokenValue]") + print("") + print("Options:") + print(" --index-only Upload only apps.json") + +def exit_with_error(message): + print_error(message) + sys.exit(1) + +def main(path: str, sdk_version: str, cloudflare_account_id, cloudflare_token_name: str, cloudflare_token_value: str, index_only: bool): + if not os.path.exists(path): + exit_with_error(f"Path not found: {path}") + s3 = boto3.client( + service_name="s3", + endpoint_url=f"https://{cloudflare_account_id}.r2.cloudflarestorage.com", + aws_access_key_id=cloudflare_token_name, + aws_secret_access_key=cloudflare_token_value, + region_name="auto" + ) + files_to_upload = os.listdir(path) + counter = 1 + total = len(files_to_upload) + for file_name in files_to_upload: + if not index_only or file_name == 'apps.json': + object_path = f"apps/{sdk_version}/{file_name}" + print(f"[{counter}/{total}] Uploading {file_name} to {object_path}") + file_path = os.path.join(path, file_name) + try: + s3.upload_file(file_path, "tactility", object_path) + except Exception as e: + exit_with_error(f"Failed to upload {file_name}: {str(e)}") + counter += 1 + +if __name__ == "__main__": + print("Tactility CDN Apps Uploader") + if "--help" in sys.argv: + print_help() + sys.exit() + # Argument validation + if len(sys.argv) < 6: + print_help() + sys.exit(1) + main( + path=sys.argv[1], + sdk_version=sys.argv[2], + cloudflare_account_id=sys.argv[3], + cloudflare_token_name=sys.argv[4], + cloudflare_token_value=sys.argv[5], + index_only="--index-only" in sys.argv + ) diff --git a/release.py b/release.py index 9e7e47b..7ec897e 100644 --- a/release.py +++ b/release.py @@ -1,8 +1,10 @@ import json +import subprocess import tarfile import os import tempfile import sys +from datetime import datetime, UTC def read_properties_file(path): properties = {} @@ -81,6 +83,17 @@ def get_os_version(manifest): else: return sdk +def check_and_get_sdk_version(manifest_map): + """Ensure all apps target the same (simplified) SDK version and return it.""" + versions = {get_os_version(manifest) for manifest in manifest_map.values()} + if len(versions) != 1: + print(f"ERROR: Apps target multiple SDK versions: {sorted(versions)}. All apps must target the same SDK version.") + sys.exit(1) + return next(iter(versions)) + +def get_git_commit_hash(): + return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('ascii').strip() + def manifest_config_to_flat_json(manifest): """Convert a flat (V2) manifest dict into a flat JSON-like dict. @@ -136,15 +149,23 @@ def write_json(filepath, data): sys.exit() app_directory = sys.argv[1] manifest_map = {} - output_json = { - "apps": [] - } any_manifest = None if os.path.exists(app_directory): for file in os.listdir(app_directory): if file.endswith(".app"): file_path = os.path.join(app_directory, file) manifest_map[file_path] = get_manifest(file_path) + # All bundled apps must target the same SDK version; this becomes the CDN path segment + sdk_version = check_and_get_sdk_version(manifest_map) + with open("sdk_version.txt", "w") as f: + f.write(sdk_version) + print(f"SDK version: {sdk_version}") + output_json = { + "sdkVersion": sdk_version, + "created": datetime.now(UTC).strftime('%Y-%m-%dT%H:%M:%S'), + "gitCommit": get_git_commit_hash(), + "apps": [] + } # Rename files and collect manifest data into output json object for file_path in manifest_map.keys(): print(f"Processing {file_path}: {manifest_map[file_path]}") From 19d7a92da65fd73b3c2165f912751c1948ebd6bc Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 3 Jul 2026 21:31:54 +0200 Subject: [PATCH 2/2] Fixes --- .github/workflows/main.yml | 4 ++++ Buildscripts/CDN/upload-app-files.py | 23 ++++++++++++++--------- release.py | 4 ++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 965ca6d..559827f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: "Build" uses: ./.github/actions/build-app with: @@ -36,6 +38,8 @@ jobs: if: (github.event_name == 'push' && github.ref == 'refs/heads/main') steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: "Publish Apps" env: CDN_ID: ${{ secrets.CDN_ID }} diff --git a/Buildscripts/CDN/upload-app-files.py b/Buildscripts/CDN/upload-app-files.py index 2b693e5..eab6986 100644 --- a/Buildscripts/CDN/upload-app-files.py +++ b/Buildscripts/CDN/upload-app-files.py @@ -33,18 +33,23 @@ def main(path: str, sdk_version: str, cloudflare_account_id, cloudflare_token_na region_name="auto" ) files_to_upload = os.listdir(path) + if index_only: + files_to_upload = [f for f in files_to_upload if f == 'apps.json'] + else: + # Ensure apps.json is uploaded last so it never references files that + # haven't finished uploading yet. + files_to_upload.sort(key=lambda f: f == 'apps.json') counter = 1 total = len(files_to_upload) for file_name in files_to_upload: - if not index_only or file_name == 'apps.json': - object_path = f"apps/{sdk_version}/{file_name}" - print(f"[{counter}/{total}] Uploading {file_name} to {object_path}") - file_path = os.path.join(path, file_name) - try: - s3.upload_file(file_path, "tactility", object_path) - except Exception as e: - exit_with_error(f"Failed to upload {file_name}: {str(e)}") - counter += 1 + object_path = f"apps/{sdk_version}/{file_name}" + print(f"[{counter}/{total}] Uploading {file_name} to {object_path}") + file_path = os.path.join(path, file_name) + try: + s3.upload_file(file_path, "tactility", object_path) + except Exception as e: + exit_with_error(f"Failed to upload {file_name}: {str(e)}") + counter += 1 if __name__ == "__main__": print("Tactility CDN Apps Uploader") diff --git a/release.py b/release.py index 7ec897e..4456f5f 100644 --- a/release.py +++ b/release.py @@ -162,7 +162,7 @@ def write_json(filepath, data): print(f"SDK version: {sdk_version}") output_json = { "sdkVersion": sdk_version, - "created": datetime.now(UTC).strftime('%Y-%m-%dT%H:%M:%S'), + "created": datetime.now(UTC).strftime('%Y-%m-%dT%H:%M:%SZ'), "gitCommit": get_git_commit_hash(), "apps": [] } @@ -179,4 +179,4 @@ def write_json(filepath, data): any_manifest = manifest # Write JSON output_json_path = os.path.join(app_directory, "apps.json") - write_json(output_json_path, output_json) \ No newline at end of file + write_json(output_json_path, output_json)