packaging: harden shell scripts (SonarCloud S7688/S7679/S7682/S131) (#1640)

- Use bash's [[ ]] instead of [ ] for conditional tests (S7688).
- Copy positional parameters into named locals in render(), has() and
  sha256() (S7679).
- End the env.sh helper functions with an explicit return, which keeps
  the last command's status (S7682).
- Give the appimagetool checksum case a default branch for architectures
  without a pinned download (S131).

No behaviour change; shellcheck -x is clean.
This commit is contained in:
Dylan M. Taylor
2026-10-23 11:16:54 -04:00
committed by GitHub
parent df1db1646b
commit 2e36dcf909
8 changed files with 58 additions and 45 deletions
+12 -7
View File
@@ -19,10 +19,11 @@ workspace_version() {
/^\[/ { in_pkg = ($0 == "[workspace.package]") ; next } /^\[/ { in_pkg = ($0 == "[workspace.package]") ; next }
in_pkg && $1 == "version" { gsub(/[" ]/, "", $3); print $3; exit } in_pkg && $1 == "version" { gsub(/[" ]/, "", $3); print $3; exit }
' "$ROOT/Cargo.toml" ' "$ROOT/Cargo.toml"
return
} }
VERSION="${DESIGNCRAFT_VERSION:-$(workspace_version)}" VERSION="${DESIGNCRAFT_VERSION:-$(workspace_version)}"
if [ -z "$VERSION" ]; then if [[ -z "$VERSION" ]]; then
echo "error: could not read [workspace.package] version from $ROOT/Cargo.toml" >&2 echo "error: could not read [workspace.package] version from $ROOT/Cargo.toml" >&2
exit 1 exit 1
fi fi
@@ -32,7 +33,7 @@ DIST="${DIST:-$ROOT/dist/release}"
mkdir -p "$DIST" mkdir -p "$DIST"
export DIST export DIST
if [ -z "${DESIGNCRAFT_BUILD_SHA:-}" ]; then if [[ -z "${DESIGNCRAFT_BUILD_SHA:-}" ]]; then
DESIGNCRAFT_BUILD_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || true)" DESIGNCRAFT_BUILD_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || true)"
fi fi
export DESIGNCRAFT_BUILD_SHA export DESIGNCRAFT_BUILD_SHA
@@ -41,29 +42,33 @@ export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}"
# Emit a GitHub Actions warning (plain stderr outside Actions). # Emit a GitHub Actions warning (plain stderr outside Actions).
warn() { 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 and readme files that exist into a package directory. # Copy licence and readme files that exist into a package directory.
copy_docs() { copy_docs() {
local dest="$1" f local dest="$1" f
for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE COPYRIGHT; do for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE COPYRIGHT; do
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi if [[ -f "$ROOT/$f" ]]; then cp "$ROOT/$f" "$dest/"; fi
done done
copy_font_licences "$dest" copy_font_licences "$dest"
return
} }
# Builds made with craft-fonts (CRAFT_FONTS_DIR, set by the release workflow) embed its fonts, so # Builds made with craft-fonts (CRAFT_FONTS_DIR, set by the release workflow) embed its fonts, so
# the package carries their licences as OFL-<family-dir>.txt. Nothing without CRAFT_FONTS_DIR. # the package carries their licences as OFL-<family-dir>.txt. Nothing without CRAFT_FONTS_DIR.
copy_font_licences() { copy_font_licences() {
local dest="$1" f local dest="$1" f
[ -n "${CRAFT_FONTS_DIR:-}" ] || return 0 [[ -n "${CRAFT_FONTS_DIR:-}" ]] || return 0
for f in "$CRAFT_FONTS_DIR"/fonts/*/OFL.txt; do for f in "$CRAFT_FONTS_DIR"/fonts/*/OFL.txt; do
if [ -f "$f" ]; then cp "$f" "$dest/OFL-$(basename "$(dirname "$f")").txt"; fi if [[ -f "$f" ]]; then cp "$f" "$dest/OFL-$(basename "$(dirname "$f")").txt"; fi
done done
} }
# Portable SHA-256 of a file (prints just the hash). # Portable SHA-256 of a file (prints just the hash).
sha256() { 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
} }
+7 -7
View File
@@ -20,7 +20,7 @@ APP_ID=ai.storyteller.designcraft
SKIP_BUILD=0 SKIP_BUILD=0
DRY_RUN=0 DRY_RUN=0
while [ $# -gt 0 ]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--skip-build) SKIP_BUILD=1; shift ;; --skip-build) SKIP_BUILD=1; shift ;;
--dry-run) DRY_RUN=1; SKIP_BUILD=1; shift ;; --dry-run) DRY_RUN=1; SKIP_BUILD=1; shift ;;
@@ -36,7 +36,7 @@ case "$MACHINE" in
arm64 | aarch64) ARCH=aarch64 ;; arm64 | aarch64) ARCH=aarch64 ;;
*) echo "unsupported architecture $MACHINE" >&2; exit 2 ;; *) echo "unsupported architecture $MACHINE" >&2; exit 2 ;;
esac 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 echo "error: build on FreeBSD (or pass --dry-run to check the tree layout)" >&2
exit 2 exit 2
fi fi
@@ -46,7 +46,7 @@ echo "==> DesignCraft $VERSION for FreeBSD $ARCH"
# The release VM has 12 GB; full parallelism on the biggest crates runs it out of memory. # The release VM has 12 GB; full parallelism on the biggest crates runs it out of memory.
export CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-4}" 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 designcraft -p designcraft-cli) (cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli)
fi fi
WORK="$CARGO_TARGET_DIR/freebsd-package" WORK="$CARGO_TARGET_DIR/freebsd-package"
@@ -56,7 +56,7 @@ mkdir -p "$WORK"
BIN="$CARGO_TARGET_DIR/release" BIN="$CARGO_TARGET_DIR/release"
OUT_DIR="$DIST" OUT_DIR="$DIST"
if [ "$DRY_RUN" = 1 ]; then if [[ "$DRY_RUN" = 1 ]]; then
# Stub binaries, and the tarball stays in the work dir so dist/ only ever holds real builds. # Stub binaries, and the tarball stays in the work dir so dist/ only ever holds real builds.
BIN="$WORK/stub-bin" BIN="$WORK/stub-bin"
OUT_DIR="$WORK" OUT_DIR="$WORK"
@@ -81,13 +81,13 @@ sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$DESIGNCRAFT_BUILD_DATE/g" \
cp -R "$ROOT/assets/app-icon/hicolor" "$STAGE/share/icons/" cp -R "$ROOT/assets/app-icon/hicolor" "$STAGE/share/icons/"
copy_docs "$STAGE/share/doc/designcraft" copy_docs "$STAGE/share/doc/designcraft"
for f in NOTICE ATTRIBUTION.md; do for f in NOTICE ATTRIBUTION.md; do
if [ -f "$ROOT/$f" ]; then install -m 644 "$ROOT/$f" "$STAGE/share/doc/designcraft/"; fi if [[ -f "$ROOT/$f" ]]; then install -m 644 "$ROOT/$f" "$STAGE/share/doc/designcraft/"; fi
done done
for f in bin/designcraft bin/designcraft-cli "share/applications/$APP_ID.desktop" \ for f in bin/designcraft bin/designcraft-cli "share/applications/$APP_ID.desktop" \
"share/icons/hicolor/256x256/apps/$APP_ID.png" "share/icons/hicolor/scalable/apps/$APP_ID.svg" \ "share/icons/hicolor/256x256/apps/$APP_ID.png" "share/icons/hicolor/scalable/apps/$APP_ID.svg" \
share/doc/designcraft/LICENSE-MIT share/doc/designcraft/LICENSE-APACHE; do share/doc/designcraft/LICENSE-MIT share/doc/designcraft/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 done
# ---- .tar.gz ------------------------------------------------------------------------------------ # ---- .tar.gz ------------------------------------------------------------------------------------
@@ -96,7 +96,7 @@ tar -C "$WORK" -czf "$OUT_DIR/$BASENAME.tar.gz" "$BASENAME"
echo "wrote $OUT_DIR/$BASENAME.tar.gz" echo "wrote $OUT_DIR/$BASENAME.tar.gz"
"$STAGE/bin/designcraft-cli" --version "$STAGE/bin/designcraft-cli" --version
if [ "$DRY_RUN" = 1 ]; then if [[ "$DRY_RUN" = 1 ]]; then
echo "==> dry run: tarball contents" echo "==> dry run: tarball contents"
tar -tzvf "$OUT_DIR/$BASENAME.tar.gz" tar -tzvf "$OUT_DIR/$BASENAME.tar.gz"
fi fi
+4 -1
View File
@@ -21,7 +21,10 @@ MAC="$TMP/mac.svg"
sed 's/viewBox="0 0 512 512"/viewBox="-62.136 -62.136 636.272 636.272"/' "$SVG" >"$MAC" sed 's/viewBox="0 0 512 512"/viewBox="-62.136 -62.136 636.272 636.272"/' "$SVG" >"$MAC"
grep -q 'viewBox="-62.136' "$MAC" || { echo "error: expected viewBox=\"0 0 512 512\" in $SVG" >&2; exit 1; } grep -q 'viewBox="-62.136' "$MAC" || { echo "error: expected viewBox=\"0 0 512 512\" in $SVG" >&2; exit 1; }
render() { resvg -w "$2" -h "$2" "$1" "$3" </dev/null; } render() {
local svg="$1" size="$2" out="$3"
resvg -w "$size" -h "$size" "$svg" "$out" </dev/null
}
render "$SVG" 1024 "$DIR/designcraft-1024.png" render "$SVG" 1024 "$DIR/designcraft-1024.png"
# Runtime window/Dock icon on macOS (embedded by apps/designcraft); other platforms use hicolor 256. # Runtime window/Dock icon on macOS (embedded by apps/designcraft); other platforms use hicolor 256.
+4 -4
View File
@@ -21,7 +21,7 @@ FLATHUB=https://dl.flathub.org/repo/flathub.flatpakrepo
TEST=1 TEST=1
TARBALL="" TARBALL=""
while [ $# -gt 0 ]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--no-test) TEST=0; shift ;; --no-test) TEST=0; shift ;;
-h | --help) sed -n '2,14p' "$0"; exit 0 ;; -h | --help) sed -n '2,14p' "$0"; exit 0 ;;
@@ -38,7 +38,7 @@ case "$ARCH" in
esac esac
BASENAME="designcraft-$VERSION-linux-$ARCH" BASENAME="designcraft-$VERSION-linux-$ARCH"
TARBALL="${TARBALL:-$DIST/$BASENAME.tar.gz}" 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 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; } command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; }
done done
@@ -49,7 +49,7 @@ WORK="$CARGO_TARGET_DIR/flatpak-bundle"
rm -rf "$WORK" rm -rf "$WORK"
mkdir -p "$WORK/stage" mkdir -p "$WORK/stage"
tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1 tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1
[ -x "$WORK/stage/bin/designcraft" ] || { echo "error: $TARBALL has no bin/designcraft" >&2; exit 1; } [[ -x "$WORK/stage/bin/designcraft" ]] || { echo "error: $TARBALL has no bin/designcraft" >&2; exit 1; }
cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml" cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml"
flatpak remote-add --user --if-not-exists flathub "$FLATHUB" flatpak remote-add --user --if-not-exists flathub "$FLATHUB"
@@ -62,7 +62,7 @@ OUT="$DIST/$BASENAME.flatpak"
flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable
echo "wrote $OUT" echo "wrote $OUT"
if [ "$TEST" = 1 ]; then if [[ "$TEST" = 1 ]]; then
flatpak install --user -y --noninteractive --reinstall "$OUT" flatpak install --user -y --noninteractive --reinstall "$OUT"
flatpak info --user "$APP_ID" flatpak info --user "$APP_ID"
flatpak run --command=designcraft-cli "$APP_ID" --version flatpak run --command=designcraft-cli "$APP_ID" --version
+14 -9
View File
@@ -22,7 +22,7 @@ APP_ID=ai.storyteller.designcraft
SKIP_BUILD=0 SKIP_BUILD=0
FORMATS="appimage deb rpm arch tar" FORMATS="appimage deb rpm arch tar"
while [ $# -gt 0 ]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--skip-build) SKIP_BUILD=1; shift ;; --skip-build) SKIP_BUILD=1; shift ;;
--formats) FORMATS="$2"; shift 2 ;; --formats) FORMATS="$2"; shift 2 ;;
@@ -43,8 +43,8 @@ BASENAME="designcraft-$VERSION-linux-$ARCH"
echo "==> DesignCraft $VERSION for Linux $ARCH ($FORMATS)" echo "==> DesignCraft $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then if [[ "$SKIP_BUILD" = 0 ]]; then
if [ -n "${CROSS_TARGET:-}" ]; then if [[ -n "${CROSS_TARGET:-}" ]]; then
(cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli --target "$CROSS_TARGET") (cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli --target "$CROSS_TARGET")
else else
(cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli) (cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli)
@@ -76,7 +76,10 @@ if command -v appstreamcli >/dev/null; then
appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml" appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
fi fi
has() { case " $FORMATS " in *" $1 "*) return 0 ;; *) return 1 ;; esac; } has() {
local fmt="$1"
case " $FORMATS " in *" $fmt "*) return 0 ;; *) return 1 ;; esac
}
# ---- .tar.gz ------------------------------------------------------------------------------------ # ---- .tar.gz ------------------------------------------------------------------------------------
if has tar; then if has tar; then
@@ -134,6 +137,8 @@ if has appimage; then
case "$ARCH" in case "$ARCH" in
x86_64) APPIMAGETOOL_SHA256=ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 ;; x86_64) APPIMAGETOOL_SHA256=ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 ;;
aarch64) APPIMAGETOOL_SHA256=f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158 ;; aarch64) APPIMAGETOOL_SHA256=f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158 ;;
# No pinned download (e.g. riscv64): set APPIMAGETOOL or put appimagetool on PATH.
*) APPIMAGETOOL_SHA256="" ;;
esac esac
APPDIR="$WORK/DesignCraft.AppDir" APPDIR="$WORK/DesignCraft.AppDir"
cp -R "$STAGE" "$APPDIR" cp -R "$STAGE" "$APPDIR"
@@ -144,9 +149,9 @@ if has appimage; then
ln -s "$APP_ID.png" "$APPDIR/.DirIcon" ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}" TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
if [ -z "$TOOL" ]; then if [[ -z "$TOOL" ]]; then
TOOL="$CARGO_TARGET_DIR/appimagetool-$APPIMAGETOOL_VERSION-$ARCH.AppImage" TOOL="$CARGO_TARGET_DIR/appimagetool-$APPIMAGETOOL_VERSION-$ARCH.AppImage"
if [ ! -x "$TOOL" ]; then if [[ ! -x "$TOOL" ]]; then
download="$TOOL.download" download="$TOOL.download"
curl --proto '=https' -fsSL -o "$download" \ curl --proto '=https' -fsSL -o "$download" \
"https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage" "https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage"
@@ -174,16 +179,16 @@ if has appimage; then
# package) appimagetool also writes the .zsync, into its working directory, hence the cd. # 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") (cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream -u "$UPDATE_INFO" "$APPDIR" "$OUT")
echo "wrote $OUT" echo "wrote $OUT"
if [ -s "$OUT.zsync" ]; then if [[ -s "$OUT.zsync" ]]; then
echo "wrote $OUT.zsync" echo "wrote $OUT.zsync"
else else
warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it" warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it"
fi fi
fi fi
if [ -z "${CROSS_TARGET:-}" ]; then if [[ -z "${CROSS_TARGET:-}" ]]; then
"$STAGE/usr/bin/designcraft-cli" --version "$STAGE/usr/bin/designcraft-cli" --version
elif [ -n "${EMULATOR:-}" ]; then elif [[ -n "${EMULATOR:-}" ]]; then
"$EMULATOR" "$STAGE/usr/bin/designcraft-cli" --version "$EMULATOR" "$STAGE/usr/bin/designcraft-cli" --version
fi fi
echo "==> done" echo "==> done"
+3 -3
View File
@@ -12,7 +12,7 @@ umask 077
# shellcheck source=../env.sh # shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../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" warn "APPLE_CERTIFICATE is not set: macOS builds will be ad-hoc signed and not notarized"
exit 0 exit 0
fi fi
@@ -36,13 +36,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 '"') 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 }')" 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 security find-identity -v -p codesigning "$KEYCHAIN" >&2
echo "error: no 'Developer ID Application' identity in APPLE_CERTIFICATE" >&2 echo "error: no 'Developer ID Application' identity in APPLE_CERTIFICATE" >&2
exit 1 exit 1
fi fi
echo "Imported signing identity $IDENTITY" 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" { echo "MACOS_SIGN_IDENTITY=$IDENTITY"; echo "MACOS_KEYCHAIN=$KEYCHAIN"; } >>"$GITHUB_ENV"
else else
echo "MACOS_SIGN_IDENTITY=$IDENTITY" echo "MACOS_SIGN_IDENTITY=$IDENTITY"
+12 -12
View File
@@ -19,7 +19,7 @@ HERE="$ROOT/packaging/macos"
ARCH=universal ARCH=universal
SKIP_BUILD=0 SKIP_BUILD=0
while [ $# -gt 0 ]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--arch) ARCH="$2"; shift 2 ;; --arch) ARCH="$2"; shift 2 ;;
--skip-build) SKIP_BUILD=1; shift ;; --skip-build) SKIP_BUILD=1; shift ;;
@@ -44,9 +44,9 @@ DMG="$DIST/designcraft-$VERSION-macos-$ARCH.dmg"
CLI_ZIP="$DIST/designcraft-cli-$VERSION-macos-$ARCH.zip" CLI_ZIP="$DIST/designcraft-cli-$VERSION-macos-$ARCH.zip"
NOTARIZE=0 NOTARIZE=0
if [ "$IDENTITY" = "-" ]; then if [[ "$IDENTITY" = "-" ]]; then
warn "macOS: ad-hoc signing (no MACOS_SIGN_IDENTITY); artifacts are not notarized" 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 NOTARIZE=1
else else
warn "macOS: APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID incomplete; signed but not notarized" warn "macOS: APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID incomplete; signed but not notarized"
@@ -55,7 +55,7 @@ fi
echo "==> DesignCraft $VERSION for macOS ($ARCH), identity: $IDENTITY, notarize: $NOTARIZE" echo "==> DesignCraft $VERSION for macOS ($ARCH), identity: $IDENTITY, notarize: $NOTARIZE"
# ---- build ------------------------------------------------------------------------------------- # ---- build -------------------------------------------------------------------------------------
if [ "$SKIP_BUILD" = 0 ]; then if [[ "$SKIP_BUILD" = 0 ]]; then
args=() args=()
for t in "${TARGETS[@]}"; do args+=(--target "$t"); done for t in "${TARGETS[@]}"; do args+=(--target "$t"); done
(cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli "${args[@]}") (cd "$ROOT" && cargo build --release --locked -p designcraft -p designcraft-cli "${args[@]}")
@@ -74,9 +74,9 @@ done
sign() { sign() {
# Hardened runtime + secure timestamp for a real identity; ad-hoc can't be timestamped. # Hardened runtime + secure timestamp for a real identity; ad-hoc can't be timestamped.
local ts=(--timestamp) local ts=(--timestamp)
[ "$IDENTITY" = "-" ] && ts=(--timestamp=none) [[ "$IDENTITY" = "-" ]] && ts=(--timestamp=none)
local kc=() local kc=()
[ -n "${MACOS_KEYCHAIN:-}" ] && kc=(--keychain "$MACOS_KEYCHAIN") [[ -n "${MACOS_KEYCHAIN:-}" ]] && kc=(--keychain "$MACOS_KEYCHAIN")
codesign --force --sign "$IDENTITY" ${kc[@]+"${kc[@]}"} "${ts[@]}" "$@" codesign --force --sign "$IDENTITY" ${kc[@]+"${kc[@]}"} "${ts[@]}" "$@"
} }
@@ -88,8 +88,8 @@ notarize() {
echo "$out" echo "$out"
id="$(printf '%s' "$out" | plutil -extract id raw -o - - 2>/dev/null || true)" 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)" status="$(printf '%s' "$out" | plutil -extract status raw -o - - 2>/dev/null || true)"
if [ "$status" != "Accepted" ]; then if [[ "$status" != "Accepted" ]]; then
if [ -n "$id" ]; then if [[ -n "$id" ]]; then
xcrun notarytool log "$id" --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" || true xcrun notarytool log "$id" --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" || true
fi fi
echo "error: notarization of $(basename "$file") failed (status: ${status:-unknown})" >&2 echo "error: notarization of $(basename "$file") failed (status: ${status:-unknown})" >&2
@@ -117,7 +117,7 @@ sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP/Contents/
sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP" sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP"
codesign --verify --strict --deep --verbose=2 "$APP" codesign --verify --strict --deep --verbose=2 "$APP"
if [ "$NOTARIZE" = 1 ]; then if [[ "$NOTARIZE" = 1 ]]; then
ditto -c -k --keepParent "$APP" "$WORK/DesignCraft-notarize.zip" ditto -c -k --keepParent "$APP" "$WORK/DesignCraft-notarize.zip"
notarize "$WORK/DesignCraft-notarize.zip" notarize "$WORK/DesignCraft-notarize.zip"
xcrun stapler staple "$APP" xcrun stapler staple "$APP"
@@ -151,7 +151,7 @@ hdiutil_retry() {
if hdiutil "$@"; then if hdiutil "$@"; then
return 0 return 0
fi fi
[ "$attempt" = 5 ] && { echo "hdiutil $what failed 5 times" >&2; return 1; } [[ "$attempt" = 5 ]] && { echo "hdiutil $what failed 5 times" >&2; return 1; }
echo "hdiutil $what failed (attempt $attempt of 5); retrying in $((attempt * 10))s" >&2 echo "hdiutil $what failed (attempt $attempt of 5); retrying in $((attempt * 10))s" >&2
sleep $((attempt * 10)) sleep $((attempt * 10))
done done
@@ -169,7 +169,7 @@ if ! codesign --verify --strict --deep --verbose=2 "$MOUNT/DesignCraft.app"; the
exit 1 exit 1
fi fi
hdiutil_retry detach detach "$MOUNT" >/dev/null hdiutil_retry detach detach "$MOUNT" >/dev/null
if [ "$NOTARIZE" = 1 ]; then if [[ "$NOTARIZE" = 1 ]]; then
notarize "$DMG" notarize "$DMG"
xcrun stapler staple "$DMG" xcrun stapler staple "$DMG"
xcrun stapler validate "$DMG" xcrun stapler validate "$DMG"
@@ -187,7 +187,7 @@ codesign --verify --strict --verbose=2 "$CLI_DIR/designcraft-cli"
rm -f "$CLI_ZIP" rm -f "$CLI_ZIP"
ditto -c -k --keepParent "$CLI_DIR" "$CLI_ZIP" ditto -c -k --keepParent "$CLI_DIR" "$CLI_ZIP"
# A bare Mach-O can't carry a stapled ticket; Gatekeeper looks the notarization up online. # A bare Mach-O can't carry a stapled ticket; Gatekeeper looks the notarization up online.
if [ "$NOTARIZE" = 1 ]; then notarize "$CLI_ZIP"; fi if [[ "$NOTARIZE" = 1 ]]; then notarize "$CLI_ZIP"; fi
"$WORK/bin/designcraft-cli" --version "$WORK/bin/designcraft-cli" --version
echo "==> done" echo "==> done"
+2 -2
View File
@@ -11,13 +11,13 @@ set -euo pipefail
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh" . "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
HERE="$ROOT/packaging/web" 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; } command -v trunk >/dev/null || { echo "error: trunk not found (cargo install trunk --locked)" >&2; exit 1; }
(cd "$ROOT/apps/designcraft-web" && trunk build --release) (cd "$ROOT/apps/designcraft-web" && trunk build --release)
fi fi
SITE="$ROOT/dist/web" 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). # 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 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 echo "error: $SITE/index.html has root-absolute URLs; it would break when served from a sub-path" >&2