packaging: [[ ]] tests, named positional parameters and explicit returns (SonarCloud S7688, S7679, S7682) (#808)

* packaging: [[ ]] tests, named positional parameters and explicit returns (SonarCloud S7688, S7679, S7682)

Every packaging script is bash (macOS ships bash 3.2, which has [[ ]]), so the
single-bracket tests become [[ ]]: no word splitting or globbing of unquoted
operands. Every right-hand side of = is quoted or a plain literal, so no test
turns into a pattern match.

Functions that read positional parameters copy them into named locals, and the
env.sh helpers end with a bare return, which keeps the last command's status.

* fix: use POSIX test in generated /bin/sh helper
This commit is contained in:
Dylan M. Taylor
2026-10-23 11:28:35 -04:00
committed by GitHub
parent 3829b32c3b
commit ce3b29f4f2
10 changed files with 84 additions and 76 deletions
+10 -5
View File
@@ -20,10 +20,11 @@ workspace_version() {
/^\[/ { in_pkg = ($0 == "[workspace.package]") ; next }
in_pkg && $1 == "version" { gsub(/[" ]/, "", $3); print $3; exit }
' "$ROOT/Cargo.toml"
return
}
VERSION="${ARTCRAFT_LAUNCHER_VERSION:-$(workspace_version)}"
if [ -z "$VERSION" ]; then
if [[ -z "$VERSION" ]]; then
echo "error: could not read [workspace.package] version from $ROOT/Cargo.toml" >&2
exit 1
fi
@@ -33,7 +34,7 @@ DIST="${DIST:-$ROOT/dist/release}"
mkdir -p "$DIST"
export DIST
if [ -z "${ARTCRAFT_LAUNCHER_BUILD_SHA:-}" ]; then
if [[ -z "${ARTCRAFT_LAUNCHER_BUILD_SHA:-}" ]]; then
ARTCRAFT_LAUNCHER_BUILD_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || true)"
fi
export ARTCRAFT_LAUNCHER_BUILD_SHA
@@ -42,18 +43,22 @@ export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}"
# Emit a GitHub Actions warning (plain stderr outside Actions).
warn() {
if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi
if [[ -n "${GITHUB_ACTIONS:-}" ]]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi
return
}
# Copy licence, notice and readme files that exist into a package directory.
copy_docs() {
local dest="$1" f
for f in README.md LICENSE-MIT LICENSE-APACHE NOTICE ATTRIBUTION.md; do
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi
if [[ -f "$ROOT/$f" ]]; then cp "$ROOT/$f" "$dest/"; fi
done
return
}
# Portable SHA-256 of a file (prints just the hash).
sha256() {
if command -v sha256sum >/dev/null; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi
local file="$1"
if command -v sha256sum >/dev/null; then sha256sum "$file" | cut -d' ' -f1; else shasum -a 256 "$file" | cut -d' ' -f1; fi
return
}
+6 -6
View File
@@ -21,7 +21,7 @@ PKG=artcraft-launcher
SKIP_BUILD=0
DRY_RUN=0
while [ $# -gt 0 ]; do
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--dry-run) DRY_RUN=1; SKIP_BUILD=1; shift ;;
@@ -37,7 +37,7 @@ case "$MACHINE" in
arm64 | aarch64) ARCH=aarch64 ;;
*) echo "unsupported architecture $MACHINE" >&2; exit 2 ;;
esac
if [ "$DRY_RUN" = 0 ] && [ "$(uname -s)" != FreeBSD ]; then
if [[ "$DRY_RUN" = 0 ]] && [[ "$(uname -s)" != FreeBSD ]]; then
echo "error: build on FreeBSD (or pass --dry-run to check the tree layout)" >&2
exit 2
fi
@@ -46,7 +46,7 @@ BASENAME="$PKG-$VERSION-freebsd-$ARCH"
echo "==> ArtCraft Launcher $VERSION for FreeBSD $ARCH"
export CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-4}"
if [ "$SKIP_BUILD" = 0 ]; then
if [[ "$SKIP_BUILD" = 0 ]]; then
(cd "$ROOT" && cargo build --release --locked -p "$PKG")
fi
WORK="$CARGO_TARGET_DIR/freebsd-package"
@@ -56,7 +56,7 @@ mkdir -p "$WORK"
BIN="$CARGO_TARGET_DIR/release"
OUT_DIR="$DIST"
if [ "$DRY_RUN" = 1 ]; then
if [[ "$DRY_RUN" = 1 ]]; then
# A stub binary, and the tarball stays in the work dir so dist/ only ever holds real builds.
BIN="$WORK/stub-bin"
OUT_DIR="$WORK"
@@ -81,7 +81,7 @@ copy_docs "$STAGE/share/doc/$PKG"
for f in "bin/$PKG" "share/applications/$APP_ID.desktop" \
"share/icons/hicolor/256x256/apps/$APP_ID.png" "share/icons/hicolor/scalable/apps/$APP_ID.svg" \
"share/doc/$PKG/LICENSE-MIT" "share/doc/$PKG/LICENSE-APACHE"; do
if [ ! -e "$STAGE/$f" ]; then echo "error: $f is missing from the package" >&2; exit 1; fi
if [[ ! -e "$STAGE/$f" ]]; then echo "error: $f is missing from the package" >&2; exit 1; fi
done
# ---- .tar.gz ------------------------------------------------------------------------------------
@@ -89,7 +89,7 @@ mkdir -p "$OUT_DIR"
tar -C "$WORK" -czf "$OUT_DIR/$BASENAME.tar.gz" "$BASENAME"
echo "wrote $OUT_DIR/$BASENAME.tar.gz"
if [ "$DRY_RUN" = 1 ]; then
if [[ "$DRY_RUN" = 1 ]]; then
echo "==> dry run: tarball contents"
tar -tzvf "$OUT_DIR/$BASENAME.tar.gz"
fi
+4 -1
View File
@@ -30,7 +30,10 @@ MAC="$TMP/mac.svg"
sed "s/$BOX/viewBox=\"-17.5 -26.92 151.34 151.34\"/" "$SVG" >"$MAC"
FLAT="$SVG"
render() { resvg -w "$2" -h "$2" "$1" "$3" </dev/null; }
render() {
local src="$1" size="$2" out="$3"
resvg -w "$size" -h "$size" "$src" "$out" </dev/null
}
render "$MAC" 1024 "$DIR/artcraft-launcher-1024.png"
+19 -19
View File
@@ -52,10 +52,10 @@ fail() {
# 1. Launch works and passes arguments through.
out="$("$APPDIR/AppRun" open file.png)" || fail "AppRun exited non-zero"
[ "$out" = "stub: open file.png" ] || fail "arguments not forwarded: $out"
[[ "$out" = "stub: open file.png" ]] || fail "arguments not forwarded: $out"
DESKTOP="$XDG_DATA_HOME/applications/$APP_ID.desktop"
[ -f "$DESKTOP" ] || fail "desktop entry not installed"
[[ -f "$DESKTOP" ]] || fail "desktop entry not installed"
# 2. Exec rewritten to the AppImage (quoted, spaces intact), TryExec dropped,
# everything else preserved from the packaged entry.
@@ -70,22 +70,22 @@ fi
# 3. Icons landed under the name the .desktop entry references.
for f in "16x16/apps/$APP_ID.png" "256x256/apps/$APP_ID.png" "scalable/apps/$APP_ID.svg"; do
[ -f "$XDG_DATA_HOME/icons/hicolor/$f" ] || fail "icon missing: $f"
[[ -f "$XDG_DATA_HOME/icons/hicolor/$f" ]] || fail "icon missing: $f"
done
[ -f "$CACHE_CALLS" ] || fail "installed icons did not refresh the icon cache"
[ "$(wc -l <"$CACHE_CALLS")" -eq 1 ] || fail "icon cache refreshed more than once"
[[ -f "$CACHE_CALLS" ]] || fail "installed icons did not refresh the icon cache"
[[ "$(wc -l <"$CACHE_CALLS")" -eq 1 ]] || fail "icon cache refreshed more than once"
grep -Fxq -- "-f -t $XDG_DATA_HOME/icons/hicolor" "$CACHE_CALLS" || fail "wrong icon cache arguments"
if [ -n "$REAL_CACHE_UPDATER" ]; then
[ -f "$XDG_DATA_HOME/icons/hicolor/icon-theme.cache" ] || fail "GTK did not build the icon cache"
if [[ -n "$REAL_CACHE_UPDATER" ]]; then
[[ -f "$XDG_DATA_HOME/icons/hicolor/icon-theme.cache" ]] || fail "GTK did not build the icon cache"
fi
# 4. Idempotent: a second launch must not rewrite anything (inode unchanged).
before="$(stat -c %i "$DESKTOP")"
out="$("$APPDIR/AppRun")" || fail "second launch exited non-zero"
[ "$out" = "stub: " ] || fail "second launch output: $out"
[[ "$out" = "stub: " ]] || fail "second launch output: $out"
after="$(stat -c %i "$DESKTOP")"
[ "$before" = "$after" ] || fail "desktop entry rewritten although unchanged"
[ "$(wc -l <"$CACHE_CALLS")" -eq 1 ] || fail "unchanged icons refreshed the cache"
[[ "$before" = "$after" ]] || fail "desktop entry rewritten although unchanged"
[[ "$(wc -l <"$CACHE_CALLS")" -eq 1 ]] || fail "unchanged icons refreshed the cache"
# 5. Moving the AppImage refreshes Exec.
mv "$APPIMAGE" "$TMP/moved.AppImage"
@@ -97,31 +97,31 @@ cp "$DESKTOP" "$TMP/generated-entry"
# 6. Opt-out installs nothing.
rm -rf "$XDG_DATA_HOME"
ARTCRAFT_LAUNCHER_NO_DESKTOP_INTEGRATION=1 "$APPDIR/AppRun" >/dev/null || fail "opt-out launch exited non-zero"
[ ! -e "$XDG_DATA_HOME" ] || fail "opt-out still integrated"
[[ ! -e "$XDG_DATA_HOME" ]] || fail "opt-out still integrated"
# 7. An unwritable data dir never blocks the launch.
mkdir -p "$TMP/ro" && chmod a-w "$TMP/ro"
out="$(XDG_DATA_HOME="$TMP/ro/data" "$APPDIR/AppRun" out.txt)" || fail "launch blocked by unwritable data dir"
[ "$out" = "stub: out.txt" ] || fail "stub not run against unwritable data dir: $out"
[[ "$out" = "stub: out.txt" ]] || fail "stub not run against unwritable data dir: $out"
chmod u+w "$TMP/ro"
# 8. No $APPIMAGE (extracted AppDir): launch only, nothing installed.
rm -rf "$XDG_DATA_HOME"
env -u APPIMAGE "$APPDIR/AppRun" >/dev/null || fail "extracted-AppDir launch exited non-zero"
[ ! -e "$XDG_DATA_HOME" ] || fail "integrated without $APPIMAGE"
[[ ! -e "$XDG_DATA_HOME" ]] || fail "integrated without $APPIMAGE"
# 9. A path that would need Exec escaping (here a `$`): launch only, nothing installed.
rm -rf "$XDG_DATA_HOME"
odd="$TMP/odd\$(id).AppImage"
: >"$odd"
out="$(APPIMAGE="$odd" "$APPDIR/AppRun" x)" || fail "launch with an odd path exited non-zero"
[ "$out" = "stub: x" ] || fail "odd path: $out"
[ ! -e "$XDG_DATA_HOME" ] || fail "integrated an AppImage path that needs escaping"
[[ "$out" = "stub: x" ]] || fail "odd path: $out"
[[ ! -e "$XDG_DATA_HOME" ]] || fail "integrated an AppImage path that needs escaping"
# 10. Gear Lever's environment flag suppresses integration, including icon writes.
out="$(DESKTOPINTEGRATION=1 "$APPDIR/AppRun" managed)" || fail "manager launch failed"
[ "$out" = "stub: managed" ] || fail "manager launch lost arguments"
[ ! -e "$XDG_DATA_HOME" ] || fail "manager-integrated AppImage integrated again"
[[ "$out" = "stub: managed" ]] || fail "manager launch lost arguments"
[[ ! -e "$XDG_DATA_HOME" ]] || fail "manager-integrated AppImage integrated again"
# 11. A manager entry under the native id keeps TryExec and its launch environment.
mkdir -p "$(dirname "$DESKTOP")"
@@ -172,13 +172,13 @@ printf '#!/bin/sh\nexit 1\n' >"$TMP/tools/gtk-update-icon-cache"
icon="16x16/apps/$APP_ID.png"
printf 'stale icon' >"$XDG_DATA_HOME/icons/hicolor/$icon"
out="$("$APPDIR/AppRun" cache-failure)" || fail "cache failure blocked launch"
[ "$out" = "stub: cache-failure" ] || fail "cache failure lost arguments"
[[ "$out" = "stub: cache-failure" ]] || fail "cache failure lost arguments"
cmp -s "$APPDIR/usr/share/icons/hicolor/$icon" "$XDG_DATA_HOME/icons/hicolor/$icon" || fail "changed icon not refreshed"
# 14. A symlink to someone else's entry must not be replaced.
rm "$DESKTOP"
ln -s "$TMP/manager-entry" "$DESKTOP"
"$APPDIR/AppRun" >/dev/null || fail "launch with desktop symlink failed"
[ -L "$DESKTOP" ] || fail "desktop symlink replaced"
[[ -L "$DESKTOP" ]] || fail "desktop symlink replaced"
echo "AppRun desktop integration ok"
+4 -4
View File
@@ -22,7 +22,7 @@ FLATHUB=https://dl.flathub.org/repo/flathub.flatpakrepo
TEST=1
TARBALL=""
while [ $# -gt 0 ]; do
while [[ $# -gt 0 ]]; do
case "$1" in
--no-test) TEST=0; shift ;;
-h | --help) sed -n '2,14p' "$0"; exit 0 ;;
@@ -39,7 +39,7 @@ case "$ARCH" in
esac
BASENAME="$PKG-$VERSION-linux-$ARCH"
TARBALL="${TARBALL:-$DIST/$BASENAME.tar.gz}"
[ -f "$TARBALL" ] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; }
[[ -f "$TARBALL" ]] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; }
for tool in flatpak flatpak-builder; do
command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; }
done
@@ -50,7 +50,7 @@ WORK="$CARGO_TARGET_DIR/flatpak-bundle"
rm -rf "$WORK"
mkdir -p "$WORK/stage"
tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1
[ -x "$WORK/stage/bin/$PKG" ] || { echo "error: $TARBALL has no bin/$PKG" >&2; exit 1; }
[[ -x "$WORK/stage/bin/$PKG" ]] || { echo "error: $TARBALL has no bin/$PKG" >&2; exit 1; }
cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml"
flatpak remote-add --user --if-not-exists flathub "$FLATHUB"
@@ -63,7 +63,7 @@ OUT="$DIST/$BASENAME.flatpak"
flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable
echo "wrote $OUT"
if [ "$TEST" = 1 ]; then
if [[ "$TEST" = 1 ]]; then
flatpak install --user -y --noninteractive --reinstall "$OUT"
flatpak info --user "$APP_ID"
flatpak run --command=sh "$APP_ID" -c 'test -x /app/bin/artcraft-launcher && ls /app/share/applications /app/share/metainfo /app/share/icons/hicolor/scalable/apps'
+9 -9
View File
@@ -26,7 +26,7 @@ PKG=artcraft-launcher
SKIP_BUILD=0
FORMATS="appimage deb rpm tar"
while [ $# -gt 0 ]; do
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--formats) FORMATS="$2"; shift 2 ;;
@@ -55,8 +55,8 @@ BASENAME="$PKG-$VERSION-linux-$ARCH"
echo "==> ArtCraft Launcher $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then
if [ -n "${CROSS_TARGET:-}" ]; then
if [[ "$SKIP_BUILD" = 0 ]]; then
if [[ -n "${CROSS_TARGET:-}" ]]; then
(cd "$ROOT" && cargo build --release --locked -p "$PKG" --target "$CROSS_TARGET")
else
(cd "$ROOT" && cargo build --release --locked -p "$PKG")
@@ -86,7 +86,7 @@ if command -v appstreamcli >/dev/null; then
appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
fi
has() { case " $FORMATS " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
has() { local format="$1"; case " $FORMATS " in *" $format "*) return 0 ;; *) return 1 ;; esac; }
# ---- .tar.gz ------------------------------------------------------------------------------------
if has tar; then
@@ -123,9 +123,9 @@ if has appimage; then
ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
if [ -z "$TOOL" ]; then
if [[ -z "$TOOL" ]]; then
TOOL="$CARGO_TARGET_DIR/appimagetool-$ARCH.AppImage"
if [ ! -x "$TOOL" ]; then
if [[ ! -x "$TOOL" ]]; then
curl --proto '=https' -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage"
chmod +x "$TOOL"
fi
@@ -149,16 +149,16 @@ if has appimage; then
# package) appimagetool also writes the .zsync, into its working directory, hence the cd.
(cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream -u "$UPDATE_INFO" "$APPDIR" "$OUT")
echo "wrote $OUT"
if [ -s "$OUT.zsync" ]; then
if [[ -s "$OUT.zsync" ]]; then
echo "wrote $OUT.zsync"
else
warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it"
fi
fi
if [ -z "${CROSS_TARGET:-}" ]; then
if [[ -z "${CROSS_TARGET:-}" ]]; then
"$STAGE/usr/bin/$PKG" --version
elif [ -n "${EMULATOR:-}" ]; then
elif [[ -n "${EMULATOR:-}" ]]; then
"$EMULATOR" "$STAGE/usr/bin/$PKG" --version
fi
echo "==> done"
+3 -3
View File
@@ -12,7 +12,7 @@ umask 077
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
if [ -z "${APPLE_CERTIFICATE:-}" ]; then
if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then
warn "APPLE_CERTIFICATE is not set: macOS builds will be ad-hoc signed and not notarized"
exit 0
fi
@@ -43,13 +43,13 @@ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KC_PASS"
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" | awk '/Developer ID Application/ { print $2; exit }')"
if [ -z "$IDENTITY" ]; then
if [[ -z "$IDENTITY" ]]; then
security find-identity -v -p codesigning "$KEYCHAIN" >&2
echo "error: no 'Developer ID Application' identity in APPLE_CERTIFICATE" >&2
exit 1
fi
echo "Imported signing identity $IDENTITY"
if [ -n "${GITHUB_ENV:-}" ]; then
if [[ -n "${GITHUB_ENV:-}" ]]; then
{ echo "MACOS_SIGN_IDENTITY=$IDENTITY"; echo "MACOS_KEYCHAIN=$KEYCHAIN"; } >>"$GITHUB_ENV"
else
echo "MACOS_SIGN_IDENTITY=$IDENTITY"
+11 -11
View File
@@ -22,7 +22,7 @@ APP_NAME="ArtCraft Launcher" # display name: bundle, executable, icon, DMG vol
ARCH=universal
SKIP_BUILD=0
while [ $# -gt 0 ]; do
while [[ $# -gt 0 ]]; do
case "$1" in
--arch) ARCH="$2"; shift 2 ;;
--skip-build) SKIP_BUILD=1; shift ;;
@@ -46,9 +46,9 @@ APP="$WORK/$APP_NAME.app"
DMG="$DIST/$PKG-$VERSION-macos-$ARCH.dmg"
NOTARIZE=0
if [ "$IDENTITY" = "-" ]; then
if [[ "$IDENTITY" = "-" ]]; then
warn "macOS: ad-hoc signing (no MACOS_SIGN_IDENTITY); artifacts are not notarized"
elif [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then
elif [[ -n "${APPLE_ID:-}" ]] && [[ -n "${APPLE_PASSWORD:-}" ]] && [[ -n "${APPLE_TEAM_ID:-}" ]]; then
NOTARIZE=1
else
warn "macOS: APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID incomplete; signed but not notarized"
@@ -57,7 +57,7 @@ fi
echo "==> $APP_NAME $VERSION for macOS ($ARCH), identity: $IDENTITY, notarize: $NOTARIZE"
# ---- build -------------------------------------------------------------------------------------
if [ "$SKIP_BUILD" = 0 ]; then
if [[ "$SKIP_BUILD" = 0 ]]; then
args=()
for t in "${TARGETS[@]}"; do args+=(--target "$t"); done
(cd "$ROOT" && cargo build --release --locked -p "$PKG" "${args[@]}")
@@ -74,9 +74,9 @@ lipo -info "$WORK/bin/$PKG"
sign() {
# Hardened runtime + secure timestamp for a real identity; ad-hoc can't be timestamped.
local ts=(--timestamp)
[ "$IDENTITY" = "-" ] && ts=(--timestamp=none)
[[ "$IDENTITY" = "-" ]] && ts=(--timestamp=none)
local kc=()
[ -n "${MACOS_KEYCHAIN:-}" ] && kc=(--keychain "$MACOS_KEYCHAIN")
[[ -n "${MACOS_KEYCHAIN:-}" ]] && kc=(--keychain "$MACOS_KEYCHAIN")
codesign --force --sign "$IDENTITY" ${kc[@]+"${kc[@]}"} "${ts[@]}" "$@"
}
@@ -88,8 +88,8 @@ notarize() {
echo "$out"
id="$(printf '%s' "$out" | plutil -extract id raw -o - - 2>/dev/null || true)"
status="$(printf '%s' "$out" | plutil -extract status raw -o - - 2>/dev/null || true)"
if [ "$status" != "Accepted" ]; then
if [ -n "$id" ]; then
if [[ "$status" != "Accepted" ]]; then
if [[ -n "$id" ]]; then
xcrun notarytool log "$id" --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" || true
fi
echo "error: notarization of $(basename "$file") failed (status: ${status:-unknown})" >&2
@@ -123,7 +123,7 @@ sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP/Contents/
sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP"
codesign --verify --strict --deep --verbose=2 "$APP"
if [ "$NOTARIZE" = 1 ]; then
if [[ "$NOTARIZE" = 1 ]]; then
ditto -c -k --keepParent "$APP" "$WORK/notarize.zip"
notarize "$WORK/notarize.zip"
xcrun stapler staple "$APP"
@@ -156,7 +156,7 @@ hdiutil_retry() {
if hdiutil "$@"; then
return 0
fi
[ "$attempt" = 5 ] && { echo "hdiutil $what failed 5 times" >&2; return 1; }
[[ "$attempt" = 5 ]] && { echo "hdiutil $what failed 5 times" >&2; return 1; }
warn "hdiutil $what failed (attempt $attempt of 5); retrying in $((attempt * 10))s"
sleep $((attempt * 10))
done
@@ -164,7 +164,7 @@ hdiutil_retry() {
hdiutil_retry create create -srcfolder "$STAGE" -volname "$APP_NAME" -fs HFS+ -format UDZO -imagekey zlib-level=9 -ov "$DMG"
sign "$DMG"
codesign --verify --strict --verbose=2 "$DMG"
if [ "$NOTARIZE" = 1 ]; then
if [[ "$NOTARIZE" = 1 ]]; then
notarize "$DMG"
xcrun stapler staple "$DMG"
xcrun stapler validate "$DMG"
+13 -13
View File
@@ -20,7 +20,7 @@ PKG=artcraft-launcher
APP_NAME="ArtCraft Launcher"
ARCH=universal
while [ $# -gt 0 ]; do
while [[ $# -gt 0 ]]; do
case "$1" in
--arch) ARCH="$2"; shift 2 ;;
-h | --help) sed -n '2,14p' "$0"; exit 0 ;;
@@ -37,13 +37,13 @@ esac
DMG="$DIST/$PKG-$VERSION-macos-$ARCH.dmg"
IDENTITY="${MACOS_SIGN_IDENTITY:--}"
STRICT=0
if [ "$IDENTITY" != "-" ] && [ -n "${APPLE_ID:-}" ] && [ -n "${APPLE_PASSWORD:-}" ] && [ -n "${APPLE_TEAM_ID:-}" ]; then
if [[ "$IDENTITY" != "-" ]] && [[ -n "${APPLE_ID:-}" ]] && [[ -n "${APPLE_PASSWORD:-}" ]] && [[ -n "${APPLE_TEAM_ID:-}" ]]; then
STRICT=1
fi
FAILED=0
fail() {
if [ "$STRICT" = 1 ]; then
if [[ "$STRICT" = 1 ]]; then
echo "error: $*" >&2
FAILED=1
else
@@ -54,19 +54,19 @@ fail() {
# spctl's online ticket lookup can lag a few seconds behind notarytool's "Accepted".
assess() {
local out attempt tries=1
[ "$STRICT" = 1 ] && tries=5
[[ "$STRICT" = 1 ]] && tries=5
for attempt in $(seq "$tries"); do
if out="$(spctl "$@" 2>&1)"; then
printf '%s\n' "$out"
return 0
fi
[ "$attempt" = "$tries" ] || sleep 10
[[ "$attempt" = "$tries" ]] || sleep 10
done
printf '%s\n' "$out"
return 1
}
if [ ! -f "$DMG" ]; then
if [[ ! -f "$DMG" ]]; then
echo "error: missing $DMG" >&2
exit 1
fi
@@ -82,7 +82,7 @@ trap cleanup EXIT
# ---- DMG ---------------------------------------------------------------------------------------
echo "==> $(basename "$DMG")"
codesign --verify --strict --verbose=2 "$DMG"
if [ "$STRICT" = 1 ]; then
if [[ "$STRICT" = 1 ]]; then
xcrun stapler validate "$DMG" || fail "no notarization ticket stapled to the DMG"
assess --assess --type open --context context:primary-signature -vv "$DMG" \
|| fail "Gatekeeper rejects the DMG"
@@ -93,7 +93,7 @@ mkdir -p "$MNT"
hdiutil attach "$DMG" -readonly -nobrowse -noautoopen -mountpoint "$MNT" -quiet
APP="$MNT/$APP_NAME.app"
EXE="$APP/Contents/MacOS/$APP_NAME"
if [ ! -x "$EXE" ]; then
if [[ ! -x "$EXE" ]]; then
echo "error: $(basename "$DMG") has no $APP_NAME.app/Contents/MacOS/$APP_NAME" >&2
exit 1
fi
@@ -128,11 +128,11 @@ printf '%s\n' "$details" | grep -q '^Authority=Developer ID Application:' \
|| fail "$APP_NAME.app is not signed with a Developer ID Application identity"
printf '%s\n' "$details" | grep -q '^Timestamp=' \
|| fail "$APP_NAME.app signature has no secure timestamp"
if [ -n "${APPLE_TEAM_ID:-}" ]; then
if [[ -n "${APPLE_TEAM_ID:-}" ]]; then
printf '%s\n' "$details" | grep -qx "TeamIdentifier=$APPLE_TEAM_ID" \
|| fail "$APP_NAME.app is signed by a different team"
fi
if [ "$STRICT" = 1 ]; then
if [[ "$STRICT" = 1 ]]; then
xcrun stapler validate "$APP" || fail "no notarization ticket stapled to $APP_NAME.app"
if gk="$(assess --assess --type execute -vv "$APP")"; then
printf '%s\n' "$gk"
@@ -144,17 +144,17 @@ if [ "$STRICT" = 1 ]; then
fi
fi
if [ "$FAILED" != 0 ]; then
if [[ "$FAILED" != 0 ]]; then
echo "error: macOS artifacts failed verification" >&2
exit 1
fi
if [ "$STRICT" = 1 ]; then
if [[ "$STRICT" = 1 ]]; then
result="signed (Developer ID, hardened runtime), notarized; DMG and app stapled"
else
result="signature intact; Developer ID and notarization not required (no signing secrets)"
fi
echo "==> verified: $result"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
echo "- macOS \`$(basename "$DMG")\`: $result" >>"$GITHUB_STEP_SUMMARY"
fi
+5 -5
View File
@@ -11,13 +11,13 @@ set -euo pipefail
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
HERE="$ROOT/packaging/web"
if [ "${1:-}" != "--skip-build" ]; then
if [[ "${1:-}" != "--skip-build" ]]; then
command -v trunk >/dev/null || { echo "error: trunk not found (cargo install trunk --locked)" >&2; exit 1; }
(cd "$ROOT/apps/artcraft-launcher-web" && trunk build --release --cargo-profile wasm-release)
fi
SITE="$ROOT/dist/web"
[ -f "$SITE/index.html" ] || { echo "error: $SITE/index.html missing; run without --skip-build" >&2; exit 1; }
[[ -f "$SITE/index.html" ]] || { echo "error: $SITE/index.html missing; run without --skip-build" >&2; exit 1; }
# Paths must be relative so the site works under any prefix (public_url = "./" in Trunk.toml).
if grep -Eq '(src|href)="/[^/]' "$SITE/index.html"; then
echo "error: $SITE/index.html has root-absolute URLs; it would break when served from a sub-path" >&2
@@ -30,16 +30,16 @@ fi
MAX_WASM_BYTES="${ARTCRAFT_LAUNCHER_WASM_MAX_BYTES:-25165824}" # 24 MiB
WASM_COUNT=0
for wasm in "$SITE"/*.wasm; do
[ -f "$wasm" ] || continue
[[ -f "$wasm" ]] || continue
WASM_COUNT=$((WASM_COUNT + 1))
size=$(wc -c <"$wasm" | tr -d ' ')
echo "$(basename "$wasm"): $size bytes ($((size / 1048576)) MiB; limit $MAX_WASM_BYTES)"
if [ "$size" -gt "$MAX_WASM_BYTES" ]; then
if [[ "$size" -gt "$MAX_WASM_BYTES" ]]; then
echo "error: $(basename "$wasm") is $size bytes, over the $MAX_WASM_BYTES-byte limit (Cloudflare's per-file cap is 25 MiB)" >&2
exit 1
fi
done
[ "$WASM_COUNT" -gt 0 ] || { echo "error: no .wasm in $SITE" >&2; exit 1; }
[[ "$WASM_COUNT" -gt 0 ]] || { echo "error: no .wasm in $SITE" >&2; exit 1; }
NAME="artcraft-launcher-web-$VERSION"
WORK="$CARGO_TARGET_DIR/web-package"