create-release-zip.sh 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env bash
  2. #
  3. # Builds a release package for a managed project.
  4. #
  5. # The product name, the version file and the exclude list are variables at the
  6. # top instead of hardcoded paths.
  7. #
  8. # It ships with the manage client package and is configured here for the
  9. # photography portfolio. Run it from the project root:
  10. #
  11. # ./scripts/create-release-zip.sh v1.3.0
  12. #
  13. # Then upload build/releases/foto-portfolio-v1.3.0.zip in the manage server
  14. # under "Releases". See docs/SETUP.md.
  15. #
  16. # It writes the version into the version file, packs every git-tracked file
  17. # minus the exclusions, and prints the SHA-256 and size. Upload the resulting
  18. # ZIP in the manage server under "Releases".
  19. set -euo pipefail
  20. # --- CONFIGURATION ----------------------------------------------------------
  21. # Package name prefix. Must match MANAGE_PACKAGE_PREFIX on the manage server.
  22. PRODUCT="foto-portfolio"
  23. # File holding the installed version, relative to the project root.
  24. VERSION_FILE="app/version.php"
  25. # Name of the constant inside that file. Empty means a plain text file that
  26. # contains nothing but the version.
  27. VERSION_CONSTANT="APP_VERSION"
  28. # Output directory for built packages, relative to the project root.
  29. BUILD_DIR="build/releases"
  30. # Paths excluded from the package. Anything holding credentials or runtime data
  31. # of the target installation MUST be listed here.
  32. #
  33. # The list is short because the file list comes from `git ls-files`: the target
  34. # installation's config, credentials and content are gitignored and cannot end
  35. # up in the package in the first place. The entries below name the files that
  36. # ARE tracked but must not travel — plus the three config files as belt to that
  37. # braces, in case one is ever force-added.
  38. #
  39. # NOT excluded, on purpose:
  40. # manage-client/ this is how the update client updates itself
  41. # migrations/ release migrations must travel with the release
  42. # config/*.sample.php, docs/
  43. # a new install needs them; both blocked by .htaccess
  44. # data/.htaccess, media/.htaccess
  45. # the only tracked files under those two directories, and
  46. # the deny-all fallback a fresh install must not be
  47. # missing. An update skips them anyway — data/ and media/
  48. # are in MANAGE_UPDATE_PROTECTED_PATHS.
  49. EXCLUDES=(
  50. "config/config.php"
  51. "config/credentials.php"
  52. "manage-client/config.php"
  53. "data/galleries/"
  54. "build/"
  55. "scripts/"
  56. "client-package/"
  57. ".gitignore"
  58. )
  59. # --- END CONFIGURATION ------------------------------------------------------
  60. usage() {
  61. cat <<USAGE
  62. Usage: $(basename "$0") vX.Y.Z
  63. Builds ${BUILD_DIR}/${PRODUCT}-vX.Y.Z.zip from the git-tracked files of the
  64. current repository and writes the version into ${VERSION_FILE}.
  65. USAGE
  66. }
  67. VERSION="${1:-}"
  68. if [[ -z "$VERSION" || "$VERSION" == "-h" || "$VERSION" == "--help" ]]; then
  69. usage
  70. exit 1
  71. fi
  72. if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  73. echo "Error: version must look like v1.3.0" >&2
  74. exit 1
  75. fi
  76. for tool in git zip sed awk; do
  77. if ! command -v "$tool" >/dev/null 2>&1; then
  78. echo "Error: required tool not found: $tool" >&2
  79. exit 1
  80. fi
  81. done
  82. if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
  83. echo "Error: not inside a git repository. Run this from the project root." >&2
  84. exit 1
  85. fi
  86. REPO_ROOT="$(git rev-parse --show-toplevel)"
  87. cd "$REPO_ROOT"
  88. if [[ ! -f "$VERSION_FILE" ]]; then
  89. echo "Error: version file not found: $VERSION_FILE" >&2
  90. exit 1
  91. fi
  92. # Uncommitted changes would silently stay out of the package, because the file
  93. # list comes from git. Warn rather than refuse: building from a dirty tree is
  94. # sometimes deliberate.
  95. if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
  96. echo "Warning: the working tree has uncommitted changes." >&2
  97. echo " Only committed content is packaged." >&2
  98. fi
  99. # --- write the version ------------------------------------------------------
  100. write_version() {
  101. if [[ -n "$VERSION_CONSTANT" ]]; then
  102. # PHP file with a define(). Replace only the version literal.
  103. sed -i.bak -E \
  104. "s/(define\\(\\s*[\"']${VERSION_CONSTANT}[\"']\\s*,\\s*[\"'])[^\"']*([\"'])/\\1${VERSION}\\2/" \
  105. "$VERSION_FILE"
  106. rm -f "${VERSION_FILE}.bak"
  107. else
  108. printf '%s\n' "$VERSION" > "$VERSION_FILE"
  109. fi
  110. }
  111. read_version() {
  112. if [[ -n "$VERSION_CONSTANT" ]]; then
  113. grep -oE "define\\(\\s*[\"']${VERSION_CONSTANT}[\"']\\s*,\\s*[\"']v[0-9]+\\.[0-9]+\\.[0-9]+[\"']" \
  114. "$VERSION_FILE" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1
  115. else
  116. tr -d '[:space:]' < "$VERSION_FILE"
  117. fi
  118. }
  119. write_version
  120. WRITTEN="$(read_version)"
  121. if [[ "$WRITTEN" != "$VERSION" ]]; then
  122. echo "Error: could not write the version into $VERSION_FILE (found: '${WRITTEN}')." >&2
  123. echo " Check VERSION_CONSTANT and the file's format." >&2
  124. exit 1
  125. fi
  126. echo "Version written to ${VERSION_FILE}: ${VERSION}"
  127. # --- collect the files ------------------------------------------------------
  128. is_excluded() {
  129. local path="$1"
  130. local pattern
  131. for pattern in "${EXCLUDES[@]}"; do
  132. if [[ "$pattern" == */ ]]; then
  133. [[ "$path" == "${pattern}"* ]] && return 0
  134. else
  135. [[ "$path" == "$pattern" ]] && return 0
  136. fi
  137. done
  138. return 1
  139. }
  140. FILE_LIST="$(mktemp)"
  141. trap 'rm -f "$FILE_LIST"' EXIT
  142. COUNT=0
  143. while IFS= read -r path; do
  144. if is_excluded "$path"; then
  145. continue
  146. fi
  147. [[ -f "$path" ]] || continue
  148. printf '%s\n' "$path" >> "$FILE_LIST"
  149. COUNT=$((COUNT + 1))
  150. done < <(git ls-files)
  151. if [[ "$COUNT" -eq 0 ]]; then
  152. echo "Error: no files to package." >&2
  153. exit 1
  154. fi
  155. # --- build ------------------------------------------------------------------
  156. mkdir -p "$BUILD_DIR"
  157. ARCHIVE="${BUILD_DIR}/${PRODUCT}-${VERSION}.zip"
  158. rm -f "$ARCHIVE"
  159. zip -q -X "$ARCHIVE" -@ < "$FILE_LIST"
  160. if command -v sha256sum >/dev/null 2>&1; then
  161. SHA="$(sha256sum "$ARCHIVE" | awk '{print $1}')"
  162. else
  163. SHA="$(shasum -a 256 "$ARCHIVE" | awk '{print $1}')"
  164. fi
  165. SIZE="$(wc -c < "$ARCHIVE" | tr -d '[:space:]')"
  166. cat <<SUMMARY
  167. Package: ${ARCHIVE}
  168. Files: ${COUNT}
  169. Size: ${SIZE} bytes
  170. SHA-256: ${SHA}
  171. Next: upload it in the manage server under "Releases" with version ${VERSION}.
  172. Checksum and size are recomputed there; the values above are for checking.
  173. SUMMARY