Release Nightly #289
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Release Nightly | |
permissions: | |
contents: write | |
on: | |
# Run nightly | |
schedule: | |
- cron: "0 0 * * *" | |
# Allow for manual dispatch on GitHub | |
workflow_dispatch: | |
jobs: | |
create-nightly-release: | |
name: Create Nightly Release | |
runs-on: ubuntu-22.04 | |
outputs: | |
is_active: ${{ steps.activity.outputs.is_active }} | |
date: ${{ steps.current_time_underscores.outputs.formattedTime }} | |
upload_url: ${{ steps.create_release.outputs.upload_url }} | |
package_prefix: ruffle-nightly-${{ steps.current_time_underscores.outputs.formattedTime }} | |
tag_name: nightly-${{ steps.current_time_dashes.outputs.formattedTime }} | |
# Only run the scheduled workflows on the main repo. | |
if: github.repository == 'ruffle-rs/ruffle' || github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch' | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Check for repo activity | |
id: activity | |
run: | | |
# Skip activity check when manually triggered. | |
if [ "${{ github.event_name }}" == "repository_dispatch" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
is_active=true | |
elif [ "$(git rev-list --after="24 hours" ${{ github.sha }})" ]; then | |
is_active=true | |
else | |
is_active=false | |
fi | |
echo "is_active=$is_active" >> $GITHUB_OUTPUT | |
- name: Get current time with dashes | |
uses: 1466587594/get-current-time@v2.1.2 | |
id: current_time_dashes | |
with: | |
format: YYYY-MM-DD-hh-mm-ss | |
- name: Get current time with underscores | |
uses: 1466587594/get-current-time@v2.1.2 | |
id: current_time_underscores | |
with: | |
format: YYYY_MM_DD_HH_MM_SS | |
- name: Create release | |
if: steps.activity.outputs.is_active == 'true' | |
id: create_release | |
run: | | |
tag_name="nightly-${{ steps.current_time_dashes.outputs.formattedTime }}" | |
release_name="Nightly ${{ steps.current_time_dashes.outputs.formattedTime }}" | |
gh release create "$tag_name" --title "$release_name" --generate-notes --prerelease | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
build: | |
name: Build ${{ matrix.build_name }} | |
needs: create-nightly-release | |
if: needs.create-nightly-release.outputs.is_active == 'true' | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
# Mac does two Rust builds to make a universal binary | |
- build_name: macos-x86_64 | |
os: macos-14 | |
target: x86_64-apple-darwin | |
MIN_MACOS_VERSION: 10.12 | |
DESKTOP_FEATURES: sandbox,jpegxr | |
- build_name: macos-aarch64 | |
os: macos-14 | |
target: aarch64-apple-darwin | |
MIN_MACOS_VERSION: 11.0 | |
DESKTOP_FEATURES: sandbox,jpegxr | |
- build_name: windows-x86_32 | |
os: windows-latest | |
target: i686-pc-windows-msvc | |
RUSTFLAGS: -Ctarget-feature=+crt-static | |
DESKTOP_FEATURES: jpegxr | |
- build_name: windows-x86_64 | |
os: windows-latest | |
target: x86_64-pc-windows-msvc | |
RUSTFLAGS: -Ctarget-feature=+crt-static | |
DESKTOP_FEATURES: jpegxr | |
env: | |
PACKAGE_FILE: ${{ needs.create-nightly-release.outputs.package_prefix }}-${{ matrix.build_name }}.${{ startsWith(matrix.build_name, 'win') && 'zip' || 'tar.gz' }} | |
CARGO_BUILD_DIR: target/${{ matrix.target }}/release | |
runs-on: ${{ matrix.os }} | |
steps: | |
- name: Clone Ruffle repo | |
uses: actions/checkout@v4 | |
- name: Install Rust toolchain | |
uses: dtolnay/rust-toolchain@stable | |
with: | |
toolchain: stable | |
targets: ${{ matrix.target }} | |
- name: Install Linux dependencies | |
if: runner.os == 'Linux' | |
run: | | |
sudo apt-get update | |
sudo apt install -y libasound2-dev libxcb-shape0-dev libxcb-xfixes0-dev libgtk-3-dev libudev-dev | |
- name: Cargo build | |
run: cargo build --locked --package ruffle_desktop --release ${{matrix.DESKTOP_FEATURES && '--features' }} ${{matrix.DESKTOP_FEATURES}} ${{ matrix.target && '--target' }} ${{ matrix.target }} | |
env: | |
RUSTFLAGS: ${{ matrix.RUSTFLAGS }} | |
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.MIN_MACOS_VERSION }} | |
- name: Package common files | |
run: | | |
mkdir package | |
cp README.md package/README.md | |
cp LICENSE.md package/LICENSE.md | |
- name: Package Windows files | |
if: runner.os == 'Windows' | |
run: | | |
cp ${{ env.CARGO_BUILD_DIR }}/ruffle_desktop.exe package/ruffle.exe | |
7z a ${{ env.PACKAGE_FILE }} ./package/* | |
- name: Package Linux files | |
if: runner.os == 'Linux' | |
run: | | |
cp ${{ env.CARGO_BUILD_DIR }}/ruffle_desktop package/ruffle | |
# We must enter the package/ directory in order to create a flat tarball (i.e. without a directory in it). | |
cd package | |
tar -czvf ../${{ env.PACKAGE_FILE }} * | |
- name: Upload package | |
if: runner.os != 'macOS' | |
run: gh release upload "${{ needs.create-nightly-release.outputs.tag_name }}" "${{ env.PACKAGE_FILE }}" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Build Safari Web Extension stub binary | |
if: runner.os == 'macOS' | |
run: cargo build --locked --package ruffle_web_safari --release ${{ matrix.target && '--target' }} ${{ matrix.target }} | |
env: | |
RUSTFLAGS: ${{ matrix.RUSTFLAGS }} | |
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.MIN_MACOS_VERSION }} | |
- name: Upload macOS build artifact | |
if: runner.os == 'macOS' | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ matrix.build_name }} | |
path: | | |
${{ env.CARGO_BUILD_DIR }}/ruffle_desktop | |
${{ env.CARGO_BUILD_DIR }}/ruffle_web_safari | |
package | |
build-mac-universal-binary: | |
name: Build macOS universal binary | |
needs: [create-nightly-release, build, build-web] | |
runs-on: macos-14 | |
env: | |
PACKAGE_FILE: ${{ needs.create-nightly-release.outputs.package_prefix }}-macos-universal.tar.gz | |
MIN_MACOS_VERSION: 10.12 | |
steps: | |
- name: Clone Ruffle repo | |
uses: actions/checkout@v4 | |
- name: Download aarch64 binary | |
uses: actions/download-artifact@v4 | |
with: | |
name: macos-aarch64 | |
- name: Download x86_64 binary | |
uses: actions/download-artifact@v4 | |
with: | |
name: macos-x86_64 | |
- name: Download Safari extension | |
uses: actions/download-artifact@v4 | |
with: | |
name: macos-safari | |
- name: Make universal desktop binary | |
run: | | |
lipo -create -output package/ruffle target/x86_64-apple-darwin/release/ruffle_desktop target/aarch64-apple-darwin/release/ruffle_desktop | |
chmod +x package/ruffle | |
- name: Make universal Safari stub binary | |
continue-on-error: true | |
run: | | |
lipo -create -output package/ruffle_web_safari target/x86_64-apple-darwin/release/ruffle_web_safari target/aarch64-apple-darwin/release/ruffle_web_safari | |
chmod +x package/ruffle_web_safari | |
- name: Create app bundle | |
run: | | |
VERSION=$(yq eval '.workspace.package.version' Cargo.toml) | |
sed -i "" "s/{Version}/$VERSION/" desktop/packaging/macOS/Info.plist | |
sed -i "" "s/{MinimumMacOSVersion}/${{ matrix.MIN_MACOS_VERSION }}/" desktop/packaging/macOS/Info.plist | |
sed -i "" "s/{CurrentYear}/$(date +%Y)/" desktop/packaging/macOS/Info.plist | |
mkdir package/Ruffle.app | |
mkdir package/Ruffle.app/Contents | |
mkdir package/Ruffle.app/Contents/MacOS | |
mkdir package/Ruffle.app/Contents/Resources | |
cp desktop/packaging/macOS/Info.plist package/Ruffle.app/Contents/Info.plist | |
mv package/ruffle package/Ruffle.app/Contents/MacOS/ruffle | |
- name: Compile asset catalog | |
run: | | |
xcrun actool --compile package/Ruffle.app/Contents/Resources desktop/packaging/macOS/Assets.xcassets --minimum-deployment-target ${{ matrix.MIN_MACOS_VERSION }} --platform macosx --warnings --errors --notices --include-all-app-icons | |
- name: Create extension bundle | |
continue-on-error: true | |
run: | | |
mkdir package/Ruffle.app/Contents/PlugIns | |
cp -r web/packages/extension/safari/package package/Ruffle.app/Contents/PlugIns/Ruffle\ Web.appex | |
mkdir package/Ruffle.app/Contents/PlugIns/Ruffle\ Web.appex/Contents/Resources | |
mkdir package/Ruffle.app/Contents/PlugIns/Ruffle\ Web.appex/Contents/MacOS | |
mv package/ruffle_web_safari package/Ruffle.app/Contents/PlugIns/Ruffle\ Web.appex/Contents/MacOS/ruffle_web_safari | |
cp ruffle_extension.zip package/Ruffle.app/Contents/PlugIns/Ruffle\ Web.appex/Contents/Resources | |
cd package/Ruffle.app/Contents/PlugIns/Ruffle\ Web.appex/Contents/Resources | |
unzip ruffle_extension.zip | |
rm ruffle_extension.zip | |
cd ../../../../../../.. | |
- name: Sign bundle | |
continue-on-error: true | |
env: | |
APPLE_DEVELOPER_KEY: ${{ secrets.APPLE_DEVELOPER_KEY }} | |
APPLE_DEVELOPER_KEY_PASSWORD: ${{ secrets.APPLE_DEVELOPER_KEY_PASSWORD }} | |
run: | | |
echo $APPLE_DEVELOPER_KEY | base64 --decode > certificate.p12 | |
security create-keychain -p correct-horse-battery-staple build.keychain | |
security default-keychain -s build.keychain | |
security unlock-keychain -p correct-horse-battery-staple build.keychain | |
security import certificate.p12 -k build.keychain -P $APPLE_DEVELOPER_KEY_PASSWORD -T /usr/bin/codesign | |
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k correct-horse-battery-staple build.keychain | |
codesign --deep -s ${{ secrets.APPLE_DEVELOPER_IDENTITY }} -f --entitlements desktop/packaging/macOS/Entitlements.plist --options runtime package/Ruffle.app | |
codesign --verify -vvvv package/Ruffle.app | |
- name: Notarize bundle | |
continue-on-error: true | |
run: | | |
xcrun notarytool store-credentials "Ruffle" --apple-id ${{ secrets.APPLE_ID }} --team-id ${{ secrets.APPLE_TEAM }} --password ${{ secrets.APPLE_APP_PASSWORD }} | |
cd package | |
zip -r Ruffle.zip Ruffle.app | |
mv Ruffle.zip .. | |
cd .. | |
xcrun notarytool submit Ruffle.zip --keychain-profile Ruffle --wait | |
xcrun stapler staple package/Ruffle.app | |
- name: Package macOS files | |
run: | | |
pip install dmgbuild | |
dmgbuild -s desktop/packaging/macOS/settings.py "Ruffle" package/Ruffle.dmg | |
# We must enter the package/ directory in order to create a flat tarball (i.e. without a directory in it). | |
cd package | |
tar -czvf ../${{ env.PACKAGE_FILE }} * | |
- name: Upload package | |
run: gh release upload "${{ needs.create-nightly-release.outputs.tag_name }}" "${{ env.PACKAGE_FILE }}" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
build-stub-report: | |
name: Build AVM2 stub repository | |
needs: create-nightly-release | |
if: needs.create-nightly-release.outputs.is_active == 'true' | |
runs-on: ubuntu-22.04 | |
steps: | |
- name: Clone Ruffle repo | |
uses: actions/checkout@v4 | |
- name: Install Rust toolchain | |
uses: dtolnay/rust-toolchain@stable | |
with: | |
toolchain: stable | |
- name: Generate report | |
run: ./stub-report/generate-report.sh | |
- name: Upload report | |
run: gh release upload "${{ needs.create-nightly-release.outputs.tag_name }}" avm2_report.json | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
build-web: | |
name: Build web${{ matrix.demo && ' demo' || '' }} | |
needs: create-nightly-release | |
if: needs.create-nightly-release.outputs.is_active == 'true' | |
runs-on: ubuntu-22.04 | |
strategy: | |
matrix: | |
demo: [false, true] | |
steps: | |
- name: Clone Ruffle repo | |
uses: actions/checkout@v4 | |
- name: Install Rust toolchain | |
uses: dtolnay/rust-toolchain@stable | |
with: | |
toolchain: stable | |
targets: wasm32-unknown-unknown | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: "20" | |
registry-url: https://registry.npmjs.org | |
# wasm-bindgen-cli version must match wasm-bindgen crate version. | |
# Be sure to update in test_web.yml, web/Cargo.toml and web/README.md. | |
- name: Install wasm-bindgen | |
run: cargo install wasm-bindgen-cli --version 0.2.92 | |
# Keep the version number in sync in all workflows, | |
# and in the extension builder Dockerfile! | |
- name: Install wasm-opt | |
uses: sigoden/install-binary@v1 | |
with: | |
repo: WebAssembly/binaryen | |
tag: version_118 | |
name: wasm-opt | |
- name: Install node packages | |
working-directory: web | |
shell: bash -l {0} | |
run: | | |
npm ci | |
- name: Seal version data | |
shell: bash -l {0} | |
working-directory: web | |
env: | |
BUILD_ID: ${{ github.run_number }} | |
ENABLE_VERSION_SEAL: "true" | |
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} # Needed to inject into manifest.json | |
run: npm run version-seal | |
- name: Build web | |
env: | |
BUILD_ID: ${{ github.run_number }} | |
# Build web demo with WebGPU support for testing in Chrome origin trial on ruffle.rs | |
CARGO_FEATURES: jpegxr${{ matrix.demo && ',webgpu' || '' }} | |
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} # Needed to inject into manifest.json | |
WASM_SOURCE: cargo_and_store | |
working-directory: web | |
shell: bash -l {0} | |
run: npm run build:repro | |
- name: Produce reproducible source archive | |
if: ${{ !matrix.demo }} | |
shell: bash -l {0} | |
run: | | |
zip -r reproducible-source.zip . -x '/web/node_modules/*' '/web/*/node_modules/*' '/web/packages/*/dist/*' '/web/docker/docker_builds/packages/*' '/target/*' '/.git/*' '/tests/tests/swfs/*' | |
cp reproducible-source.zip "${{ needs.create-nightly-release.outputs.package_prefix }}-reproducible-source.zip" | |
- name: Upload reproducible source archive | |
if: ${{ !matrix.demo }} | |
env: | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
GH_REPO: ${{ github.repository }} | |
run: | | |
tag_name="${{ needs.create-nightly-release.outputs.tag_name }}" | |
package_file="${{ needs.create-nightly-release.outputs.package_prefix }}-reproducible-source.zip" | |
gh release upload "$tag_name" "$package_file" | |
- name: Build web docs | |
working-directory: web | |
run: npm run docs | |
- name: Publish npm package | |
if: ${{ !matrix.demo }} | |
# npm scoped packages are private by default, explicitly make public | |
run: npm publish --access public | |
continue-on-error: true | |
working-directory: web/packages/selfhosted/dist | |
env: | |
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
- name: Package selfhosted | |
if: ${{ !matrix.demo }} | |
run: zip -r "${{ needs.create-nightly-release.outputs.package_prefix }}-web-selfhosted.zip" . | |
working-directory: web/packages/selfhosted/dist | |
- name: Upload selfhosted | |
if: ${{ !matrix.demo }} | |
run: | | |
tag_name="${{ needs.create-nightly-release.outputs.tag_name }}" | |
package_file="${{ needs.create-nightly-release.outputs.package_prefix }}-web-selfhosted.zip" | |
gh release upload "$tag_name" "$package_file" | |
working-directory: web/packages/selfhosted/dist | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Upload generic extension | |
if: ${{ !matrix.demo }} | |
run: | | |
tag_name="${{ needs.create-nightly-release.outputs.tag_name }}" | |
package_file="${{ needs.create-nightly-release.outputs.package_prefix }}-web-extension.zip" | |
cp "./web/packages/extension/dist/ruffle_extension.zip" "$package_file" | |
gh release upload "$tag_name" "$package_file" | |
rm "$package_file" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Upload Safari build artifact | |
if: ${{ !matrix.demo }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: macos-safari | |
path: ./web/packages/extension/dist/ruffle_extension.zip | |
- name: Upload Firefox extension (unsigned) | |
if: ${{ !matrix.demo }} | |
run: | | |
tag_name="${{ needs.create-nightly-release.outputs.tag_name }}" | |
package_file="${{ needs.create-nightly-release.outputs.package_prefix }}-web-extension-firefox-unsigned.xpi" | |
cp "./web/packages/extension/dist/firefox_unsigned.xpi" "$package_file" | |
gh release upload "$tag_name" "$package_file" | |
rm "$package_file" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Publish Chrome extension | |
if: env.CHROME_EXTENSION_ID != '' && !matrix.demo | |
id: publish-chrome-extension | |
continue-on-error: true | |
env: | |
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} | |
uses: mnao305/chrome-extension-upload@v5.0.0 | |
with: | |
extension-id: ${{ secrets.CHROME_EXTENSION_ID }} | |
client-id: ${{ secrets.CHROME_CLIENT_ID }} | |
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} | |
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} | |
file-path: ./web/packages/extension/dist/ruffle_extension.zip | |
- name: Publish Edge extension | |
if: env.EDGE_PRODUCT_ID != '' && !matrix.demo | |
id: publish-edge-extension | |
continue-on-error: true | |
env: | |
EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} | |
uses: wdzeng/edge-addon@v1 | |
with: | |
product-id: ${{ secrets.EDGE_PRODUCT_ID }} | |
zip-path: ./web/packages/extension/dist/ruffle_extension.zip | |
client-id: ${{ secrets.EDGE_CLIENT_ID }} | |
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }} | |
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }} | |
- name: Clone web demo | |
if: matrix.demo | |
uses: actions/checkout@v4 | |
with: | |
repository: ruffle-rs/demo | |
path: demo | |
ref: master | |
fetch-depth: 0 | |
persist-credentials: false # Needed to allow commit via RUFFLE_BUILD_TOKEN below | |
- name: Update web demo | |
if: matrix.demo | |
run: | | |
# Delete the old build. | |
rm -fr * | |
# Copy the fresh build into this folder. | |
cp -fr ../web/packages/demo/dist/* . | |
# Restore our custom swfs | |
git restore swfs swfs.json | |
# Create git commit. Amend previous commit to avoid daily commit spam. | |
git config user.name "RuffleBuild" | |
git config user.email "ruffle@ruffle.rs" | |
git add -A | |
git commit --amend -m "Nightly build ${{ needs.create-nightly-release.outputs.date }}" | |
working-directory: demo | |
- name: Push web demo | |
if: github.repository == 'ruffle-rs/ruffle' && matrix.demo | |
uses: ad-m/github-push-action@master | |
with: | |
repository: ruffle-rs/demo | |
github_token: ${{ secrets.RUFFLE_BUILD_TOKEN }} | |
directory: demo | |
force: true | |
- name: Clone JS docs | |
if: ${{ !matrix.demo }} | |
uses: actions/checkout@v4 | |
with: | |
repository: ruffle-rs/js-docs | |
path: js-docs | |
ref: master | |
fetch-depth: 0 | |
persist-credentials: false # Needed to allow commit via RUFFLE_BUILD_TOKEN below | |
- name: Update JS docs | |
if: ${{ !matrix.demo }} | |
run: | | |
# Delete the old docs | |
rm -rf master/ | |
# Copy the fresh docs into this folder. | |
cp -r ../web/packages/core/docs master | |
# Create git commit. Amend previous commit to avoid daily commit spam. | |
git config user.name "RuffleBuild" | |
git config user.email "ruffle@ruffle.rs" | |
git add -A | |
git commit --amend -m "Nightly build ${{ needs.create-nightly-release.outputs.date }}" | |
working-directory: js-docs | |
- name: Push JS docs | |
if: github.repository == 'ruffle-rs/ruffle' && !matrix.demo | |
uses: ad-m/github-push-action@master | |
with: | |
repository: ruffle-rs/js-docs | |
github_token: ${{ secrets.RUFFLE_BUILD_TOKEN }} | |
directory: js-docs | |
force: true | |
publish-aur-package: | |
name: Publish AUR package | |
needs: [create-nightly-release, build] | |
runs-on: ubuntu-22.04 | |
if: github.repository == 'ruffle-rs/ruffle' | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Get current time with dashes | |
uses: 1466587594/get-current-time@v2.1.2 | |
id: current_time_dashes | |
with: | |
format: YYYY-MM-DD | |
- name: Get current time with dots | |
uses: 1466587594/get-current-time@v2.1.2 | |
id: current_time_dots | |
with: | |
format: YYYY.MM.DD | |
- name: Download package | |
run: gh release download "${{ needs.create-nightly-release.outputs.tag_name }}" --pattern "${{ needs.create-nightly-release.outputs.package_prefix }}-linux-x86_64.tar.gz" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Update PKGBUILD | |
run: > | |
sed -i ./PKGBUILD | |
-e "s/@VERSION@/${{ steps.current_time_dots.outputs.formattedTime }}/" | |
-e "s/@SHA512SUM@/$(sha512sum ${{ needs.create-nightly-release.outputs.package_prefix }}-linux-x86_64.tar.gz | cut -d' ' -f1)/" | |
- name: Publish AUR package | |
uses: KSXGitHub/github-actions-deploy-aur@v2.7.2 | |
with: | |
pkgname: ruffle-nightly-bin | |
pkgbuild: ./PKGBUILD | |
commit_username: RuffleBuild | |
commit_email: ruffle@ruffle.rs | |
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
commit_message: Update to Nightly ${{ steps.current_time_dashes.outputs.formattedTime }} |