photocraft/packaging/web/package.sh
Zuujis df055f3dcb
Some checks are pending
Documentation / mdBook build (push) Waiting to run
Packaging lint / lint (push) Waiting to run
Packaging lint / Verified AppImage inputs (push) Waiting to run
CI: fix the tests that keep main red, and ship the wasm as compressed parts (#5926, #5302) (#5941)
* Fix two ui-egui tests broken on main by concurrent landings

- profile_open_tests: #5614 renamed the built-in profile to "(PhotoCraft)" after #5208
  hard-coded "(Photocraft)" in the Use-embedded button label.
- smart_object_curves_preview_keeps_its_size_on_a_reduced_proxy (#5779): the reduced
  square is in the right place, but its corner pixels are 220/255 after the smart
  object's resampling; assert full coverage inside and over half at the corners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Web: ship the wasm as compressed parts behind a boot module (#5302)

The release site held the app's wasm as one file, which sits at the 25 MiB
per-file cap of Cloudflare Pages, so the 24 MiB gate failed unrelated PRs.

packaging/web/package.sh now gzips the wasm and cuts it into parts of at
most 20 MiB under app/, and ships the page of a new crate,
apps/photocraft-web-boot. That module fetches the parts in order, has the
browser inflate them (DecompressionStream) and passes the stream to the
app's own wasm-bindgen loader, so the browser still compiles while it
downloads. A failed download is retried twice, then the page says so.

The check is now that no file in the site is over 24 MiB. trunk build in
apps/photocraft-web and the Docker image still give one .wasm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Web boot: inflate the parts in the module where the browser can't (#5302)

* Tests: the adjustment-layer drag preview test follows #5778 (#5926)

#5778 caps a live adjustment-layer preview by the view's zoom, so at 100 %
the drag shows full resolution even with Low Resolution Previews on. The
test still expected the reduced copy there. It now checks that at 100 % every
column shows by default, and moves the low-res on/off comparison to 50 %,
where the reduced copy is still used: on, the drag shows only the (inverted)
black columns; off, it matches the committed view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:21:30 +02:00

112 lines
5.6 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Build the browser version and zip it: $DIST/photocraft-web-<version>.zip
#
# Usage: packaging/web/package.sh [--skip-build]
#
# Needs: trunk (brew install trunk / cargo install trunk --locked) and the wasm32-unknown-unknown
# target. The zip holds a self-contained static site in photocraft-web-<version>/ that works
# from any URL path and inside an <iframe>. Hosting notes: packaging/web/README.md.
#
# The site holds the app's wasm gzipped and cut into parts (app/), started by the small
# photocraft-web-boot module, so no file is larger than a part however large the app grows
# (issue #5302). dist/web still holds the app as Trunk builds it, in one .wasm.
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
HERE="$ROOT/packaging/web"
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/photocraft-web" && trunk build --release)
(cd "$ROOT/apps/photocraft-web-boot" && trunk build --release)
fi
APP="$ROOT/dist/web" # the app: index.html, wasm-bindgen glue, one .wasm
BOOT="$ROOT/dist/web-boot" # the page that ships, and the module that starts the app from parts
for site in "$APP" "$BOOT"; do
[ -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
exit 1
fi
done
# Size limit (issues #198, #5302): Cloudflare Pages/Workers reject any single file over 25 MiB,
# and other hosts and CDNs have similar caps. No file in the site may come near that; the app's
# wasm, the one file that does, ships as parts of PART_BYTES. Override only to investigate.
MAX_FILE_BYTES="${PHOTOCRAFT_WEB_MAX_FILE_BYTES:-25165824}" # 24 MiB
PART_BYTES="${PHOTOCRAFT_WEB_PART_BYTES:-20971520}" # 20 MiB
if [ "$PART_BYTES" -le 0 ] || [ "$PART_BYTES" -gt "$MAX_FILE_BYTES" ]; then
echo "error: a part of $PART_BYTES bytes doesn't fit the $MAX_FILE_BYTES-byte file limit" >&2
exit 1
fi
WASM=""
for wasm in "$APP"/*_bg.wasm; do
[ -f "$wasm" ] || continue
[ -z "$WASM" ] || { echo "error: more than one .wasm in $APP; remove it and run without --skip-build" >&2; exit 1; }
WASM="$wasm"
done
[ -n "$WASM" ] || { echo "error: no .wasm in $APP" >&2; exit 1; }
STEM="$(basename "$WASM" _bg.wasm)" # photocraft-web-<content hash>
GLUE="$APP/$STEM.js"
[ -f "$GLUE" ] || { echo "error: $GLUE missing" >&2; exit 1; }
NAME="photocraft-web-$VERSION"
WORK="$CARGO_TARGET_DIR/web-package"
rm -rf "$WORK"
mkdir -p "$WORK/$NAME/app"
cp -R "$BOOT/." "$WORK/$NAME/"
# The boot module imports the app's glue by this name (it can't know Trunk's content hash).
cp "$GLUE" "$WORK/$NAME/app/photocraft-web.js"
# The parts keep the content hash, so they can be cached forever. Their extension is one no
# server knows: a server that recognised gzip might decode it or label it, and the page inflates
# the parts itself.
gzip -9 -n -c "$WASM" | (cd "$WORK/$NAME/app" && split -b "$PART_BYTES" - "$STEM.part-")
{
echo "photocraft-parts 1"
for part in "$WORK/$NAME/app/$STEM".part-*; do basename "$part"; done
} >"$WORK/$NAME/app/parts.txt"
size() { wc -c <"$1" | tr -d ' '; }
wasm_bytes=$(size "$WASM")
gzip_bytes=$(cat "$WORK/$NAME/app/$STEM".part-* | wc -c | tr -d ' ')
part_count=$(($(wc -l <"$WORK/$NAME/app/parts.txt") - 1))
echo "$(basename "$WASM"): $wasm_bytes bytes ($((wasm_bytes / 1048576)) MiB), shipped as $gzip_bytes bytes of gzip in $part_count part(s) of up to $PART_BYTES"
# Sample server configs (MIME type, caching, compression); harmless where unused.
cp "$HERE/_headers" "$HERE/.htaccess" "$WORK/$NAME/"
cp "$HERE/README.md" "$WORK/$NAME/HOSTING.md"
copy_docs "$WORK/$NAME"
# Optional served fonts (README.md › Fonts): PHOTOCRAFT_WEB_FONTS_DIR=<a craft-fonts checkout, or any
# folder with a fonts/manifest.txt in its format> copies the manifest, every font and licence file
# it lists into the site, at the same paths. The wasm is unchanged; the app fetches fonts on demand.
if [ -n "${PHOTOCRAFT_WEB_FONTS_DIR:-}" ]; then
FONTS_MANIFEST="$PHOTOCRAFT_WEB_FONTS_DIR/fonts/manifest.txt"
[ -f "$FONTS_MANIFEST" ] || { echo "error: $FONTS_MANIFEST missing (PHOTOCRAFT_WEB_FONTS_DIR)" >&2; exit 1; }
mkdir -p "$WORK/$NAME/fonts"
cp "$FONTS_MANIFEST" "$WORK/$NAME/fonts/manifest.txt"
# Field 3 is the font file, field 6 (if any) its licence file; both relative to the folder.
awk -F' [|] ' '!/^[[:space:]]*(#|$)/ { print $3; if ($6 != "") print $6 }' "$FONTS_MANIFEST" | sort -u | while IFS= read -r f; do
case "$f" in
"" | /* | *..* | *\\* | *://*) echo "error: $FONTS_MANIFEST: '$f' is not a relative path inside the folder" >&2; exit 1 ;;
esac
mkdir -p "$WORK/$NAME/$(dirname "$f")"
cp "$PHOTOCRAFT_WEB_FONTS_DIR/$f" "$WORK/$NAME/$f"
done
echo "served fonts: $(grep -cvE '^[[:space:]]*(#|$)' "$FONTS_MANIFEST") manifest lines from $PHOTOCRAFT_WEB_FONTS_DIR"
fi
largest=0
while IFS= read -r file; do
bytes=$(size "$file")
[ "$bytes" -le "$largest" ] || largest=$bytes
if [ "$bytes" -gt "$MAX_FILE_BYTES" ]; then
echo "error: ${file#"$WORK/$NAME/"} is $bytes bytes, over the $MAX_FILE_BYTES-byte limit (Cloudflare's per-file cap is 25 MiB)" >&2
exit 1
fi
done < <(find "$WORK/$NAME" -type f)
echo "largest file in the site: $largest bytes (limit $MAX_FILE_BYTES)"
rm -f "$DIST/$NAME.zip"
(cd "$WORK" && zip -qr9 "$DIST/$NAME.zip" "$NAME")
echo "wrote $DIST/$NAME.zip"
ls -lh "$DIST/$NAME.zip"