#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: COPY_DEST=/path/or/container:/dir \ copy_artifact.sh FILE [FILE ...] COPY_SCRIPT=.pouch/builder/publish-apk.sh \ copy_artifact.sh FILE [FILE ...] Copy built artifacts using COPY_SCRIPT when configured; otherwise copy to COPY_DEST. COPY_DEST is a local directory or a Docker copy target `container:/absolute/dir` (docker cp). Does not read `.env`. Environment (project root .env.builder; shell values win): COPY_DEST Optional fallback. Local dir or container:/dir COPY_SCRIPT Optional .pouch/builder/ relative path or absolute executable. Receives FILE paths as positional args. COPY_AS Optional. Extra basenames for the first file (comma or space separated) ALLOW_UNCOMMITTED=1 Publish despite a dirty working tree Options: -d DEST Override COPY_DEST -a NAMES Override COPY_AS --dry-run Print the copies without executing -h Show help The working tree must be clean to publish; set ALLOW_UNCOMMITTED=1 to override. EOF } project_root=$(git rev-parse --show-toplevel 2>/dev/null || true) if [[ -n "$project_root" && -f "$project_root/.env.builder" ]]; then while IFS='=' read -r key value; do key=${key%%[[:space:]]*} [[ -z "$key" || "$key" == \#* ]] && continue if [[ -n "${!key:-}" ]]; then continue fi value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'} [[ -z "$value" ]] && continue printf -v "$key" '%s' "$value" export "$key" done < <(grep -v '^[[:space:]]*$' "$project_root/.env.builder") fi dest=${COPY_DEST:-} extra_as=${COPY_AS:-} dry_run=false while (($#)); do case "$1" in -d) dest=$2; shift 2 ;; -a) extra_as=$2; shift 2 ;; --dry-run) dry_run=true; shift ;; -h|--help) usage; exit 0 ;; --) shift; break ;; -*) echo "Error: unknown option: $1" >&2; usage >&2; exit 2 ;; *) break ;; esac done if [[ -z "$dest" && -z "${COPY_SCRIPT:-}" || $# -eq 0 ]]; then if [[ $# -eq 0 ]]; then echo "Error: at least one file is required." >&2 else echo "Error: set COPY_DEST or COPY_SCRIPT." >&2 fi echo "Set the value in the environment or the project root .env.builder." >&2 usage >&2 exit 2 fi if [[ -n "$project_root" ]] && git -C "$project_root" rev-parse HEAD >/dev/null 2>&1; then # Refresh Git's cached stat data after build tools rewrite tracked files. git -C "$project_root" update-index -q --refresh 2>/dev/null || true if [[ ${ALLOW_UNCOMMITTED:-0} != 1 ]] && ! git -C "$project_root" diff-index --quiet HEAD -- 2>/dev/null; then echo "Error: working tree has uncommitted changes; refusing to publish." >&2 echo "Commit first, or set ALLOW_UNCOMMITTED=1 to publish anyway." >&2 exit 3 fi fi if [[ -n "${COPY_SCRIPT:-}" ]]; then copy_script=$COPY_SCRIPT if [[ "$copy_script" != /* ]]; then relative_script=${copy_script#./} if [[ "$relative_script" != .pouch/builder/* || -z "$project_root" ]]; then echo "Error: relative COPY_SCRIPT must be under .pouch/builder/." >&2 exit 2 fi copy_script="$project_root/$relative_script" fi if [[ ! -f "$copy_script" ]]; then echo "Error: COPY_SCRIPT is not a file: $copy_script" >&2 exit 2 fi if [[ ! -x "$copy_script" ]]; then echo "Error: COPY_SCRIPT is not executable: $copy_script" >&2 exit 2 fi artifact_args=() for src in "$@"; do if [[ ! -f "$src" ]]; then echo "Error: not a file: $src" >&2 exit 2 fi if [[ "$src" == /* ]]; then artifact_args+=("$src") else artifact_args+=("$PWD/$src") fi done if $dry_run; then printf 'custom copy script:' printf ' %q' "$copy_script" "${artifact_args[@]}" printf '\n' exit 0 fi export BUILDER_PROJECT_ROOT=${project_root:-} if "$copy_script" "${artifact_args[@]}"; then if command -v sha256sum >/dev/null 2>&1; then for src in "${artifact_args[@]}"; do sha256sum -- "$src" done fi echo "copy_artifact: custom script completed for $# file(s)" exit 0 else status=$? echo "Error: COPY_SCRIPT failed with exit code $status." >&2 exit "$status" fi fi if [[ -z "$dest" ]]; then echo "Error: COPY_DEST is required when COPY_SCRIPT is not configured." >&2 exit 2 fi is_docker_dest() { local d=$1 [[ "$d" != /* && "$d" != ./* && "$d" != ../* && "$d" != ~* ]] \ && [[ "$d" != *://* ]] \ && [[ "$d" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*:/ ]] } docker_container= docker_dir= local_dir= if is_docker_dest "$dest"; then docker_container=${dest%%:*} docker_dir=${dest#*:} if [[ -z "$docker_container" || -z "$docker_dir" || "$docker_dir" != /* ]]; then echo "Error: docker dest must be container:/absolute/dir" >&2 exit 2 fi docker_dir=${docker_dir%/} else local_dir=${dest%/} fi split_as() { local raw=$1 raw=${raw//,/ } # shellcheck disable=SC2086 printf '%s\n' $raw } copy_one() { local src=$1 name=$2 if [[ ! -f "$src" ]]; then echo "Error: not a file: $src" >&2 exit 2 fi if [[ -n "$docker_container" ]]; then local target="${docker_container}:${docker_dir}/${name}" if $dry_run; then echo "docker cp $src $target" return fi docker cp "$src" "$target" docker exec "$docker_container" ls -l "${docker_dir}/${name}" >/dev/null echo "copied $(basename -- "$src") -> $target" else local target="${local_dir}/${name}" if $dry_run; then echo "cp $src $target" return fi mkdir -p "$local_dir" cp -a "$src" "$target" test -f "$target" echo "copied $(basename -- "$src") -> $target" fi } first=true success=0 for src in "$@"; do copy_one "$src" "$(basename -- "$src")" if $first; then first=false if [[ -n "$extra_as" ]]; then while IFS= read -r alias; do [[ -z "$alias" ]] && continue copy_one "$src" "$alias" done < <(split_as "$extra_as") fi fi if ! $dry_run && command -v sha256sum >/dev/null 2>&1; then sha256sum -- "$src" fi success=$((success + 1)) done echo "copy_artifact: $success file(s) to $dest"