Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
lib
lib
.DS_Store
139 changes: 139 additions & 0 deletions .kiro/settings/lsp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"languages": {
"cpp": {
"name": "clangd",
"command": "clangd",
"args": ["--background-index"],
"file_extensions": ["cpp", "cc", "cxx", "c", "h", "hpp", "hxx"],
"project_patterns": [
"CMakeLists.txt",
"compile_commands.json",
"Makefile"
],
"exclude_patterns": ["**/build/**", "**/cmake-build-**/**"],
"multi_workspace": false,
"initialization_options": {},
"request_timeout_secs": 60
},
"java": {
"name": "jdtls",
"command": "jdtls",
"args": [],
"file_extensions": ["java"],
"project_patterns": [
"pom.xml",
"build.gradle",
"build.gradle.kts",
".project"
],
"exclude_patterns": ["**/target/**", "**/build/**", "**/.gradle/**"],
"multi_workspace": false,
"initialization_options": {
"settings": {
"java": {
"compile": {
"nullAnalysis": {
"mode": "automatic"
}
},
"configuration": {
"annotationProcessing": {
"enabled": true
}
}
}
}
},
"request_timeout_secs": 60
},
"go": {
"name": "gopls",
"command": "gopls",
"args": [],
"file_extensions": ["go"],
"project_patterns": ["go.mod", "go.sum"],
"exclude_patterns": ["**/vendor/**"],
"multi_workspace": false,
"initialization_options": {
"usePlaceholders": true,
"completeUnimported": true
},
"request_timeout_secs": 60
},
"ruby": {
"name": "solargraph",
"command": "solargraph",
"args": ["stdio"],
"file_extensions": ["rb"],
"project_patterns": ["Gemfile", "Rakefile"],
"exclude_patterns": ["**/vendor/**", "**/tmp/**"],
"multi_workspace": false,
"initialization_options": {},
"request_timeout_secs": 60
},
"rust": {
"name": "rust-analyzer",
"command": "rust-analyzer",
"args": [],
"file_extensions": ["rs"],
"project_patterns": ["Cargo.toml"],
"exclude_patterns": ["**/target/**"],
"multi_workspace": false,
"initialization_options": {
"cargo": {
"buildScripts": {
"enable": true
}
},
"diagnostics": {
"enable": true,
"enableExperimental": true
},
"workspace": {
"symbol": {
"search": {
"scope": "workspace"
}
}
}
},
"request_timeout_secs": 60
},
"typescript": {
"name": "typescript-language-server",
"command": "typescript-language-server",
"args": ["--stdio"],
"file_extensions": ["ts", "js", "tsx", "jsx"],
"project_patterns": ["package.json", "tsconfig.json"],
"exclude_patterns": ["**/node_modules/**", "**/dist/**"],
"multi_workspace": false,
"initialization_options": {
"preferences": {
"disableSuggestions": false
}
},
"request_timeout_secs": 60
},
"python": {
"name": "pyright",
"command": "pyright-langserver",
"args": ["--stdio"],
"file_extensions": ["py"],
"project_patterns": [
"pyproject.toml",
"setup.py",
"requirements.txt",
"pyrightconfig.json"
],
"exclude_patterns": [
"**/__pycache__/**",
"**/venv/**",
"**/.venv/**",
"**/.pytest_cache/**"
],
"multi_workspace": false,
"initialization_options": {},
"request_timeout_secs": 60
}
}
}
78 changes: 78 additions & 0 deletions CERTIFICATE_CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Certificate Management Changes

## Summary

Updated the action to manually create certificates via the App Store Connect API with a unique identifier embedded in the Certificate Signing Request (CSR). This ensures reliable certificate cleanup in the post action, even when multiple CI jobs run in parallel.

## Problem Solved

Previously, the action relied on finding certificates by serial number or by the "Created via API" name pattern. This caused issues:

- Race conditions when multiple jobs ran simultaneously
- Certificates not found if already deleted by another job
- Unreliable matching when serial numbers weren't unique enough

## Solution

### Certificate Creation (Main Action)

1. **Generate Unique Identifier**: Create a UUID for each certificate
2. **Create CSR with Unique CN**: Embed the UUID in the Common Name field as `GHA-{UUID}`
3. **Submit to App Store Connect API**: Create the certificate via API
4. **Import to Keychain**: Convert to PKCS12 and import for code signing
5. **Store Identifier**: Save the UUID in GitHub Actions state for cleanup

### Certificate Cleanup (Post Action)

1. **Retrieve Unique Identifier**: Get the UUID from saved state
2. **Find in Local Keychain**: Search for certificate with matching CN `GHA-{UUID}`
3. **Delete from Keychain**: Remove using certificate hash
4. **Find in App Store Connect**: Query API and match by CN in certificate content
5. **Revoke from API**: Delete the certificate from App Store Connect

## Key Benefits

