#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: COPY_DEST=/path/or/container:/dir \ copy_artifact.sh FILE [FILE ...] Copy built artifacts to COPY_DEST. 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 Required. Local dir or container:/dir 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" || $# -eq 0 ]]; then echo "Error: COPY_DEST and at least one file are required." >&2 echo "Set them 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 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 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"