Files
.pouch/skills/builder/scripts/copy_artifact.sh
T

219 lines
6.0 KiB
Bash
Executable File

#!/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
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"