426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
355 lines
16 KiB
YAML
355 lines
16 KiB
YAML
name: Build Example App Images
|
|
|
|
# Builds + pushes standalone example-app images as a smoke/publish lane. The
|
|
# #9300 real-staging showcase loop deploys these apps from source through the
|
|
# normal app deploy builder (`POST /api/v1/apps/:id/deploy` with repo/ref/
|
|
# Dockerfile), not by pinning these tags operator-side.
|
|
#
|
|
# Why we bundle on the runner instead of building ./Dockerfile from scratch:
|
|
# server.ts imports `@elizaos/cloud-sdk` (workspace:*). That only resolves
|
|
# inside the monorepo, and the SDK's dist/ is gitignored. A standalone docker
|
|
# build of ./Dockerfile would crash at boot ("Cannot find package
|
|
# @elizaos/cloud-sdk"). Instead we run `bun build server.ts` on the runner —
|
|
# where the workspace IS resolvable — which inlines the SDK into a single
|
|
# self-contained server.js (verified: 0 residual @elizaos/cloud-sdk imports),
|
|
# then ship that with Dockerfile.bundle from a tiny context. Same pre-build
|
|
# sequence build-agent-image.yml uses to make the cloud-sdk dist available.
|
|
|
|
on:
|
|
push:
|
|
branches: [develop]
|
|
paths:
|
|
- "packages/examples/cloud/edad/**"
|
|
- "packages/examples/cloud/clone-ur-crush/**"
|
|
- "packages/cloud/sdk/**"
|
|
- ".github/workflows/build-example-app-images.yml"
|
|
workflow_dispatch:
|
|
|
|
# One in-flight build per ref; a newer push cancels the stale one (avoids the
|
|
# queue pile-up the agent-image build learned the hard way).
|
|
concurrency:
|
|
group: build-example-edad-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: elizaos/example-edad
|
|
|
|
# Least privilege by default; the build job opts into packages: write.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build-edad:
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
submodules: false
|
|
show-progress: false
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
|
with:
|
|
bun-version: "canary"
|
|
|
|
- name: Cache Bun install
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
|
with:
|
|
path: ~/.bun/install/cache
|
|
key: bun-${{ runner.os }}-canary-${{ hashFiles('bun.lock') }}
|
|
restore-keys: |
|
|
bun-${{ runner.os }}-canary-
|
|
bun-${{ runner.os }}-
|
|
|
|
- name: Install dependencies
|
|
# Creates the @elizaos/cloud-sdk workspace symlink edad resolves through.
|
|
# --no-frozen-lockfile mirrors build-agent-image.yml: a downstream
|
|
# lockfile drift on develop shouldn't short-circuit the install.
|
|
env:
|
|
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
|
|
run: bun install --no-frozen-lockfile
|
|
|
|
- name: Build @elizaos/cloud-sdk
|
|
# The SDK's `exports.import` points at ./dist/index.js, which is
|
|
# gitignored (absent on a cold checkout). Without this build, the
|
|
# `bun build` below fails to resolve "@elizaos/cloud-sdk" even with the
|
|
# workspace symlink in place. build-agent-image.yml builds this same
|
|
# package for the same reason.
|
|
env:
|
|
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
|
|
run: |
|
|
cd packages/cloud/sdk
|
|
bun install --no-frozen-lockfile --ignore-scripts
|
|
bun run build
|
|
|
|
- name: Bundle the EDAD server (inline the workspace SDK)
|
|
# Produces a single self-contained server.js with @elizaos/cloud-sdk
|
|
# inlined — no workspace dep, no bun install needed in the image. Output
|
|
# goes to ./dist (already gitignored, same dir edad's `build` script
|
|
# uses); dist/ (server.js + public/) is the tiny docker build context.
|
|
run: |
|
|
set -euo pipefail
|
|
cd packages/examples/cloud/edad
|
|
node ../../../../packages/scripts/rm-path-recursive.mjs ./dist
|
|
bun build server.ts --outdir=./dist --target=bun --format=esm
|
|
cp -r public ./dist/public
|
|
test -f ./dist/server.js
|
|
# Guard: the bundle must NOT still reference the workspace dep, else
|
|
# the image would crash at boot.
|
|
if grep -q "@elizaos/cloud-sdk" ./dist/server.js; then
|
|
echo "::error::server.js still references @elizaos/cloud-sdk — bundle did not inline the workspace dep" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Smoke-test the image (gate the GHCR push on a working container)
|
|
# Build + run the exact bundle image and assert it actually serves before
|
|
# we publish it — so the showcase deploy never pulls a broken :showcase
|
|
# tag. Mirrors the local verification in DEPLOY_AND_VALIDATE.md.
|
|
run: |
|
|
set -euo pipefail
|
|
assert_asset() {
|
|
local asset_path="$1"
|
|
local expected_type="$2"
|
|
local min_bytes="$3"
|
|
local headers
|
|
local body
|
|
headers="$(mktemp)"
|
|
body="$(mktemp)"
|
|
curl -fsS -D "$headers" -o "$body" "http://127.0.0.1:3000/${asset_path}"
|
|
if ! grep -qi "content-type: ${expected_type}" "$headers"; then
|
|
echo "::error::${asset_path} content-type was not ${expected_type}" >&2
|
|
cat "$headers" >&2
|
|
exit 1
|
|
fi
|
|
local bytes
|
|
bytes="$(wc -c < "$body" | tr -d ' ')"
|
|
if [ "$bytes" -lt "$min_bytes" ]; then
|
|
echo "::error::${asset_path} was too small (${bytes} bytes)" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
docker build --load -t edad-smoke:ci \
|
|
-f packages/examples/cloud/edad/Dockerfile.bundle \
|
|
packages/examples/cloud/edad/dist
|
|
docker rm -f edad-smoke >/dev/null 2>&1 || true
|
|
trap 'docker rm -f edad-smoke >/dev/null 2>&1 || true' EXIT
|
|
docker run -d --name edad-smoke -p 3000:3000 edad-smoke:ci
|
|
ok=""
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS http://127.0.0.1:3000/health >/dev/null 2>&1; then ok=1; break; fi
|
|
sleep 1
|
|
done
|
|
if [ -z "$ok" ]; then
|
|
echo "::error::EDAD image never became healthy" >&2
|
|
docker logs edad-smoke || true
|
|
exit 1
|
|
fi
|
|
test "$(curl -fsS http://127.0.0.1:3000/health)" = "ok"
|
|
curl -fsS http://127.0.0.1:3000/ | grep -q "eDad" \
|
|
|| { echo "::error::EDAD did not serve its UI" >&2; exit 1; }
|
|
curl -fsS http://127.0.0.1:3000/ | grep -q 'content="http://127.0.0.1:3000/og-image.png"' \
|
|
|| { echo "::error::EDAD did not emit an absolute social image URL" >&2; exit 1; }
|
|
curl -fsS http://127.0.0.1:3000/api/config | grep -q "cloud_url" \
|
|
|| { echo "::error::EDAD /api/config is broken" >&2; exit 1; }
|
|
assert_asset dad-portrait.svg image/svg+xml 4000
|
|
assert_asset dad-avatar.svg image/svg+xml 2000
|
|
assert_asset you-avatar.svg image/svg+xml 500
|
|
assert_asset favicon.svg image/svg+xml 500
|
|
assert_asset favicon.png image/png 1000
|
|
assert_asset apple-touch-icon.png image/png 4000
|
|
assert_asset og-image.png image/png 100000
|
|
docker rm -f edad-smoke >/dev/null 2>&1 || true
|
|
trap - EXIT
|
|
echo "EDAD image smoke test passed: healthy + serves UI + /api/config + generated assets."
|
|
|
|
- uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c
|
|
|
|
- id: meta
|
|
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
# :showcase → the moving tag the showcase spec deploys
|
|
# :sha-<short> → immutable per-commit tag (mirrors build-agent-image.yml)
|
|
tags: |
|
|
type=raw,value=showcase
|
|
type=sha,prefix=sha-,format=short
|
|
|
|
- id: build
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
|
with:
|
|
context: packages/examples/cloud/edad/dist
|
|
file: packages/examples/cloud/edad/Dockerfile.bundle
|
|
push: true
|
|
provenance: false
|
|
platforms: linux/amd64
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
- name: Make image public
|
|
# The deploy puller pulls anonymously; keep the package public.
|
|
# Idempotent — patching to public when already public is a no-op.
|
|
run: |
|
|
curl --fail-with-body --show-error --silent \
|
|
-X PATCH \
|
|
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
|
-H "Accept: application/vnd.github.v3+json" \
|
|
https://api.github.com/orgs/elizaOS/packages/container/example-edad/visibility \
|
|
-d '{"visibility":"public"}' || \
|
|
echo "::warning::Could not set package visibility to public — set it once manually in the GHCR package settings"
|
|
|
|
build-clone-ur-crush:
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
submodules: false
|
|
show-progress: false
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
|
with:
|
|
bun-version: "canary"
|
|
|
|
- name: Cache Bun install
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
|
with:
|
|
path: ~/.bun/install/cache
|
|
key: bun-${{ runner.os }}-canary-${{ hashFiles('bun.lock') }}
|
|
restore-keys: |
|
|
bun-${{ runner.os }}-canary-
|
|
bun-${{ runner.os }}-
|
|
|
|
- name: Install dependencies
|
|
env:
|
|
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
|
|
run: bun install --no-frozen-lockfile
|
|
|
|
- name: Build clone-ur-crush (Next.js standalone bundle)
|
|
# next.config sets output:"standalone"; with outputFileTracingRoot at the
|
|
# monorepo root the server lands at the repo-relative path inside the
|
|
# bundle. Next does NOT copy static/public into standalone, so we place
|
|
# them next to server.js where the standalone runtime serves them.
|
|
#
|
|
# CRITICAL (#9300): build.mjs builds under a temp distDir
|
|
# (.next-build-<pid>) then renames it to .next, but the standalone server
|
|
# BAKES that original distDir name into required-server-files.json and
|
|
# serves /_next/static from <bakedDistDir>/static. Copying static to the
|
|
# literal .next/static (the renamed dir) puts it at a path the runtime
|
|
# never reads, so every CSS/JS chunk 404s. Copy static to the BAKED dist
|
|
# dir instead.
|
|
env:
|
|
NODE_LLAMA_CPP_SKIP_DOWNLOAD: "true"
|
|
run: |
|
|
set -euo pipefail
|
|
cd packages/examples/cloud/clone-ur-crush
|
|
node ../../../../packages/scripts/rm-path-recursive.mjs .next
|
|
bun run build
|
|
app=.next/standalone/packages/examples/cloud/clone-ur-crush
|
|
test -f "$app/server.js"
|
|
# The standalone's baked distDir = the dir holding required-server-files.json.
|
|
rsf="$(find "$app" -maxdepth 2 -name required-server-files.json | head -1)"
|
|
test -n "$rsf"
|
|
baked="$(basename "$(dirname "$rsf")")"
|
|
echo "standalone baked distDir: $baked"
|
|
mkdir -p "$app/$baked/static"
|
|
cp -r .next/static/. "$app/$baked/static/"
|
|
cp -r public "$app/public"
|
|
|
|
- name: Smoke-test the image (gate the GHCR push on a working container)
|
|
# Build + run the exact standalone image and assert it actually serves its
|
|
# UI before we publish it, so the showcase deploy never pulls a broken
|
|
# :showcase tag. Mirrors the EDAD job's gate above (DoD: failures surface
|
|
# loudly; the showcase deploy can never pull a broken tag).
|
|
run: |
|
|
set -euo pipefail
|
|
docker build --load -t cuc-smoke:ci \
|
|
-f packages/examples/cloud/clone-ur-crush/Dockerfile.bundle \
|
|
packages/examples/cloud/clone-ur-crush/.next/standalone
|
|
docker rm -f cuc-smoke >/dev/null 2>&1 || true
|
|
trap 'docker rm -f cuc-smoke >/dev/null 2>&1 || true' EXIT
|
|
docker run -d --name cuc-smoke -p 3000:3000 cuc-smoke:ci
|
|
ok=""
|
|
for _ in $(seq 1 40); do
|
|
if curl -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then ok=1; break; fi
|
|
sleep 1
|
|
done
|
|
if [ -z "$ok" ]; then
|
|
echo "::error::Clone Ur Crush image never started serving" >&2
|
|
docker logs cuc-smoke || true
|
|
exit 1
|
|
fi
|
|
home="$(curl -fsS http://127.0.0.1:3000/)"
|
|
echo "$home" | grep -q "Clone Your Crush" \
|
|
|| { echo "::error::Clone Ur Crush did not serve its UI" >&2; docker logs cuc-smoke || true; exit 1; }
|
|
# Assert a real /_next/static chunk serves 200 — this is what catches the
|
|
# distDir/static path mismatch (a broken bundle serves HTML but 404s
|
|
# every CSS/JS asset). Pull a static URL out of the rendered HTML.
|
|
asset="$(printf '%s' "$home" | grep -oE '/_next/static/[^"]+\.(js|css)' | head -1)"
|
|
test -n "$asset" || { echo "::error::no /_next/static asset referenced in HTML" >&2; exit 1; }
|
|
code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:3000${asset}")"
|
|
test "$code" = "200" \
|
|
|| { echo "::error::static asset ${asset} returned ${code} (expected 200) — broken /_next/static path" >&2; docker logs cuc-smoke || true; exit 1; }
|
|
# OG card must exist (was a 404 before #9300 fix).
|
|
og="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/og-image.png)"
|
|
test "$og" = "200" || { echo "::error::og-image.png returned ${og} (expected 200)" >&2; exit 1; }
|
|
docker rm -f cuc-smoke >/dev/null 2>&1 || true
|
|
trap - EXIT
|
|
echo "Clone Ur Crush image smoke test passed: serves UI + /_next/static (${asset}) + og-image.png."
|
|
|
|
- uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c
|
|
|
|
- id: meta
|
|
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
|
|
with:
|
|
images: ${{ env.REGISTRY }}/elizaos/example-clone-ur-crush
|
|
tags: |
|
|
type=raw,value=showcase
|
|
type=sha,prefix=sha-,format=short
|
|
|
|
- id: build
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
|
with:
|
|
context: packages/examples/cloud/clone-ur-crush/.next/standalone
|
|
file: packages/examples/cloud/clone-ur-crush/Dockerfile.bundle
|
|
push: true
|
|
provenance: false
|
|
platforms: linux/amd64
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
- name: Make image public
|
|
run: |
|
|
curl --fail-with-body --show-error --silent \
|
|
-X PATCH \
|
|
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
|
-H "Accept: application/vnd.github.v3+json" \
|
|
https://api.github.com/orgs/elizaOS/packages/container/example-clone-ur-crush/visibility \
|
|
-d '{"visibility":"public"}' || \
|
|
echo "::warning::Could not set package visibility to public — set it once manually in the GHCR package settings"
|