- **Unique Identification**: Each job creates a certificate with a globally unique identifier
- **No Race Conditions**: Each job only deletes its own certificate
- **Reliable Cleanup**: CN-based matching is deterministic and collision-free
- **Graceful Failures**: If certificate is already gone, cleanup succeeds silently

## Technical Details

### CSR Subject Format

```
/CN=GHA-{UUID}/O=GitHub Actions/C=US
```

Example:

```
/CN=GHA-A1B2C3D4-E5F6-7890-ABCD-EF1234567890/O=GitHub Actions/C=US
```

### State Management

- `certificateUniqueId`: The UUID used in the CN
- `apiKeyId`: App Store Connect API key ID
- `apiKeyIssuerId`: App Store Connect API issuer ID
- `keychainPath`: Path to temporary keychain

### API Endpoints Used

- `POST /v1/certificates` - Create certificate
- `GET /v1/certificates` - List certificates
- `DELETE /v1/certificates/{id}` - Revoke certificate

## Files Modified

- `src/lib.ts`: Added `createCertificateViaApi()`, `createKeychainForApi()`, `generateJwtToken()`, and updated `deleteApiCreatedCertificates()`
- `src/index.ts`: Added certificate creation call when using API key authentication

## Testing Recommendations

1. Test single job execution
2. Test parallel matrix builds
3. Test job cancellation (ensure post action still runs)
4. Verify certificates are cleaned up in App Store Connect
5. Check for orphaned certificates after multiple runs
68 changes: 68 additions & 0 deletions SDK_REFACTORING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Refactoring Complete: App Store Connect API SDK Integration

## Summary

Successfully refactored the codebase to use the `@rage-against-the-pixel/app-store-connect-api` TypeScript SDK instead of manually crafting JWT tokens and making curl requests.

## Changes Made

### 1. Added SDK Dependency

- Installed `@rage-against-the-pixel/app-store-connect-api` package
- Added to package.json dependencies

### 2. Created SDK Client Helper

- Added `createAppStoreConnectClient()` function in lib.ts
- Reads .p8 key file and initializes AppStoreConnectClient
- Handles authentication automatically via the SDK

### 3. Refactored Certificate Creation

- Replaced manual curl POST request with `client.api.Certificates.certificatesCreateInstance()`
- Uses `CertificateType.DEVELOPMENT` enum for type safety
- Improved error handling with SDK's structured error responses
- Added null checks for optional response fields

### 4. Refactored Certificate Deletion

- Replaced manual curl GET request with `client.api.Certificates.certificatesGetCollection()`
- Replaced manual curl DELETE request with `client.api.Certificates.certificatesDeleteInstance()`
- Uses SDK's typed responses for cleaner code

### 5. Removed Manual JWT Generation

- Deleted entire `generateJwtToken()` function (~70 lines)
- Removed OpenSSL-based JWT signing logic
- SDK handles JWT generation internally using the `jose` library

### 6. Improved Type Safety

- All API calls now use TypeScript types from the SDK
- Proper enum usage for certificate types
- Better compile-time error checking

## Benefits

✅ **Cleaner Code**: Removed ~100 lines of manual JWT and curl logic
✅ **Type Safety**: Full TypeScript support with auto-generated types
✅ **Better Errors**: Structured error responses from SDK
✅ **Maintainability**: SDK auto-updates with Apple's OpenAPI spec
✅ **Less Dependencies**: No longer need manual OpenSSL JWT signing
✅ **Reliability**: SDK handles edge cases and API changes

## Code Size Impact

- Before: ~1736kB compiled
- After: ~2958kB compiled (+1222kB due to SDK and jose library)

The size increase is acceptable given the benefits of type safety, maintainability, and reduced custom code.

## Testing Recommendations

1. Test certificate creation with API key authentication
2. Verify certificate appears in keychain
3. Test post action cleanup
4. Verify certificate is deleted from both keychain and App Store Connect
5. Test error scenarios (invalid API key, network issues)
6. Verify parallel job execution still works correctly
7 changes: 6 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ inputs:
required: false
platform:
description: |
Either `iOS`, `tvOS`, `macOS`, `watchOS`, `visionOS` or (more rarely)
Either `iOS`, `iOS-simulator`, `tvOS`, `macOS`, `watchOS`, `visionOS` or (more rarely)
`mac-catalyst`
Leave unset and `xcodebuild` decides itself.
required: false
Expand All @@ -38,6 +38,11 @@ inputs:
does without an action specified (usually `build`)
required: false
default: test
archive-path:
description: |
The path in which to store the resulting archive when running archive builds
Leave unset to use the default path
required: false
code-coverage:
description: Enables code coverage
required: false
Expand Down
24 changes: 12 additions & 12 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@actions/artifact": "^2.0.0",
"@actions/core": "^1.6.0",
"@actions/exec": "^1.1.0",
"@rage-against-the-pixel/app-store-connect-api": "^4.2.0",
"semver": "^7.7.1"
},
"devDependencies": {
Expand Down
Loading