#!/usr/bin/env bash set -euo pipefail usage() { cat <<'USAGE' Usage: ./create-chatgpt-archive.sh [archive.zip] ./create-chatgpt-archive.sh --list Creates a zip archive with project files useful for code analysis. Environment: MAX_FILE_SIZE_BYTES Per-file size limit. Default: 1048576 The script includes source, configuration, build metadata, CI, Helm and docs. It excludes build outputs, logs, local caches, IDE files, archives, binaries and env files. USAGE } mode="create" if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then usage exit 0 fi if [[ "${1:-}" == "--list" ]]; then mode="list" shift fi if (( $# > 1 )); then usage >&2 exit 2 fi project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" archive_arg="${1:-pcp-chatgpt-context.zip}" max_file_size_bytes="${MAX_FILE_SIZE_BYTES:-1048576}" if [[ "$archive_arg" = /* ]]; then archive_path="$archive_arg" else archive_path="$project_root/$archive_arg" fi include_paths=( "AGENTS.md" "README.md" "HELP.md" ".dockerignore" ".gitattributes" ".gitignore" ".gitlab-ci.yml" "build.gradle.kts" "settings.gradle.kts" "gradle.properties" "gradlew" "gradlew.bat" "create-chatgpt-archive.sh" "changeLogsDynamicSlots.md" ".gitlab" "config-repo" "docs" "gradle" "helm" "libs" "schemes" "services" ) should_skip_path() { local path="$1" case "$path" in .git|.git/*|.gradle|.gradle/*|.gradle-local|.gradle-local/*|.idea|.idea/*|.kotlin|.kotlin/*|.vscode|.vscode/*) return 0 ;; build|build/*|logs|logs/*|out|out/*|bin|bin/*|target|target/*|node_modules|node_modules/*) return 0 ;; */build|*/build/*|*/logs|*/logs/*|*/out|*/out/*|*/bin|*/bin/*|*/target|*/target/*|*/node_modules|*/node_modules/*) return 0 ;; esac case "$path" in .env|.env.*|*/.env|*/.env.*) return 0 ;; *.log|*.tmp|*.bak|*.orig|*.class|*.jar|*.war|*.ear|*.zip|*.tar|*.tgz|*.tar.gz|*.gz|*.7z|*.rar) return 0 ;; *.png|*.jpg|*.jpeg|*.gif|*.webp|*.ico|*.pdf|*.doc|*.docx|*.xls|*.xlsx|*.ppt|*.pptx) return 0 ;; *.keystore|*.jks|*.p12|*.pem|*.key|*.crt) return 0 ;; esac return 1 } is_allowed_file() { local path="$1" case "$path" in AGENTS.md|README.md|HELP.md|.dockerignore|.gitattributes|.gitignore|.gitlab-ci.yml) return 0 ;; build.gradle.kts|settings.gradle.kts|gradle.properties|gradlew|gradlew.bat|create-chatgpt-archive.sh|changeLogsDynamicSlots.md) return 0 ;; Dockerfile|Makefile|Jenkinsfile) return 0 ;; *.kt|*.kts|*.java|*.groovy|*.xml|*.yaml|*.yml|*.properties|*.json|*.toml|*.md|*.txt) return 0 ;; *.sql|*.graphql|*.graphqls|*.proto|*.bpmn|*.dmn|*.tpl|*.sh) return 0 ;; *.html|*.css|*.scss|*.js|*.jsx|*.ts|*.tsx|*.vue) return 0 ;; esac return 1 } manifest_file="$(mktemp)" trap 'rm -f "$manifest_file"' EXIT collect_files() { local root_path local relative_path local file_size for root_path in "${include_paths[@]}"; do if [[ ! -e "$project_root/$root_path" ]]; then continue fi if [[ -f "$project_root/$root_path" ]]; then printf '%s\n' "$root_path" else (cd "$project_root" && find "$root_path" -type f -print) fi done | while IFS= read -r relative_path; do relative_path="${relative_path#./}" if should_skip_path "$relative_path"; then continue fi if ! is_allowed_file "$relative_path"; then continue fi file_size="$(stat -c '%s' "$project_root/$relative_path")" if (( file_size > max_file_size_bytes )); then continue fi printf '%s\n' "$relative_path" done | sort -u } collect_files > "$manifest_file" file_count="$(wc -l < "$manifest_file" | tr -d ' ')" if (( file_count == 0 )); then echo "No files selected for archive." >&2 exit 1 fi if [[ "$mode" == "list" ]]; then cat "$manifest_file" echo echo "Selected files: $file_count" >&2 exit 0 fi if ! command -v zip >/dev/null 2>&1; then echo "zip command is required but was not found." >&2 exit 1 fi mkdir -p "$(dirname "$archive_path")" rm -f "$archive_path" (cd "$project_root" && zip -q -X "$archive_path" -@ < "$manifest_file") archive_size="$(du -h "$archive_path" | awk '{print $1}')" echo "Archive created: $archive_path" echo "Selected files: $file_count" echo "Archive size: $archive_size"