create-release-zip.sh 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. ".gitignore"
  57. )
  58. # --- END CONFIGURATION ------------------------------------------------------
  59. usage() {
  60. cat <<USAGE
  61. Usage: $(basename "$0") vX.Y.Z
  62. Builds ${BUILD_DIR}/${PRODUCT}-vX.Y.Z.zip from the git-tracked files of the
  63. current repository and writes the version into ${VERSION_FILE}.
  64. USAGE
  65. }
  66. VERSION="${1:-}"
  67. if [[ -z "$VERSION" || "$VERSION" == "-h" || "$VERSION" == "--help" ]]; then
  68. usage
  69. exit 1
  70. fi
  71. if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  72. echo "Error: version must look like v1.3.0" >&2
  73. exit 1
  74. fi
  75. for tool in git zip sed awk; do
  76. if ! command -v "$tool" >/dev/null 2>&1; then
  77. echo "Error: required tool not found: $tool" >&2
  78. exit 1
  79. fi
  80. done
  81. if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
  82. echo "Error: not inside a git repository. Run this from the project root." >&2
  83. exit 1
  84. fi
  85. REPO_ROOT="$(git rev-parse --show-toplevel)"
  86. cd "$REPO_ROOT"
  87. if [[ ! -f "$VERSION_FILE" ]]; then
  88. echo "Error: version file not found: $VERSION_FILE" >&2
  89. exit 1
  90. fi
  91. # Uncommitted changes would silently stay out of the package, because the file
  92. # list comes from git. Warn rather than refuse: building from a dirty tree is
  93. # sometimes deliberate.
  94. if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
  95. echo "Warning: the working tree has uncommitted changes." >&2
  96. echo " Only committed content is packaged." >&2
  97. fi
  98. # --- write the version ------------------------------------------------------
  99. write_version() {
  100. if [[ -n "$VERSION_CONSTANT" ]]; then
  101. # PHP file with a define(). Replace only the version literal.
  102. sed -i.bak -E \
  103. "s/(define\\(\\s*[\"']${VERSION_CONSTANT}[\"']\\s*,\\s*[\"'])[^\"']*([\"'])/\\1${VERSION}\\2/" \
  104. "$VERSION_FILE"
  105. rm -f "${VERSION_FILE}.bak"
  106. else
  107. printf '%s\n' "$VERSION" > "$VERSION_FILE"
  108. fi
  109. }
  110. read_version() {
  111. if [[ -n "$VERSION_CONSTANT" ]]; then
  112. grep -oE "define\\(\\s*[\"']${VERSION_CONSTANT}[\"']\\s*,\\s*[\"']v[0-9]+\\.[0-9]+\\.[0-9]+[\"']" \
  113. "$VERSION_FILE" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1
  114. else
  115. tr -d '[:space:]' < "$VERSION_FILE"
  116. fi
  117. }
  118. write_version
  119. WRITTEN="$(read_version)"
  120. if [[ "$WRITTEN" != "$VERSION" ]]; then
  121. echo "Error: could not write the version into $VERSION_FILE (found: '${WRITTEN}')." >&2
  122. echo " Check VERSION_CONSTANT and the file's format." >&2
  123. exit 1
  124. fi
  125. echo "Version written to ${VERSION_FILE}: ${VERSION}"
  126. # --- collect the files ------------------------------------------------------
  127. is_excluded() {
  128. local path="$1"
  129. local pattern
  130. for pattern in "${EXCLUDES[@]}"; do
  131. if [[ "$pattern" == */ ]]; then
  132. [[ "$path" == "${pattern}"* ]] && return 0
  133. else
  134. [[ "$path" == "$pattern" ]] && return 0
  135. fi
  136. done
  137. return 1
  138. }
  139. FILE_LIST="$(mktemp)"
  140. trap 'rm -f "$FILE_LIST"' EXIT
  141. COUNT=0
  142. while IFS= read -r path; do
  143. if is_excluded "$path"; then
  144. continue
  145. fi
  146. [[ -f "$path" ]] || continue
  147. printf '%s\n' "$path" >> "$FILE_LIST"
  148. COUNT=$((COUNT + 1))
  149. done < <(git ls-files)
  150. if [[ "$COUNT" -eq 0 ]]; then
  151. echo "Error: no files to package." >&2
  152. exit 1
  153. fi
  154. # --- build ------------------------------------------------------------------
  155. mkdir -p "$BUILD_DIR"
  156. ARCHIVE="${BUILD_DIR}/${PRODUCT}-${VERSION}.zip"
  157. rm -f "$ARCHIVE"
  158. zip -q -X "$ARCHIVE" -@ < "$FILE_LIST"
  159. if command -v sha256sum >/dev/null 2>&1; then
  160. SHA="$(sha256sum "$ARCHIVE" | awk '{print $1}')"
  161. else
  162. SHA="$(shasum -a 256 "$ARCHIVE" | awk '{print $1}')"
  163. fi
  164. SIZE="$(wc -c < "$ARCHIVE" | tr -d '[:space:]')"
  165. cat <<SUMMARY
  166. Package: ${ARCHIVE}
  167. Files: ${COUNT}
  168. Size: ${SIZE} bytes
  169. SHA-256: ${SHA}
  170. Next: upload it in the manage server under "Releases" with version ${VERSION}.
  171. Checksum and size are recomputed there; the values above are for checking.
  172. SUMMARY