diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index da600796..234bbe0c 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -11,7 +11,7 @@ on: - 'docs/**' - '.gitignore' - 'LICENSE' - pull_request: + pull_request: branches: [ main ] paths-ignore: - 'docs/**' @@ -39,6 +39,9 @@ jobs: - os: ubuntu-24.04 arch: x86_64 platform: linux + - os: ubuntu-24.04-arm + arch: arm64 + platform: linux - os: macos-13 arch: x86_64 platform: darwin @@ -90,7 +93,8 @@ jobs: - name: Run smoke tests env: - GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }} # Primary: APM module access (FG PAT) + GITHUB_TOKEN: ${{ secrets.GH_MODELS_PAT }} # Models access + GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }} # APM module access run: uv run pytest tests/integration/test_runtime_smoke.py -v # Build binaries @@ -104,6 +108,10 @@ jobs: platform: linux arch: x86_64 binary_name: apm-linux-x86_64 + - os: ubuntu-24.04-arm + platform: linux + arch: arm64 + binary_name: apm-linux-arm64 - os: macos-13 platform: darwin arch: x86_64 @@ -179,6 +187,10 @@ jobs: arch: x86_64 platform: linux binary_name: apm-linux-x86_64 + - os: ubuntu-24.04-arm + arch: arm64 + platform: linux + binary_name: apm-linux-arm64 - os: macos-13 arch: x86_64 platform: darwin @@ -250,6 +262,10 @@ jobs: arch: x86_64 platform: linux binary_name: apm-linux-x86_64 + - os: ubuntu-24.04-arm + arch: arm64 + platform: linux + binary_name: apm-linux-arm64 - os: macos-13 arch: x86_64 platform: darwin @@ -309,7 +325,7 @@ jobs: - name: Run release validation tests env: - APM_E2E_TESTS: "1" # Avoids interactive prompts for MCP env values with apm install + APM_E2E_TESTS: "1" # Avoids interactive prompts for MCP env values with apm install GITHUB_TOKEN: ${{ secrets.GH_MODELS_PAT }} GITHUB_APM_PAT: ${{ secrets.GH_CLI_PAT }} # Primary: APM module access run: | @@ -345,7 +361,7 @@ jobs: # Create tar.gz archives from directory structure for release and Homebrew cd dist - for binary in apm-linux-x86_64 apm-darwin-x86_64 apm-darwin-arm64; do + for binary in apm-linux-x86_64 apm-linux-arm64 apm-darwin-x86_64 apm-darwin-arm64; do # With artifacts containing both scripts and dist/, the binary is in artifact/dist/binary/ artifact_dir="${binary}" binary_dir="${artifact_dir}/dist/${binary}" @@ -402,6 +418,8 @@ jobs: files: | ./dist/apm-linux-x86_64.tar.gz ./dist/apm-linux-x86_64.tar.gz.sha256 + ./dist/apm-linux-arm64.tar.gz + ./dist/apm-linux-arm64.tar.gz.sha256 ./dist/apm-darwin-x86_64.tar.gz ./dist/apm-darwin-x86_64.tar.gz.sha256 ./dist/apm-darwin-arm64.tar.gz @@ -467,19 +485,24 @@ jobs: "https://github.com/${{ github.repository }}/releases/download/$RELEASE_TAG/apm-darwin-x86_64.tar.gz.sha256" curl -L -o apm-linux-x86_64.tar.gz.sha256 \ "https://github.com/${{ github.repository }}/releases/download/$RELEASE_TAG/apm-linux-x86_64.tar.gz.sha256" + curl -L -o apm-linux-arm64.tar.gz.sha256 \ + "https://github.com/${{ github.repository }}/releases/download/$RELEASE_TAG/apm-linux-arm64.tar.gz.sha256" # Extract SHA256 checksums DARWIN_ARM64_SHA=$(cat apm-darwin-arm64.tar.gz.sha256 | cut -d' ' -f1) DARWIN_X86_64_SHA=$(cat apm-darwin-x86_64.tar.gz.sha256 | cut -d' ' -f1) LINUX_X86_64_SHA=$(cat apm-linux-x86_64.tar.gz.sha256 | cut -d' ' -f1) + LINUX_ARM64_SHA=$(cat apm-linux-arm64.tar.gz.sha256 | cut -d' ' -f1) echo "darwin-arm64-sha=$DARWIN_ARM64_SHA" >> $GITHUB_OUTPUT echo "darwin-x86_64-sha=$DARWIN_X86_64_SHA" >> $GITHUB_OUTPUT echo "linux-x86_64-sha=$LINUX_X86_64_SHA" >> $GITHUB_OUTPUT + echo "linux-arm64-sha=$LINUX_ARM64_SHA" >> $GITHUB_OUTPUT echo "Darwin ARM64 SHA: $DARWIN_ARM64_SHA" echo "Darwin x86_64 SHA: $DARWIN_X86_64_SHA" echo "Linux x86_64 SHA: $LINUX_X86_64_SHA" + echo "Linux ARM64 SHA: $LINUX_ARM64_SHA" - name: Trigger Homebrew tap repository update uses: peter-evans/repository-dispatch@v3 @@ -497,6 +520,7 @@ jobs: "checksums": { "darwin_arm64": "${{ steps.checksums.outputs.darwin-arm64-sha }}", "darwin_x86_64": "${{ steps.checksums.outputs.darwin-x86_64-sha }}", - "linux_x86_64": "${{ steps.checksums.outputs.linux-x86_64-sha }}" + "linux_x86_64": "${{ steps.checksums.outputs.linux-x86_64-sha }}", + "linux_arm64": "${{ steps.checksums.outputs.linux-arm64-sha }}" } } diff --git a/scripts/github-token-helper.sh b/scripts/github-token-helper.sh index e4911a95..ff6eb0af 100755 --- a/scripts/github-token-helper.sh +++ b/scripts/github-token-helper.sh @@ -25,13 +25,25 @@ setup_github_tokens() { echo -e "${BLUE}Setting up GitHub tokens...${NC}" fi + # DEBUG: Show all GitHub-related environment variables + echo -e "${YELLOW}🔍 DEBUG: GitHub token environment analysis:${NC}" + echo " GITHUB_TOKEN: ${GITHUB_TOKEN:+SET(${#GITHUB_TOKEN} chars)}${GITHUB_TOKEN:-UNSET}" + echo " GITHUB_APM_PAT: ${GITHUB_APM_PAT:+SET(${#GITHUB_APM_PAT} chars)}${GITHUB_APM_PAT:-UNSET}" + echo " GH_MODELS_PAT: ${GH_MODELS_PAT:+SET(${#GH_MODELS_PAT} chars)}${GH_MODELS_PAT:-UNSET}" + echo " GH_CLI_PAT: ${GH_CLI_PAT:+SET(${#GH_CLI_PAT} chars)}${GH_CLI_PAT:-UNSET}" + echo " GH_PKG_PAT: ${GH_PKG_PAT:+SET(${#GH_PKG_PAT} chars)}${GH_PKG_PAT:-UNSET}" + echo " CI environment: ${CI:-UNSET}" + echo " GITHUB_ACTIONS: ${GITHUB_ACTIONS:-UNSET}" + # CRITICAL: Preserve existing GITHUB_TOKEN if set (for Models access) local preserve_github_token="" if [[ -n "${GITHUB_TOKEN:-}" ]]; then preserve_github_token="$GITHUB_TOKEN" if [[ "$quiet_mode" != "true" ]]; then - echo -e "${GREEN}✓ Preserving existing GITHUB_TOKEN for Models access${NC}" + echo -e "${GREEN}✓ Preserving existing GITHUB_TOKEN for Models access (${#GITHUB_TOKEN} chars)${NC}" fi + else + echo -e "${YELLOW}⚠️ No GITHUB_TOKEN found initially${NC}" fi # 2. Setup APM module access @@ -61,6 +73,12 @@ setup_github_tokens() { export GITHUB_MODELS_KEY="${GITHUB_TOKEN}" fi + # DEBUG: Show final token state + echo -e "${YELLOW}🔍 DEBUG: Final token configuration:${NC}" + echo " GITHUB_TOKEN: ${GITHUB_TOKEN:+SET(${#GITHUB_TOKEN} chars)}${GITHUB_TOKEN:-UNSET}" + echo " GITHUB_APM_PAT: ${GITHUB_APM_PAT:+SET(${#GITHUB_APM_PAT} chars)}${GITHUB_APM_PAT:-UNSET}" + echo " GITHUB_MODELS_KEY: ${GITHUB_MODELS_KEY:+SET(${#GITHUB_MODELS_KEY} chars)}${GITHUB_MODELS_KEY:-UNSET}" + if [[ "$quiet_mode" != "true" ]]; then echo -e "${GREEN}✅ GitHub token environment configured${NC}" fi diff --git a/scripts/runtime/setup-codex.sh b/scripts/runtime/setup-codex.sh index d934e357..043cb481 100755 --- a/scripts/runtime/setup-codex.sh +++ b/scripts/runtime/setup-codex.sh @@ -61,6 +61,9 @@ setup_codex() { linux-x86_64) codex_platform="x86_64-unknown-linux-gnu" ;; + linux-aarch64) + codex_platform="aarch64-unknown-linux-gnu" + ;; *) log_error "Unsupported platform: $DETECTED_PLATFORM" exit 1 @@ -88,18 +91,24 @@ setup_codex() { local latest_release_url="https://api.github.com/repos/$CODEX_REPO/releases/latest" local latest_tag + # DEBUG: Show token state right before API call + log_info "🔍 DEBUG: Token state before GitHub API call:" + log_info " GITHUB_TOKEN: ${GITHUB_TOKEN:+SET(${#GITHUB_TOKEN} chars)}${GITHUB_TOKEN:-UNSET}" + log_info " GITHUB_APM_PAT: ${GITHUB_APM_PAT:+SET(${#GITHUB_APM_PAT} chars)}${GITHUB_APM_PAT:-UNSET}" + # Try to get the latest release tag using curl if command -v curl >/dev/null 2>&1; then # Use authenticated request if GITHUB_TOKEN or GITHUB_APM_PAT is available if [[ -n "${GITHUB_TOKEN:-}" ]]; then - log_info "Using authenticated GitHub API request (GITHUB_TOKEN)" + log_info "Using authenticated GitHub API request (GITHUB_TOKEN - ${#GITHUB_TOKEN} chars)" local auth_header="Authorization: Bearer ${GITHUB_TOKEN}" latest_tag=$(curl -s -H "$auth_header" "$latest_release_url" | grep '"tag_name":' | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/') elif [[ -n "${GITHUB_APM_PAT:-}" ]]; then - log_info "Using authenticated GitHub API request (GITHUB_APM_PAT)" + log_info "Using authenticated GitHub API request (GITHUB_APM_PAT - ${#GITHUB_APM_PAT} chars)" local auth_header="Authorization: Bearer ${GITHUB_APM_PAT}" latest_tag=$(curl -s -H "$auth_header" "$latest_release_url" | grep '"tag_name":' | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/') else + log_info "🚨 DEBUG: No tokens available - GITHUB_TOKEN and GITHUB_APM_PAT both unset!" log_info "Using unauthenticated GitHub API request (60 requests/hour limit)" latest_tag=$(curl -s "$latest_release_url" | grep '"tag_name":' | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/') fi diff --git a/scripts/runtime/setup-common.sh b/scripts/runtime/setup-common.sh index 7b25ff1d..3dfe4af2 100755 --- a/scripts/runtime/setup-common.sh +++ b/scripts/runtime/setup-common.sh @@ -40,6 +40,9 @@ detect_platform() { x86_64|amd64) DETECTED_PLATFORM="linux-x86_64" ;; + aarch64|arm64) + DETECTED_PLATFORM="linux-aarch64" + ;; *) log_error "Unsupported Linux architecture: $arch" exit 1 diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index e4eaa061..d6971d81 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -74,6 +74,9 @@ detect_platform() { x86_64|amd64) BINARY_NAME="apm-linux-x86_64" ;; + aarch64|arm64) + BINARY_NAME="apm-linux-arm64" + ;; *) log_error "Unsupported Linux architecture: $arch" exit 1 diff --git a/src/apm_cli/core/token_manager.py b/src/apm_cli/core/token_manager.py index d9aabd19..99666476 100644 --- a/src/apm_cli/core/token_manager.py +++ b/src/apm_cli/core/token_manager.py @@ -23,7 +23,7 @@ class GitHubTokenManager: # Define token precedence for different use cases TOKEN_PRECEDENCE = { 'copilot': ['GITHUB_COPILOT_PAT', 'GITHUB_TOKEN', 'GITHUB_APM_PAT'], - 'models': ['GITHUB_TOKEN'], # GitHub Models requires user-scoped PAT + 'models': ['GITHUB_TOKEN', 'GITHUB_APM_PAT'], # GitHub Models prefers user-scoped PAT, falls back to APM PAT 'modules': ['GITHUB_APM_PAT', 'GITHUB_TOKEN'], # APM module access } @@ -144,14 +144,23 @@ def _setup_copilot_tokens(self, env: Dict[str, str], available_tokens: Dict[str, env[env_var] = copilot_token def _setup_codex_tokens(self, env: Dict[str, str], available_tokens: Dict[str, str]): - """Set up tokens for Codex CLI (preserve existing GITHUB_TOKEN).""" - # Codex uses GITHUB_TOKEN directly - only set if missing - if self.preserve_existing and 'GITHUB_TOKEN' in env: - return - - models_token = self.get_token_for_purpose('models', available_tokens) - if models_token and 'GITHUB_TOKEN' not in env: - env['GITHUB_TOKEN'] = models_token + """Set up tokens for Codex CLI (preserve existing tokens).""" + # Codex script checks for both GITHUB_TOKEN and GITHUB_APM_PAT + # Set up GITHUB_TOKEN if not present + if not (self.preserve_existing and 'GITHUB_TOKEN' in env): + models_token = self.get_token_for_purpose('models', available_tokens) + if models_token and 'GITHUB_TOKEN' not in env: + env['GITHUB_TOKEN'] = models_token + if models_token and 'GITHUB_TOKEN' not in env: + env['GITHUB_TOKEN'] = models_token + + # Ensure GITHUB_APM_PAT is available if we have it + if not (self.preserve_existing and 'GITHUB_APM_PAT' in env): + apm_token = available_tokens.get('GITHUB_APM_PAT') + if apm_token and 'GITHUB_APM_PAT' not in env: + env['GITHUB_APM_PAT'] = apm_token + if apm_token and 'GITHUB_APM_PAT' not in env: + env['GITHUB_APM_PAT'] = apm_token def _setup_llm_tokens(self, env: Dict[str, str], available_tokens: Dict[str, str]): """Set up tokens for LLM CLI.""" diff --git a/src/apm_cli/utils/console.py b/src/apm_cli/utils/console.py index 8210de9d..b2be9de2 100644 --- a/src/apm_cli/utils/console.py +++ b/src/apm_cli/utils/console.py @@ -39,6 +39,7 @@ 'warning': '⚠️', 'error': '❌', 'check': '✅', + 'cross': '❌', 'list': '📋', 'preview': '👀', 'robot': '🤖', diff --git a/tests/integration/test_runtime_smoke.py b/tests/integration/test_runtime_smoke.py index d10edefe..281ab52f 100644 --- a/tests/integration/test_runtime_smoke.py +++ b/tests/integration/test_runtime_smoke.py @@ -40,6 +40,19 @@ def run_command(cmd, check=True, capture_output=True, timeout=60, cwd=None): if cwd is None: cwd = Path(__file__).parent.parent.parent + # Ensure environment variables are properly passed to subprocess + env = os.environ.copy() + + # DEBUG: Show GitHub-related environment variables being passed + print(f"🔍 DEBUG: Environment variables being passed to subprocess:") + for key in ['GITHUB_TOKEN', 'GITHUB_APM_PAT', 'GH_MODELS_PAT', 'GH_CLI_PAT', 'GH_PKG_PAT']: + value = env.get(key) + if value: + print(f" {key}: SET ({len(value)} chars)") + else: + print(f" {key}: UNSET") + print(f" Command: {cmd}") + result = subprocess.run( cmd, shell=True, @@ -47,7 +60,8 @@ def run_command(cmd, check=True, capture_output=True, timeout=60, cwd=None): capture_output=capture_output, text=True, timeout=timeout, - cwd=str(cwd) + cwd=str(cwd), + env=env # Explicitly pass environment ) return result except subprocess.TimeoutExpired: