Skip to content

Commit 57623e3

Browse files
IONOS(ci): add detect-app-cache.sh — per-app JFrog + GitHub cache probe
Helper script consumed by the cached parallel matrix CI pipeline (added in the next commit). For each app emitted by IONOS/Makefile's generate_apps_matrix_json target, it computes a per-app cache key from the submodule SHA, probes JFrog Artifactory first, falls back to a GitHub Actions cache lookup via gh cache list, and writes three JSON outputs: - apps_to_build: apps whose artifact is missing and must be built - apps_to_restore: apps whose artifact exists in JFrog or GH cache - apps_sha_map: full {name: SHA} map for traceability Cache-key shape is uniform: \${CACHE_VERSION}-app-build-\${APP_NAME}-\${SHORT_SHA} (8-char short SHA). The custom-npms compound-key branch from PR #226's earlier iterations is gone — custom-npms submodules no longer exist and generate_apps_matrix_json always emits needs_custom_npms:false. JFrog setup is idempotent (jf config show short-circuits the add) and gracefully degrades to GH-cache-only when JF_URL/JF_USER/JF_ACCESS_TOKEN are absent. Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
1 parent ee58979 commit 57623e3

1 file changed

Lines changed: 363 additions & 0 deletions

File tree

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
#!/bin/bash
2+
3+
# SPDX-FileCopyrightText: 2025 STRATO AG
4+
# SPDX-License-Identifier: AGPL-3.0-or-later
5+
6+
# Script to detect which apps need building vs. can be restored from cache
7+
# Supports multiple cache sources: GitHub Actions cache and JFrog Artifactory
8+
# Outputs JSON arrays for apps to build and apps to restore
9+
10+
set -e # Exit on error
11+
set -u # Exit on undefined variable
12+
set -o pipefail # Exit if any command in pipeline fails
13+
14+
# Required environment variables
15+
: "${GH_TOKEN:?GH_TOKEN not set}"
16+
: "${CACHE_VERSION:?CACHE_VERSION not set}"
17+
: "${FORCE_REBUILD:?FORCE_REBUILD not set}"
18+
: "${ARTIFACTORY_REPOSITORY_SNAPSHOT:?ARTIFACTORY_REPOSITORY_SNAPSHOT not set}"
19+
20+
# Optional variables
21+
APPS_TO_REBUILD="${APPS_TO_REBUILD:-}"
22+
JF_URL="${JF_URL:-}"
23+
JF_USER="${JF_USER:-}"
24+
JF_ACCESS_TOKEN="${JF_ACCESS_TOKEN:-}"
25+
26+
# Input: MATRIX (JSON array of app configurations)
27+
# Input: GITHUB_REF (current GitHub ref)
28+
# Input: GITHUB_STEP_SUMMARY (path to step summary file)
29+
30+
# Outputs to $GITHUB_OUTPUT:
31+
# - apps_to_build: JSON array of apps that need building
32+
# - apps_to_restore: JSON array of apps that can be restored from cache
33+
# - apps_sha_map: JSON object mapping app names to their SHAs
34+
# - has_apps_to_build: boolean flag
35+
# - has_apps_to_restore: boolean flag
36+
37+
echo "Collecting app SHAs and checking cache status..."
38+
echo "Cache version: $CACHE_VERSION"
39+
echo "Force rebuild mode: $FORCE_REBUILD"
40+
if [ -n "$APPS_TO_REBUILD" ]; then
41+
echo "Apps to rebuild: $APPS_TO_REBUILD"
42+
fi
43+
echo ""
44+
45+
# Setup JFrog CLI if credentials are available
46+
JFROG_AVAILABLE="false"
47+
echo "=== JFrog Setup ==="
48+
echo "JF_URL present: $([ -n "$JF_URL" ] && echo 'YES' || echo 'NO')"
49+
echo "JF_USER present: $([ -n "$JF_USER" ] && echo 'YES' || echo 'NO')"
50+
echo "JF_ACCESS_TOKEN present: $([ -n "$JF_ACCESS_TOKEN" ] && echo 'YES' || echo 'NO')"
51+
52+
if [ -n "$JF_URL" ] && [ -n "$JF_USER" ] && [ -n "$JF_ACCESS_TOKEN" ]; then
53+
echo "✓ All JFrog credentials available"
54+
if ! command -v jf >/dev/null 2>&1; then
55+
echo "⚠ JFrog CLI (jf) not in PATH; skipping JFrog cache checks"
56+
echo " (jf should be installed by jfrog/setup-jfrog-cli before this script runs)"
57+
else
58+
echo "JFrog CLI version: $(jf --version)"
59+
60+
# Configure JFrog (skip if already configured — avoids "Server ID already exists" when
61+
# a previous step in the same job has already run jf config add with the same server ID)
62+
if jf config show jfrog-server >/dev/null 2>&1; then
63+
echo "JFrog server 'jfrog-server' already configured, reusing"
64+
else
65+
echo "Configuring JFrog server: $JF_URL"
66+
jf config add jfrog-server --url="$JF_URL" --user="$JF_USER" --access-token="$JF_ACCESS_TOKEN" --interactive=false
67+
fi
68+
69+
# Test connection with verbose output
70+
echo "Testing JFrog connection..."
71+
if jf rt ping; then
72+
JFROG_AVAILABLE="true"
73+
echo "✓ JFrog connection successful"
74+
echo "Repository: $ARTIFACTORY_REPOSITORY_SNAPSHOT"
75+
else
76+
echo "⚠ JFrog ping failed, will fall back to GitHub cache"
77+
fi
78+
fi
79+
else
80+
echo "⚠ JFrog credentials not available, using GitHub cache only"
81+
[ -z "$JF_URL" ] && echo " - Missing: JF_URL"
82+
[ -z "$JF_USER" ] && echo " - Missing: JF_USER"
83+
[ -z "$JF_ACCESS_TOKEN" ] && echo " - Missing: JF_ACCESS_TOKEN"
84+
fi
85+
echo "JFROG_AVAILABLE=$JFROG_AVAILABLE"
86+
echo "==================="
87+
echo ""
88+
89+
# Get the matrix from input (passed as argument)
90+
MATRIX="$1"
91+
92+
# Build JSON arrays for apps that need building/restoring
93+
APPS_TO_BUILD="[]"
94+
APPS_TO_RESTORE="[]"
95+
APPS_CHECKED=0
96+
APPS_CACHED=0
97+
APPS_IN_JFROG=0
98+
APPS_TO_BUILD_COUNT=0
99+
APPS_TO_RESTORE_COUNT=0
100+
APPS_SHA_MAP="{}"
101+
echo ""
102+
103+
echo "### 📦 Cache Status Report for ($GITHUB_REF)" >> "$GITHUB_STEP_SUMMARY"
104+
echo "" >> "$GITHUB_STEP_SUMMARY"
105+
echo "**Cache Version:** \`$CACHE_VERSION\`" >> "$GITHUB_STEP_SUMMARY"
106+
echo "" >> "$GITHUB_STEP_SUMMARY"
107+
if [ "$FORCE_REBUILD" == "true" ]; then
108+
echo "**🔄 FORCE REBUILD MODE ENABLED** - All caches bypassed" >> "$GITHUB_STEP_SUMMARY"
109+
echo "" >> "$GITHUB_STEP_SUMMARY"
110+
fi
111+
if [ -n "$APPS_TO_REBUILD" ]; then
112+
echo "**🔨 Specific apps to rebuild:** \`$APPS_TO_REBUILD\`" >> "$GITHUB_STEP_SUMMARY"
113+
echo "" >> "$GITHUB_STEP_SUMMARY"
114+
fi
115+
if [ "$JFROG_AVAILABLE" == "true" ]; then
116+
echo "**🎯 JFrog Artifact Cache**: Enabled for all branches" >> "$GITHUB_STEP_SUMMARY"
117+
echo "" >> "$GITHUB_STEP_SUMMARY"
118+
fi
119+
echo "| App | SHA | Cache Key | Status |" >> "$GITHUB_STEP_SUMMARY"
120+
echo "|-----|-----|-----------|--------|" >> "$GITHUB_STEP_SUMMARY"
121+
122+
# Convert comma-separated apps list to array for easier checking
123+
if [ -n "$APPS_TO_REBUILD" ]; then
124+
IFS=',' read -ra REBUILD_APPS_ARRAY <<< "$APPS_TO_REBUILD"
125+
# Trim whitespace from each app name using xargs for readability
126+
for i in "${!REBUILD_APPS_ARRAY[@]}"; do
127+
REBUILD_APPS_ARRAY[$i]=$(echo "${REBUILD_APPS_ARRAY[$i]}" | xargs)
128+
done
129+
else
130+
REBUILD_APPS_ARRAY=()
131+
fi
132+
133+
# Iterate through each app in the matrix
134+
while IFS= read -r app_json; do
135+
APP_NAME=$(echo "$app_json" | jq -r '.name')
136+
APP_PATH=$(echo "$app_json" | jq -r '.path')
137+
138+
APPS_CHECKED=$((APPS_CHECKED + 1))
139+
140+
# Get current submodule SHA
141+
if [ -d "$APP_PATH" ]; then
142+
CURRENT_SHA=$(git -C "$APP_PATH" rev-parse HEAD 2>/dev/null || echo "")
143+
else
144+
echo "ERROR: $APP_NAME - directory not found at '$APP_PATH'. Submodule not initialised?"
145+
echo "| $APP_NAME | N/A | N/A | ❌ Directory not found |" >> "$GITHUB_STEP_SUMMARY"
146+
exit 1
147+
fi
148+
149+
if [ -z "$CURRENT_SHA" ]; then
150+
echo "ERROR: $APP_NAME - '$APP_PATH' exists but is not a git repo (cannot determine SHA)."
151+
echo "| $APP_NAME | N/A | N/A | ❌ Not a git repo |" >> "$GITHUB_STEP_SUMMARY"
152+
exit 1
153+
fi
154+
155+
# Add SHA to the map for all apps (regardless of cache status)
156+
APPS_SHA_MAP=$(echo "$APPS_SHA_MAP" | jq -c --arg app "$APP_NAME" --arg sha "$CURRENT_SHA" '.[$app] = $sha')
157+
158+
# Cache key for this app — use 8-char short SHAs throughout.
159+
SHORT_SHA="${CURRENT_SHA:0:8}"
160+
CACHE_KEY="${CACHE_VERSION}-app-build-${APP_NAME}-${SHORT_SHA}"
161+
ARCHIVE_SUFFIX="${SHORT_SHA}"
162+
163+
echo -n " Checking $APP_NAME (SHA: $SHORT_SHA)... "
164+
165+
# If force rebuild is enabled, skip cache check and rebuild everything
166+
if [ "$FORCE_REBUILD" == "true" ]; then
167+
echo "🔄 force rebuild"
168+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | 🔄 Force rebuild |" >> "$GITHUB_STEP_SUMMARY"
169+
APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \
170+
--arg app "$APP_NAME" --arg sha "$CURRENT_SHA" \
171+
--arg archive_name "${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
172+
--arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
173+
'. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]')
174+
APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1))
175+
continue
176+
fi
177+
178+
# Check if this specific app should be rebuilt (ignore cache)
179+
REBUILD_THIS_APP=false
180+
for rebuild_app in "${REBUILD_APPS_ARRAY[@]}"; do
181+
if [ "$APP_NAME" == "$rebuild_app" ]; then
182+
REBUILD_THIS_APP=true
183+
break
184+
fi
185+
done
186+
187+
if [ "$REBUILD_THIS_APP" == "true" ]; then
188+
echo "🔨 rebuild requested"
189+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | 🔨 Rebuild requested |" >> "$GITHUB_STEP_SUMMARY"
190+
APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \
191+
--arg app "$APP_NAME" --arg sha "$CURRENT_SHA" \
192+
--arg archive_name "${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
193+
--arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
194+
'. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]')
195+
APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1))
196+
continue
197+
fi
198+
199+
# Check JFrog first before GitHub cache (available for all branches)
200+
if [ "$JFROG_AVAILABLE" == "true" ]; then
201+
JFROG_PATH="${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz"
202+
FOUND_IN_JFROG="false"
203+
204+
echo ""
205+
echo " 🔍 Checking JFrog for $APP_NAME..."
206+
echo " Path: $JFROG_PATH"
207+
echo " SHA: $SHORT_SHA"
208+
209+
# Check if artifact exists in JFrog with verbose output
210+
echo " Running: jf rt s \"$JFROG_PATH\""
211+
if SEARCH_OUTPUT=$(jf rt s "$JFROG_PATH" 2>&1); then
212+
echo " Search exit code: 0"
213+
echo " Search output:"
214+
echo "$SEARCH_OUTPUT" | sed 's/^/ /'
215+
216+
if echo "$SEARCH_OUTPUT" | grep -q "$JFROG_PATH"; then
217+
echo " ✓ Artifact found in JFrog!"
218+
ARCHIVE_NAME="${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz"
219+
echo "✓ in JFrog"
220+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$JFROG_PATH\` | 📦 In JFrog |" >> "$GITHUB_STEP_SUMMARY"
221+
APPS_IN_JFROG=$((APPS_IN_JFROG + 1))
222+
APPS_TO_RESTORE_COUNT=$((APPS_TO_RESTORE_COUNT + 1))
223+
# Add to restore list with JFrog source
224+
APPS_TO_RESTORE=$(echo "$APPS_TO_RESTORE" | jq -c --argjson app "$app_json" --arg sha "$CURRENT_SHA" --arg jfrog_path "$JFROG_PATH" --arg archive_name "$ARCHIVE_NAME" --arg source "jfrog" '. + [($app + {sha: $sha, jfrog_path: $jfrog_path, archive_name: $archive_name, source: $source})]')
225+
FOUND_IN_JFROG="true"
226+
else
227+
echo " ✗ Artifact not found in search results"
228+
fi
229+
else
230+
echo " Search exit code: non-zero"
231+
echo " ✗ Search failed with error:"
232+
echo "$SEARCH_OUTPUT" | sed 's/^/ /'
233+
fi
234+
235+
if [ "$FOUND_IN_JFROG" == "true" ]; then
236+
continue
237+
fi
238+
239+
echo "⚡ needs build (JFrog miss)"
240+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$JFROG_PATH\` | 🔨 Needs build (not in JFrog) |" >> "$GITHUB_STEP_SUMMARY"
241+
APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \
242+
--arg app "$APP_NAME" --arg sha "$CURRENT_SHA" \
243+
--arg archive_name "${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
244+
--arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
245+
'. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]')
246+
APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1))
247+
continue
248+
fi
249+
250+
# Check if cache exists using GitHub CLI
251+
# Include --ref to access caches from the current ref (branch, PR, etc.)
252+
if ! CACHE_LIST=$(gh cache list --ref "$GITHUB_REF" --key "$CACHE_KEY" --json key --jq ".[].key" 2>&1); then
253+
echo "⚠️ Warning: Failed to query cache for $APP_NAME: $CACHE_LIST"
254+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | ⚠️ Cache check failed - will build |" >> "$GITHUB_STEP_SUMMARY"
255+
APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \
256+
--arg app "$APP_NAME" --arg sha "$CURRENT_SHA" \
257+
--arg archive_name "${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
258+
--arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
259+
'. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]')
260+
APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1))
261+
continue
262+
fi
263+
if echo "$CACHE_LIST" | grep -q "^${CACHE_KEY}$"; then
264+
APPS_CACHED=$((APPS_CACHED + 1))
265+
APPS_TO_RESTORE_COUNT=$((APPS_TO_RESTORE_COUNT + 1))
266+
echo "✓ cached"
267+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | ✅ Cached |" >> "$GITHUB_STEP_SUMMARY"
268+
# Add to restore list with GitHub cache source
269+
APPS_TO_RESTORE=$(echo "$APPS_TO_RESTORE" | jq -c --argjson app "$app_json" --arg sha "$CURRENT_SHA" --arg cache_key "$CACHE_KEY" --arg source "github-cache" '. + [($app + {sha: $sha, cache_key: $cache_key, source: $source})]')
270+
else
271+
echo "⚡ needs build"
272+
echo "| $APP_NAME | \`$SHORT_SHA\` | \`$CACHE_KEY\` | 🔨 Needs build |" >> "$GITHUB_STEP_SUMMARY"
273+
APPS_TO_BUILD=$(echo "$APPS_TO_BUILD" | jq -c \
274+
--arg app "$APP_NAME" --arg sha "$CURRENT_SHA" \
275+
--arg archive_name "${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
276+
--arg jfrog_path "${ARTIFACTORY_REPOSITORY_SNAPSHOT}/apps/${CACHE_VERSION}/${APP_NAME}/${APP_NAME}-${ARCHIVE_SUFFIX}.tar.gz" \
277+
'. + [{name: $app, sha: $sha, archive_name: $archive_name, jfrog_path: $jfrog_path}]')
278+
APPS_TO_BUILD_COUNT=$((APPS_TO_BUILD_COUNT + 1))
279+
fi
280+
281+
done < <(echo "$MATRIX" | jq -c '.[]')
282+
283+
echo "" >> "$GITHUB_STEP_SUMMARY"
284+
echo "**Summary:**" >> "$GITHUB_STEP_SUMMARY"
285+
echo "- Total apps checked: $APPS_CHECKED" >> "$GITHUB_STEP_SUMMARY"
286+
echo "- 📦 Apps in JFrog: $APPS_IN_JFROG" >> "$GITHUB_STEP_SUMMARY"
287+
echo "- ✅ Apps with cached builds: $APPS_CACHED" >> "$GITHUB_STEP_SUMMARY"
288+
echo "- 🔨 Apps needing build: $APPS_TO_BUILD_COUNT" >> "$GITHUB_STEP_SUMMARY"
289+
echo "" >> "$GITHUB_STEP_SUMMARY"
290+
291+
TOTAL_AVAILABLE=$((APPS_IN_JFROG + APPS_CACHED))
292+
if [ $TOTAL_AVAILABLE -gt 0 ] && [ $APPS_CHECKED -gt 0 ]; then
293+
CACHE_HIT_PERCENT=$((TOTAL_AVAILABLE * 100 / APPS_CHECKED))
294+
echo "**Cache hit rate: ${CACHE_HIT_PERCENT}%** 🎯" >> "$GITHUB_STEP_SUMMARY"
295+
echo "" >> "$GITHUB_STEP_SUMMARY"
296+
fi
297+
298+
echo ""
299+
echo "Summary:"
300+
echo " Total apps: $APPS_CHECKED"
301+
echo " In JFrog: $APPS_IN_JFROG"
302+
echo " Cached: $APPS_CACHED"
303+
echo " To build: $APPS_TO_BUILD_COUNT"
304+
305+
# Validate no duplicate apps in build and restore lists
306+
BUILD_APPS=$(echo "$APPS_TO_BUILD" | jq -r '.[].name' | sort)
307+
RESTORE_APPS=$(echo "$APPS_TO_RESTORE" | jq -r '.[].name' | sort)
308+
DUPLICATE_APPS=$(comm -12 <(echo "$BUILD_APPS") <(echo "$RESTORE_APPS"))
309+
310+
if [ -n "$DUPLICATE_APPS" ]; then
311+
echo "ERROR: Apps appear in both build and restore lists:"
312+
echo "$DUPLICATE_APPS"
313+
exit 1
314+
fi
315+
316+
# Validate that we built valid JSON
317+
if ! echo "$APPS_TO_BUILD" | jq empty 2>/dev/null; then
318+
echo "ERROR: Failed to build valid JSON for apps_to_build"
319+
echo "Content: $APPS_TO_BUILD"
320+
exit 1
321+
fi
322+
323+
if ! echo "$APPS_TO_RESTORE" | jq empty 2>/dev/null; then
324+
echo "ERROR: Failed to build valid JSON for apps_to_restore"
325+
echo "Content: $APPS_TO_RESTORE"
326+
exit 1
327+
fi
328+
329+
# Output app list with SHAs for the build job to use
330+
# Use proper multiline output format for GitHub Actions
331+
echo "apps_to_build<<APPS_TO_BUILD_JSON_EOF" >> "$GITHUB_OUTPUT"
332+
echo "$APPS_TO_BUILD" >> "$GITHUB_OUTPUT"
333+
echo "APPS_TO_BUILD_JSON_EOF" >> "$GITHUB_OUTPUT"
334+
335+
# Output the unified list of apps to restore (from either GitHub cache or JFrog)
336+
echo "apps_to_restore<<APPS_TO_RESTORE_JSON_EOF" >> "$GITHUB_OUTPUT"
337+
echo "$APPS_TO_RESTORE" >> "$GITHUB_OUTPUT"
338+
echo "APPS_TO_RESTORE_JSON_EOF" >> "$GITHUB_OUTPUT"
339+
340+
# Output the SHA map for all apps
341+
echo "apps_sha_map<<APPS_SHA_MAP_JSON_EOF" >> "$GITHUB_OUTPUT"
342+
echo "$APPS_SHA_MAP" >> "$GITHUB_OUTPUT"
343+
echo "APPS_SHA_MAP_JSON_EOF" >> "$GITHUB_OUTPUT"
344+
345+
# Output flags for conditional job execution
346+
if [ $APPS_TO_BUILD_COUNT -gt 0 ]; then
347+
echo "has_apps_to_build=true" >> "$GITHUB_OUTPUT"
348+
else
349+
echo "has_apps_to_build=false" >> "$GITHUB_OUTPUT"
350+
fi
351+
352+
if [ $APPS_TO_RESTORE_COUNT -gt 0 ]; then
353+
echo "has_apps_to_restore=true" >> "$GITHUB_OUTPUT"
354+
else
355+
echo "has_apps_to_restore=false" >> "$GITHUB_OUTPUT"
356+
fi
357+
358+
echo ""
359+
if [ $APPS_TO_BUILD_COUNT -eq 0 ]; then
360+
echo "🎉 All apps are cached! No builds needed."
361+
else
362+
echo "✓ Will build $APPS_TO_BUILD_COUNT app(s)"
363+
fi

0 commit comments

Comments
 (0)