| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | if [[ $# -ne 2 ]]; then |
| 5 | echo "usage: $0 INPUT_ARTIFACT_DIR OUTPUT_BUNDLE_DIR" >&2 |
| 6 | exit 2 |
| 7 | fi |
| 8 | |
| 9 | artifact_dir="$1" |
| 10 | bundle_dir="$2" |
| 11 | |
| 12 | # Archive metadata must be stable across recovery builds. The public workflow |
| 13 | # still refuses to replace existing release assets; reproducible packaging is a |
| 14 | # diagnostic and provenance aid, not permission to overwrite published bytes. |
| 15 | export TZ=UTC |
| 16 | archive_timestamp="200001010000" |
| 17 | |
| 18 | if [[ ! -d "${artifact_dir}" ]]; then |
| 19 | echo "input artifact directory does not exist: ${artifact_dir}" >&2 |
| 20 | exit 1 |
| 21 | fi |
| 22 | artifact_dir="$(cd "${artifact_dir}" && pwd)" |
| 23 | |
| 24 | if [[ -e "${bundle_dir}" && -n "$(find "${bundle_dir}" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then |
| 25 | echo "output bundle directory must be empty: ${bundle_dir}" >&2 |
| 26 | exit 1 |
| 27 | fi |
| 28 | mkdir -p "${bundle_dir}" |
| 29 | bundle_dir="$(cd "${bundle_dir}" && pwd)" |
| 30 | |
| 31 | manifest="${bundle_dir}/codewhale-bundles-sha256.txt" |
| 32 | : > "${manifest}" |
| 33 | |
| 34 | bundle() { |
| 35 | local platform="$1" |
| 36 | local cli_src="$2" |
| 37 | local shim_src="$3" |
| 38 | local tui_src="$4" |
| 39 | local ext="$5" |
| 40 | local variant="$6" |
| 41 | |
| 42 | local stem="codewhale-${platform}${variant:+-}${variant}" |
| 43 | local stage_root |
| 44 | stage_root="$(mktemp -d)" |
| 45 | local stage_dir="${stage_root}/${stem}" |
| 46 | mkdir -p "${stage_dir}" |
| 47 | |
| 48 | local cli_dst="codewhale" |
| 49 | local shim_dst="codew" |
| 50 | local tui_dst="codewhale-tui" |
| 51 | if [[ "${platform}" == windows-* ]]; then |
| 52 | cli_dst="codewhale.exe" |
| 53 | shim_dst="codew.exe" |
| 54 | tui_dst="codewhale-tui.exe" |
| 55 | fi |
| 56 | |
| 57 | cp "${artifact_dir}/${cli_src}/${cli_src}" "${stage_dir}/${cli_dst}" |
| 58 | cp "${artifact_dir}/${shim_src}/${shim_src}" "${stage_dir}/${shim_dst}" |
| 59 | cp "${artifact_dir}/${tui_src}/${tui_src}" "${stage_dir}/${tui_dst}" |
| 60 | |
| 61 | # actions/upload-artifact intentionally normalizes downloaded files to 0644. |
| 62 | # Restore the executable contract before constructing Unix archives. |
| 63 | if [[ "${platform}" != windows-* ]]; then |
| 64 | chmod 0755 \ |
| 65 | "${stage_dir}/${cli_dst}" \ |
| 66 | "${stage_dir}/${shim_dst}" \ |
| 67 | "${stage_dir}/${tui_dst}" |
| 68 | fi |
| 69 | |
| 70 | if [[ "${variant}" != "portable" ]]; then |
| 71 | if [[ "${platform}" == windows-* ]]; then |
| 72 | cp scripts/release/install.bat "${stage_dir}/" |
| 73 | sed -i 's/$/\r/' "${stage_dir}/install.bat" 2>/dev/null || true |
| 74 | else |
| 75 | cp scripts/release/install.sh "${stage_dir}/" |
| 76 | chmod +x "${stage_dir}/install.sh" |
| 77 | fi |
| 78 | fi |
| 79 | |
| 80 | # zip and tar both record mtimes; normalize every staged entry so identical |
| 81 | # inputs do not produce packaging-only checksum drift on a rerun. |
| 82 | find "${stage_dir}" -exec touch -t "${archive_timestamp}" {} + |
| 83 | |
| 84 | local archive="${bundle_dir}/${stem}.${ext}" |
| 85 | if [[ "${ext}" == "zip" ]]; then |
| 86 | (cd "${stage_root}" && zip -Xqr "${archive}" "${stem}/") |
| 87 | elif tar --version 2>/dev/null | grep -q 'GNU tar'; then |
| 88 | tar \ |
| 89 | --sort=name \ |
| 90 | --mtime='2000-01-01 00:00:00 UTC' \ |
| 91 | --owner=0 \ |
| 92 | --group=0 \ |
| 93 | --numeric-owner \ |
| 94 | --format=ustar \ |
| 95 | -cf - \ |
| 96 | -C "${stage_root}" \ |
| 97 | "${stem}/" | gzip -n > "${archive}" |
| 98 | else |
| 99 | COPYFILE_DISABLE=1 tar -cf - -C "${stage_root}" "${stem}/" | gzip -n > "${archive}" |
| 100 | fi |
| 101 | |
| 102 | local checksum |
| 103 | checksum="$(sha256sum "${archive}" | awk '{print $1}')" |
| 104 | printf '%s %s\n' "${checksum}" "$(basename "${archive}")" >> "${manifest}" |
| 105 | rm -rf "${stage_root}" |
| 106 | echo "Created ${archive}" |
| 107 | } |
| 108 | |
| 109 | bundle linux-x64 \ |
| 110 | codewhale-linux-x64 codew-linux-x64 codewhale-tui-linux-x64 tar.gz "" |
| 111 | bundle linux-arm64 \ |
| 112 | codewhale-linux-arm64 codew-linux-arm64 codewhale-tui-linux-arm64 tar.gz "" |
| 113 | bundle android-arm64 \ |
| 114 | codewhale-android-arm64 codew-android-arm64 codewhale-tui-android-arm64 tar.gz "" |
| 115 | bundle macos-x64 \ |
| 116 | codewhale-macos-x64 codew-macos-x64 codewhale-tui-macos-x64 tar.gz "" |
| 117 | bundle macos-arm64 \ |
| 118 | codewhale-macos-arm64 codew-macos-arm64 codewhale-tui-macos-arm64 tar.gz "" |
| 119 | bundle windows-x64 \ |
| 120 | codewhale-windows-x64.exe codew-windows-x64.exe codewhale-tui-windows-x64.exe zip "" |
| 121 | bundle windows-x64 \ |
| 122 | codewhale-windows-x64.exe codew-windows-x64.exe codewhale-tui-windows-x64.exe zip portable |
| 123 | bundle windows-arm64 \ |
| 124 | codewhale-windows-arm64.exe codew-windows-arm64.exe codewhale-tui-windows-arm64.exe zip "" |
| 125 | bundle windows-arm64 \ |
| 126 | codewhale-windows-arm64.exe codew-windows-arm64.exe codewhale-tui-windows-arm64.exe zip portable |
| 127 | |
| 128 | sort -o "${manifest}" "${manifest}" |
| 129 | echo "Bundle checksum manifest:" |
| 130 | cat "${manifest}" |
| 131 |