Merge pull request #1479 from selfxyz/staging

Release to Production - 2025-12-07
This commit is contained in:
Justin Hernandez
2025-12-25 09:25:43 -08:00
committed by GitHub
517 changed files with 33808 additions and 36091 deletions

View File

@@ -1,291 +0,0 @@
---
description: Comprehensive migration strategy and testing-first approach for porting identity verification logic from app to mobile-sdk-alpha package
version: 1.0.0
status: active
owners:
- team: mobile-identity
- team: sdk-platform
lastUpdated: 2025-01-12
specId: mobile-sdk-migration
importanceScore: 90
importanceJustification: Critical framework for systematically migrating core identity verification functionality to a partner-consumable SDK while maintaining quality and testing coverage.
contextUsageNote: If this file is used to add in-context notes, include a single italicized line stating what specific information was used from this file in sentence case.
---
# Mobile SDK Migration Context
## Migration Strategy Overview
### Testing-First Approach
- **Create tests BEFORE migrating logic** to verify functionality works correctly
- **Dual testing environment**: Jest (app) + Vitest (mobile-sdk-alpha)
- **Validation commands**: `yarn test:build` in both app and mobile-sdk-alpha directories
- **Incremental migration**: One checklist item at a time with thorough validation
### Test Environment Differences
#### App (Jest)
- **Location**: `app/` directory
- **Config**: `jest.config.cjs` with React Native preset
- **Setup**: `jest.setup.js` with comprehensive mocks
- **Module mapping**: `@/` → `src/`, `@tests/` → `tests/src/`
- **Test command**: `yarn test:build` (builds deps + types + bundle analysis + tests)
#### Mobile SDK Alpha (Vitest)
- **Location**: `packages/mobile-sdk-alpha/` directory
- **Config**: `vitest.config.ts` with Node environment
- **Setup**: `tests/setup.ts` with console noise suppression
- **Test command**: `yarn test:build` (build + test + types + lint)
### Migration Validation Workflow
1. **Pre-migration**: Create comprehensive tests in mobile-sdk-alpha for target functionality
2. **Migration**: Port logic from app to mobile-sdk-alpha
3. **Validation**: Run `yarn test:build` in both directories
4. **Integration**: Update app to consume mobile-sdk-alpha
5. **Final validation**: Ensure app tests pass with new SDK consumption
## Migration Checklist Items
### 1. Processing Helpers (MRZ)
**Current Location**: `app/src/utils/` (MRZ utilities)
**Target Location**: `packages/mobile-sdk-alpha/src/processing/`
**Testing Strategy**:
- Create MRZ parsing tests with sample passport data
- Test cross-platform compatibility (React Native vs Web)
### 2. Validation Module
**Current Location**: `app/src/utils/` (document validation logic)
**Target Location**: `packages/mobile-sdk-alpha/src/validation/`
**Testing Strategy**:
- Unit tests for each validation rule
- Test with valid/invalid document data
- Test edge cases and error conditions
### 3. Proof Input Generation
**Current Location**: `app/src/utils/proving/`
**Target Location**: `packages/mobile-sdk-alpha/src/proving/`
**Testing Strategy**:
- Test register input generation with mock data
- Test disclose input generation with various scenarios
- Validate TEE input format compliance
### 4. Crypto Adapters
**Current Location**: `app/src/utils/` (crypto utilities)
**Target Location**: `packages/mobile-sdk-alpha/src/crypto/`
**Testing Strategy**:
- Test WebCrypto vs @noble/* fallback detection
- Test CSPRNG generation across platforms
- Test timing-safe comparison functions
- Parity tests between implementations
### 5. TEE Session Management
**Current Location**: `app/src/utils/` (WebSocket handling)
**Target Location**: `packages/mobile-sdk-alpha/src/tee/`
**Testing Strategy**:
- Test WebSocket wrapper with mock server
- Test abort, timeout, and progress events
- Test connection lifecycle management
### 6. Attestation Verification
**Current Location**: `app/src/utils/` (certificate validation)
**Target Location**: `packages/mobile-sdk-alpha/src/attestation/`
**Testing Strategy**:
- Test PCR0 validation with sample data
- Test public key extraction
- Test certificate chain validation
### 7. Protocol Synchronization
**Current Location**: `app/src/utils/` (protocol tree handling)
**Target Location**: `packages/mobile-sdk-alpha/src/protocol/`
**Testing Strategy**:
- Test protocol tree fetching with pagination
- Test TTL cache behavior
- Test rate limiting and exponential backoff
- Test memory bounds enforcement
### 8. Artifact Management
**Current Location**: `app/src/utils/` (manifest handling)
**Target Location**: `packages/mobile-sdk-alpha/src/artifacts/`
**Testing Strategy**:
- Test manifest schema validation
- Test CDN download with caching
- Test signature verification
- Test storage adapter integration
### 9. Sample Applications
**Target Location**: `packages/mobile-sdk-alpha/samples/`
**Testing Strategy**:
- Create React Native demo with MRZ → proof flow
- Create web demo with browser-based MRZ input
- Test iOS `OpenPassport` URL scheme
### 10. SDK Integration into App
**Migration Strategy**:
- Replace existing modules with SDK imports
- Update import paths throughout app
- Validate all existing functionality works
- Ensure no regression in app behavior
### 11. In-SDK Lightweight Demo
**Target Location**: `packages/mobile-sdk-alpha/demo/`
**Testing Strategy**:
- Embedded React Native demo using MRZ → proof flow
- Test theming hooks integration
- Validate build and run instructions
## Testing Best Practices
### Test Data Management
- **Mock data**: Create comprehensive test fixtures for each module
- **Sensitive data**: Never log PII, credentials, or private keys
- **Redaction**: Use consistent patterns for sensitive field masking
- **Environment flags**: Use `DEBUG_SECRETS_TOKEN` for debug-level secrets
### Cross-Platform Testing
- **React Native**: Test on both iOS and Android simulators
- **Web**: Test with browser adapters
- **Platform detection**: Test platform-specific code paths
- **Native modules**: Mock native dependencies appropriately
### Performance Testing
- **Bundle size**: Monitor SDK bundle size impact
- **Memory usage**: Test memory bounds for large operations
- **Network efficiency**: Test rate limiting and caching
- **Startup time**: Measure SDK initialization impact
### Integration Testing
- **End-to-end flows**: Test complete user journeys
- **Error handling**: Test graceful degradation
- **Recovery mechanisms**: Test error recovery and retry logic
- **Backward compatibility**: Ensure existing app functionality works
## Migration Validation Checklist
### Pre-Migration
- [ ] Create comprehensive test suite in mobile-sdk-alpha
- [ ] Define test fixtures and mock data
- [ ] Set up cross-platform testing environment
- [ ] Document current functionality and edge cases
### During Migration
- [ ] Port logic incrementally (one checklist item at a time)
- [ ] Run `yarn test:build` in mobile-sdk-alpha after each item
- [ ] Validate functionality matches original implementation
- [ ] Update documentation and type definitions
- [ ] Re-export new modules via `packages/mobile-sdk-alpha/src/index.ts` and document them in `packages/mobile-sdk-alpha/README.md`
### Post-Migration
- [ ] Update app to consume mobile-sdk-alpha
- [ ] Run `yarn test:build` in app directory
- [ ] Validate all existing app tests pass
- [ ] Test integration with existing app functionality
- [ ] Performance validation and bundle size analysis
### Final Validation
- [ ] End-to-end testing of complete flows
- [ ] Cross-platform compatibility verification
- [ ] Partner SDK consumption testing
- [ ] Documentation and example updates
- [ ] Release preparation and versioning
## Common Migration Patterns
### Module Structure
```typescript
// Before (app/src/utils/module.ts)
export function processData(data: InputType): OutputType {
// Implementation
}
// After (packages/mobile-sdk-alpha/src/module/index.ts)
export function processData(data: InputType): OutputType {
// Same implementation with enhanced error handling
}
// Test (packages/mobile-sdk-alpha/tests/module.test.ts)
describe('processData', () => {
it('should process valid data correctly', () => {
// Test implementation
});
});
```
### Adapter Pattern
```typescript
// Cross-platform adapter interface
export interface ScannerAdapter {
scan(): Promise<ScanResult>;
isSupported(): boolean;
}
// Platform-specific implementations
export class ReactNativeScannerAdapter implements ScannerAdapter {
// React Native implementation
}
export class WebScannerAdapter implements ScannerAdapter {
// Web implementation
}
```
### Error Handling
```typescript
// Consistent error types across SDK
export class SDKError extends Error {
constructor(
message: string,
public code: string,
public details?: Record<string, unknown>
) {
super(message);
this.name = 'SDKError';
}
}
```
## Security & Privacy Considerations
### Data Protection
- **Sensitive data**: Never log PII, credentials, or private keys in production
- **Secure storage**: Use appropriate storage mechanisms for sensitive data
- **Cleanup**: Properly clean up sensitive data after use
- **Validation**: Validate all inputs and outputs for security
### Privacy Features
- **Zero-knowledge proofs**: Ensure privacy-preserving verification
- **Selective disclosure**: Support minimal necessary attribute revelation
- **Identity commitments**: Maintain privacy of identity data
- **Audit trails**: Log access to sensitive operations without exposing data
## Performance Optimization
### Bundle Size
- **Tree shaking**: Ensure all exports support tree shaking
- **Code splitting**: Split large modules into smaller chunks
- **Dependency analysis**: Monitor and optimize dependencies
- **Bundle analysis**: Regular bundle size monitoring
### Runtime Performance
- **Lazy loading**: Load modules only when needed
- **Caching**: Implement appropriate caching strategies
- **Memory management**: Prevent memory leaks in long-running operations
- **Async operations**: Use proper async patterns for non-blocking operations
## Partner SDK Requirements
### API Design
- **Consistent interfaces**: Maintain consistent API patterns
- **Type safety**: Provide comprehensive TypeScript definitions
- **Error handling**: Clear error messages and error codes
- **Documentation**: Comprehensive API documentation
### Integration Support
- **Branding**: Support for partner branding and theming
- **Callbacks**: Async operation callbacks for integration
- **Configuration**: Flexible configuration options
- **Examples**: Comprehensive integration examples
This context provides a comprehensive framework for executing the migration checklist with a testing-first approach, ensuring quality and reliability throughout the migration process.
$END$

View File

@@ -0,0 +1,203 @@
---
description: Critical rules for avoiding out-of-memory issues in tests, specifically preventing nested require() calls that cause pipeline failures
version: 1.0.0
status: active
owners:
- team: mobile-identity
- team: platform-infrastructure
lastUpdated: 2025-01-12
specId: test-memory-optimization
importanceScore: 100
importanceJustification: Prevents catastrophic pipeline failures due to out-of-memory errors caused by nested require() calls, especially with react-native modules in test environments.
contextUsageNote: If this file is used to add in-context notes, include a single italicized line stating what specific information was used from this file in sentence case.
---
# Test Memory Optimization Rules
## Critical: Never Nest require() Calls
### The Problem
Nested `require('react-native')` calls within tests cause **out-of-memory (OOM) errors** in CI/CD pipelines. This happens because:
1. **Module Resolution Loops**: Each nested require can trigger additional module resolution and initialization
2. **Memory Accumulation**: React Native modules are large and complex; nested requires multiply memory usage
3. **Test Environment Overhead**: Jest/Vitest test runners already load modules; nested requires create duplicate module instances
4. **Hermes Parser Issues**: Nested requires can trigger WASM memory issues with hermes-parser
### The Rule
**NEVER create nested `require('react-native')` calls within test files or test setup files.**
### Examples of FORBIDDEN Patterns
#### ❌ FORBIDDEN: Nested require in test files
```typescript
// BAD - This will cause OOM issues
describe('MyComponent', () => {
beforeEach(() => {
const RN = require('react-native');
const Component = require('./MyComponent');
// Component internally does: require('react-native') again
// This creates nested requires = OOM
});
});
```
#### ❌ FORBIDDEN: require() inside module that's required in tests
```typescript
// BAD - If this module is required in tests, it creates nested requires
// app/src/utils/myUtil.ts
export function myFunction() {
const RN = require('react-native'); // Nested if called from test
return RN.Platform.OS;
}
```
#### ❌ FORBIDDEN: Dynamic requires in test hooks
```typescript
// BAD - Dynamic requires in beforeEach/afterEach create nested requires
beforeEach(() => {
jest.resetModules();
const RN = require('react-native'); // First require
const service = require('@/utils/service'); // May internally require RN again
});
```
### Examples of CORRECT Patterns
#### ✅ CORRECT: Use ES6 imports at top level
```typescript
// GOOD - Single import at top level
import { Platform } from 'react-native';
describe('MyComponent', () => {
it('should work', () => {
expect(Platform.OS).toBe('ios');
});
});
```
**Key Rule**: Use `import` statements, not `require()`. React Native is already mocked in setup files (`jest.setup.js` for Jest, `tests/setup.ts` for Vitest), so imports work correctly.
## React Native Module Handling in Tests
### Jest Setup Pattern (app/jest.setup.js)
The project uses a custom require override in `jest.setup.js` to handle React Native mocks:
```javascript
// This is OK - it's in setup file, runs once
const Module = require('module');
const originalRequire = Module.prototype.require;
Module.prototype.require = function (id) {
if (id === 'react-native') {
const RN = originalRequire.apply(this, arguments);
// Add mocks if needed
return RN;
}
return originalRequire.apply(this, arguments);
};
```
**Key Point**: This override runs ONCE during test setup. Tests should NOT create additional require() calls that would trigger this override multiple times.
### Vitest Setup Pattern (packages/mobile-sdk-alpha/tests/setup.ts)
Vitest uses `vi.mock()` to mock React Native:
```typescript
// This is OK - runs once during setup
vi.mock('react-native', () => ({
Platform: { OS: 'web' },
// ... other mocks
}));
```
**Key Point**: Tests should use `import` statements, not `require()`, after mocks are set up.
## Best Practices
1. **Always use ES6 `import` statements** - Never use `require('react')` or `require('react-native')` in test files
2. **Put all imports at the top of the file** - No dynamic imports in hooks
3. **Avoid `jest.resetModules()`** - Only use when absolutely necessary for module initialization tests
4. **Use setup file mocks** - React Native is already mocked in `jest.setup.js` (Jest) or `tests/setup.ts` (Vitest)
## Automated Enforcement
The project has multiple layers of protection against nested require() patterns:
### 1. ESLint Rule (app/.eslintrc.cjs)
ESLint will fail on `require('react')` and `require('react-native')` in test files:
```javascript
'no-restricted-syntax': [
'error',
{
selector: "CallExpression[callee.name='require'][arguments.0.value='react']",
message: "Do not use require('react') in tests..."
},
{
selector: "CallExpression[callee.name='require'][arguments.0.value='react-native']",
message: "Do not use require('react-native') in tests..."
}
]
```
Run `yarn lint` to check for violations.
### 2. Validation Script (app/scripts/check-test-requires.cjs)
Automated script to detect nested require patterns:
```bash
node scripts/check-test-requires.cjs
```
This script:
- Scans all test files for `require('react')` and `require('react-native')`
- Reports exact file locations and line numbers
- Exits with error code 1 if issues found
### 3. CI Fast-Fail Check
GitHub Actions runs the validation script before tests:
```yaml
- name: Check for nested require() in tests
run: node scripts/check-test-requires.cjs
working-directory: ./app
```
This prevents wasting CI time on tests that will OOM.
## Quick Checklist
Before committing test changes:
- [ ] No `require('react')` calls in test files (use `import React from 'react'` instead)
- [ ] No `require('react-native')` calls in test files (use `import { ... } from 'react-native'` instead)
- [ ] All imports at top of file (not in hooks or jest.mock() factories)
- [ ] Run validation: `node scripts/check-test-requires.cjs`
- [ ] Run lint: `yarn lint`
## Detection
**Signs of nested require issues**: CI OOM errors, test timeouts, memory spikes, "Call stack size exceeded" errors, tests hiding actual failures
**Fix**:
1. Search for `require('react')` and `require('react-native')` in tests
2. Replace with `import` statements at the top of the file
3. Run `node scripts/check-test-requires.cjs` to verify
## Related Files
- `app/.eslintrc.cjs` - ESLint rules blocking nested requires
- `app/scripts/check-test-requires.cjs` - Validation script
- `.github/workflows/mobile-ci.yml` - CI enforcement
- `app/jest.setup.js` - Jest setup with React Native mocks
- `packages/mobile-sdk-alpha/tests/setup.ts` - Vitest setup with React Native mocks
- `app/jest.config.cjs` - Jest configuration
- `packages/mobile-sdk-alpha/vitest.config.ts` - Vitest configuration
$END$

View File

@@ -214,9 +214,6 @@ NOTICE
**/*.zip **/*.zip
**/*.tar.gz **/*.tar.gz
# Patch files
patches/
# ======================================== # ========================================
# Project Specific Patterns # Project Specific Patterns
# ======================================== # ========================================

View File

@@ -2,7 +2,10 @@
# This file configures which files and secrets to ignore during scanning # This file configures which files and secrets to ignore during scanning
# Ignore specific file patterns # Ignore specific file patterns
paths-ignore: paths_ignore:
# Gitleaks configuration file (contains example secrets/patterns for detection)
- ".gitleaks.toml"
# Mock certificates for testing (these are intentionally committed test data) # Mock certificates for testing (these are intentionally committed test data)
- "**/mock_certificates/**/*.key" - "**/mock_certificates/**/*.key"
- "**/mock_certificates/**/*.crt" - "**/mock_certificates/**/*.crt"
@@ -46,47 +49,47 @@ paths-ignore:
- "**/packages/mobile-sdk-alpha/ios/Frameworks/**" - "**/packages/mobile-sdk-alpha/ios/Frameworks/**"
- "**/packages/mobile-sdk-alpha/ios/SelfSDK/**" - "**/packages/mobile-sdk-alpha/ios/SelfSDK/**"
# Ignore specific secret types for mock files # Ignore specific secret types for mock files
secrets-ignore: secrets_ignore:
- "Generic Private Key" # For mock certificate keys - "Generic Private Key" # For mock certificate keys
- "Generic Certificate" # For mock certificates - "Generic Certificate" # For mock certificates
- "RSA Private Key" # For mock RSA keys - "RSA Private Key" # For mock RSA keys
- "EC Private Key" # For mock EC keys - "EC Private Key" # For mock EC keys
secret: secret:
ignored_matches: ignored_matches:
- match: 2036b4e50ad3042969b290e354d9864465107a14de6f5a36d49f81ea8290def8 - match: 2036b4e50ad3042969b290e354d9864465107a14de6f5a36d49f81ea8290def8
name: prebuilt-ios-arm64-apple-ios.private.swiftinterface name: prebuilt-ios-arm64-apple-ios.private.swiftinterface
ignored_paths: ignored_paths:
- '**/*.swiftinterface' - ".gitleaks.toml"
- '**/*.xcframework/**' - "**/*.swiftinterface"
- '**/packages/mobile-sdk-alpha/ios/Frameworks/**' - "**/*.xcframework/**"
- '**/OpenSSL.xcframework/**' - "**/packages/mobile-sdk-alpha/ios/Frameworks/**"
- '**/demo-app/**/mock/**' - "**/OpenSSL.xcframework/**"
- common/src/mock_certificates/aadhaar/mockAadhaarCert.ts - "**/demo-app/**/mock/**"
- '**/NFCPassportReader.xcframework/**' - common/src/mock_certificates/aadhaar/mockAadhaarCert.ts
- common/src/utils/passports/genMockIdDoc.ts - "**/NFCPassportReader.xcframework/**"
- '**/tests/**/*.crt' - common/src/utils/passports/genMockIdDoc.ts
- '**/mock_certificates/**/*.crt' - "**/tests/**/*.crt"
- '**/mock_certificates/**/*.key' - "**/mock_certificates/**/*.crt"
- '**/demo-app/**/test-data/**' - "**/mock_certificates/**/*.key"
- '**/generated/**/*.key' - "**/demo-app/**/test-data/**"
- '**/SelfSDK.xcframework/**' - "**/generated/**/*.key"
- '**/mock/**/*.crt' - "**/SelfSDK.xcframework/**"
- '**/generated/**/*.crt' - "**/mock/**/*.crt"
- '**/test/**/*.key' - "**/generated/**/*.crt"
- '**/mock/**/*.key' - "**/test/**/*.key"
- '**/test/**/*.crt' - "**/mock/**/*.key"
- '**/test/**/*.pem' - "**/test/**/*.crt"
- '**/constants/mockCertificates.ts' - "**/test/**/*.pem"
- '**/mock/**/*.pem' - "**/constants/mockCertificates.ts"
- '**/mock_certificates/**/*.pem' - "**/mock/**/*.pem"
- '**/mock-data/**' - "**/mock_certificates/**/*.pem"
- '**/packages/mobile-sdk-alpha/ios/SelfSDK/**' - "**/mock-data/**"
- '**/tests/**/*.key' - "**/packages/mobile-sdk-alpha/ios/SelfSDK/**"
- '**/generated/**/*.pem' - "**/tests/**/*.key"
- '**/tests/**/*.pem' - "**/generated/**/*.pem"
- '**/test-data/**' - "**/tests/**/*.pem"
- common/src/mock_certificates/** - "**/test-data/**"
- '**/*.xcframework' - common/src/mock_certificates/**
- "**/*.xcframework"
version: 2 version: 2

View File

@@ -24,12 +24,22 @@ outputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
- id: get-hash
name: Hash lock file
shell: bash
run: |
if [ -f "${{ inputs.lock-file }}" ]; then
echo "hash=$(shasum -a 256 "${{ inputs.lock-file }}" | awk '{ print $1 }')" >> $GITHUB_OUTPUT
else
echo "::warning::Lock file '${{ inputs.lock-file }}' not found."
echo "hash=no-lock-file" >> $GITHUB_OUTPUT
fi
- id: cache - id: cache
name: Cache Ruby gems name: Cache Ruby gems
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ inputs.path }} path: ${{ inputs.path }}
key: ${{ runner.os }}-gems-${{ inputs.cache-version }}-${{ hashFiles(inputs.lock-file) }} key: ${{ runner.os }}-gems-${{ inputs.cache-version }}-${{ steps.get-hash.outputs.hash }}
restore-keys: | restore-keys: |
${{ runner.os }}-gems-${{ inputs.cache-version }}- ${{ runner.os }}-gems-${{ inputs.cache-version }}-
${{ runner.os }}-gems- ${{ runner.os }}-gems-

View File

@@ -29,7 +29,7 @@ runs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ inputs.path }} path: ${{ inputs.path }}
key: ${{ runner.os }}-gradle-${{ inputs.cache-version }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} key: ${{ runner.os }}-gradle-${{ inputs.cache-version }}-${{ hashFiles('**/build.gradle', '**/settings.gradle', '**/gradle-wrapper.properties', '**/gradle.properties') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gradle-${{ inputs.cache-version }}- ${{ runner.os }}-gradle-${{ inputs.cache-version }}-
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-

View File

@@ -9,7 +9,7 @@ inputs:
default: | default: |
ios/Pods ios/Pods
~/Library/Caches/CocoaPods ~/Library/Caches/CocoaPods
lock-file: lockfile:
description: Path to Podfile.lock description: Path to Podfile.lock
required: false required: false
default: ios/Podfile.lock default: ios/Podfile.lock
@@ -31,7 +31,7 @@ runs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ inputs.path }} path: ${{ inputs.path }}
key: ${{ runner.os }}-pods-${{ inputs.cache-version }}-${{ hashFiles(inputs.lock-file) }} key: ${{ runner.os }}-pods-${{ inputs.cache-version }}-${{ hashFiles(inputs.lockfile) }}
restore-keys: | restore-keys: |
${{ runner.os }}-pods-${{ inputs.cache-version }}- ${{ runner.os }}-pods-${{ inputs.cache-version }}-
${{ runner.os }}-pods- ${{ runner.os }}-pods-

View File

@@ -25,12 +25,22 @@ outputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
- id: get-hash
name: Hash lock file
shell: bash
run: |
if [ -f "${{ inputs.lock-file }}" ]; then
echo "hash=$(shasum -a 256 "${{ inputs.lock-file }}" | awk '{ print $1 }')" >> $GITHUB_OUTPUT
else
echo "::warning::Lock file '${{ inputs.lock-file }}' not found."
echo "hash=no-lock-file" >> $GITHUB_OUTPUT
fi
- id: cache - id: cache
name: Cache Yarn dependencies name: Cache Yarn dependencies
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ inputs.path }} path: ${{ inputs.path }}
key: ${{ runner.os }}-yarn-${{ inputs.cache-version }}-${{ hashFiles(inputs.lock-file) }} key: ${{ runner.os }}-yarn-${{ inputs.cache-version }}-${{ steps.get-hash.outputs.hash }}
restore-keys: | restore-keys: |
${{ runner.os }}-yarn-${{ inputs.cache-version }}- ${{ runner.os }}-yarn-${{ inputs.cache-version }}-
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-

View File

@@ -0,0 +1,56 @@
name: "Generate GitHub App Token"
description: "Generates a GitHub App token for accessing repositories in the selfxyz organization"
inputs:
app-id:
description: "The GitHub App ID"
required: true
private-key:
description: "The GitHub App private key"
required: true
configure-netrc:
description: "If true, writes a ~/.netrc entry for github.com using the generated token (useful for CocoaPods / git HTTPS fetches)"
required: false
default: "false"
netrc-machine:
description: "The machine hostname to write into ~/.netrc (default: github.com)"
required: false
default: "github.com"
owner:
description: "The owner (organization) of the repositories"
required: false
default: "selfxyz"
repositories:
description: "Comma-separated list of repository names to grant access to"
required: false
default: "NFCPassportReader,android-passport-nfc-reader,react-native-passport-reader,mobile-sdk-native"
outputs:
token:
description: "The generated GitHub App installation token"
value: ${{ steps.app-token.outputs.token }}
runs:
using: "composite"
steps:
- name: Generate GitHub App Token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
id: app-token
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
owner: ${{ inputs.owner }}
repositories: ${{ inputs.repositories }}
- name: Configure Git auth via ~/.netrc (optional)
if: ${{ inputs.configure-netrc == 'true' }}
shell: bash
run: |
set -euo pipefail
TOKEN="${{ steps.app-token.outputs.token }}"
MACHINE="${{ inputs.netrc-machine }}"
# Mask the token in logs defensively (it shouldn't print, but this protects against future edits).
echo "::add-mask::${TOKEN}"
printf "machine %s\n login x-access-token\n password %s\n" "${MACHINE}" "${TOKEN}" > "${HOME}/.netrc"
chmod 600 "${HOME}/.netrc"

View File

@@ -22,9 +22,11 @@ runs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version-file: .nvmrc
cache: "yarn" cache: "yarn"
cache-dependency-path: yarn.lock cache-dependency-path: |
yarn.lock
.yarnrc.yml
- name: Install dependencies - name: Install dependencies
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3

25
.github/actions/yarnrc-hash/action.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Compute .yarnrc.yml hash
description: Compute a stable hash for .yarnrc.yml to use in cache keys.
outputs:
hash:
description: Hash of .yarnrc.yml (or "no-yarnrc" if the file is missing)
value: ${{ steps.compute-yarnrc-hash.outputs.hash }}
runs:
using: composite
steps:
- name: Compute .yarnrc.yml hash
id: compute-yarnrc-hash
shell: bash
run: |
if [ -f .yarnrc.yml ]; then
if command -v shasum >/dev/null 2>&1; then
echo "hash=$(shasum -a 256 .yarnrc.yml | awk '{ print $1 }')" >> "$GITHUB_OUTPUT"
else
echo "hash=$(sha256sum .yarnrc.yml | awk '{ print $1 }')" >> "$GITHUB_OUTPUT"
fi
else
echo "hash=no-yarnrc" >> "$GITHUB_OUTPUT"
fi

11
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,11 @@
### Description
_A brief description of the changes, what and how is being changed._
### Tested
_Explain how the change has been tested (for example by manual testing, unit tests etc) or why it's not necessary (for example version bump)._
### How to QA
_How can the change be tested in a repeatable manner?_

View File

@@ -42,7 +42,7 @@ jobs:
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install cpp dependencies - name: Install cpp dependencies
run: | run: |

View File

@@ -5,12 +5,45 @@ on:
- dev - dev
- staging - staging
- main - main
paths:
- "circuits/**"
jobs: jobs:
check_changes:
runs-on: ubuntu-slim
outputs:
should_run: ${{ steps.filter.outputs.should_run }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check if should run
id: filter
run: |
set -e
if [[ "${{ github.base_ref }}" == "main" ]] || [[ "${{ github.base_ref }}" == "staging" ]]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "Running for ${{ github.base_ref }} - no path filter"
else
# For dev branch, check if circuits files changed
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) || {
echo "Error: Failed to diff against base branch"
exit 1
}
if echo "$CHANGED_FILES" | grep -qE "^circuits/"; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "Running for dev - circuits files changed"
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo "Skipping for dev - no circuits files changed"
fi
fi
run_circuit_tests: run_circuit_tests:
if: github.event.pull_request.draft == false needs: check_changes
runs-on: ubuntu-latest if: github.event.pull_request.draft == false && needs.check_changes.outputs.should_run == 'true'
runs-on:
- "self-hosted"
- "selfxyz-org"
- "ubuntu-24-04"
environment: development environment: development
permissions: permissions:
contents: read contents: read
@@ -18,7 +51,7 @@ jobs:
CIRCOM_VERSION: "2.1.9" CIRCOM_VERSION: "2.1.9"
CIRCOM_SHA256: "e5575829252d763b7818049df9de2ef9304df834697de77fa63ce7babc23c967" CIRCOM_SHA256: "e5575829252d763b7818049df9de2ef9304df834697de77fa63ce7babc23c967"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
# Circom installation from https://github.com/erhant/circomkit/blob/main/.github/workflows/tests.yml # Circom installation from https://github.com/erhant/circomkit/blob/main/.github/workflows/tests.yml
- name: Install dependencies - name: Install dependencies
@@ -106,6 +139,14 @@ jobs:
- name: Print Circom version - name: Print Circom version
run: circom --version run: circom --version
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Enable Corepack
run: corepack enable
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:

View File

@@ -8,7 +8,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -34,7 +34,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -54,7 +54,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -79,6 +79,8 @@ jobs:
run: yarn workspace @selfxyz/common build run: yarn workspace @selfxyz/common build
- name: Build @selfxyz/mobile-sdk-alpha - name: Build @selfxyz/mobile-sdk-alpha
run: yarn workspace @selfxyz/mobile-sdk-alpha build run: yarn workspace @selfxyz/mobile-sdk-alpha build
- name: Build @selfxyz/qrcode
run: yarn workspace @selfxyz/qrcode build:deps
- name: Yarn types - name: Yarn types
run: yarn types run: yarn types
@@ -88,7 +90,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:

View File

@@ -5,21 +5,50 @@ on:
- dev - dev
- staging - staging
- main - main
paths:
- "contracts/**"
- "common/**"
concurrency: concurrency:
group: contracts-ci-${{ github.workflow }}-${{ github.ref }} group: contracts-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
check_changes:
runs-on: ubuntu-slim
outputs:
should_run: ${{ steps.filter.outputs.should_run }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check if should run
id: filter
run: |
set -e
if [[ "${{ github.base_ref }}" == "main" ]] || [[ "${{ github.base_ref }}" == "staging" ]]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "Running for ${{ github.base_ref }} - no path filter"
else
# For dev branch, check if contracts or common files changed
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) || {
echo "Error: Failed to diff against base branch"
exit 1
}
if echo "$CHANGED_FILES" | grep -qE "^(contracts|common)/"; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "Running for dev - contracts or common files changed"
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo "Skipping for dev - no contracts or common files changed"
fi
fi
test_contracts: test_contracts:
if: github.event.pull_request.draft == false needs: check_changes
if: github.event.pull_request.draft == false && needs.check_changes.outputs.should_run == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: development environment: development
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |

View File

@@ -14,7 +14,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Build dependencies - name: Build dependencies
@@ -38,7 +38,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Corepack - name: Setup Corepack
run: | run: |
corepack enable corepack enable
@@ -67,7 +67,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Corepack - name: Setup Corepack
run: | run: |
corepack enable corepack enable
@@ -96,7 +96,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Corepack - name: Setup Corepack
run: | run: |
corepack enable corepack enable

View File

@@ -1,21 +0,0 @@
name: GitGuardian Scan
on:
pull_request:
jobs:
gitguardian:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # fetch all history so multiple commits can be scanned
- name: GitGuardian scan
uses: GitGuardian/ggshield/actions/secret@v1.41.0
env:
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}

View File

@@ -7,13 +7,13 @@ jobs:
gitleaks: gitleaks:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install gitleaks - name: Install gitleaks
uses: gitleaks/gitleaks-action@v2.3.9 uses: gitleaks/gitleaks-action@v2.3.9
with: with:
config-path: .gitleaks.toml config-path: gitleaks-override.toml
fail: true fail: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,6 +5,7 @@ env:
JAVA_VERSION: 17 JAVA_VERSION: 17
WORKSPACE: ${{ github.workspace }} WORKSPACE: ${{ github.workspace }}
APP_PATH: ${{ github.workspace }}/app APP_PATH: ${{ github.workspace }}/app
NODE_ENV: "production"
on: on:
pull_request: pull_request:
@@ -19,7 +20,7 @@ jobs:
analyze-android: analyze-android:
runs-on: macos-latest-large runs-on: macos-latest-large
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -37,32 +38,34 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- name: Cache Node Modules - name: Cache Yarn
uses: actions/cache@v4 uses: ./.github/actions/cache-yarn
with: with:
path: | path: |
.yarn/cache .yarn/cache
node_modules node_modules
app/node_modules app/node_modules
key: ${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn-${{ hashFiles('yarn.lock') }} cache-version: node-${{ env.NODE_VERSION_SANITIZED }}
restore-keys: | - name: Cache Bundler
${{ runner.os }}-node-${{ env.NODE_VERSION_SANITIZED }}-yarn- uses: ./.github/actions/cache-bundler
- name: Cache Ruby Bundler
uses: actions/cache@v4
with: with:
path: app/vendor/bundle path: app/vendor/bundle
key: ${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-${{ hashFiles('app/Gemfile.lock') }} lock-file: app/Gemfile.lock
restore-keys: | cache-version: ruby${{ env.RUBY_VERSION }}
${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-
- name: Cache Gradle - name: Cache Gradle
uses: actions/cache@v4 uses: ./.github/actions/cache-gradle
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('app/android/**/gradle-wrapper.properties', 'app/android/**/gradle-wrapper.jar') }} - name: Generate token for self repositories
restore-keys: | if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
${{ runner.os }}-gradle- uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies - name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup uses: ./.github/actions/mobile-setup
with: with:
@@ -71,7 +74,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }} ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }} workspace: ${{ env.WORKSPACE }}
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Build dependencies - name: Build dependencies
shell: bash shell: bash
run: yarn workspace @selfxyz/common build run: yarn workspace @selfxyz/common build
@@ -82,7 +85,7 @@ jobs:
analyze-ios: analyze-ios:
runs-on: macos-latest-large runs-on: macos-latest-large
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -100,30 +103,33 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- name: Cache Node Modules - name: Cache Yarn
uses: actions/cache@v4 uses: ./.github/actions/cache-yarn
with: with:
path: | path: |
.yarn/cache .yarn/cache
node_modules node_modules
app/node_modules app/node_modules
key: ${{ runner.os }}-node${{ env.NODE_VERSION }}-yarn-${{ hashFiles('yarn.lock') }} cache-version: node-${{ env.NODE_VERSION_SANITIZED }}
restore-keys: | - name: Cache Bundler
${{ runner.os }}-node${{ env.NODE_VERSION }}-yarn- uses: ./.github/actions/cache-bundler
- name: Cache Ruby Bundler
uses: actions/cache@v4
with: with:
path: app/vendor/bundle path: app/vendor/bundle
key: ${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-${{ hashFiles('app/Gemfile.lock') }} lock-file: app/Gemfile.lock
restore-keys: | cache-version: ruby${{ env.RUBY_VERSION }}
${{ runner.os }}-ruby${{ env.RUBY_VERSION }}-gems-
- name: Cache CocoaPods - name: Cache CocoaPods
uses: actions/cache@v4 uses: ./.github/actions/cache-pods
with: with:
path: app/ios/Pods path: app/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('app/ios/Podfile.lock') }} lockfile: app/ios/Podfile.lock
restore-keys: | - name: Generate token for self repositories
${{ runner.os }}-pods- if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies - name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup uses: ./.github/actions/mobile-setup
with: with:
@@ -132,7 +138,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }} ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }} workspace: ${{ env.WORKSPACE }}
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Build dependencies - name: Build dependencies
shell: bash shell: bash
run: yarn workspace @selfxyz/common build run: yarn workspace @selfxyz/common build

View File

@@ -10,7 +10,7 @@ env:
WORKSPACE: ${{ github.workspace }} WORKSPACE: ${{ github.workspace }}
APP_PATH: ${{ github.workspace }}/app APP_PATH: ${{ github.workspace }}/app
# Cache versions # Cache versions
GH_CACHE_VERSION: v1 # Global cache version GH_CACHE_VERSION: v2 # Global cache version - bumped to invalidate caches
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
# Performance optimizations # Performance optimizations
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.caching=true GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.caching=true
@@ -35,10 +35,10 @@ concurrency:
jobs: jobs:
build-deps: build-deps:
runs-on: macos-latest-large runs-on: ubuntu-latest
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -76,7 +76,7 @@ jobs:
with: with:
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }} cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
- name: Build dependencies (cache miss) - name: Build dependencies (cache miss)
# Temporarily disabled due to `yarn types` failures when cache is used. # Temporarily disabled due to `yarn types` failures when cache is used.
# if: steps.built-deps.outputs.cache-hit != 'true' # if: steps.built-deps.outputs.cache-hit != 'true'
run: | run: |
echo "Cache miss for built dependencies. Building now..." echo "Cache miss for built dependencies. Building now..."
@@ -90,16 +90,13 @@ jobs:
- name: Check App Types - name: Check App Types
run: yarn types run: yarn types
working-directory: ./app working-directory: ./app
- name: Check license headers
run: node scripts/check-license-headers.mjs --check
working-directory: ./
test: test:
runs-on: macos-latest-large runs-on: ubuntu-latest
needs: build-deps needs: build-deps
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -147,10 +144,11 @@ jobs:
ls -la common/dist/ || echo "❌ common dist not found" ls -la common/dist/ || echo "❌ common dist not found"
ls -la common/dist/cjs/ || echo "❌ common dist/cjs not found" ls -la common/dist/cjs/ || echo "❌ common dist/cjs not found"
ls -la common/dist/cjs/index.cjs || echo "❌ common dist/cjs/index.cjs not found" ls -la common/dist/cjs/index.cjs || echo "❌ common dist/cjs/index.cjs not found"
- name: Build dependencies (cache miss) - name: Build dependencies (always - debugging CI)
if: steps.built-deps.outputs.cache-hit != 'true' # Temporarily always build to debug CI issues
# TODO: Re-enable cache after fixing: if: steps.built-deps.outputs.cache-hit != 'true'
run: | run: |
echo "Cache miss for built dependencies. Building now..." echo "Building dependencies (cache temporarily disabled for debugging)..."
yarn workspace @selfxyz/mobile-app run build:deps yarn workspace @selfxyz/mobile-app run build:deps
# Verify build completed successfully # Verify build completed successfully
if [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "common/dist/cjs/index.cjs" ]; then if [ ! -f "packages/mobile-sdk-alpha/dist/cjs/index.cjs" ] || [ ! -f "common/dist/cjs/index.cjs" ]; then
@@ -182,7 +180,15 @@ jobs:
else else
echo "✅ All required dependency files exist" echo "✅ All required dependency files exist"
fi fi
- name: Check for nested require() in tests
run: node scripts/check-test-requires.cjs
working-directory: ./app
- name: App Tests - name: App Tests
env:
# Increase Node.js memory to prevent hermes-parser WASM memory errors
NODE_OPTIONS: --max-old-space-size=4096
# Override production NODE_ENV for tests - React's production build doesn't include testing utilities
NODE_ENV: test
run: | run: |
# Final verification from app directory perspective # Final verification from app directory perspective
echo "Final verification before running tests (from app directory)..." echo "Final verification before running tests (from app directory)..."
@@ -205,7 +211,7 @@ jobs:
IOS_PROJECT_NAME: "Self" IOS_PROJECT_NAME: "Self"
IOS_PROJECT_SCHEME: "OpenPassport" IOS_PROJECT_SCHEME: "OpenPassport"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -261,6 +267,7 @@ jobs:
- name: Cache Ruby gems - name: Cache Ruby gems
uses: ./.github/actions/cache-bundler uses: ./.github/actions/cache-bundler
with: with:
# TODO(jcortejoso): Confirm the path of the bundle cache
path: app/ios/vendor/bundle path: app/ios/vendor/bundle
lock-file: app/Gemfile.lock lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }} cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
@@ -270,7 +277,7 @@ jobs:
path: | path: |
app/ios/Pods app/ios/Pods
~/Library/Caches/CocoaPods ~/Library/Caches/CocoaPods
lock-file: app/ios/Podfile.lock lockfile: app/ios/Podfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }} cache-version: ${{ env.GH_CACHE_VERSION }}
- name: Cache Xcode build - name: Cache Xcode build
uses: actions/cache@v4 uses: actions/cache@v4
@@ -308,6 +315,14 @@ jobs:
bundle config set --local path 'vendor/bundle' bundle config set --local path 'vendor/bundle'
bundle install --jobs 4 --retry 3 bundle install --jobs 4 --retry 3
working-directory: ./app working-directory: ./app
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install iOS Dependencies - name: Install iOS Dependencies
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
with: with:
@@ -318,7 +333,7 @@ jobs:
cd app/ios cd app/ios
bundle exec bash scripts/pod-install-with-cache-fix.sh bundle exec bash scripts/pod-install-with-cache-fix.sh
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Resolve iOS workspace - name: Resolve iOS workspace
run: | run: |
WORKSPACE_OPEN="ios/OpenPassport.xcworkspace" WORKSPACE_OPEN="ios/OpenPassport.xcworkspace"
@@ -392,7 +407,7 @@ jobs:
needs: build-deps needs: build-deps
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -463,11 +478,20 @@ jobs:
run: | run: |
echo "Cache miss for built dependencies. Building now..." echo "Cache miss for built dependencies. Building now..."
yarn workspace @selfxyz/mobile-app run build:deps yarn workspace @selfxyz/mobile-app run build:deps
- name: Clone android-passport-nfc-reader - name: Generate token for self repositories
uses: ./.github/actions/clone-android-passport-nfc-reader if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with: with:
working_directory: ${{ env.APP_PATH }} app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Setup Android private modules
run: |
cd ${{ env.APP_PATH }}
PLATFORM=android node scripts/setup-private-modules.cjs
env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android (with AAPT2 symlink fix) - name: Build Android (with AAPT2 symlink fix)
run: yarn android:ci run: yarn android:ci
working-directory: ./app working-directory: ./app

View File

@@ -31,6 +31,7 @@ name: Mobile Deploy
env: env:
# Build environment versions # Build environment versions
RUBY_VERSION: 3.2 RUBY_VERSION: 3.2
NODE_ENV: "production"
JAVA_VERSION: 17 JAVA_VERSION: 17
ANDROID_API_LEVEL: 35 ANDROID_API_LEVEL: 35
ANDROID_NDK_VERSION: 27.0.12077973 ANDROID_NDK_VERSION: 27.0.12077973
@@ -150,9 +151,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
# Guard against auto-closed PRs (GitHub reports merged == false when a PR closes without merging)
if: | if: |
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) && (
!contains(github.event.pull_request.labels.*.name, 'deploy:skip') github.event_name != 'pull_request' ||
(github.event.action == 'closed' && github.event.pull_request.merged == true)
) &&
(
github.event_name != 'pull_request' ||
!contains(github.event.pull_request.labels.*.name, 'deploy:skip')
)
outputs: outputs:
version: ${{ steps.bump.outputs.version }} version: ${{ steps.bump.outputs.version }}
ios_build: ${{ steps.bump.outputs.ios_build }} ios_build: ${{ steps.bump.outputs.ios_build }}
@@ -160,7 +168,7 @@ jobs:
version_bump_type: ${{ steps.determine-bump.outputs.version_bump }} version_bump_type: ${{ steps.determine-bump.outputs.version_bump }}
platform: ${{ steps.determine-platform.outputs.platform }} platform: ${{ steps.determine-platform.outputs.platform }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
# Build from the branch that triggered the workflow (staging, feature branch, etc.) # Build from the branch that triggered the workflow (staging, feature branch, etc.)
@@ -262,7 +270,8 @@ jobs:
contents: read contents: read
actions: write actions: write
if: | if: |
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) && (github.event_name != 'pull_request' ||
(github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
( (
(inputs.platform == 'ios' || inputs.platform == 'both') || (inputs.platform == 'ios' || inputs.platform == 'both') ||
(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:android')) (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:android'))
@@ -282,7 +291,7 @@ jobs:
fi fi
fi fi
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
# Checkout the branch that triggered the workflow # Checkout the branch that triggered the workflow
@@ -359,6 +368,10 @@ jobs:
echo "Xcode path:" echo "Xcode path:"
xcode-select -p xcode-select -p
- name: Compute .yarnrc.yml hash
id: yarnrc-hash
uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn artifacts - name: Cache Yarn artifacts
id: yarn-cache id: yarn-cache
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -367,12 +380,13 @@ jobs:
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.yarn/unplugged .yarn/unplugged
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }} cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Cache Ruby gems - name: Cache Ruby gems
id: gems-cache id: gems-cache
uses: ./.github/actions/cache-bundler uses: ./.github/actions/cache-bundler
with: with:
# TODO(jcortejoso): Confirm the path of the bundle cache
path: ${{ env.APP_PATH }}/ios/vendor/bundle path: ${{ env.APP_PATH }}/ios/vendor/bundle
lock-file: app/Gemfile.lock lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }} cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
@@ -384,7 +398,7 @@ jobs:
path: | path: |
${{ env.APP_PATH }}/ios/Pods ${{ env.APP_PATH }}/ios/Pods
~/Library/Caches/CocoaPods ~/Library/Caches/CocoaPods
lock-file: app/ios/Podfile.lock lockfile: app/ios/Podfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }} cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}
- name: Log cache status - name: Log cache status
@@ -416,6 +430,14 @@ jobs:
fi fi
echo "✅ Lock files exist" echo "✅ Lock files exist"
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies (main repo) - name: Install Mobile Dependencies (main repo)
if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
@@ -426,7 +448,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }} ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }} workspace: ${{ env.WORKSPACE }}
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install Mobile Dependencies (forked PRs - no secrets) - name: Install Mobile Dependencies (forked PRs - no secrets)
if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
@@ -679,8 +701,11 @@ jobs:
IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }} IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }}
NODE_OPTIONS: "--max-old-space-size=8192" NODE_OPTIONS: "--max-old-space-size=8192"
SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }} SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
TURNKEY_ORGANIZATION_ID: ${{ secrets.TURNKEY_ORGANIZATION_ID }}
timeout-minutes: 90 timeout-minutes: 90
run: | run: |
cd ${{ env.APP_PATH }} cd ${{ env.APP_PATH }}
@@ -826,7 +851,8 @@ jobs:
actions: write actions: write
id-token: write id-token: write
if: | if: |
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) && (github.event_name != 'pull_request' ||
(github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
( (
(inputs.platform == 'android' || inputs.platform == 'both') || (inputs.platform == 'android' || inputs.platform == 'both') ||
(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:ios')) (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'deploy:ios'))
@@ -846,7 +872,7 @@ jobs:
fi fi
fi fi
- uses: actions/checkout@v4 - uses: actions/checkout@v6
if: inputs.platform != 'ios' if: inputs.platform != 'ios'
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -964,6 +990,10 @@ jobs:
# Use version-manager script to apply versions # Use version-manager script to apply versions
node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD" node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"
- name: Compute .yarnrc.yml hash
id: yarnrc-hash
uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn artifacts - name: Cache Yarn artifacts
id: yarn-cache id: yarn-cache
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -972,7 +1002,7 @@ jobs:
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.yarn/unplugged .yarn/unplugged
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }} cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Cache Ruby gems - name: Cache Ruby gems
id: gems-cache id: gems-cache
@@ -1026,6 +1056,14 @@ jobs:
echo "✅ Lock files exist" echo "✅ Lock files exist"
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install Mobile Dependencies (main repo) - name: Install Mobile Dependencies (main repo)
if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
uses: ./.github/actions/mobile-setup uses: ./.github/actions/mobile-setup
@@ -1035,7 +1073,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }} ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }} workspace: ${{ env.WORKSPACE }}
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
PLATFORM: ${{ inputs.platform }} PLATFORM: ${{ inputs.platform }}
- name: Install Mobile Dependencies (forked PRs - no secrets) - name: Install Mobile Dependencies (forked PRs - no secrets)
@@ -1086,12 +1124,14 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
- name: Clone android-passport-nfc-reader - name: Setup Android private modules
if: inputs.platform != 'ios' if: inputs.platform != 'ios'
uses: ./.github/actions/clone-android-passport-nfc-reader run: |
with: cd ${{ env.APP_PATH }}
working_directory: ${{ env.APP_PATH }} PLATFORM=android node scripts/setup-private-modules.cjs
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Dependencies (Android) - name: Build Dependencies (Android)
if: inputs.platform != 'ios' if: inputs.platform != 'ios'
@@ -1121,6 +1161,9 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=6144" NODE_OPTIONS: "--max-old-space-size=6144"
SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }} SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
TURNKEY_ORGANIZATION_ID: ${{ secrets.TURNKEY_ORGANIZATION_ID }}
run: | run: |
cd ${{ env.APP_PATH }} cd ${{ env.APP_PATH }}
@@ -1233,12 +1276,13 @@ jobs:
needs: [bump-version, build-ios, build-android] needs: [bump-version, build-ios, build-android]
if: | if: |
always() && always() &&
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) && (github.event_name != 'pull_request' ||
(github.event.action == 'closed' && github.event.pull_request.merged == true)) &&
(needs.build-ios.result == 'success' || needs.build-android.result == 'success') (needs.build-ios.result == 'success' || needs.build-android.result == 'success')
env: env:
APP_PATH: ${{ github.workspace }}/app APP_PATH: ${{ github.workspace }}/app
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
# Checkout target branch for version bump PR (default: dev, override with bump_target_branch input) # Checkout target branch for version bump PR (default: dev, override with bump_target_branch input)
@@ -1276,17 +1320,21 @@ jobs:
const version = JSON.parse(fs.readFileSync('version.json', 'utf8')); const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
version.ios.build = $IOS_BUILD; // Only bump build numbers for successful builds
version.android.build = $ANDROID_BUILD;
// Update lastDeployed timestamp for successful builds
if ('$IOS_SUCCESS' === 'success') { if ('$IOS_SUCCESS' === 'success') {
version.ios.build = $IOS_BUILD;
version.ios.lastDeployed = timestamp; version.ios.lastDeployed = timestamp;
console.log('✅ Updated iOS lastDeployed timestamp'); console.log('✅ Updated iOS build number to $IOS_BUILD and lastDeployed timestamp');
} else {
console.log('⏭️ Skipped iOS build number bump (build did not succeed)');
} }
if ('$ANDROID_SUCCESS' === 'success') { if ('$ANDROID_SUCCESS' === 'success') {
version.android.build = $ANDROID_BUILD;
version.android.lastDeployed = timestamp; version.android.lastDeployed = timestamp;
console.log('✅ Updated Android lastDeployed timestamp'); console.log('✅ Updated Android build number to $ANDROID_BUILD and lastDeployed timestamp');
} else {
console.log('⏭️ Skipped Android build number bump (build did not succeed)');
} }
fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n'); fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
@@ -1423,7 +1471,7 @@ jobs:
(needs.build-ios.result == 'success' || needs.build-android.result == 'success') && (needs.build-ios.result == 'success' || needs.build-android.result == 'success') &&
(inputs.deployment_track == 'production') (inputs.deployment_track == 'production')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
# Checkout target branch for tagging (usually dev) # Checkout target branch for tagging (usually dev)

View File

@@ -7,7 +7,7 @@ env:
ANDROID_NDK_VERSION: 27.0.12077973 ANDROID_NDK_VERSION: 27.0.12077973
XCODE_VERSION: 16.4 XCODE_VERSION: 16.4
# Cache versions # Cache versions
GH_CACHE_VERSION: v1 # Global cache version GH_CACHE_VERSION: v2 # Global cache version - bumped to invalidate caches
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
# Performance optimizations # Performance optimizations
GRADLE_OPTS: -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.caching=true GRADLE_OPTS: -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.caching=true
@@ -37,7 +37,7 @@ jobs:
timeout-minutes: 120 timeout-minutes: 120
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -56,6 +56,9 @@ jobs:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- run: corepack enable - run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate - run: corepack prepare yarn@4.6.0 --activate
- name: Compute .yarnrc.yml hash
id: yarnrc-hash
uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -63,10 +66,18 @@ jobs:
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.yarn/unplugged .yarn/unplugged
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }} cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Toggle Yarn hardened mode for trusted PRs - name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install deps (internal PRs and protected branches) - name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -76,7 +87,7 @@ jobs:
retry_wait_seconds: 5 retry_wait_seconds: 5
command: yarn install --immutable --silent command: yarn install --immutable --silent
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets) - name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -130,11 +141,13 @@ jobs:
corepack prepare yarn@4.6.0 --activate corepack prepare yarn@4.6.0 --activate
yarn workspace @selfxyz/mobile-app run build:deps || { echo "❌ Dependency build failed"; exit 1; } yarn workspace @selfxyz/mobile-app run build:deps || { echo "❌ Dependency build failed"; exit 1; }
echo "✅ Dependencies built successfully" echo "✅ Dependencies built successfully"
- name: Clone android-passport-nfc-reader - name: Setup Android private modules
uses: ./.github/actions/clone-android-passport-nfc-reader run: |
with: cd app
working_directory: app PLATFORM=android node scripts/setup-private-modules.cjs
selfxyz_internal_pat: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android APK - name: Build Android APK
run: | run: |
echo "Building Android APK..." echo "Building Android APK..."
@@ -144,6 +157,8 @@ jobs:
- name: Clean up Gradle build artifacts - name: Clean up Gradle build artifacts
uses: ./.github/actions/cleanup-gradle-artifacts uses: ./.github/actions/cleanup-gradle-artifacts
- name: Verify APK and android-passport-nfc-reader integration - name: Verify APK and android-passport-nfc-reader integration
env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
run: | run: |
echo "🔍 Verifying build artifacts..." echo "🔍 Verifying build artifacts..."
APK_PATH="app/android/app/build/outputs/apk/debug/app-debug.apk" APK_PATH="app/android/app/build/outputs/apk/debug/app-debug.apk"
@@ -154,16 +169,29 @@ jobs:
APK_SIZE=$(stat -f%z "$APK_PATH" 2>/dev/null || stat -c%s "$APK_PATH" 2>/dev/null || echo "unknown") APK_SIZE=$(stat -f%z "$APK_PATH" 2>/dev/null || stat -c%s "$APK_PATH" 2>/dev/null || echo "unknown")
echo "📱 APK size: $APK_SIZE bytes" echo "📱 APK size: $APK_SIZE bytes"
# Verify android-passport-nfc-reader was properly integrated (skip for forks) # Verify private modules were properly integrated (skip for forks)
if [ -z "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]; then if [ -z "${SELFXYZ_APP_TOKEN:-}" ]; then
echo "🔕 No PAT available — skipping private module verification" echo "🔕 No SELFXYZ_APP_TOKEN available — skipping private module verification"
elif [ -d "app/android/android-passport-nfc-reader" ]; then
echo "✅ android-passport-nfc-reader directory exists"
echo "📁 android-passport-nfc-reader contents:"
ls -la app/android/android-passport-nfc-reader/ | head -10
else else
echo "❌ android-passport-nfc-reader directory not found" # Verify android-passport-nfc-reader
exit 1 if [ -d "app/android/android-passport-nfc-reader" ]; then
echo "✅ android-passport-nfc-reader directory exists"
echo "📁 android-passport-nfc-reader contents:"
ls -la app/android/android-passport-nfc-reader/ | head -10
else
echo "❌ android-passport-nfc-reader directory not found"
exit 1
fi
# Verify react-native-passport-reader
if [ -d "app/android/react-native-passport-reader" ]; then
echo "✅ react-native-passport-reader directory exists"
echo "📁 react-native-passport-reader contents:"
ls -la app/android/react-native-passport-reader/ | head -10
else
echo "❌ react-native-passport-reader directory not found"
exit 1
fi
fi fi
echo "🎉 Build verification completed successfully!" echo "🎉 Build verification completed successfully!"
@@ -212,7 +240,7 @@ jobs:
IOS_PROJECT_NAME: "Self" IOS_PROJECT_NAME: "Self"
IOS_PROJECT_SCHEME: "OpenPassport" IOS_PROJECT_SCHEME: "OpenPassport"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -231,6 +259,9 @@ jobs:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- run: corepack enable - run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate - run: corepack prepare yarn@4.6.0 --activate
- name: Compute .yarnrc.yml hash
id: yarnrc-hash
uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -238,10 +269,18 @@ jobs:
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.yarn/unplugged .yarn/unplugged
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }} cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Toggle Yarn hardened mode for trusted PRs - name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install deps (internal PRs and protected branches) - name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -251,7 +290,7 @@ jobs:
retry_wait_seconds: 5 retry_wait_seconds: 5
command: yarn install --immutable --silent command: yarn install --immutable --silent
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets) - name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -296,12 +335,13 @@ jobs:
xcodebuild -version xcodebuild -version
echo "Xcode path:" echo "Xcode path:"
xcode-select -p xcode-select -p
- name: Setup ccache # Temporarily disabled ccache to debug CI issues
uses: hendrikmuhs/ccache-action@v1.2 # - name: Setup ccache
with: # uses: hendrikmuhs/ccache-action@v1.2
key: ${{ github.job }}-${{ runner.os }} # with:
- name: Add ccache to PATH # key: ${{ github.job }}-${{ runner.os }}
run: echo "/usr/local/opt/ccache/libexec" >> $GITHUB_PATH # - name: Add ccache to PATH
# run: echo "/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
- name: Set up Ruby - name: Set up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
@@ -314,15 +354,9 @@ jobs:
path: | path: |
app/ios/Pods app/ios/Pods
~/Library/Caches/CocoaPods ~/Library/Caches/CocoaPods
lock-file: app/ios/Podfile.lock lockfile: app/ios/Podfile.lock
- name: Cache DerivedData # DerivedData caching disabled - caused intermittent build failures due to stale cache
uses: actions/cache@v4 # Pod caching still speeds up pod install significantly
with:
path: app/ios/build
key: ${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock', 'yarn.lock') }}
restore-keys: |
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-
- name: Verify iOS Runtime - name: Verify iOS Runtime
run: | run: |
echo "📱 Verifying iOS Runtime availability..." echo "📱 Verifying iOS Runtime availability..."
@@ -344,7 +378,7 @@ jobs:
echo "📦 Installing pods via centralized script…" echo "📦 Installing pods via centralized script…"
BUNDLE_GEMFILE=../Gemfile bundle exec bash scripts/pod-install-with-cache-fix.sh BUNDLE_GEMFILE=../Gemfile bundle exec bash scripts/pod-install-with-cache-fix.sh
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Setup iOS Simulator - name: Setup iOS Simulator
run: | run: |
echo "Setting up iOS Simulator..." echo "Setting up iOS Simulator..."

View File

@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Build dependencies - name: Build dependencies
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Restore build artifacts - name: Restore build artifacts
@@ -56,7 +56,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Restore build artifacts - name: Restore build artifacts
@@ -77,7 +77,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Restore build artifacts - name: Restore build artifacts
@@ -98,7 +98,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Restore build artifacts - name: Restore build artifacts

View File

@@ -12,7 +12,7 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:

View File

@@ -27,10 +27,11 @@ on:
paths: paths:
- "packages/mobile-sdk-demo/**" - "packages/mobile-sdk-demo/**"
- "packages/mobile-sdk-alpha/**" - "packages/mobile-sdk-alpha/**"
- ".github/workflows/mobile-sdk-e2e.yml" - ".github/workflows/mobile-sdk-demo-e2e.yml"
jobs: jobs:
android-e2e: android-e2e:
name: Android E2E Tests Demo App
# Currently build-only for Android. E2E steps are preserved but skipped (if: false). # Currently build-only for Android. E2E steps are preserved but skipped (if: false).
# To re-enable full E2E: change `if: false` to `if: true` on emulator steps. # To re-enable full E2E: change `if: false` to `if: true` on emulator steps.
concurrency: concurrency:
@@ -39,7 +40,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -58,6 +59,9 @@ jobs:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- run: corepack enable - run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate - run: corepack prepare yarn@4.6.0 --activate
- name: Compute .yarnrc.yml hash
id: yarnrc-hash
uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -65,10 +69,17 @@ jobs:
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.yarn/unplugged .yarn/unplugged
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }} cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Toggle Yarn hardened mode for trusted PRs - name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install deps (internal PRs and protected branches) - name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -78,7 +89,7 @@ jobs:
retry_wait_seconds: 5 retry_wait_seconds: 5
command: yarn install --immutable --silent command: yarn install --immutable --silent
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets) - name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -192,6 +203,7 @@ jobs:
ios-e2e: ios-e2e:
timeout-minutes: 60 timeout-minutes: 60
runs-on: macos-latest-large runs-on: macos-latest-large
name: iOS E2E Tests Demo App
concurrency: concurrency:
group: ${{ github.workflow }}-ios-${{ github.ref }} group: ${{ github.workflow }}-ios-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
@@ -199,7 +211,7 @@ jobs:
IOS_WORKSPACE_PATH: packages/mobile-sdk-demo/ios/SelfDemoApp.xcworkspace IOS_WORKSPACE_PATH: packages/mobile-sdk-demo/ios/SelfDemoApp.xcworkspace
IOS_PROJECT_SCHEME: SelfDemoApp IOS_PROJECT_SCHEME: SelfDemoApp
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -218,6 +230,9 @@ jobs:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- run: corepack enable - run: corepack enable
- run: corepack prepare yarn@4.6.0 --activate - run: corepack prepare yarn@4.6.0 --activate
- name: Compute .yarnrc.yml hash
id: yarnrc-hash
uses: ./.github/actions/yarnrc-hash
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
with: with:
@@ -225,10 +240,17 @@ jobs:
.yarn/cache .yarn/cache
.yarn/install-state.gz .yarn/install-state.gz
.yarn/unplugged .yarn/unplugged
cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ hashFiles('.yarnrc.yml') }} cache-version: ${{ env.GH_CACHE_VERSION }}-node-${{ env.NODE_VERSION_SANITIZED }}-${{ steps.yarnrc-hash.outputs.hash }}
- name: Toggle Yarn hardened mode for trusted PRs - name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install deps (internal PRs and protected branches) - name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -238,7 +260,7 @@ jobs:
retry_wait_seconds: 5 retry_wait_seconds: 5
command: yarn install --immutable --silent command: yarn install --immutable --silent
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets) - name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3 uses: nick-fields/retry@v3
@@ -291,15 +313,9 @@ jobs:
path: | path: |
packages/mobile-sdk-demo/ios/Pods packages/mobile-sdk-demo/ios/Pods
~/Library/Caches/CocoaPods ~/Library/Caches/CocoaPods
lock-file: packages/mobile-sdk-demo/ios/Podfile.lock lockfile: packages/mobile-sdk-demo/ios/Podfile.lock
- name: Cache DerivedData # DerivedData caching disabled - caused intermittent build failures due to stale cache
uses: actions/cache@v4 # Pod caching still speeds up pod install significantly
with:
path: packages/mobile-sdk-demo/ios/build
key: ${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-${{ hashFiles('packages/mobile-sdk-demo/ios/Podfile.lock', 'yarn.lock') }}
restore-keys: |
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-${{ env.XCODE_VERSION }}-
${{ runner.os }}-derived-data-${{ env.GH_CACHE_VERSION }}-
- name: Verify iOS Runtime - name: Verify iOS Runtime
run: | run: |
echo "📱 Verifying iOS Runtime availability..." echo "📱 Verifying iOS Runtime availability..."
@@ -320,14 +336,15 @@ jobs:
max_attempts: 3 max_attempts: 3
retry_wait_seconds: 10 retry_wait_seconds: 10
command: | command: |
if [ -n "${SELFXYZ_INTERNAL_REPO_PAT}" ]; then if [ -n "${SELFXYZ_APP_TOKEN}" ]; then
echo "🔑 Using SELFXYZ_INTERNAL_REPO_PAT for private pod access" echo "🔑 Using GitHub App token for private pod access"
echo "::add-mask::${SELFXYZ_INTERNAL_REPO_PAT}" echo "::add-mask::${SELFXYZ_APP_TOKEN}"
fi fi
cd packages/mobile-sdk-demo/ios cd packages/mobile-sdk-demo/ios
pod install echo "📦 Installing pods via cache-fix script…"
bash scripts/pod-install-with-cache-fix.sh
env: env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
GIT_TERMINAL_PROMPT: 0 GIT_TERMINAL_PROMPT: 0
- name: Setup iOS Simulator - name: Setup iOS Simulator
run: | run: |

View File

@@ -8,11 +8,15 @@ on:
- "sdk/core/package.json" - "sdk/core/package.json"
- "sdk/qrcode/package.json" - "sdk/qrcode/package.json"
- "common/package.json" - "common/package.json"
- 'packages/mobile-sdk-alpha/package.json' - "packages/mobile-sdk-alpha/package.json"
- "sdk/qrcode-angular/package.json" - "sdk/qrcode-angular/package.json"
- "contracts/package.json" - "contracts/package.json"
workflow_dispatch: workflow_dispatch:
permissions:
id-token: write # Required for OIDC
contents: read
jobs: jobs:
detect-changes: detect-changes:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -24,7 +28,7 @@ jobs:
qrcode_angular_changed: ${{ steps.check-version.outputs.qrcode_angular_changed }} qrcode_angular_changed: ${{ steps.check-version.outputs.qrcode_angular_changed }}
msdk_changed: ${{ steps.check-version.outputs.msdk_changed }} msdk_changed: ${{ steps.check-version.outputs.msdk_changed }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 2 fetch-depth: 2
@@ -59,7 +63,7 @@ jobs:
echo "qrcode_angular_changed=true" >> $GITHUB_OUTPUT echo "qrcode_angular_changed=true" >> $GITHUB_OUTPUT
fi fi
if git diff HEAD^ HEAD -- sdk/mobile-sdk-alpha/package.json | grep -q '"version":' || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then if git diff HEAD^ HEAD -- packages/mobile-sdk-alpha/package.json | grep -q '"version":' || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "msdk_changed=true" >> $GITHUB_OUTPUT echo "msdk_changed=true" >> $GITHUB_OUTPUT
fi fi
@@ -68,7 +72,7 @@ jobs:
if: needs.detect-changes.outputs.core_changed == 'true' if: needs.detect-changes.outputs.core_changed == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -85,7 +89,6 @@ jobs:
- name: Publish to npm - name: Publish to npm
working-directory: sdk/core working-directory: sdk/core
run: | run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess public yarn config set npmPublishAccess public
yarn npm publish --access public yarn npm publish --access public
env: env:
@@ -97,7 +100,7 @@ jobs:
if: needs.detect-changes.outputs.qrcode_changed == 'true' if: needs.detect-changes.outputs.qrcode_changed == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -114,7 +117,6 @@ jobs:
- name: Publish to npm - name: Publish to npm
working-directory: sdk/qrcode working-directory: sdk/qrcode
run: | run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess public yarn config set npmPublishAccess public
yarn npm publish --access public yarn npm publish --access public
env: env:
@@ -126,13 +128,12 @@ jobs:
if: needs.detect-changes.outputs.common_changed == 'true' if: needs.detect-changes.outputs.common_changed == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: .nvmrc node-version-file: .nvmrc
registry-url: "https://registry.npmjs.org" registry-url: "https://registry.npmjs.org"
- uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
@@ -143,7 +144,6 @@ jobs:
- name: Publish to npm - name: Publish to npm
working-directory: common working-directory: common
run: | run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess public yarn config set npmPublishAccess public
yarn npm publish --access public yarn npm publish --access public
env: env:
@@ -154,13 +154,12 @@ jobs:
if: needs.detect-changes.outputs.contracts_changed == 'true' if: needs.detect-changes.outputs.contracts_changed == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: .nvmrc node-version-file: .nvmrc
registry-url: "https://registry.npmjs.org" registry-url: "https://registry.npmjs.org"
- uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Build package - name: Build package
@@ -169,7 +168,6 @@ jobs:
- name: Publish to npm - name: Publish to npm
working-directory: contracts working-directory: contracts
run: | run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess public yarn config set npmPublishAccess public
yarn npm publish --access public yarn npm publish --access public
env: env:
@@ -180,7 +178,7 @@ jobs:
if: needs.detect-changes.outputs.qrcode_angular_changed == 'true' if: needs.detect-changes.outputs.qrcode_angular_changed == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -197,7 +195,6 @@ jobs:
- name: Publish to npm - name: Publish to npm
working-directory: sdk/qrcode-angular working-directory: sdk/qrcode-angular
run: | run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess public yarn config set npmPublishAccess public
yarn npm publish --access public yarn npm publish --access public
env: env:
@@ -209,7 +206,7 @@ jobs:
if: needs.detect-changes.outputs.msdk_changed == 'true' if: needs.detect-changes.outputs.msdk_changed == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -227,7 +224,6 @@ jobs:
- name: Publish to npm - name: Publish to npm
working-directory: packages/mobile-sdk-alpha working-directory: packages/mobile-sdk-alpha
run: | run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess restricted yarn config set npmPublishAccess restricted
yarn npm publish --access restricted --tag alpha yarn npm publish --access restricted --tag alpha
env: env:

View File

@@ -25,7 +25,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -68,6 +68,7 @@ jobs:
shell: bash shell: bash
run: | run: |
yarn workspace @selfxyz/common build yarn workspace @selfxyz/common build
yarn workspace @selfxyz/sdk-common build
yarn workspace @selfxyz/qrcode build yarn workspace @selfxyz/qrcode build
- name: Cache build artifacts - name: Cache build artifacts
@@ -75,6 +76,7 @@ jobs:
with: with:
path: | path: |
common/dist common/dist
sdk/sdk-common/dist
sdk/qrcode/dist sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }} key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
@@ -83,7 +85,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -127,6 +129,7 @@ jobs:
with: with:
path: | path: |
common/dist common/dist
sdk/sdk-common/dist
sdk/qrcode/dist sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }} key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true fail-on-cache-miss: true
@@ -150,7 +153,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -194,6 +197,7 @@ jobs:
with: with:
path: | path: |
common/dist common/dist
sdk/sdk-common/dist
sdk/qrcode/dist sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }} key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true fail-on-cache-miss: true
@@ -202,6 +206,7 @@ jobs:
run: | run: |
echo "Verifying build artifacts..." echo "Verifying build artifacts..."
ls -la common/dist/ ls -la common/dist/
ls -la sdk/sdk-common/dist/
ls -la sdk/qrcode/dist/ ls -la sdk/qrcode/dist/
echo "✅ Build artifacts verified" echo "✅ Build artifacts verified"
@@ -210,7 +215,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Read and sanitize Node.js version - name: Read and sanitize Node.js version
shell: bash shell: bash
run: | run: |
@@ -254,6 +259,7 @@ jobs:
with: with:
path: | path: |
common/dist common/dist
sdk/sdk-common/dist
sdk/qrcode/dist sdk/qrcode/dist
key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }} key: qrcode-sdk-build-${{ env.GH_SDK_CACHE_VERSION }}-${{ github.sha }}
fail-on-cache-miss: true fail-on-cache-miss: true

View File

@@ -77,7 +77,7 @@ jobs:
- name: Check out repository - name: Check out repository
if: ${{ steps.guard_schedule.outputs.continue == 'true' }} if: ${{ steps.guard_schedule.outputs.continue == 'true' }}
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -256,7 +256,7 @@ jobs:
- name: Check out repository - name: Check out repository
if: ${{ steps.guard_schedule.outputs.continue == 'true' }} if: ${{ steps.guard_schedule.outputs.continue == 'true' }}
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: false if: false
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install Dependencies - name: Install Dependencies
uses: ./.github/actions/yarn-install uses: ./.github/actions/yarn-install
- name: Build dependencies - name: Build dependencies

View File

@@ -18,7 +18,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -47,7 +47,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -76,7 +76,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -106,7 +106,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -147,7 +147,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Cache Yarn dependencies - name: Cache Yarn dependencies
uses: ./.github/actions/cache-yarn uses: ./.github/actions/cache-yarn
@@ -176,7 +176,7 @@ jobs:
# permissions: # permissions:
# contents: read # contents: read
# steps: # steps:
# - uses: actions/checkout@v4 # - uses: actions/checkout@v6
# - name: Cache Yarn dependencies # - name: Cache Yarn dependencies
# uses: ./.github/actions/cache-yarn # uses: ./.github/actions/cache-yarn

5
.gitignore vendored
View File

@@ -24,3 +24,8 @@ packages/mobile-sdk-alpha/docs/docstrings-report.json
# Private Android modules (cloned at build time) # Private Android modules (cloned at build time)
app/android/android-passport-nfc-reader/ app/android/android-passport-nfc-reader/
# Foundry
contracts/out/
contracts/cache_forge/
contracts/broadcast/

File diff suppressed because it is too large Load Diff

View File

@@ -3,3 +3,8 @@
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:73 1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:73
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:74 1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:74
8bc1e85075f73906767652ab35d5563efce2a931:packages/mobile-sdk-alpha/src/animations/passport_verify.json:aws-access-token:6 8bc1e85075f73906767652ab35d5563efce2a931:packages/mobile-sdk-alpha/src/animations/passport_verify.json:aws-access-token:6
0e4555eee6589aa9cca68f451227b149277d8c90:app/tests/src/utils/points/api.test.ts:generic-api-key:34
circuits/circuits/gcp_jwt_verifier/example_jwt.txt:jwt:1
cadd7ae5b768c261230f84426eac879c1853ce70:app/ios/Podfile.lock:generic-api-key:2586
aeb8287078f088ecd8e9430e3f6a9f2c593ef1fc:app/src/utils/points/constants.ts:generic-api-key:7
app/src/services/points/constants.ts:generic-api-key:10

7
.gitmodules vendored
View File

@@ -1,3 +1,10 @@
[submodule "contracts/lib/openzeppelin-foundry-upgrades"]
path = contracts/lib/openzeppelin-foundry-upgrades
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "packages/mobile-sdk-alpha/mobile-sdk-native"] [submodule "packages/mobile-sdk-alpha/mobile-sdk-native"]
path = packages/mobile-sdk-alpha/mobile-sdk-native path = packages/mobile-sdk-alpha/mobile-sdk-native
url = git@github.com:selfxyz/mobile-sdk-native.git url = git@github.com:selfxyz/mobile-sdk-native.git

View File

@@ -1,5 +1,11 @@
nodeLinker: node-modules checksumBehavior: update
nmHoistingLimits: workspaces
enableGlobalCache: true enableGlobalCache: true
enableScripts: true enableScripts: true
checksumBehavior: "update"
nmHoistingLimits: workspaces
nodeLinker: node-modules
npmPublishAccess: public

View File

@@ -130,6 +130,20 @@ yarn types # Verify type checking
- For Noir circuits, run `nargo test -p <crate>` in each `noir/crates/*` directory. - For Noir circuits, run `nargo test -p <crate>` in each `noir/crates/*` directory.
- Tests for `@selfxyz/contracts` are currently disabled in CI and may be skipped. - Tests for `@selfxyz/contracts` are currently disabled in CI and may be skipped.
- E2E tests (mobile app) - **Run automatically in CI/CD, not required locally**:
- E2E tests execute automatically in GitHub Actions on PRs and main branch
- Local E2E testing is optional (see `app/AGENTS.md` for local setup if needed)
- Commands available: `yarn workspace @selfxyz/mobile-app test:e2e:ios` / `test:e2e:android`
#### Test Memory Optimization
**CRITICAL**: Never create nested `require('react-native')` calls in tests. This causes out-of-memory (OOM) errors in CI/CD pipelines.
- Use ES6 `import` statements instead of `require()` when possible
- Avoid dynamic `require()` calls in `beforeEach`/`afterEach` hooks
- Prefer top-level imports over nested requires
- See `.cursor/rules/test-memory-optimization.mdc` for detailed guidelines
### CI Caching ### CI Caching
Use the shared composite actions in `.github/actions` when caching dependencies in GitHub workflows. They provide consistent cache paths and keys: Use the shared composite actions in `.github/actions` when caching dependencies in GitHub workflows. They provide consistent cache paths and keys:
@@ -151,6 +165,43 @@ Each action accepts an optional `cache-version` input (often combined with `GH_C
- Write short, imperative commit messages (e.g. `Fix address validation`). - Write short, imperative commit messages (e.g. `Fix address validation`).
- The pull request body should summarize the changes and mention test results. - The pull request body should summarize the changes and mention test results.
## Workspace-Specific Instructions
Some workspaces have additional instructions in their own `AGENTS.md` files:
- `app/AGENTS.md` - Mobile app development, E2E testing, deployment
- `packages/mobile-sdk-alpha/AGENTS.md` - SDK development, testing guidelines, package validation
- `noir/AGENTS.md` - Noir circuit development
These workspace-specific files override or extend the root instructions for their respective areas.
## Troubleshooting
### Common Issues
#### Yarn Install Fails
- Ensure Node.js 22.x is installed: `nvm use`
- Clear Yarn cache: `yarn cache clean`
- Remove `node_modules` and reinstall: `rm -rf node_modules && yarn install`
#### Build Failures
- Run `yarn build:deps` in affected workspace first
- Check workspace-specific `AGENTS.md` for platform requirements
- For mobile app: ensure iOS/Android prerequisites are met (see `app/AGENTS.md`)
#### Test Failures
- Check workspace-specific test setup requirements
- For mobile app tests: ensure native modules are properly mocked
- See `.cursor/rules/test-memory-optimization.mdc` for test memory issues
#### Type Errors
- Run `yarn types` to see all type errors across workspaces
- Some packages may need to be built first: `yarn build:deps`
## Scope ## Scope
These instructions apply to the entire repository unless overridden by a nested `AGENTS.md`. These instructions apply to the entire repository unless overridden by a nested `AGENTS.md`.

View File

@@ -24,7 +24,7 @@ Currently, Self supports electronic passports, biometric ID cards following the
**Passports:** Biometric passports have the [biometric passport logo](https://en.wikipedia.org/wiki/Biometric_passport) on their front cover. **Passports:** Biometric passports have the [biometric passport logo](https://en.wikipedia.org/wiki/Biometric_passport) on their front cover.
**Aadhaar:** Indian [Aadhaar](https://en.wikipedia.org/wiki/Aadhaar) cards are supported for privacy-preserving identity verification. **Aadhaar:** Indian [Aadhaar](https://en.wikipedia.org/wiki/Aadhaar) cards are supported for privacy-preserving identity verification. Use the mAadhaar app to generate a QR code and import it into Self.
**Coverage:** Checkout our [coverage map here](http://map.self.xyz/) to see supported documents and countries. **Coverage:** Checkout our [coverage map here](http://map.self.xyz/) to see supported documents and countries.
@@ -77,6 +77,8 @@ Gitleaks will scan staged changes on each commit via `yarn gitleaks`.
## Development Documentation ## Development Documentation
> **Note:** We do not accept text-only pull request changes. While we appreciate the feedback, we will not merge external pull requests that only modify markdown files or code comments (e.g., typo fixes in documentation or comments). Pull requests must include functional code changes.
For detailed development patterns and conventions, see: For detailed development patterns and conventions, see:
- **[Development Patterns](docs/development-patterns.md)** - React Native architecture, navigation, state management, and code organization - **[Development Patterns](docs/development-patterns.md)** - React Native architecture, navigation, state management, and code organization
@@ -88,12 +90,15 @@ These guides provide comprehensive context for AI-assisted development with Chat
We are actively looking for contributors. Please check the [open issues](https://github.com/selfxyz/self/issues) if you don't know where to start! We offer bounties for significant contributions. We are actively looking for contributors. Please check the [open issues](https://github.com/selfxyz/self/issues) if you don't know where to start! We offer bounties for significant contributions.
> **Important:** Please open your pull request from the `staging` branch. Pull requests from other branches will be automatically closed. > **Important:** Please read and follow the guidelines in [contribute.md](contribute.md) when opening your pull request.
## Contact us ## Contact us
[Contact us](https://t.me/selfprotocolbuilder) on telegram for feedback or questions. - [Discord](https://discord.gg/AQ3TrX6dce) for technical support or reporting a bug.
- [Telegram's Self builder channel](https://t.me/selfprotocolbuilder) for technical questions about the sdk implementation.
- [Telegram's Self public group](https://t.me/selfxyz) for general questions and updates.
Thanks [Rémi](https://github.com/remicolin), [Florent](https://github.com/0xturboblitz), [Ayman](https://github.com/Nesopie), [Justin](https://github.com/transphorm), [Seshanth](https://github.com/seshanthS), [Nico](https://github.com/motemotech) and all other contributors for building Self. Thanks [Rémi](https://github.com/remicolin), [Florent](https://github.com/0xturboblitz), [Ayman](https://github.com/Nesopie), [Justin](https://github.com/transphorm), [Seshanth](https://github.com/seshanthS), [Nico](https://github.com/motemotech) and all other contributors for building Self.
Thanks [Aayush](https://twitter.com/yush_g), [Vivek](https://twitter.com/viv_boop), [Andy](https://twitter.com/AndyGuzmanEth) and [Vitalik](https://github.com/vbuterin) for contributing ideas and inspiring us to build this technology, and [PSE](https://pse.dev/) for supporting the initial work through grants! Thanks [Aayush](https://twitter.com/yush_g), [Vivek](https://twitter.com/viv_boop), [Andy](https://twitter.com/AndyGuzmanEth) and [Vitalik](https://github.com/vbuterin) for contributing ideas and inspiring us to build this technology, and [PSE](https://pse.dev/) for supporting the initial work through grants!

View File

@@ -165,6 +165,18 @@ module.exports = {
}, },
], ],
// Custom rule to prevent export * (bad for tree shaking)
// This rule prevents the use of export * which disables tree shaking
// and can significantly increase bundle size. Use selective exports instead.
'no-restricted-syntax': [
'error',
{
selector: 'ExportAllDeclaration',
message:
'export * is forbidden. Use selective exports for better tree shaking. Example: export { specific1, specific2 } from "./module"',
},
],
// Override rules conflicting with TypeScript union formatting // Override rules conflicting with TypeScript union formatting
'@typescript-eslint/indent': 'off', '@typescript-eslint/indent': 'off',
@@ -191,11 +203,11 @@ module.exports = {
{ {
// Disable export sorting for files with dependency issues // Disable export sorting for files with dependency issues
files: [ files: [
'src/components/NavBar/BaseNavBar.tsx', 'src/components/navbar/BaseNavBar.tsx',
'src/navigation/index.tsx', 'src/navigation/index.tsx',
'src/providers/passportDataProvider.tsx', 'src/providers/passportDataProvider.tsx',
'src/utils/cloudBackup/helpers.ts', 'src/services/cloud-backup/helpers.ts',
'src/utils/haptic/index.ts', 'src/integrations/haptics/index.ts',
], ],
rules: { rules: {
'sort-exports/sort-exports': 'off', 'sort-exports/sort-exports': 'off',
@@ -213,10 +225,54 @@ module.exports = {
rules: { rules: {
// Allow console logging and relaxed typing in tests // Allow console logging and relaxed typing in tests
'no-console': 'off', 'no-console': 'off',
// Allow require() imports in tests for mocking // Allow require() imports in tests for mocking, but block react/react-native
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-require-imports': 'off',
// Block require('react') and require('react-native') to prevent OOM issues
'no-restricted-syntax': [
'error',
{
selector:
"CallExpression[callee.name='require'][arguments.0.value='react']",
message:
"Do not use require('react') in tests. Use 'import React from \"react\"' at the top of the file to avoid out-of-memory issues in CI.",
},
{
selector:
"CallExpression[callee.name='require'][arguments.0.value='react-native']",
message:
"Do not use require('react-native') in tests. Use 'import { ... } from \"react-native\"' at the top of the file to avoid out-of-memory issues in CI.",
},
],
// Allow any types in tests for mocking // Allow any types in tests for mocking
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
// Allow test skipping without warnings
'jest/no-disabled-tests': 'off',
},
},
{
files: ['tests/**/*.js'],
env: {
jest: true,
},
rules: {
// Allow console logging in test JS files
'no-console': 'off',
// Block require('react') and require('react-native') to prevent OOM issues
'no-restricted-syntax': [
'error',
{
selector:
"CallExpression[callee.name='require'][arguments.0.value='react']",
message:
"Do not use require('react') in tests. Use 'import React from \"react\"' at the top of the file to avoid out-of-memory issues in CI.",
},
{
selector:
"CallExpression[callee.name='require'][arguments.0.value='react-native']",
message:
"Do not use require('react-native') in tests. Use 'import { ... } from \"react-native\"' at the top of the file to avoid out-of-memory issues in CI.",
},
],
}, },
}, },
{ {
@@ -226,13 +282,6 @@ module.exports = {
'no-console': 'off', 'no-console': 'off',
}, },
}, },
{
// Allow require imports for dynamic imports in proving machine
files: ['src/utils/proving/provingMachine.ts'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
{ {
// Allow require imports for conditional loading in navigation // Allow require imports for conditional loading in navigation
files: ['src/navigation/index.tsx'], files: ['src/navigation/index.tsx'],

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4

View File

@@ -18,6 +18,7 @@ Before creating a PR for the mobile app:
- [ ] `yarn nice` passes (fixes linting and formatting) - [ ] `yarn nice` passes (fixes linting and formatting)
- [ ] `yarn types` passes (TypeScript validation) - [ ] `yarn types` passes (TypeScript validation)
- [ ] `yarn test` passes (unit tests) - [ ] `yarn test` passes (unit tests)
- [ ] No nested `require('react-native')` calls in tests (causes OOM in CI) - check with `grep -r "require('react-native')" app/tests/` and verify no nested patterns
- [ ] App builds successfully on target platforms - [ ] App builds successfully on target platforms
### Mobile-Specific Validation ### Mobile-Specific Validation
@@ -25,12 +26,14 @@ Before creating a PR for the mobile app:
- [ ] Android build succeeds: `yarn android` (emulator/device) - [ ] Android build succeeds: `yarn android` (emulator/device)
- [ ] Web build succeeds: `yarn web` - [ ] Web build succeeds: `yarn web`
- [ ] No sensitive data in logs (PII, credentials, tokens) - [ ] No sensitive data in logs (PII, credentials, tokens)
- [ ] Environment variables properly configured (check `.env` setup)
- [ ] E2E tests run in CI (not required locally - CI will run E2E tests automatically)
### AI Review Preparation ### AI Review Preparation
- [ ] Complex native module changes documented - [ ] Complex native module changes documented
- [ ] Platform-specific code paths explained - [ ] Platform-specific code paths explained
- [ ] Security-sensitive operations flagged - [ ] Security-sensitive operations flagged
- [ ] Performance implications noted - [ ] Performance implications noted (including test memory patterns if tests were modified)
## Post-PR Validation ## Post-PR Validation
@@ -45,8 +48,11 @@ After PR creation:
### Mobile-Specific Checks ### Mobile-Specific Checks
- [ ] App launches without crashes - [ ] App launches without crashes
- [ ] Core functionality works on target platforms - [ ] Core functionality works on target platforms
- [ ] No memory leaks introduced - [ ] No memory leaks introduced (including test memory patterns - see Test Memory Optimization section)
- [ ] Bundle size within acceptable limits - [ ] Bundle size within acceptable limits
- [ ] No nested `require('react-native')` calls in tests (causes OOM in CI)
- [ ] Native modules work correctly (if native code was modified)
- [ ] Platform-specific code paths tested (iOS/Android/Web)
### Review Integration ### Review Integration
- [ ] Address CodeRabbitAI feedback - [ ] Address CodeRabbitAI feedback
@@ -92,4 +98,143 @@ yarn types # Verify type checking
## Running the App ## Running the App
- `yarn ios` - `yarn ios` - Run on iOS simulator (builds dependencies automatically)
- `yarn android` - Run on Android emulator/device (builds dependencies automatically)
- `yarn web` - Run web version
### Development Tips
- Use `yarn build:deps` to build all workspace dependencies before running the app
- For iOS: Ensure Xcode scheme is set to "OpenPassport" (see memory)
- For Android: Ensure emulator is running or device is connected before `yarn android`
- Metro bundler starts automatically; use `yarn start` to run it separately
## E2E Testing
The app uses Maestro for end-to-end testing. **E2E tests run automatically in CI/CD pipelines - they are not required to run locally.**
### CI/CD E2E Testing
- E2E tests run automatically in GitHub Actions workflows
- iOS and Android E2E tests run on PRs and main branch
- No local setup required - CI handles all E2E test execution
### Local E2E Testing (Optional)
If you need to run E2E tests locally for debugging:
**Prerequisites:**
- Maestro CLI installed: `curl -Ls "https://get.maestro.mobile.dev" | bash`
- iOS: Simulator running or device connected
- Android: Emulator running or device connected
- App built and installed on target device/simulator
**Running Locally:**
```bash
# iOS E2E tests
yarn test:e2e:ios
# Android E2E tests
yarn test:e2e:android
# Or use the local test script (handles setup automatically)
./scripts/test-e2e-local.sh ios
./scripts/test-e2e-local.sh android
```
**E2E Test Files:**
- iOS: `tests/e2e/launch.ios.flow.yaml`
- Android: `tests/e2e/launch.android.flow.yaml`
## Environment Variables
The app uses `react-native-dotenv` for environment configuration.
### Setup
- Create `.env` file in `app/` directory (see `.env.example` if available)
- Environment variables are loaded via `@env` import
- For secrets: Use `.env.secrets` (gitignored) for local development
- In CI: Environment variables are set in workflow files
### Common Environment Variables
- `GOOGLE_SIGNIN_ANDROID_CLIENT_ID` - Google Sign-In configuration
- Various API endpoints and keys (check `app/env.ts` for full list)
### Testing with Environment Variables
- Tests use mocked environment variables (see `jest.setup.js`)
- E2E tests use actual environment configuration
- Never commit `.env.secrets` or sensitive values
## Deployment
### Mobile Deployment
The app uses Fastlane for iOS and Android deployment.
### Deployment Commands
```bash
# Deploy both platforms (requires confirmation)
yarn mobile-deploy
# Deploy iOS only
yarn mobile-deploy:ios
# Deploy Android only
yarn mobile-deploy:android
# Force local deployment (for testing deployment scripts)
yarn mobile-local-deploy
```
### Deployment Prerequisites
- See `app/docs/MOBILE_DEPLOYMENT.md` for detailed deployment guide
- Required secrets configured in CI/CD or `.env.secrets` for local
- iOS: App Store Connect API keys, certificates, provisioning profiles
- Android: Play Store service account, keystore
### Deployment Checklist
- [ ] Version bumped in `package.json` and `app.json`
- [ ] Changelog updated
- [ ] All unit tests pass (`yarn test`)
- [ ] Build succeeds for target platform
- [ ] Required secrets/environment variables configured
- [ ] Fastlane configuration verified
- [ ] CI E2E tests pass (automatically run in CI, no local action needed)
## Test Memory Optimization
**CRITICAL**: Never create nested `require('react')` or `require('react-native')` calls in tests. This causes out-of-memory (OOM) errors in CI/CD pipelines that hide actual test failures.
### Automated Enforcement
The project has multiple layers of protection:
1. **ESLint Rule**: Blocks `require('react')` and `require('react-native')` in test files
2. **Pre-commit Script**: Run `node scripts/check-test-requires.cjs` to validate
3. **CI Fast-Fail**: GitHub Actions checks for nested requires before running tests
### Quick Check
Before committing, verify no nested requires:
```bash
# Automated check (recommended)
node scripts/check-test-requires.cjs
# Manual check
grep -r "require('react')" app/tests/
grep -r "require('react-native')" app/tests/
```
### Best Practices
- **Always use ES6 `import` statements** - Never use `require('react')` or `require('react-native')` in test files
- Put all imports at the top of the file - No dynamic imports in hooks
- Avoid `require()` calls in `beforeEach`/`afterEach` hooks
- React and React Native are already mocked in `jest.setup.js` - use imports in test files
### Detailed Guidelines
See `.cursor/rules/test-memory-optimization.mdc` for comprehensive guidelines, examples, and anti-patterns.

View File

@@ -5,9 +5,25 @@
// CI/CD Pipeline Test - July 31, 2025 - With Permissions Fix // CI/CD Pipeline Test - July 31, 2025 - With Permissions Fix
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import React from 'react'; import React from 'react';
import { Platform } from 'react-native';
import { YStack } from 'tamagui'; import { YStack } from 'tamagui';
import type {
TurnkeyCallbacks,
TurnkeyProviderConfig,
} from '@turnkey/react-native-wallet-kit';
import { TurnkeyProvider } from '@turnkey/react-native-wallet-kit';
import {
TURNKEY_AUTH_PROXY_CONFIG_ID,
TURNKEY_GOOGLE_CLIENT_ID,
TURNKEY_ORGANIZATION_ID,
} from './env';
import ErrorBoundary from './src/components/ErrorBoundary'; import ErrorBoundary from './src/components/ErrorBoundary';
import { initSentry, wrapWithSentry } from './src/config/sentry';
import {
TURNKEY_OAUTH_REDIRECT_URI_ANDROID,
TURNKEY_OAUTH_REDIRECT_URI_IOS,
} from './src/devtools/mocks';
import AppNavigation from './src/navigation'; import AppNavigation from './src/navigation';
import { AuthProvider } from './src/providers/authProvider'; import { AuthProvider } from './src/providers/authProvider';
import { DatabaseProvider } from './src/providers/databaseProvider'; import { DatabaseProvider } from './src/providers/databaseProvider';
@@ -17,12 +33,63 @@ import { NotificationTrackingProvider } from './src/providers/notificationTracki
import { PassportProvider } from './src/providers/passportDataProvider'; import { PassportProvider } from './src/providers/passportDataProvider';
import { RemoteConfigProvider } from './src/providers/remoteConfigProvider'; import { RemoteConfigProvider } from './src/providers/remoteConfigProvider';
import { SelfClientProvider } from './src/providers/selfClientProvider'; import { SelfClientProvider } from './src/providers/selfClientProvider';
import { initSentry, wrapWithSentry } from './src/Sentry';
import 'react-native-get-random-values';
import 'react-native-url-polyfill/auto';
import '@walletconnect/react-native-compat';
import '@noble/curves/p256';
import 'sha256-uint8array';
import '@turnkey/encoding';
import '@turnkey/api-key-stamper';
initSentry(); initSentry();
global.Buffer = Buffer; global.Buffer = Buffer;
export const TURNKEY_CALLBACKS: TurnkeyCallbacks = {
beforeSessionExpiry: ({ sessionKey: _sessionKey }) => {
console.log('[Turnkey] Session nearing expiry');
},
onSessionExpired: ({ sessionKey: _sessionKey }) => {
console.log('[Turnkey] Session expired');
},
onAuthenticationSuccess: ({
action: _action,
method: _method,
identifier: _identifier,
}) => {
// console.log('[Turnkey] Auth success:', { action, method, identifier });
},
onError: error => {
console.error('[Turnkey] Error:', error);
},
};
export const TURNKEY_CONFIG: TurnkeyProviderConfig = {
organizationId: TURNKEY_ORGANIZATION_ID!,
authProxyConfigId: TURNKEY_AUTH_PROXY_CONFIG_ID!,
autoRefreshManagedState: false,
auth: {
passkey: false,
oauth: {
// Should use custom scheme, NOT 'https' for IOS
appScheme:
Platform.OS === 'ios' ? 'com.warroom.proofofpassport' : 'https',
redirectUri:
Platform.OS === 'ios'
? TURNKEY_OAUTH_REDIRECT_URI_IOS
: TURNKEY_OAUTH_REDIRECT_URI_ANDROID,
google: {
clientId: TURNKEY_GOOGLE_CLIENT_ID!,
redirectUri:
Platform.OS === 'ios'
? TURNKEY_OAUTH_REDIRECT_URI_IOS
: TURNKEY_OAUTH_REDIRECT_URI_ANDROID,
},
},
},
};
function App(): React.JSX.Element { function App(): React.JSX.Element {
return ( return (
<ErrorBoundary> <ErrorBoundary>
@@ -35,7 +102,12 @@ function App(): React.JSX.Element {
<DatabaseProvider> <DatabaseProvider>
<NotificationTrackingProvider> <NotificationTrackingProvider>
<FeedbackProvider> <FeedbackProvider>
<AppNavigation /> <TurnkeyProvider
config={TURNKEY_CONFIG}
callbacks={TURNKEY_CALLBACKS}
>
<AppNavigation />
</TurnkeyProvider>
</FeedbackProvider> </FeedbackProvider>
</NotificationTrackingProvider> </NotificationTrackingProvider>
</DatabaseProvider> </DatabaseProvider>

View File

@@ -1,10 +1,7 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (3.0.7) CFPropertyList (3.0.8)
base64
nkf
rexml
activesupport (7.2.3) activesupport (7.2.3)
base64 base64
benchmark (>= 0.3) benchmark (>= 0.3)
@@ -17,16 +14,16 @@ GEM
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7) addressable (2.8.8)
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 8.0)
algoliasearch (1.27.5) algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3) httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1) json (>= 1.5.1)
artifactory (3.0.17) artifactory (3.0.17)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.4.0) aws-eventstream (1.4.0)
aws-partitions (1.1178.0) aws-partitions (1.1194.0)
aws-sdk-core (3.235.0) aws-sdk-core (3.239.2)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.9)
@@ -34,10 +31,10 @@ GEM
bigdecimal bigdecimal
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
logger logger
aws-sdk-kms (1.115.0) aws-sdk-kms (1.118.0)
aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-core (~> 3, >= 3.239.1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.202.0) aws-sdk-s3 (1.206.0)
aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
@@ -89,8 +86,8 @@ GEM
colored2 (3.1.2) colored2 (3.1.2)
commander (4.6.0) commander (4.6.0)
highline (~> 2.0.0) highline (~> 2.0.0)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.6)
connection_pool (2.5.4) connection_pool (3.0.2)
declarative (0.0.20) declarative (0.0.20)
digest-crc (0.7.0) digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0) rake (>= 12.0.0, < 14.0.0)
@@ -114,9 +111,9 @@ GEM
faraday-rack (~> 1.0) faraday-rack (~> 1.0)
faraday-retry (~> 1.0) faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7) faraday-cookie_jar (0.0.8)
faraday (>= 0.8.0) faraday (>= 0.8.0)
http-cookie (~> 1.0.0) http-cookie (>= 1.0.0)
faraday-em_http (1.0.0) faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.1) faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0) faraday-excon (1.1.0)
@@ -225,27 +222,26 @@ GEM
i18n (1.14.7) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.15.2) json (2.18.0)
jwt (2.10.2) jwt (2.10.2)
base64 base64
logger (1.7.0) logger (1.7.0)
mini_magick (4.13.2) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9) mini_portile2 (2.8.9)
minitest (5.26.0) minitest (5.27.0)
molinillo (0.8.0) molinillo (0.8.0)
multi_json (1.17.0) multi_json (1.18.0)
multipart-post (2.4.1) multipart-post (2.4.1)
mutex_m (0.3.0) mutex_m (0.3.0)
nanaimo (0.4.0) nanaimo (0.4.0)
nap (1.1.0) nap (1.1.0)
naturally (2.3.0) naturally (2.3.0)
netrc (0.11.0) netrc (0.11.0)
nkf (0.2.0)
nokogiri (1.18.10) nokogiri (1.18.10)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
optparse (0.6.0) optparse (0.8.1)
os (1.1.4) os (1.1.4)
plist (3.7.2) plist (3.7.2)
public_suffix (4.0.7) public_suffix (4.0.7)

View File

@@ -1,4 +1,4 @@
# OpenPassport App # Self.xyz Mobile App
## Requirements ## Requirements

View File

@@ -1 +1,8 @@
build build
# Private modules cloned dynamically by setup-private-modules.cjs
android-passport-nfc-reader/
react-native-passport-reader/
# Temporary credential helper scripts created during CI setup
.git-credential-helper-*.sh

View File

@@ -125,12 +125,17 @@ android {
preDexLibraries false preDexLibraries false
} }
buildFeatures {
buildConfig = true
viewBinding = true
}
defaultConfig { defaultConfig {
applicationId "com.proofofpassportapp" applicationId "com.proofofpassportapp"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 113 versionCode 121
versionName "2.7.3" versionName "2.9.5"
manifestPlaceholders = [appAuthRedirectScheme: 'com.proofofpassportapp'] manifestPlaceholders = [appAuthRedirectScheme: 'com.proofofpassportapp']
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@@ -3,6 +3,13 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
> >
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="whatsapp" />
</intent>
</queries>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />

Binary file not shown.

View File

@@ -4,11 +4,11 @@ buildscript {
ext { ext {
buildToolsVersion = "35.0.0" buildToolsVersion = "35.0.0"
minSdkVersion = 24 minSdkVersion = 24
compileSdkVersion = 35 compileSdkVersion = 36
targetSdkVersion = 35 targetSdkVersion = 36
// Updated NDK to support 16k page size devices // Updated NDK to support 16k page size devices
ndkVersion = "27.0.12077973" ndkVersion = "27.0.12077973"
kotlinVersion = "1.9.24" kotlinVersion = "2.0.0"
firebaseMessagingVersion = "23.4.0" firebaseMessagingVersion = "23.4.0"
firebaseBomVersion = "32.7.3" firebaseBomVersion = "32.7.3"
} }

View File

@@ -1,2 +0,0 @@
node_modules/
android/src/main/jniLibs/arm64-v8a/libark_circom_passport.so

View File

@@ -1,15 +0,0 @@
## License
Apache License, Version 2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,61 +0,0 @@
# react-native-passport-reader
Adapted from [passport-reader](https://github.com/tananaev/passport-reader). Individual modifications are too many to enumerate, but essentially: the workflow code was adapted to the needs of a React Native module, and the scanning code was largely left as is.
## Getting started
```sh
$ npm install react-native-passport-reader --save
$ react-native link react-native-passport-reader
```
In your `android/app/build.gradle` add `packagingOptions`:
```
android {
...
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
}
}
```
In `AndroidManifest.xml` add:
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
```
If your app will not function without nfc capabilities, set `android:required` above to `true`
## Usage
```js
import PassportReader from 'react-native-passport-reader'
// { scan, cancel, isSupported }
async function scan () {
// 1. start a scan
// 2. press the back of your android phone against the passport
// 3. wait for the scan(...) Promise to get resolved/rejected
const {
firstName,
lastName,
gender,
issuer,
nationality,
photo
} = await PassportReader.scan({
// yes, you need to know a bunch of data up front
// this is data you can get from reading the MRZ zone of the passport
documentNumber: 'ofDocumentBeingScanned',
dateOfBirth: 'yyMMdd',
dateOfExpiry: 'yyMMdd'
})
const { base64, width, height } = photo
}
```

View File

@@ -1,48 +0,0 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "io.tradle.nfc"
// Use NDK that supports 16k page size
ndkVersion = "27.0.12077973"
compileSdkVersion 35
defaultConfig {
minSdkVersion 21
targetSdkVersion 35
versionCode 1
versionName "1.0"
multiDexEnabled = true
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
}
lintOptions {
warning 'InvalidPackage'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation 'com.google.code.gson:gson:2.8.9' // Check for the latest version
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'com.wdullaer:materialdatetimepicker:3.5.2'
implementation 'org.jmrtd:jmrtd:0.8.1'
implementation 'net.sf.scuba:scuba-sc-android:0.0.18'
implementation 'com.gemalto.jp2:jp2-android:1.0.3'
implementation 'com.github.mhshams:jnbis:1.1.0'
implementation 'commons-io:commons-io:2.8.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.facebook.react:react-native:+'
implementation "io.sentry:sentry-android:8.20.0"
}

View File

@@ -1,5 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
</manifest>

View File

@@ -1,114 +0,0 @@
package io.tradle.nfc
import net.sf.scuba.smartcards.APDUEvent
import net.sf.scuba.smartcards.APDUListener
import net.sf.scuba.smartcards.CommandAPDU
import net.sf.scuba.smartcards.ResponseAPDU
import org.jmrtd.WrappedAPDUEvent
import android.util.Log
class APDULogger : APDUListener {
private var moduleReference: RNPassportReaderModule? = null
private val sessionContext = mutableMapOf<String, Any>()
fun setModuleReference(module: RNPassportReaderModule) {
moduleReference = module
}
fun setContext(key: String, value: Any) {
sessionContext[key] = value
}
fun clearContext() {
sessionContext.clear()
}
override fun exchangedAPDU(event: APDUEvent) {
try {
val entry = createLogEntry(event)
logToAnalytics(entry)
} catch (e: Exception) {
Log.e("APDULogger", "Error exchanging APDU", e)
}
}
private fun createLogEntry(event: APDUEvent): APDULogEntry {
val command = event.commandAPDU
val response = event.responseAPDU
val timestamp = System.currentTimeMillis()
val entry = APDULogEntry(
timestamp = timestamp,
commandHex = command.bytes.toHexString(),
responseHex = response.bytes.toHexString(),
statusWord = response.sw,
statusWordHex = "0x${response.sw.toString(16).uppercase().padStart(4, '0')}",
commandLength = command.bytes.size,
responseLength = response.bytes.size,
dataLength = response.data.size,
isWrapped = event is WrappedAPDUEvent,
plainCommandHex = if (event is WrappedAPDUEvent) event.plainTextCommandAPDU.bytes.toHexString() else null,
plainResponseHex = if (event is WrappedAPDUEvent) event.plainTextResponseAPDU.bytes.toHexString() else null,
plainCommandLength = if (event is WrappedAPDUEvent) event.plainTextCommandAPDU.bytes.size else null,
plainResponseLength = if (event is WrappedAPDUEvent) event.plainTextResponseAPDU.bytes.size else null,
plainDataLength = if (event is WrappedAPDUEvent) event.plainTextResponseAPDU.data.size else null,
context = sessionContext.toMap()
)
return entry
}
private fun ByteArray.toHexString(): String {
return joinToString("") { "%02X".format(it) }
}
private fun logToAnalytics(entry: APDULogEntry) {
try {
val params = mutableMapOf<String, Any>().apply {
put("timestamp", entry.timestamp)
put("command_hex", entry.commandHex)
put("response_hex", entry.responseHex)
put("status_word", entry.statusWord)
put("status_word_hex", entry.statusWordHex)
put("command_length", entry.commandLength)
put("response_length", entry.responseLength)
put("data_length", entry.dataLength)
put("is_wrapped", entry.isWrapped)
put("context", entry.context)
entry.plainCommandHex?.let { put("plain_command_hex", it) }
entry.plainResponseHex?.let { put("plain_response_hex", it) }
entry.plainCommandLength?.let { put("plain_command_length", it) }
entry.plainResponseLength?.let { put("plain_response_length", it) }
entry.plainDataLength?.let { put("plain_data_length", it) }
}
moduleReference?.logAnalyticsEvent("nfc_apdu_exchange", params)
} catch (e: Exception) {
Log.e("APDULogger", "Error logging to analytics", e)
}
}
}
data class APDULogEntry(
val timestamp: Long,
val commandHex: String,
val responseHex: String,
val statusWord: Int,
val statusWordHex: String,
val commandLength: Int,
val responseLength: Int,
val dataLength: Int,
val isWrapped: Boolean,
val plainCommandHex: String?,
val plainResponseHex: String?,
val plainCommandLength: Int?,
val plainResponseLength: Int?,
val plainDataLength: Int?,
val context: Map<String, Any>
)

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2016 - 2022 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.tradle.nfc
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.gemalto.jp2.JP2Decoder
import org.jnbis.WsqDecoder
import java.io.InputStream
object ImageUtil {
fun decodeImage(context: Context?, mimeType: String, inputStream: InputStream?): Bitmap {
return if (mimeType.equals("image/jp2", ignoreCase = true) || mimeType.equals(
"image/jpeg2000",
ignoreCase = true
)
) {
JP2Decoder(inputStream).decode()
} else if (mimeType.equals("image/x-wsq", ignoreCase = true)) {
val wsqDecoder = WsqDecoder()
val bitmap = wsqDecoder.decode(inputStream)
val byteData = bitmap.pixels
val intData = IntArray(byteData.size)
for (j in byteData.indices) {
intData[j] = 0xFF000000.toInt() or
(byteData[j].toInt() and 0xFF shl 16) or
(byteData[j].toInt() and 0xFF shl 8) or
(byteData[j].toInt() and 0xFF)
}
Bitmap.createBitmap(
intData,
0,
bitmap.width,
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
} else {
BitmapFactory.decodeStream(inputStream)
}
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.tradle.nfc
import io.tradle.nfc.RNPassportReaderModule
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class RNPassportReaderPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(RNPassportReaderModule(reactContext))
}
// No need to override createJSModules method as it's removed in newer versions of React Native
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}

View File

@@ -1,30 +0,0 @@
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
import { NativeModules } from 'react-native'
const { RNPassportReader } = NativeModules
const DATE_REGEX = /^\d{6}$/
module.exports = {
...RNPassportReader,
scan,
reset: RNPassportReader.reset
}
function scan({ documentNumber, dateOfBirth, dateOfExpiry, canNumber, useCan, quality=1 }) {
assert(typeof documentNumber === 'string', 'expected string "documentNumber"')
assert(isDate(dateOfBirth), 'expected string "dateOfBirth" in format "yyMMdd"')
assert(isDate(dateOfExpiry), 'expected string "dateOfExpiry" in format "yyMMdd"')
return RNPassportReader.scan({ documentNumber, dateOfBirth, dateOfExpiry, quality, useCan, canNumber })
}
function assert (statement, err) {
if (!statement) {
throw new Error(err || 'Assertion failed')
}
}
function isDate (str) {
return typeof str === 'string' && DATE_REGEX.test(str)
}

View File

@@ -1,15 +0,0 @@
{
"name": "react-native-passport-reader",
"version": "1.0.3",
"description": "read the NFC chip in a passport",
"keywords": [
"react-native",
"react-component",
"nfc",
"android",
"scanner"
],
"license": "APLv2",
"author": "Mark Vayngrib <mark@tradle.io> (http://github.com/mvayngrib)",
"main": "index.android.js"
}

View File

@@ -13,6 +13,7 @@ module.exports = {
}, },
], ],
['@babel/plugin-transform-private-methods', { loose: true }], ['@babel/plugin-transform-private-methods', { loose: true }],
'@babel/plugin-transform-export-namespace-from',
[ [
'module:react-native-dotenv', 'module:react-native-dotenv',
{ {
@@ -25,4 +26,9 @@ module.exports = {
}, },
], ],
], ],
env: {
production: {
plugins: ['transform-remove-console'],
},
},
}; };

71
app/babel.config.test.cjs Normal file
View File

@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
// Babel config for Jest tests that excludes hermes-parser to avoid WebAssembly issues
// Based on React Native babel preset but with hermes parser plugin removed
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
'@babel/preset-typescript',
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
],
plugins: [
// Module resolver for @ alias
[
'module-resolver',
{
root: ['./src'],
alias: { '@': './src' },
},
],
// Core React Native transforms (minimal set needed for tests)
['@babel/plugin-transform-class-properties', { loose: true }],
['@babel/plugin-transform-classes', { loose: true }],
['@babel/plugin-transform-private-methods', { loose: true }],
['@babel/plugin-transform-private-property-in-object', { loose: true }],
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-export-default-from',
'@babel/plugin-transform-export-namespace-from',
'@babel/plugin-transform-unicode-regex',
['@babel/plugin-transform-destructuring', { useBuiltIns: true }],
'@babel/plugin-transform-spread',
[
'@babel/plugin-transform-object-rest-spread',
{ loose: true, useBuiltIns: true },
],
['@babel/plugin-transform-optional-chaining', { loose: true }],
['@babel/plugin-transform-nullish-coalescing-operator', { loose: true }],
['@babel/plugin-transform-logical-assignment-operators', { loose: true }],
// Flow type stripping to support React Native's Flow-based sources
['@babel/plugin-syntax-flow'],
['@babel/plugin-transform-flow-strip-types', { allowDeclareFields: true }],
// Environment variable support
[
'module:react-native-dotenv',
{
moduleName: '@env',
path: '.env',
blacklist: null,
whitelist: null,
safe: false,
allowUndefined: true,
},
],
],
};

View File

@@ -1,5 +1,4 @@
ENABLE_DEBUG_LOGS= ENABLE_DEBUG_LOGS=
GITGUARDIAN_API_KEY=
GITLEAKS_LICENSE= GITLEAKS_LICENSE=
GOOGLE_SIGNIN_ANDROID_CLIENT_ID= GOOGLE_SIGNIN_ANDROID_CLIENT_ID=
GRAFANA_LOKI_URL= GRAFANA_LOKI_URL=

View File

@@ -28,3 +28,9 @@ export const IS_TEST_BUILD = process.env.IS_TEST_BUILD === 'true';
export const MIXPANEL_NFC_PROJECT_TOKEN = undefined; export const MIXPANEL_NFC_PROJECT_TOKEN = undefined;
export const SEGMENT_KEY = process.env.SEGMENT_KEY; export const SEGMENT_KEY = process.env.SEGMENT_KEY;
export const SENTRY_DSN = process.env.SENTRY_DSN; export const SENTRY_DSN = process.env.SENTRY_DSN;
export const TURNKEY_AUTH_PROXY_CONFIG_ID =
process.env.TURNKEY_AUTH_PROXY_CONFIG_ID;
export const TURNKEY_GOOGLE_CLIENT_ID = process.env.TURNKEY_GOOGLE_CLIENT_ID;
export const TURNKEY_ORGANIZATION_ID = process.env.TURNKEY_ORGANIZATION_ID;

View File

@@ -306,6 +306,14 @@ platform :android do
skip_upload = options[:skip_upload] == true || options[:skip_upload] == "true" skip_upload = options[:skip_upload] == true || options[:skip_upload] == "true"
version_bump = options[:version_bump] || "build" version_bump = options[:version_bump] || "build"
# Automatically skip uploads in local development (no local permissions for Play Store)
# Uploads must be done by CI/CD machines with proper authentication
if local_development && !skip_upload
skip_upload = true
UI.important("🏠 LOCAL DEVELOPMENT: Automatically skipping Play Store upload")
UI.important(" Uploads require CI/CD machine permissions and will be handled automatically")
end
if local_development if local_development
if ENV["ANDROID_KEYSTORE_PATH"].nil? if ENV["ANDROID_KEYSTORE_PATH"].nil?
ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path) ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path)

View File

@@ -21,7 +21,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios sync_version [bundle exec] fastlane ios sync_version
``` ```
Sync ios version Sync ios version (DEPRECATED)
### ios internal_test ### ios internal_test
@@ -50,7 +50,7 @@ Deploy iOS app with automatic version management
[bundle exec] fastlane android sync_version [bundle exec] fastlane android sync_version
``` ```
Sync android version Sync android version (DEPRECATED)
### android internal_test ### android internal_test

View File

@@ -19,7 +19,7 @@ import App from './App';
import { name as appName } from './app.json'; import { name as appName } from './app.json';
import tamaguiConfig from './tamagui.config'; import tamaguiConfig from './tamagui.config';
import './src/utils/ethers'; import './src/utils/crypto/ethers';
import 'react-native-gesture-handler'; import 'react-native-gesture-handler';
// Set global Buffer before any other imports // Set global Buffer before any other imports

View File

@@ -19,23 +19,10 @@
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self; center.delegate = self;
// Request permission for notifications // NOTE: Notification permission request removed from app launch
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) // Permission is now requested only when user explicitly enables notifications
completionHandler:^(BOOL granted, NSError * _Nullable error) { // (e.g., in Points screen or settings)
if (error) { // The auto-request was causing unwanted permission prompts on first app launch
NSLog(@"Failed to request notification authorization: %@", error.localizedDescription);
return;
}
if (granted) {
NSLog(@"Notification authorization granted");
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] registerForRemoteNotifications];
});
} else {
NSLog(@"Notification authorization denied by user");
}
}];
} }
self.moduleName = @"OpenPassport"; self.moduleName = @"OpenPassport";

View File

@@ -21,7 +21,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.7.3</string> <string>2.9.5</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -30,6 +30,7 @@
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>proofofpassport</string> <string>proofofpassport</string>
<string>com.warroom.proofofpassport</string>
</array> </array>
</dict> </dict>
</array> </array>
@@ -39,6 +40,18 @@
<false/> <false/>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string></string> <string></string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>argent</string>
<string>cbwallet</string>
<string>coinbase</string>
<string>metamask</string>
<string>rainbow</string>
<string>sms</string>
<string>trust</string>
<string>wc</string>
<string>whatsapp</string>
</array>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NFCReaderUsageDescription</key> <key>NFCReaderUsageDescription</key>
@@ -53,7 +66,7 @@
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Needed to scan the passport MRZ.</string> <string>Needed to scan the passport MRZ.</string>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>Needed to secure the secret</string> <string>Personal information is only stored in the secure element of your device.</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string></string> <string></string>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
@@ -63,6 +76,7 @@
<key>UIAppFonts</key> <key>UIAppFonts</key>
<array> <array>
<string>Advercase-Regular.otf</string> <string>Advercase-Regular.otf</string>
<string>DINOT-Bold.otf</string>
<string>DINOT-Medium.otf</string> <string>DINOT-Medium.otf</string>
<string>IBMPlexMono-Regular.otf</string> <string>IBMPlexMono-Regular.otf</string>
</array> </array>

View File

@@ -15,6 +15,7 @@
<string>appclips:appclip.openpassport.app</string> <string>appclips:appclip.openpassport.app</string>
<string>applinks:proofofpassport-merkle-tree.xyz</string> <string>applinks:proofofpassport-merkle-tree.xyz</string>
<string>applinks:redirect.self.xyz</string> <string>applinks:redirect.self.xyz</string>
<string>webcredentials:redirect.self.xyz</string>
</array> </array>
<key>com.apple.developer.icloud-container-identifiers</key> <key>com.apple.developer.icloud-container-identifiers</key>
<array> <array>

View File

@@ -15,6 +15,9 @@
<string>appclips:appclip.openpassport.app</string> <string>appclips:appclip.openpassport.app</string>
<string>applinks:proofofpassport-merkle-tree.xyz</string> <string>applinks:proofofpassport-merkle-tree.xyz</string>
<string>applinks:redirect.self.xyz</string> <string>applinks:redirect.self.xyz</string>
<string>webcredentials:redirect.self.xyz</string>
<string>applinks:oauth-redirect.turnkey.com</string>
<string>webcredentials:oauth-redirect.turnkey.com</string>
</array> </array>
<key>com.apple.developer.icloud-container-identifiers</key> <key>com.apple.developer.icloud-container-identifiers</key>
<array> <array>

View File

@@ -33,7 +33,7 @@ def using_https_git_auth?
auth_data.include?("Logged in to github.com account") && auth_data.include?("Logged in to github.com account") &&
auth_data.include?("Git operations protocol: https") auth_data.include?("Git operations protocol: https")
rescue => e rescue => e
puts "gh auth status failed, assuming no HTTPS auth -- will try SSH" # Avoid printing auth-related details in CI logs.
false false
end end
end end
@@ -51,18 +51,16 @@ target "Self" do
# External fork - use public NFCPassportReader repository (placeholder) # External fork - use public NFCPassportReader repository (placeholder)
# TODO: Replace with actual public NFCPassportReader repository URL # TODO: Replace with actual public NFCPassportReader repository URL
nfc_repo_url = "https://github.com/PLACEHOLDER/NFCPassportReader.git" nfc_repo_url = "https://github.com/PLACEHOLDER/NFCPassportReader.git"
puts "📦 Using public NFCPassportReader for external fork (#{ENV["GITHUB_REPOSITORY"]})" elsif ENV["GITHUB_ACTIONS"] == "true"
elsif ENV["GITHUB_ACTIONS"] == "true" && ENV["SELFXYZ_INTERNAL_REPO_PAT"] # CI: NEVER embed credentials in URLs. Rely on workflow-provided auth via:
# Running in selfxyz GitHub Actions with PAT available - use private repo with token # - ~/.netrc or a Git credential helper, and token masking in logs.
nfc_repo_url = "https://#{ENV["SELFXYZ_INTERNAL_REPO_PAT"]}@github.com/selfxyz/NFCPassportReader.git" nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
puts "📦 Using private NFCPassportReader with PAT (selfxyz GitHub Actions)"
elsif using_https_git_auth? elsif using_https_git_auth?
# Local development with HTTPS GitHub auth via gh - use HTTPS to private repo # Local development with HTTPS GitHub auth via gh - use HTTPS to private repo
nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git" nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
else else
# Local development in selfxyz repo - use SSH to private repo # Local development in selfxyz repo - use SSH to private repo
nfc_repo_url = "git@github.com:selfxyz/NFCPassportReader.git" nfc_repo_url = "git@github.com:selfxyz/NFCPassportReader.git"
puts "📦 Using SSH for private NFCPassportReader (local selfxyz development)"
end end
pod "NFCPassportReader", git: nfc_repo_url, commit: "9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b" pod "NFCPassportReader", git: nfc_repo_url, commit: "9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b"

View File

@@ -1465,7 +1465,7 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- react-native-app-auth (8.0.3): - react-native-app-auth (8.1.0):
- AppAuth (>= 1.7.6) - AppAuth (>= 1.7.6)
- React-Core - React-Core
- react-native-biometrics (3.0.1): - react-native-biometrics (3.0.1):
@@ -1512,36 +1512,34 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- react-native-compat (2.23.0):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.10.14.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-get-random-values (1.11.0): - react-native-get-random-values (1.11.0):
- React-Core - React-Core
- react-native-netinfo (11.4.1): - react-native-netinfo (11.4.1):
- React-Core - React-Core
- react-native-nfc-manager (3.16.3): - react-native-nfc-manager (3.16.3):
- React-Core - React-Core
- react-native-safe-area-context (5.6.1): - react-native-passkey (3.3.1):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.10.14.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- react-native-safe-area-context/common (= 5.6.1)
- react-native-safe-area-context/fabric (= 5.6.1)
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context/common (5.6.1):
- DoubleConversion - DoubleConversion
- glog - glog
- hermes-engine - hermes-engine
@@ -1562,7 +1560,51 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- react-native-safe-area-context/fabric (5.6.1): - react-native-safe-area-context (5.6.2):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.10.14.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- react-native-safe-area-context/common (= 5.6.2)
- react-native-safe-area-context/fabric (= 5.6.2)
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context/common (5.6.2):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.10.14.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-safe-area-context/fabric (5.6.2):
- DoubleConversion - DoubleConversion
- glog - glog
- hermes-engine - hermes-engine
@@ -1956,6 +1998,8 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- RNInAppBrowser (3.7.0):
- React-Core
- RNKeychain (10.0.0): - RNKeychain (10.0.0):
- DoubleConversion - DoubleConversion
- glog - glog
@@ -1977,7 +2021,7 @@ PODS:
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Yoga - Yoga
- RNLocalize (3.5.3): - RNLocalize (3.6.0):
- DoubleConversion - DoubleConversion
- glog - glog
- hermes-engine - hermes-engine
@@ -2087,7 +2131,7 @@ PODS:
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- Sentry/HybridSDK (= 8.53.2) - Sentry/HybridSDK (= 8.53.2)
- Yoga - Yoga
- RNSVG (15.12.1): - RNSVG (15.15.0):
- DoubleConversion - DoubleConversion
- glog - glog
- hermes-engine - hermes-engine
@@ -2107,9 +2151,9 @@ PODS:
- ReactCodegen - ReactCodegen
- ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- RNSVG/common (= 15.12.1) - RNSVG/common (= 15.15.0)
- Yoga - Yoga
- RNSVG/common (15.12.1): - RNSVG/common (15.15.0):
- DoubleConversion - DoubleConversion
- glog - glog
- hermes-engine - hermes-engine
@@ -2194,9 +2238,11 @@ DEPENDENCIES:
- react-native-biometrics (from `../node_modules/react-native-biometrics`) - react-native-biometrics (from `../node_modules/react-native-biometrics`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)" - "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-cloud-storage (from `../node_modules/react-native-cloud-storage`) - react-native-cloud-storage (from `../node_modules/react-native-cloud-storage`)
- "react-native-compat (from `../node_modules/@walletconnect/react-native-compat`)"
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`) - react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`) - react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`)
- react-native-passkey (from `../node_modules/react-native-passkey`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`) - react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- react-native-webview (from `../node_modules/react-native-webview`) - react-native-webview (from `../node_modules/react-native-webview`)
@@ -2234,6 +2280,7 @@ DEPENDENCIES:
- "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)" - "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)"
- "RNFBRemoteConfig (from `../node_modules/@react-native-firebase/remote-config`)" - "RNFBRemoteConfig (from `../node_modules/@react-native-firebase/remote-config`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`)
- RNKeychain (from `../node_modules/react-native-keychain`) - RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`) - RNLocalize (from `../node_modules/react-native-localize`)
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`) - RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
@@ -2360,12 +2407,16 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/blur" :path: "../node_modules/@react-native-community/blur"
react-native-cloud-storage: react-native-cloud-storage:
:path: "../node_modules/react-native-cloud-storage" :path: "../node_modules/react-native-cloud-storage"
react-native-compat:
:path: "../node_modules/@walletconnect/react-native-compat"
react-native-get-random-values: react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values" :path: "../node_modules/react-native-get-random-values"
react-native-netinfo: react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo" :path: "../node_modules/@react-native-community/netinfo"
react-native-nfc-manager: react-native-nfc-manager:
:path: "../node_modules/react-native-nfc-manager" :path: "../node_modules/react-native-nfc-manager"
react-native-passkey:
:path: "../node_modules/react-native-passkey"
react-native-safe-area-context: react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context" :path: "../node_modules/react-native-safe-area-context"
react-native-sqlite-storage: react-native-sqlite-storage:
@@ -2440,6 +2491,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/remote-config" :path: "../node_modules/@react-native-firebase/remote-config"
RNGestureHandler: RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler" :path: "../node_modules/react-native-gesture-handler"
RNInAppBrowser:
:path: "../node_modules/react-native-inappbrowser-reborn"
RNKeychain: RNKeychain:
:path: "../node_modules/react-native-keychain" :path: "../node_modules/react-native-keychain"
RNLocalize: RNLocalize:
@@ -2530,14 +2583,16 @@ SPEC CHECKSUMS:
React-logger: c4052eb941cca9a097ef01b59543a656dc088559 React-logger: c4052eb941cca9a097ef01b59543a656dc088559
React-Mapbuffer: 9343a5c14536d4463c80f09a960653d754daae21 React-Mapbuffer: 9343a5c14536d4463c80f09a960653d754daae21
React-microtasksnativemodule: c7cafd8f4470cf8a4578ee605daa4c74d3278bf8 React-microtasksnativemodule: c7cafd8f4470cf8a4578ee605daa4c74d3278bf8
react-native-app-auth: eb42594042a25455119a8c57194b4fd25b9352f4 react-native-app-auth: e21c8ee920876b960e38c9381971bd189ebea06b
react-native-biometrics: 43ed5b828646a7862dbc7945556446be00798e7d react-native-biometrics: 43ed5b828646a7862dbc7945556446be00798e7d
react-native-blur: 6334d934a9b5e67718b8f5725c44cc0a12946009 react-native-blur: 6334d934a9b5e67718b8f5725c44cc0a12946009
react-native-cloud-storage: 8d89f2bc574cf11068dfd90933905974087fb9e9 react-native-cloud-storage: 8d89f2bc574cf11068dfd90933905974087fb9e9
react-native-compat: 44e82a19b6130e3965d6c8ff37dbc1546d477f0f
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7 react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7
react-native-safe-area-context: 90a89cb349c7f8168a707e6452288c2f665b9fd1 react-native-passkey: 8853c3c635164864da68a6dbbcec7148506c3bcf
react-native-safe-area-context: a7aad44fe544b55e2369a3086e16a01be60ce398
react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed
react-native-webview: 3f45e19f0ffc3701168768a6c37695e0f252410e react-native-webview: 3f45e19f0ffc3701168768a6c37695e0f252410e
React-nativeconfig: 415626a63057638759bcc75e0a96e2e07771a479 React-nativeconfig: 415626a63057638759bcc75e0a96e2e07771a479
@@ -2574,12 +2629,13 @@ SPEC CHECKSUMS:
RNFBMessaging: 92325b0d5619ac90ef023a23cfd16fd3b91d0a88 RNFBMessaging: 92325b0d5619ac90ef023a23cfd16fd3b91d0a88
RNFBRemoteConfig: a569bacaa410acfcaba769370e53a787f80fd13b RNFBRemoteConfig: a569bacaa410acfcaba769370e53a787f80fd13b
RNGestureHandler: a63b531307e5b2e6ea21d053a1a7ad4cf9695c57 RNGestureHandler: a63b531307e5b2e6ea21d053a1a7ad4cf9695c57
RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20
RNKeychain: 471ceef8c13f15a5534c3cd2674dbbd9d0680e52 RNKeychain: 471ceef8c13f15a5534c3cd2674dbbd9d0680e52
RNLocalize: 7683e450496a5aea9a2dab3745bfefa7341d3f5e RNLocalize: 4f5e4a46d2bccd04ccb96721e438dcb9de17c2e0
RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73 RNReactNativeHapticFeedback: e526ac4a7ca9fb23c7843ea4fd7d823166054c73
RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8 RNScreens: 806e1449a8ec63c2a4e4cf8a63cc80203ccda9b8
RNSentry: 6ad982be2c8e32dab912afb4132b6a0d88484ea0 RNSentry: 6ad982be2c8e32dab912afb4132b6a0d88484ea0
RNSVG: 0c1fc3e7b147949dc15644845e9124947ac8c9bb RNSVG: 39476f26bbbe72ffe6194c6fc8f6acd588087957
segment-analytics-react-native: a0c29c75ede1989118b50cac96b9495ea5c91a1d segment-analytics-react-native: a0c29c75ede1989118b50cac96b9495ea5c91a1d
Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
@@ -2588,6 +2644,6 @@ SPEC CHECKSUMS:
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb
Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407 Yoga: 1259c7a8cbaccf7b4c3ddf8ee36ca11be9dee407
PODFILE CHECKSUM: b5f11f935be22fce84c5395aaa203b50427a79aa PODFILE CHECKSUM: 0aa47f53692543349c43673cda7380fa23049eba
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -41,6 +41,7 @@
E9F9A99C2D57FE2900E1362E /* PassportOCRViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F9A99A2D57FE2900E1362E /* PassportOCRViewManager.m */; }; E9F9A99C2D57FE2900E1362E /* PassportOCRViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F9A99A2D57FE2900E1362E /* PassportOCRViewManager.m */; };
E9F9A99D2D57FE2900E1362E /* PassportOCRViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F9A99B2D57FE2900E1362E /* PassportOCRViewManager.swift */; }; E9F9A99D2D57FE2900E1362E /* PassportOCRViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F9A99B2D57FE2900E1362E /* PassportOCRViewManager.swift */; };
EBECCA4983EC6929A7722578 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E56E082698598B41447667BB /* PrivacyInfo.xcprivacy */; }; EBECCA4983EC6929A7722578 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E56E082698598B41447667BB /* PrivacyInfo.xcprivacy */; };
F3A8B2C9D4E5F6A7B8C9D0E1 /* DINOT-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6A7B8C9D0E1F2 /* DINOT-Bold.otf */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@@ -72,6 +73,7 @@
905B70062A72774000AFA232 /* PassportReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PassportReader.m; sourceTree = "<group>"; }; 905B70062A72774000AFA232 /* PassportReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PassportReader.m; sourceTree = "<group>"; };
905B70082A729CD400AFA232 /* OpenPassport.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = OpenPassport.entitlements; path = OpenPassport/OpenPassport.entitlements; sourceTree = "<group>"; }; 905B70082A729CD400AFA232 /* OpenPassport.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = OpenPassport.entitlements; path = OpenPassport/OpenPassport.entitlements; sourceTree = "<group>"; };
9BF744D9A73A4BAC96EC569A /* DINOT-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "DINOT-Medium.otf"; path = "../src/assets/fonts/DINOT-Medium.otf"; sourceTree = "<group>"; }; 9BF744D9A73A4BAC96EC569A /* DINOT-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "DINOT-Medium.otf"; path = "../src/assets/fonts/DINOT-Medium.otf"; sourceTree = "<group>"; };
A1B2C3D4E5F6A7B8C9D0E1F2 /* DINOT-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "DINOT-Bold.otf"; path = "../src/assets/fonts/DINOT-Bold.otf"; sourceTree = "<group>"; };
AE6147EB2DC95A8D00445C0F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; AE6147EB2DC95A8D00445C0F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexMono-Regular.otf"; path = "../src/assets/fonts/IBMPlexMono-Regular.otf"; sourceTree = SOURCE_ROOT; }; B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexMono-Regular.otf"; path = "../src/assets/fonts/IBMPlexMono-Regular.otf"; sourceTree = SOURCE_ROOT; };
BF1044802DD53540009B3688 /* LiveMRZScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveMRZScannerView.swift; sourceTree = "<group>"; }; BF1044802DD53540009B3688 /* LiveMRZScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveMRZScannerView.swift; sourceTree = "<group>"; };
@@ -155,8 +157,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7E5C3CEF7EDA4871B3D0EBE1 /* Advercase-Regular.otf */, 7E5C3CEF7EDA4871B3D0EBE1 /* Advercase-Regular.otf */,
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */, A1B2C3D4E5F6A7B8C9D0E1F2 /* DINOT-Bold.otf */,
9BF744D9A73A4BAC96EC569A /* DINOT-Medium.otf */, 9BF744D9A73A4BAC96EC569A /* DINOT-Medium.otf */,
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */,
); );
name = Resources; name = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -270,8 +273,9 @@
AE6147EC2DC95A8D00445C0F /* GoogleService-Info.plist in Resources */, AE6147EC2DC95A8D00445C0F /* GoogleService-Info.plist in Resources */,
EBECCA4983EC6929A7722578 /* PrivacyInfo.xcprivacy in Resources */, EBECCA4983EC6929A7722578 /* PrivacyInfo.xcprivacy in Resources */,
DAC618BCA5874DD8AD74FFFC /* Advercase-Regular.otf in Resources */, DAC618BCA5874DD8AD74FFFC /* Advercase-Regular.otf in Resources */,
B49D2B112E28AA7900946F64 /* IBMPlexMono-Regular.otf in Resources */, F3A8B2C9D4E5F6A7B8C9D0E1 /* DINOT-Bold.otf in Resources */,
D427791AA5714251A5EAF8AD /* DINOT-Medium.otf in Resources */, D427791AA5714251A5EAF8AD /* DINOT-Medium.otf in Resources */,
B49D2B112E28AA7900946F64 /* IBMPlexMono-Regular.otf in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -427,7 +431,7 @@
CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements; CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 178; CURRENT_PROJECT_VERSION = 189;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 5B29R5LYHQ; DEVELOPMENT_TEAM = 5B29R5LYHQ;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -542,7 +546,7 @@
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
"$(PROJECT_DIR)/MoproKit/Libs", "$(PROJECT_DIR)/MoproKit/Libs",
); );
MARKETING_VERSION = 2.7.3; MARKETING_VERSION = 2.9.5;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@@ -568,7 +572,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements; CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements;
CURRENT_PROJECT_VERSION = 178; CURRENT_PROJECT_VERSION = 189;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 5B29R5LYHQ; DEVELOPMENT_TEAM = 5B29R5LYHQ;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@@ -682,7 +686,7 @@
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
"$(PROJECT_DIR)/MoproKit/Libs", "$(PROJECT_DIR)/MoproKit/Libs",
); );
MARKETING_VERSION = 2.7.3; MARKETING_VERSION = 2.9.5;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",

View File

@@ -3,10 +3,20 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. // NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
module.exports = { module.exports = {
preset: 'react-native', moduleFileExtensions: [
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'cjs', 'json', 'node'], 'ios.js',
'android.js',
'native.js',
'ts',
'tsx',
'js',
'jsx',
'cjs',
'json',
'node',
],
transformIgnorePatterns: [ transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags)/)', 'node_modules/(?!(react-native|@react-native|@react-navigation|@react-native-community|@segment/analytics-react-native|@openpassport|react-native-keychain|react-native-check-version|react-native-nfc-manager|react-native-passport-reader|react-native-gesture-handler|uuid|@stablelib|@react-native-google-signin|react-native-cloud-storage|@react-native-clipboard|@react-native-firebase|@selfxyz|@sentry|@anon-aadhaar|react-native-svg|react-native-svg-circle-country-flags|react-native-blur-effect)/)',
], ],
setupFiles: ['<rootDir>/jest.setup.js'], setupFiles: ['<rootDir>/jest.setup.js'],
testMatch: [ testMatch: [
@@ -16,10 +26,12 @@ module.exports = {
testPathIgnorePatterns: [ testPathIgnorePatterns: [
'/node_modules/', '/node_modules/',
'/scripts/tests/', // Node.js native test runner tests '/scripts/tests/', // Node.js native test runner tests
'/babel\\.config\\.test\\.cjs',
], ],
moduleNameMapper: { moduleNameMapper: {
'^@env$': '<rootDir>/tests/__setup__/@env.js', '^@env$': '<rootDir>/tests/__setup__/@env.js',
'\\.svg$': '<rootDir>/tests/__setup__/svgMock.js', '\\.svg$': '<rootDir>/tests/__setup__/svgMock.js',
'\\.(png|jpg|jpeg|gif|webp)$': '<rootDir>/tests/__setup__/imageMock.js',
'^@/(.*)$': '<rootDir>/src/$1', '^@/(.*)$': '<rootDir>/src/$1',
'^@$': '<rootDir>/src', '^@$': '<rootDir>/src',
'^@tests/(.*)$': '<rootDir>/tests/src/$1', '^@tests/(.*)$': '<rootDir>/tests/src/$1',
@@ -30,6 +42,8 @@ module.exports = {
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/index.cjs', '<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/index.cjs',
'^@selfxyz/mobile-sdk-alpha/components$': '^@selfxyz/mobile-sdk-alpha/components$':
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/components/index.cjs', '<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/components/index.cjs',
'^@selfxyz/mobile-sdk-alpha/hooks$':
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/hooks/index.cjs',
'^@selfxyz/mobile-sdk-alpha/onboarding/(.*)$': '^@selfxyz/mobile-sdk-alpha/onboarding/(.*)$':
'<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/flows/onboarding/$1.cjs', '<rootDir>/../packages/mobile-sdk-alpha/dist/cjs/flows/onboarding/$1.cjs',
'^@selfxyz/mobile-sdk-alpha/disclosing/(.*)$': '^@selfxyz/mobile-sdk-alpha/disclosing/(.*)$':
@@ -47,6 +61,9 @@ module.exports = {
'^@anon-aadhaar/core$': '^@anon-aadhaar/core$':
'<rootDir>/../common/node_modules/@anon-aadhaar/core/dist/index.js', '<rootDir>/../common/node_modules/@anon-aadhaar/core/dist/index.js',
}, },
transform: {
'\\.[jt]sx?$': ['babel-jest', { configFile: './babel.config.test.cjs' }],
},
globals: { globals: {
'ts-jest': { 'ts-jest': {
tsconfig: 'tsconfig.test.json', tsconfig: 'tsconfig.test.json',

View File

@@ -5,6 +5,11 @@
/* global jest */ /* global jest */
/** @jest-environment jsdom */ /** @jest-environment jsdom */
// Set up Buffer globally for tests that need it
const { Buffer } = require('buffer');
global.Buffer = Buffer;
// Mock React Native PixelRatio globally before anything else loads // Mock React Native PixelRatio globally before anything else loads
const mockPixelRatio = { const mockPixelRatio = {
get: jest.fn(() => 2), get: jest.fn(() => 2),
@@ -16,21 +21,136 @@ const mockPixelRatio = {
global.PixelRatio = mockPixelRatio; global.PixelRatio = mockPixelRatio;
// Also make it available for require() calls // Define NativeModules early so it's available for react-native mock
const Module = require('module'); // This will be assigned to global.NativeModules later, but we define it here
// so the react-native mock can reference it
const originalRequire = Module.prototype.require; const NativeModules = {
Module.prototype.require = function (id) { PassportReader: {
if (id === 'react-native') { configure: jest.fn(),
const RN = originalRequire.apply(this, arguments); scanPassport: jest.fn(),
if (!RN.PixelRatio || !RN.PixelRatio.getFontScale) { trackEvent: jest.fn(),
RN.PixelRatio = mockPixelRatio; flush: jest.fn(),
} reset: jest.fn(),
return RN; },
} ReactNativeBiometrics: {
return originalRequire.apply(this, arguments); isSensorAvailable: jest.fn().mockResolvedValue({
available: true,
biometryType: 'TouchID',
}),
createKeys: jest.fn().mockResolvedValue({ publicKey: 'mock-public-key' }),
deleteKeys: jest.fn().mockResolvedValue(true),
createSignature: jest
.fn()
.mockResolvedValue({ signature: 'mock-signature' }),
simplePrompt: jest.fn().mockResolvedValue({ success: true }),
},
NativeLoggerBridge: {
log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
RNPassportReader: {
configure: jest.fn(),
scanPassport: jest.fn(),
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
},
}; };
// Assign to global so it's available everywhere
global.NativeModules = NativeModules;
// Mock react-native comprehensively - single source of truth for all tests
// Note: NativeModules will be defined later and assigned to global.NativeModules
// This mock accesses it at runtime via global.NativeModules
jest.mock('react-native', () => {
// Create AppState mock with listener tracking
// Expose listeners array globally so tests can access it
const appStateListeners = [];
global.mockAppStateListeners = appStateListeners;
const mockAppState = {
currentState: 'active',
addEventListener: jest.fn((eventType, handler) => {
appStateListeners.push(handler);
return {
remove: () => {
const index = appStateListeners.indexOf(handler);
if (index >= 0) {
appStateListeners.splice(index, 1);
}
},
};
}),
};
return {
__esModule: true,
AppState: mockAppState,
Platform: {
OS: 'ios',
select: jest.fn(obj => obj.ios || obj.default),
Version: 14,
},
// NativeModules is defined above and assigned to global.NativeModules
// Use getter to access it at runtime (jest.mock is hoisted)
get NativeModules() {
return global.NativeModules || {};
},
NativeEventEmitter: jest.fn().mockImplementation(nativeModule => {
return {
addListener: jest.fn(),
removeListener: jest.fn(),
removeAllListeners: jest.fn(),
emit: jest.fn(),
};
}),
PixelRatio: mockPixelRatio,
Dimensions: {
get: jest.fn(() => ({
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
})),
},
Linking: {
getInitialURL: jest.fn().mockResolvedValue(null),
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
removeEventListener: jest.fn(),
openURL: jest.fn().mockResolvedValue(undefined),
canOpenURL: jest.fn().mockResolvedValue(true),
},
StyleSheet: {
create: jest.fn(styles => styles),
flatten: jest.fn(style => style),
hairlineWidth: 1,
absoluteFillObject: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
},
View: 'View',
Text: 'Text',
ScrollView: 'ScrollView',
TouchableOpacity: 'TouchableOpacity',
TouchableHighlight: 'TouchableHighlight',
Image: 'Image',
ActivityIndicator: 'ActivityIndicator',
SafeAreaView: 'SafeAreaView',
requireNativeComponent: jest.fn(name => {
// Return a mock component function for any native component
const MockNativeComponent = jest.fn(props => props.children || null);
MockNativeComponent.displayName = `Mock(${name})`;
return MockNativeComponent;
}),
};
});
require('react-native-gesture-handler/jestSetup'); require('react-native-gesture-handler/jestSetup');
// Mock NativeAnimatedHelper - using virtual mock during RN 0.76.9 prep phase // Mock NativeAnimatedHelper - using virtual mock during RN 0.76.9 prep phase
@@ -51,6 +171,23 @@ global.__fbBatchedBridgeConfig = {
// Set up global React Native test environment // Set up global React Native test environment
global.__DEV__ = true; global.__DEV__ = true;
// Set up global mock navigation ref for tests
global.mockNavigationRef = {
isReady: jest.fn(() => true),
getCurrentRoute: jest.fn(() => ({ name: 'Home' })),
navigate: jest.fn(),
goBack: jest.fn(),
canGoBack: jest.fn(() => true),
dispatch: jest.fn(),
getState: jest.fn(() => ({ routes: [{ name: 'Home' }], index: 0 })),
addListener: jest.fn(() => jest.fn()),
removeListener: jest.fn(),
};
// Load grouped mocks
require('./tests/__setup__/mocks/navigation');
require('./tests/__setup__/mocks/ui');
// Mock TurboModuleRegistry to provide required native modules for BOTH main app and mobile-sdk-alpha // Mock TurboModuleRegistry to provide required native modules for BOTH main app and mobile-sdk-alpha
jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({ jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({
getEnforcing: jest.fn(name => { getEnforcing: jest.fn(name => {
@@ -84,6 +221,16 @@ jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({
}), }),
}; };
} }
if (name === 'RNDeviceInfo') {
return {
getConstants: () => ({
Dimensions: {
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
},
}),
};
}
return { return {
getConstants: () => ({}), getConstants: () => ({}),
}; };
@@ -115,25 +262,69 @@ jest.mock(
startDetecting: jest.fn(), startDetecting: jest.fn(),
}; };
const RN = jest.requireActual('react-native'); // Return a simple object with all the mocks we need
// Override the PixelRatio immediately // Avoid nested requireActual/requireMock to prevent OOM in CI
RN.PixelRatio = PixelRatio; return {
__esModule: true,
// Make sure both the default and named exports work
const mockedRN = {
...RN,
PixelRatio, PixelRatio,
default: { Platform: {
...RN, OS: 'ios',
PixelRatio, select: jest.fn(obj => obj.ios || obj.default),
Version: 14,
}, },
Dimensions: {
get: jest.fn(() => ({
window: { width: 375, height: 667, scale: 2 },
screen: { width: 375, height: 667, scale: 2 },
})),
},
StyleSheet: {
create: jest.fn(styles => styles),
flatten: jest.fn(style => style),
hairlineWidth: 1,
absoluteFillObject: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
},
View: 'View',
Text: 'Text',
ScrollView: 'ScrollView',
TouchableOpacity: 'TouchableOpacity',
requireNativeComponent: jest.fn(name => {
const MockNativeComponent = jest.fn(props => props.children || null);
MockNativeComponent.displayName = `Mock(${name})`;
return MockNativeComponent;
}),
}; };
return mockedRN;
}, },
{ virtual: true }, { virtual: true },
); );
// Mock @turnkey/react-native-wallet-kit to prevent loading of problematic dependencies
jest.mock(
'@turnkey/react-native-wallet-kit',
() => ({
AuthState: {
Authenticated: 'Authenticated',
Unauthenticated: 'Unauthenticated',
},
useTurnkey: jest.fn(() => ({
handleGoogleOauth: jest.fn(),
fetchWallets: jest.fn().mockResolvedValue([]),
exportWallet: jest.fn(),
importWallet: jest.fn(),
authState: 'Unauthenticated',
logout: jest.fn(),
})),
TurnkeyProvider: ({ children }) => children,
}),
{ virtual: true },
);
// Mock the mobile-sdk-alpha's TurboModuleRegistry to prevent native module errors // Mock the mobile-sdk-alpha's TurboModuleRegistry to prevent native module errors
jest.mock( jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/TurboModule/TurboModuleRegistry', '../packages/mobile-sdk-alpha/node_modules/react-native/Libraries/TurboModule/TurboModuleRegistry',
@@ -239,47 +430,126 @@ jest.mock('react-native/src/private/specs/modules/NativeDeviceInfo', () => ({
})), })),
})); }));
// Mock NativeStatusBarManagerIOS for react-native-edge-to-edge SystemBars
jest.mock(
'react-native/src/private/specs/modules/NativeStatusBarManagerIOS',
() => ({
setStyle: jest.fn(),
setHidden: jest.fn(),
setNetworkActivityIndicatorVisible: jest.fn(),
}),
);
// Mock react-native-gesture-handler to prevent getConstants errors // Mock react-native-gesture-handler to prevent getConstants errors
jest.mock('react-native-gesture-handler', () => { jest.mock('react-native-gesture-handler', () => {
const RN = jest.requireActual('react-native'); // Avoid requiring React to prevent nested require memory issues
// Mock the components as simple pass-through functions
const MockScrollView = jest.fn(props => props.children || null);
const MockTouchableOpacity = jest.fn(props => props.children || null);
const MockTouchableHighlight = jest.fn(props => props.children || null);
const MockFlatList = jest.fn(props => null);
return { return {
...jest.requireActual('react-native-gesture-handler/jestSetup'), // Provide gesture handler mock without requireActual to avoid OOM
GestureHandlerRootView: ({ children }) => children, GestureHandlerRootView: ({ children }) => children,
ScrollView: RN.ScrollView, ScrollView: MockScrollView,
TouchableOpacity: RN.TouchableOpacity, TouchableOpacity: MockTouchableOpacity,
TouchableHighlight: RN.TouchableHighlight, TouchableHighlight: MockTouchableHighlight,
FlatList: RN.FlatList, FlatList: MockFlatList,
Directions: {},
State: {},
Swipeable: jest.fn(() => null),
DrawerLayout: jest.fn(() => null),
PanGestureHandler: jest.fn(() => null),
TapGestureHandler: jest.fn(() => null),
LongPressGestureHandler: jest.fn(() => null),
}; };
}); });
// Mock react-native-safe-area-context // Mock react-native-safe-area-context
jest.mock('react-native-safe-area-context', () => { jest.mock('react-native-safe-area-context', () => {
const React = require('react'); // Avoid requiring React to prevent nested require memory issues
const { View } = require('react-native');
return { return {
__esModule: true, __esModule: true,
SafeAreaProvider: ({ children }) => SafeAreaProvider: jest.fn(({ children }) => children || null),
React.createElement(View, null, children), SafeAreaView: jest.fn(({ children }) => children || null),
SafeAreaView: ({ children }) => React.createElement(View, null, children),
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
}; };
}); });
// Mock NativeEventEmitter to prevent null argument errors // Mock NativeEventEmitter to prevent null argument errors
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => { jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => {
return class MockNativeEventEmitter { function MockNativeEventEmitter(nativeModule) {
constructor(nativeModule) { // Accept any nativeModule argument (including null/undefined)
// Accept any nativeModule argument (including null/undefined) this.nativeModule = nativeModule;
this.nativeModule = nativeModule; this.addListener = jest.fn();
} this.removeListener = jest.fn();
this.removeAllListeners = jest.fn();
this.emit = jest.fn();
}
addListener = jest.fn(); // The mock needs to be the constructor itself, not wrapped
removeListener = jest.fn(); MockNativeEventEmitter.default = MockNativeEventEmitter;
removeAllListeners = jest.fn(); return MockNativeEventEmitter;
emit = jest.fn();
};
}); });
// Mock react-native-device-info to prevent NativeEventEmitter errors
jest.mock('react-native-device-info', () => ({
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
default: {
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
},
}));
// Mock react-native-device-info nested in @turnkey/react-native-wallet-kit
jest.mock(
'node_modules/@turnkey/react-native-wallet-kit/node_modules/react-native-device-info',
() => ({
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
default: {
getUniqueId: jest.fn().mockResolvedValue('mock-device-id'),
getReadableVersion: jest.fn().mockReturnValue('1.0.0'),
getVersion: jest.fn().mockReturnValue('1.0.0'),
getBuildNumber: jest.fn().mockReturnValue('1'),
getModel: jest.fn().mockReturnValue('mock-model'),
getBrand: jest.fn().mockReturnValue('mock-brand'),
isTablet: jest.fn().mockReturnValue(false),
isLandscape: jest.fn().mockResolvedValue(false),
getSystemVersion: jest.fn().mockReturnValue('14.0'),
getSystemName: jest.fn().mockReturnValue('iOS'),
},
}),
{ virtual: true },
);
// Mock problematic mobile-sdk-alpha components that use React Native StyleSheet // Mock problematic mobile-sdk-alpha components that use React Native StyleSheet
jest.mock('@selfxyz/mobile-sdk-alpha', () => ({ jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
NFCScannerScreen: jest.fn(() => null), NFCScannerScreen: jest.fn(() => null),
@@ -382,6 +652,21 @@ jest.mock('@selfxyz/mobile-sdk-alpha', () => ({
PROVING_FAILED: 'PROVING_FAILED', PROVING_FAILED: 'PROVING_FAILED',
// Add other events as needed // Add other events as needed
}, },
// Mock haptic functions
buttonTap: jest.fn(),
cancelTap: jest.fn(),
confirmTap: jest.fn(),
feedbackProgress: jest.fn(),
feedbackSuccess: jest.fn(),
feedbackUnsuccessful: jest.fn(),
impactLight: jest.fn(),
impactMedium: jest.fn(),
loadingScreenProgress: jest.fn(),
notificationError: jest.fn(),
notificationSuccess: jest.fn(),
notificationWarning: jest.fn(),
selectionChange: jest.fn(),
triggerFeedback: jest.fn(),
// Add other components and hooks as needed // Add other components and hooks as needed
})); }));
@@ -627,18 +912,11 @@ jest.mock('react-native-passport-reader', () => {
}; };
}); });
const { NativeModules } = require('react-native'); // NativeModules is already defined at the top of the file and assigned to global.NativeModules
// No need to redefine it here
NativeModules.PassportReader = { // Mock @/integrations/nfc/passportReader to properly expose the interface expected by tests
configure: jest.fn(), jest.mock('./src/integrations/nfc/passportReader', () => {
scanPassport: jest.fn(),
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
};
// Mock @/utils/passportReader to properly expose the interface expected by tests
jest.mock('./src/utils/passportReader', () => {
const mockScanPassport = jest.fn(); const mockScanPassport = jest.fn();
// Mock the parameter count for scanPassport (iOS native method takes 9 parameters) // Mock the parameter count for scanPassport (iOS native method takes 9 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 9 }); Object.defineProperty(mockScanPassport, 'length', { value: 9 });
@@ -774,183 +1052,164 @@ jest.mock('react-native-localize', () => ({
languageTag: 'en-US', languageTag: 'en-US',
isRTL: false, isRTL: false,
}), }),
default: {
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
},
})); }));
jest.mock('./src/utils/notifications/notificationService', () => // Ensure mobile-sdk-alpha's bundled react-native-localize dependency is mocked as well
jest.mock(
'../packages/mobile-sdk-alpha/node_modules/react-native-localize',
() => ({
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
default: {
getLocales: jest.fn().mockReturnValue([
{
countryCode: 'US',
languageTag: 'en-US',
languageCode: 'en',
isRTL: false,
},
]),
getCountry: jest.fn().mockReturnValue('US'),
getTimeZone: jest.fn().mockReturnValue('America/New_York'),
getCurrencies: jest.fn().mockReturnValue(['USD']),
getTemperatureUnit: jest.fn().mockReturnValue('celsius'),
getFirstWeekDay: jest.fn().mockReturnValue(0),
uses24HourClock: jest.fn().mockReturnValue(false),
usesMetricSystem: jest.fn().mockReturnValue(false),
findBestAvailableLanguage: jest.fn().mockReturnValue({
languageTag: 'en-US',
isRTL: false,
}),
},
}),
);
jest.mock('./src/services/notifications/notificationService', () =>
require('./tests/__setup__/notificationServiceMock.js'), require('./tests/__setup__/notificationServiceMock.js'),
); );
// Mock react-native-svg // Mock react-native-svg
jest.mock('react-native-svg', () => { jest.mock('react-native-svg', () => {
const React = require('react'); // Avoid requiring React to prevent nested require memory issues
// Mock SvgXml component that handles XML strings // Mock SvgXml component that handles XML strings
const SvgXml = React.forwardRef( const SvgXml = jest.fn(() => null);
({ xml, width, height, style, ...props }, ref) => {
return React.createElement('div', {
ref,
style: {
width: width || 'auto',
height: height || 'auto',
display: 'inline-block',
...style,
},
dangerouslySetInnerHTML: { __html: xml },
...props,
});
},
);
SvgXml.displayName = 'SvgXml'; SvgXml.displayName = 'SvgXml';
return { return {
__esModule: true, __esModule: true,
default: SvgXml, default: SvgXml,
SvgXml, SvgXml,
Svg: props => React.createElement('Svg', props, props.children), Svg: jest.fn(() => null),
Circle: props => React.createElement('Circle', props, props.children), Circle: jest.fn(() => null),
Path: props => React.createElement('Path', props, props.children), Path: jest.fn(() => null),
G: props => React.createElement('G', props, props.children), G: jest.fn(() => null),
Rect: props => React.createElement('Rect', props, props.children), Rect: jest.fn(() => null),
Defs: props => React.createElement('Defs', props, props.children), Defs: jest.fn(() => null),
LinearGradient: props => LinearGradient: jest.fn(() => null),
React.createElement('LinearGradient', props, props.children), Stop: jest.fn(() => null),
Stop: props => React.createElement('Stop', props, props.children), ClipPath: jest.fn(() => null),
ClipPath: props => React.createElement('ClipPath', props, props.children), Polygon: jest.fn(() => null),
Polygon: props => React.createElement('Polygon', props, props.children), Polyline: jest.fn(() => null),
Polyline: props => React.createElement('Polyline', props, props.children), Line: jest.fn(() => null),
Line: props => React.createElement('Line', props, props.children), Text: jest.fn(() => null),
Text: props => React.createElement('Text', props, props.children), TSpan: jest.fn(() => null),
TSpan: props => React.createElement('TSpan', props, props.children),
}; };
}); });
// Mock React Navigation // Mock React Navigation
jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native'); // Mock react-native-biometrics to prevent NativeModules errors
jest.mock('react-native-biometrics', () => {
class MockReactNativeBiometrics {
constructor(options) {
// Constructor accepts options but doesn't need to do anything
}
isSensorAvailable = jest.fn().mockResolvedValue({
available: true,
biometryType: 'TouchID',
});
createKeys = jest.fn().mockResolvedValue({ publicKey: 'mock-public-key' });
deleteKeys = jest.fn().mockResolvedValue(true);
createSignature = jest
.fn()
.mockResolvedValue({ signature: 'mock-signature' });
simplePrompt = jest.fn().mockResolvedValue({ success: true });
}
return { return {
...actualNav, __esModule: true,
useFocusEffect: jest.fn(callback => { default: MockReactNativeBiometrics,
// Immediately invoke the effect for testing without requiring a container
return callback();
}),
useNavigation: jest.fn(() => ({
navigate: jest.fn(),
goBack: jest.fn(),
canGoBack: jest.fn(() => true),
dispatch: jest.fn(),
})),
createNavigationContainerRef: jest.fn(() => ({
current: null,
getCurrentRoute: jest.fn(),
})),
createStaticNavigation: jest.fn(() => ({ displayName: 'MockNavigation' })),
}; };
}); });
jest.mock('@react-navigation/native-stack', () => ({ // Mock NativeAppState native module to prevent getCurrentAppState errors
createNativeStackNavigator: jest.fn(() => ({ jest.mock('react-native/Libraries/AppState/NativeAppState', () => ({
displayName: 'MockStackNavigator', __esModule: true,
})), default: {
createNavigatorFactory: jest.fn(), getConstants: jest.fn(() => ({ initialAppState: 'active' })),
getCurrentAppState: jest.fn(() => Promise.resolve({ app_state: 'active' })),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
})); }));
// Mock core navigation to avoid requiring a NavigationContainer for hooks // Mock AppState to prevent getCurrentAppState errors
jest.mock('@react-navigation/core', () => { jest.mock('react-native/Libraries/AppState/AppState', () => {
const actualCore = jest.requireActual('@react-navigation/core'); // Use the global appStateListeners array so tests can access it
return { const appStateListeners = global.mockAppStateListeners || [];
...actualCore,
useNavigation: jest.fn(() => ({
navigate: jest.fn(),
goBack: jest.fn(),
canGoBack: jest.fn(() => true),
dispatch: jest.fn(),
})),
};
});
// Mock react-native-webview globally to avoid ESM parsing and native behaviors
jest.mock('react-native-webview', () => {
const React = require('react');
const { View } = require('react-native');
const MockWebView = React.forwardRef((props, ref) => {
return React.createElement(View, { ref, testID: 'webview', ...props });
});
MockWebView.displayName = 'MockWebView';
return { return {
__esModule: true, __esModule: true,
default: MockWebView, default: {
WebView: MockWebView, currentState: 'active',
addEventListener: jest.fn((eventType, handler) => {
appStateListeners.push(handler);
return {
remove: () => {
const index = appStateListeners.indexOf(handler);
if (index >= 0) {
appStateListeners.splice(index, 1);
}
},
};
}),
},
}; };
}); });
// Mock ExpandableBottomLayout to simple containers to avoid SDK internals in tests
jest.mock('@/layouts/ExpandableBottomLayout', () => {
const React = require('react');
const { View } = require('react-native');
const Layout = ({ children }) => React.createElement(View, null, children);
const TopSection = ({ children }) =>
React.createElement(View, null, children);
const BottomSection = ({ children }) =>
React.createElement(View, null, children);
const FullSection = ({ children }) =>
React.createElement(View, null, children);
return {
__esModule: true,
ExpandableBottomLayout: { Layout, TopSection, BottomSection, FullSection },
};
});
// Mock mobile-sdk-alpha components used by NavBar (Button, XStack)
jest.mock('@selfxyz/mobile-sdk-alpha/components', () => {
const React = require('react');
const { View, Text, TouchableOpacity } = require('react-native');
const Button = ({ children, onPress, icon, ...props }) =>
React.createElement(
TouchableOpacity,
{ onPress, ...props, testID: 'msdk-button' },
icon
? React.createElement(View, { testID: 'msdk-button-icon' }, icon)
: null,
children,
);
const XStack = ({ children, ...props }) =>
React.createElement(View, { ...props, testID: 'msdk-xstack' }, children);
return {
__esModule: true,
Button,
XStack,
// Provide minimal Text to satisfy potential usages
Text,
};
});
// Mock Tamagui lucide icons to simple components to avoid theme context
jest.mock('@tamagui/lucide-icons', () => {
const React = require('react');
const { View } = require('react-native');
const makeIcon = name => {
const Icon = ({ size, color, opacity }) =>
React.createElement(View, {
testID: `icon-${name}`,
size,
color,
opacity,
});
Icon.displayName = `MockIcon(${name})`;
return Icon;
};
return {
__esModule: true,
ExternalLink: makeIcon('external-link'),
X: makeIcon('x'),
};
});
// Mock WebViewFooter to avoid SDK rendering complexity
jest.mock('@/components/WebViewFooter', () => {
const React = require('react');
const { View } = require('react-native');
const WebViewFooter = () =>
React.createElement(View, { testID: 'webview-footer' });
return { __esModule: true, WebViewFooter };
});

View File

@@ -192,6 +192,73 @@ const config = {
// Handle problematic package exports and Node.js modules // Handle problematic package exports and Node.js modules
// Fix @turnkey/encoding to use CommonJS instead of ESM
if (moduleName === '@turnkey/encoding') {
const filePath = path.resolve(
projectRoot,
'node_modules/@turnkey/encoding/dist/index.js',
);
return {
type: 'sourceFile',
filePath,
};
}
// Fix @turnkey/encoding submodules to use CommonJS
if (moduleName.startsWith('@turnkey/encoding/')) {
const subpath = moduleName.replace('@turnkey/encoding/', '');
const filePath = path.resolve(
projectRoot,
`node_modules/@turnkey/encoding/dist/${subpath}.js`,
);
return {
type: 'sourceFile',
filePath,
};
}
// Fix @turnkey/api-key-stamper to use CommonJS instead of ESM
if (moduleName === '@turnkey/api-key-stamper') {
const filePath = path.resolve(
projectRoot,
'node_modules/@turnkey/api-key-stamper/dist/index.js',
);
return {
type: 'sourceFile',
filePath,
};
}
// Fix @turnkey/api-key-stamper dynamic imports by resolving submodules statically
if (moduleName.startsWith('@turnkey/api-key-stamper/')) {
const subpath = moduleName.replace('@turnkey/api-key-stamper/', '');
const filePath = path.resolve(
projectRoot,
`node_modules/@turnkey/api-key-stamper/dist/${subpath}`,
);
return {
type: 'sourceFile',
filePath,
};
}
// Fix viem dynamic import resolution
if (moduleName === 'viem') {
try {
// Viem uses package exports, so we need to resolve to the actual file path
const viemPath = path.resolve(
projectRoot,
'node_modules/viem/_cjs/index.js',
);
return {
type: 'sourceFile',
filePath: viemPath,
};
} catch (error) {
console.warn('Failed to resolve viem:', error);
}
}
// Fix @tamagui/config v2-native export resolution // Fix @tamagui/config v2-native export resolution
if (moduleName === '@tamagui/config/v2-native') { if (moduleName === '@tamagui/config/v2-native') {
try { try {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@selfxyz/mobile-app", "name": "@selfxyz/mobile-app",
"version": "2.7.3", "version": "2.9.5",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -29,11 +29,14 @@
"format": "yarn nice", "format": "yarn nice",
"ia": "yarn install-app", "ia": "yarn install-app",
"imports:fix": "node ./scripts/alias-imports.cjs", "imports:fix": "node ./scripts/alias-imports.cjs",
"postinstall": "npx patch-package --patch-dir ../patches || true",
"install-app": "yarn install-app:setup && yarn clean:xcode-env-local", "install-app": "yarn install-app:setup && yarn clean:xcode-env-local",
"install-app:mobile-deploy": "yarn install && yarn build:deps && yarn clean:xcode-env-local", "install-app:mobile-deploy": "yarn install && yarn build:deps && yarn clean:xcode-env-local",
"install-app:setup": "yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && scripts/pod-install-with-cache-fix.sh && cd ..", "install-app:setup": "yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && scripts/pod-install-with-cache-fix.sh && cd ..",
"ios": "yarn build:deps && node scripts/run-ios-simulator.cjs", "ios": "yarn build:deps && node scripts/run-ios-simulator.cjs",
"ios:fastlane-debug": "yarn reinstall && bundle exec fastlane --verbose ios internal_test", "ios:fastlane-debug": "yarn reinstall && bundle exec fastlane --verbose ios internal_test",
"jest:clear": "node ./node_modules/jest/bin/jest.js --clearCache",
"jest:run": "node ./node_modules/jest/bin/jest.js",
"lint": "eslint . --cache --cache-location .eslintcache", "lint": "eslint . --cache --cache-location .eslintcache",
"lint:fix": "eslint --fix . --cache --cache-location .eslintcache", "lint:fix": "eslint --fix . --cache --cache-location .eslintcache",
"mobile-deploy": "node scripts/mobile-deploy-confirm.cjs both", "mobile-deploy": "node scripts/mobile-deploy-confirm.cjs both",
@@ -42,7 +45,7 @@
"mobile-local-deploy": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs both", "mobile-local-deploy": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs both",
"mobile-local-deploy:android": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs android", "mobile-local-deploy:android": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs android",
"mobile-local-deploy:ios": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs ios", "mobile-local-deploy:ios": "FORCE_UPLOAD_LOCAL_DEV=true node scripts/mobile-deploy-confirm.cjs ios",
"nice": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn build:deps; fi; yarn imports:fix && yarn lint:fix && yarn fmt:fix'", "nice": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn build:deps; fi; yarn imports:fix && yarn lint:fix'",
"reinstall": "yarn --top-level run reinstall-app", "reinstall": "yarn --top-level run reinstall-app",
"release": "./scripts/release.sh", "release": "./scripts/release.sh",
"release:major": "./scripts/release.sh major", "release:major": "./scripts/release.sh major",
@@ -50,31 +53,36 @@
"release:patch": "./scripts/release.sh patch", "release:patch": "./scripts/release.sh patch",
"setup": "yarn clean:build && yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && bundle exec pod install --repo-update && cd .. && yarn clean:xcode-env-local", "setup": "yarn clean:build && yarn install && yarn build:deps && yarn setup:android-deps && cd ios && bundle install && bundle exec pod install --repo-update && cd .. && yarn clean:xcode-env-local",
"setup:android-deps": "node scripts/setup-private-modules.cjs", "setup:android-deps": "node scripts/setup-private-modules.cjs",
"start": "watchman watch-del-all && react-native start", "start": "watchman watch-del-all && yarn watch:sdk & react-native start",
"start:clean": "watchman watch-del-all && cd android && ./gradlew clean && cd .. && react-native start --reset-cache", "start:clean": "watchman watch-del-all && cd android && ./gradlew clean && cd .. && yarn watch:sdk & react-native start --reset-cache",
"sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version", "sync-versions": "bundle exec fastlane ios sync_version && bundle exec fastlane android sync_version",
"tag:release": "node scripts/tag.cjs release", "tag:release": "node scripts/tag.cjs release",
"tag:remove": "node scripts/tag.cjs remove", "tag:remove": "node scripts/tag.cjs remove",
"test": "yarn build:deps && jest --passWithNoTests && node --test scripts/tests/*.cjs", "test": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
"test:build": "yarn build:deps && yarn types && node ./scripts/bundle-analyze-ci.cjs ios && yarn test", "test:build": "yarn build:deps && yarn types && node ./scripts/bundle-analyze-ci.cjs ios && yarn test",
"test:ci": "jest --passWithNoTests && node --test scripts/tests/*.cjs", "test:ci": "yarn jest:run --passWithNoTests && node --test scripts/tests/*.cjs",
"test:coverage": "jest --coverage --passWithNoTests", "test:coverage": "yarn jest:run --coverage --passWithNoTests",
"test:coverage:ci": "jest --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json", "test:coverage:ci": "yarn jest:run --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json",
"test:e2e:android": "./scripts/mobile-ci-build-android.sh && maestro test tests/e2e/launch.android.flow.yaml", "test:e2e:android": "./scripts/mobile-ci-build-android.sh && maestro test tests/e2e/launch.android.flow.yaml",
"test:e2e:ios": "xcodebuild -workspace ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build && maestro test tests/e2e/launch.ios.flow.yaml", "test:e2e:ios": "xcodebuild -workspace ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build && maestro test tests/e2e/launch.ios.flow.yaml",
"test:fastlane": "bundle exec ruby -Itest fastlane/test/helpers_test.rb", "test:fastlane": "bundle exec ruby -Itest fastlane/test/helpers_test.rb",
"test:tree-shaking": "node ./scripts/test-tree-shaking.cjs", "test:tree-shaking": "node ./scripts/test-tree-shaking.cjs",
"test:web-build": "jest tests/web-build-render.test.ts --testTimeout=180000", "test:web-build": "yarn jest:run tests/web-build-render.test.ts --testTimeout=180000",
"types": "tsc --noEmit", "types": "tsc --noEmit",
"watch:sdk": "yarn workspace @selfxyz/mobile-sdk-alpha watch",
"web": "vite", "web": "vite",
"web:build": "yarn build:deps && vite build", "web:build": "yarn build:deps && vite build",
"web:preview": "vite preview" "web:preview": "vite preview"
}, },
"resolutions": { "resolutions": {
"punycode": "npm:punycode.js@2.3.1" "punycode": "npm:punycode.js@2.3.1",
"react-native-blur-effect": "1.1.3",
"react-native-webview": "13.16.0"
}, },
"overrides": { "overrides": {
"punycode": "npm:punycode.js@2.3.1" "punycode": "npm:punycode.js@2.3.1",
"react-native-blur-effect": "1.1.3",
"react-native-webview": "13.16.0"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.3", "@babel/runtime": "^7.28.3",
@@ -97,6 +105,7 @@
"@segment/analytics-react-native": "^2.21.2", "@segment/analytics-react-native": "^2.21.2",
"@segment/sovran-react-native": "^1.1.3", "@segment/sovran-react-native": "^1.1.3",
"@selfxyz/common": "workspace:^", "@selfxyz/common": "workspace:^",
"@selfxyz/euclid": "^0.6.1",
"@selfxyz/mobile-sdk-alpha": "workspace:^", "@selfxyz/mobile-sdk-alpha": "workspace:^",
"@sentry/react": "^9.32.0", "@sentry/react": "^9.32.0",
"@sentry/react-native": "7.0.1", "@sentry/react-native": "7.0.1",
@@ -106,11 +115,19 @@
"@tamagui/config": "1.126.14", "@tamagui/config": "1.126.14",
"@tamagui/lucide-icons": "1.126.14", "@tamagui/lucide-icons": "1.126.14",
"@tamagui/toast": "1.126.14", "@tamagui/toast": "1.126.14",
"@turnkey/api-key-stamper": "^0.5.0",
"@turnkey/core": "1.7.0",
"@turnkey/encoding": "^0.6.0",
"@turnkey/react-native-wallet-kit": "1.1.5",
"@walletconnect/react-native-compat": "^2.23.0",
"@xstate/react": "^5.0.3", "@xstate/react": "^5.0.3",
"asn1js": "^3.0.6", "asn1js": "^3.0.6",
"axios": "^1.13.2",
"buffer": "^6.0.3",
"country-emoji": "^1.5.6", "country-emoji": "^1.5.6",
"elliptic": "^6.6.1", "elliptic": "^6.6.1",
"ethers": "^6.11.0", "ethers": "^6.11.0",
"expo-application": "^7.0.7",
"expo-modules-core": "^2.2.1", "expo-modules-core": "^2.2.1",
"hash.js": "^1.1.7", "hash.js": "^1.1.7",
"js-sha1": "^0.7.0", "js-sha1": "^0.7.0",
@@ -126,6 +143,7 @@
"react-native": "0.76.9", "react-native": "0.76.9",
"react-native-app-auth": "^8.0.3", "react-native-app-auth": "^8.0.3",
"react-native-biometrics": "^3.0.1", "react-native-biometrics": "^3.0.1",
"react-native-blur-effect": "^1.1.3",
"react-native-check-version": "^1.3.0", "react-native-check-version": "^1.3.0",
"react-native-cloud-storage": "^2.2.2", "react-native-cloud-storage": "^2.2.2",
"react-native-device-info": "^14.0.4", "react-native-device-info": "^14.0.4",
@@ -134,16 +152,19 @@
"react-native-gesture-handler": "2.19.0", "react-native-gesture-handler": "2.19.0",
"react-native-get-random-values": "^1.11.0", "react-native-get-random-values": "^1.11.0",
"react-native-haptic-feedback": "^2.3.3", "react-native-haptic-feedback": "^2.3.3",
"react-native-inappbrowser-reborn": "^3.7.0",
"react-native-keychain": "^10.0.0", "react-native-keychain": "^10.0.0",
"react-native-localize": "^3.5.2", "react-native-localize": "^3.5.2",
"react-native-logs": "^5.3.0", "react-native-logs": "^5.3.0",
"react-native-nfc-manager": "3.16.3", "react-native-nfc-manager": "3.16.3",
"react-native-passkey": "^3.3.1",
"react-native-passport-reader": "1.0.3", "react-native-passport-reader": "1.0.3",
"react-native-safe-area-context": "5.6.1", "react-native-safe-area-context": "^5.6.1",
"react-native-screens": "4.15.3", "react-native-screens": "4.15.3",
"react-native-sqlite-storage": "^6.0.1", "react-native-sqlite-storage": "^6.0.1",
"react-native-svg": "15.12.1", "react-native-svg": "^15.14.0",
"react-native-svg-web": "^1.0.9", "react-native-svg-web": "^1.0.9",
"react-native-url-polyfill": "^3.0.0",
"react-native-web": "^0.19.0", "react-native-web": "^0.19.0",
"react-native-webview": "^13.16.0", "react-native-webview": "^13.16.0",
"react-qr-barcode-scanner": "^2.1.8", "react-qr-barcode-scanner": "^2.1.8",
@@ -155,8 +176,13 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.28.3", "@babel/core": "^7.28.3",
"@babel/plugin-syntax-flow": "^7.27.1",
"@babel/plugin-transform-classes": "^7.27.1",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
"@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/preset-env": "^7.28.3", "@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.27.1",
"@react-native-community/cli": "^16.0.3", "@react-native-community/cli": "^16.0.3",
"@react-native/babel-preset": "0.76.9", "@react-native/babel-preset": "0.76.9",
"@react-native/eslint-config": "0.76.9", "@react-native/eslint-config": "0.76.9",
@@ -170,7 +196,7 @@
"@types/bn.js": "^5.2.0", "@types/bn.js": "^5.2.0",
"@types/dompurify": "^3.2.0", "@types/dompurify": "^3.2.0",
"@types/elliptic": "^6.4.18", "@types/elliptic": "^6.4.18",
"@types/jest": "^29.5.14", "@types/jest": "^30.0.0",
"@types/node": "^22.18.3", "@types/node": "^22.18.3",
"@types/node-forge": "^1.3.14", "@types/node-forge": "^1.3.14",
"@types/path-browserify": "^1", "@types/path-browserify": "^1",
@@ -184,7 +210,7 @@
"@typescript-eslint/parser": "^8.39.0", "@typescript-eslint/parser": "^8.39.0",
"@vitejs/plugin-react-swc": "^3.10.2", "@vitejs/plugin-react-swc": "^3.10.2",
"babel-plugin-module-resolver": "^5.0.2", "babel-plugin-module-resolver": "^5.0.2",
"buffer": "^6.0.3", "babel-plugin-transform-remove-console": "^6.9.4",
"constants-browserify": "^1.0.0", "constants-browserify": "^1.0.0",
"dompurify": "^3.2.6", "dompurify": "^3.2.6",
"eslint": "^8.57.0", "eslint": "^8.57.0",
@@ -193,12 +219,12 @@
"eslint-plugin-ft-flow": "^3.0.11", "eslint-plugin-ft-flow": "^3.0.11",
"eslint-plugin-header": "^3.1.1", "eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.11.1", "eslint-plugin-jest": "^29.1.0",
"eslint-plugin-prettier": "^5.2.6", "eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sort-exports": "^0.9.1", "eslint-plugin-sort-exports": "^0.9.1",
"hermes-eslint": "^0.19.1", "hermes-eslint": "^0.19.1",
"jest": "^29.6.3", "jest": "^30.2.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"react-native-svg-transformer": "^1.5.1", "react-native-svg-transformer": "^1.5.1",

View File

@@ -17,8 +17,8 @@ if (!platform || !['android', 'ios'].includes(platform)) {
// Bundle size thresholds in MB - easy to update! // Bundle size thresholds in MB - easy to update!
const BUNDLE_THRESHOLDS_MB = { const BUNDLE_THRESHOLDS_MB = {
// TODO: fix temporary bundle bump // TODO: fix temporary bundle bump
ios: 42, ios: 45,
android: 42, android: 45,
}; };
function formatBytes(bytes) { function formatBytes(bytes) {

View File

@@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
/**
* Check for nested require('react') and require('react-native') in test files
* These patterns cause out-of-memory errors in CI/CD pipelines
*
* Usage: node scripts/check-test-requires.cjs
* Exit code: 0 if no issues found, 1 if issues found
*/
const fs = require('fs');
const path = require('path');
const TESTS_DIR = path.join(__dirname, '..', 'tests');
const FORBIDDEN_PATTERNS = [
{
pattern: /require\(['"]react['"]\)/g,
name: "require('react')",
fix: 'Use \'import React from "react"\' at the top of the file instead',
},
{
pattern: /require\(['"]react-native['"]\)/g,
name: "require('react-native')",
fix: 'Use \'import { ... } from "react-native"\' at the top of the file instead',
},
];
/**
* Recursively find all test files in directory
*/
function findTestFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules and other common directories
if (
!['node_modules', '.git', 'coverage', 'dist', 'build'].includes(
entry.name,
)
) {
findTestFiles(fullPath, files);
}
} else if (
entry.isFile() &&
(entry.name.endsWith('.ts') ||
entry.name.endsWith('.tsx') ||
entry.name.endsWith('.js') ||
entry.name.endsWith('.jsx'))
) {
files.push(fullPath);
}
}
return files;
}
/**
* Check a file for forbidden require patterns
*/
function checkFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const issues = [];
for (const { pattern, name, fix } of FORBIDDEN_PATTERNS) {
const matches = content.matchAll(pattern);
for (const match of matches) {
const lines = content.substring(0, match.index).split('\n');
const lineNumber = lines.length;
const columnNumber = lines[lines.length - 1].length + 1;
issues.push({
file: path.relative(process.cwd(), filePath),
line: lineNumber,
column: columnNumber,
pattern: name,
fix: fix,
});
}
}
return issues;
}
/**
* Main execution
*/
function main() {
console.log('🔍 Checking for nested require() in test files...\n');
if (!fs.existsSync(TESTS_DIR)) {
console.error(`❌ Tests directory not found: ${TESTS_DIR}`);
process.exit(1);
}
const testFiles = findTestFiles(TESTS_DIR);
console.log(`Found ${testFiles.length} test files to check\n`);
let totalIssues = 0;
const issuesByFile = new Map();
for (const file of testFiles) {
const issues = checkFile(file);
if (issues.length > 0) {
issuesByFile.set(file, issues);
totalIssues += issues.length;
}
}
if (totalIssues === 0) {
console.log('✅ No nested require() patterns found in test files!');
process.exit(0);
}
// Report issues
console.error(
`❌ Found ${totalIssues} nested require() pattern(s) that cause OOM in CI:\n`,
);
for (const [file, issues] of issuesByFile.entries()) {
console.error(`\n${path.relative(process.cwd(), file)}:`);
for (const issue of issues) {
console.error(` Line ${issue.line}:${issue.column} - ${issue.pattern}`);
console.error(` Fix: ${issue.fix}`);
}
}
console.error(
'\n⚠ These patterns cause out-of-memory errors in CI/CD pipelines.',
);
console.error(
' Use ES6 imports at the top of files instead of require() calls.',
);
console.error(
' See .cursor/rules/test-memory-optimization.mdc for details.\n',
);
process.exit(1);
}
main();

View File

@@ -87,41 +87,62 @@ cd "$PROJECT_ROOT"
log "Working directory: $(pwd)" log "Working directory: $(pwd)"
# Clone android-passport-nfc-reader if it doesn't exist (for local development) # Clone private Android modules if they don't exist (for local development)
# Note: In CI, this is usually handled by GitHub action, but we keep this as fallback # Note: In CI, this is usually handled by GitHub action, but we keep this as fallback
if [[ ! -d "app/android/android-passport-nfc-reader" ]]; then clone_private_module() {
log "Cloning android-passport-nfc-reader for build..." local repo_name=$1
local target_dir=$2
if [[ -d "$target_dir" ]]; then
if is_ci; then
log "📁 $repo_name exists (likely cloned by GitHub action)"
else
log "📁 $repo_name already exists - preserving existing directory"
fi
return 0
fi
log "Cloning $repo_name for build..."
cd app/android cd app/android
# Extract just the directory name from the full path for git clone
local dir_name=$(basename "$target_dir")
# Use different clone methods based on environment # Use different clone methods based on environment
if is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then if is_ci && [[ -n "${SELFXYZ_APP_TOKEN:-}" ]]; then
# CI environment with GitHub App installation token
git clone "https://x-access-token:${SELFXYZ_APP_TOKEN}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone $repo_name with GitHub App token"
exit 1
}
elif is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
# CI environment with PAT (fallback if action didn't run) # CI environment with PAT (fallback if action didn't run)
git clone "https://${SELFXYZ_INTERNAL_REPO_PAT}@github.com/selfxyz/android-passport-nfc-reader.git" || { git clone "https://${SELFXYZ_INTERNAL_REPO_PAT}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone android-passport-nfc-reader with PAT" log "ERROR: Failed to clone $repo_name with PAT"
exit 1 exit 1
} }
elif [[ -n "${SSH_AUTH_SOCK:-}" ]] || [[ -f "${HOME}/.ssh/id_rsa" ]] || [[ -f "${HOME}/.ssh/id_ed25519" ]]; then elif [[ -n "${SSH_AUTH_SOCK:-}" ]] || [[ -f "${HOME}/.ssh/id_rsa" ]] || [[ -f "${HOME}/.ssh/id_ed25519" ]]; then
# Local development with SSH # Local development with SSH
git clone "git@github.com:selfxyz/android-passport-nfc-reader.git" || { git clone "git@github.com:selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone android-passport-nfc-reader with SSH" log "ERROR: Failed to clone $repo_name with SSH"
log "Please ensure you have SSH access to the repository or set SELFXYZ_INTERNAL_REPO_PAT" log "Please ensure you have SSH access to the repository or set SELFXYZ_APP_TOKEN/SELFXYZ_INTERNAL_REPO_PAT"
exit 1 exit 1
} }
else else
log "ERROR: No authentication method available for cloning android-passport-nfc-reader" log "ERROR: No authentication method available for cloning $repo_name"
log "Please either:" log "Please either:"
log " - Set up SSH access (for local development)" log " - Set up SSH access (for local development)"
log " - Set SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)" log " - Set SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
exit 1 exit 1
fi fi
cd ../../ cd ../../
log "✅ android-passport-nfc-reader cloned successfully" log "$repo_name cloned successfully"
elif is_ci; then }
log "📁 android-passport-nfc-reader exists (likely cloned by GitHub action)"
else # Clone all required private modules
log "📁 android-passport-nfc-reader already exists - preserving existing directory" clone_private_module "android-passport-nfc-reader" "app/android/android-passport-nfc-reader"
fi clone_private_module "react-native-passport-reader" "app/android/react-native-passport-reader"
# Build and package the SDK with timeout (including dependencies) # Build and package the SDK with timeout (including dependencies)
log "Building SDK and dependencies..." log "Building SDK and dependencies..."
@@ -179,14 +200,15 @@ log "✅ Package files backed up successfully"
# Install SDK from tarball in app with timeout # Install SDK from tarball in app with timeout
log "Installing SDK as real files..." log "Installing SDK as real files..."
if is_ci; then if is_ci; then
# Temporarily unset PAT to skip private modules during SDK installation # Temporarily unset both auth tokens to skip private modules during SDK installation
env -u SELFXYZ_INTERNAL_REPO_PAT timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || { # Both tokens must be unset to prevent setup-private-modules.cjs from attempting clones
env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
log "SDK installation timed out after 3 minutes" log "SDK installation timed out after 3 minutes"
exit 1 exit 1
} }
else else
# Temporarily unset PAT to skip private modules during SDK installation # Temporarily unset both auth tokens to skip private modules during SDK installation
env -u SELFXYZ_INTERNAL_REPO_PAT yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
fi fi
# Verify installation (check for AAR file in both local and hoisted locations) # Verify installation (check for AAR file in both local and hoisted locations)

View File

@@ -10,18 +10,28 @@ const path = require('path');
const SCRIPT_DIR = __dirname; const SCRIPT_DIR = __dirname;
const APP_DIR = path.dirname(SCRIPT_DIR); const APP_DIR = path.dirname(SCRIPT_DIR);
const ANDROID_DIR = path.join(APP_DIR, 'android'); const ANDROID_DIR = path.join(APP_DIR, 'android');
const PRIVATE_MODULE_PATH = path.join(
ANDROID_DIR,
'android-passport-nfc-reader',
);
const GITHUB_ORG = 'selfxyz'; const GITHUB_ORG = 'selfxyz';
const REPO_NAME = 'android-passport-nfc-reader';
const BRANCH = 'main'; const BRANCH = 'main';
const PRIVATE_MODULES = [
{
repoName: 'android-passport-nfc-reader',
localPath: path.join(ANDROID_DIR, 'android-passport-nfc-reader'),
validationFiles: ['app/build.gradle', 'app/src/main/AndroidManifest.xml'],
},
{
repoName: 'react-native-passport-reader',
localPath: path.join(ANDROID_DIR, 'react-native-passport-reader'),
validationFiles: ['android/build.gradle'],
},
];
// Environment detection // Environment detection
const isCI = process.env.CI === 'true'; // CI is set by GitHub Actions, CircleCI, etc. Check for truthy value
const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
const repoToken = process.env.SELFXYZ_INTERNAL_REPO_PAT; const repoToken = process.env.SELFXYZ_INTERNAL_REPO_PAT;
const appToken = process.env.SELFXYZ_APP_TOKEN; // GitHub App installation token
const isDryRun = process.env.DRY_RUN === 'true'; const isDryRun = process.env.DRY_RUN === 'true';
// Platform detection for Android-specific modules // Platform detection for Android-specific modules
@@ -102,13 +112,13 @@ function sanitizeCommandForLogging(command) {
); );
} }
function removeExistingModule() { function removeExistingModule(modulePath, repoName) {
if (fs.existsSync(PRIVATE_MODULE_PATH)) { if (fs.existsSync(modulePath)) {
log(`Removing existing ${REPO_NAME}...`, 'cleanup'); log(`Removing existing ${repoName}...`, 'cleanup');
if (!isDryRun) { if (!isDryRun) {
// Force remove even if it's a git repo // Force remove even if it's a git repo
fs.rmSync(PRIVATE_MODULE_PATH, { fs.rmSync(modulePath, {
recursive: true, recursive: true,
force: true, force: true,
maxRetries: 3, maxRetries: 3,
@@ -116,7 +126,7 @@ function removeExistingModule() {
}); });
} }
log(`Removed existing ${REPO_NAME}`, 'success'); log(`Removed existing ${repoName}`, 'success');
} }
} }
// some of us connect to github via SSH, others via HTTPS with gh auth // some of us connect to github via SSH, others via HTTPS with gh auth
@@ -136,18 +146,22 @@ function usingHTTPSGitAuth() {
} }
} }
function clonePrivateRepo() { function clonePrivateRepo(repoName, localPath) {
log(`Setting up ${REPO_NAME}...`, 'info'); log(`Setting up ${repoName}...`, 'info');
let cloneUrl; let cloneUrl;
if (isCI && repoToken) { if (isCI && appToken) {
// CI environment with GitHub App installation token
log('CI detected: Using SELFXYZ_APP_TOKEN for clone', 'info');
cloneUrl = `https://x-access-token:${appToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
} else if (isCI && repoToken) {
// CI environment with Personal Access Token // CI environment with Personal Access Token
log('CI detected: Using SELFXYZ_INTERNAL_REPO_PAT for clone', 'info'); log('CI detected: Using SELFXYZ_INTERNAL_REPO_PAT for clone', 'info');
cloneUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${REPO_NAME}.git`; cloneUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
} else if (isCI) { } else if (isCI) {
log( log(
'CI environment detected but SELFXYZ_INTERNAL_REPO_PAT not available - skipping private module setup', 'CI environment detected but no token available - skipping private module setup',
'info', 'info',
); );
log( log(
@@ -156,17 +170,18 @@ function clonePrivateRepo() {
); );
return false; // Return false to indicate clone was skipped return false; // Return false to indicate clone was skipped
} else if (usingHTTPSGitAuth()) { } else if (usingHTTPSGitAuth()) {
cloneUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`; cloneUrl = `https://github.com/${GITHUB_ORG}/${repoName}.git`;
} else { } else {
// Local development with SSH // Local development with SSH
log('Local development: Using SSH for clone', 'info'); log('Local development: Using SSH for clone', 'info');
cloneUrl = `git@github.com:${GITHUB_ORG}/${REPO_NAME}.git`; cloneUrl = `git@github.com:${GITHUB_ORG}/${repoName}.git`;
} }
// Security: Use quiet mode for credentialed URLs to prevent token exposure // Security: Use quiet mode for credentialed URLs to prevent token exposure
const isCredentialedUrl = isCI && repoToken; const isCredentialedUrl = isCI && (appToken || repoToken);
const quietFlag = isCredentialedUrl ? '--quiet' : ''; const quietFlag = isCredentialedUrl ? '--quiet' : '';
const cloneCommand = `git clone --branch ${BRANCH} --single-branch --depth 1 ${quietFlag} "${cloneUrl}" android-passport-nfc-reader`; const targetDir = path.basename(localPath);
const cloneCommand = `git clone --branch ${BRANCH} --single-branch --depth 1 ${quietFlag} "${cloneUrl}" "${targetDir}"`;
try { try {
if (isCredentialedUrl) { if (isCredentialedUrl) {
@@ -175,12 +190,12 @@ function clonePrivateRepo() {
} else { } else {
runCommand(cloneCommand); runCommand(cloneCommand);
} }
log(`Successfully cloned ${REPO_NAME}`, 'success'); log(`Successfully cloned ${repoName}`, 'success');
return true; // Return true to indicate successful clone return true; // Return true to indicate successful clone
} catch (error) { } catch (error) {
if (isCI) { if (isCI) {
log( log(
'Clone failed in CI environment. Check SELFXYZ_INTERNAL_REPO_PAT permissions.', 'Clone failed in CI environment. Check SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT permissions.',
'error', 'error',
); );
} else { } else {
@@ -193,65 +208,99 @@ function clonePrivateRepo() {
} }
} }
function validateSetup() { function validateSetup(modulePath, validationFiles, repoName) {
const expectedFiles = [ for (const file of validationFiles) {
'app/build.gradle', const filePath = path.join(modulePath, file);
'app/src/main/AndroidManifest.xml',
];
for (const file of expectedFiles) {
const filePath = path.join(PRIVATE_MODULE_PATH, file);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
throw new Error(`Expected file not found: ${file}`); throw new Error(`Expected file not found in ${repoName}: ${file}`);
} }
} }
log('Private module validation passed', 'success'); log(`${repoName} validation passed`, 'success');
}
function setupPrivateModule(module) {
const { repoName, localPath, validationFiles } = module;
log(`Starting setup of ${repoName}...`, 'info');
// Remove existing module
removeExistingModule(localPath, repoName);
// Clone the private repository
const cloneSuccessful = clonePrivateRepo(repoName, localPath);
// If clone was skipped (e.g., in forked PRs), exit gracefully
if (cloneSuccessful === false) {
log(`${repoName} setup skipped - private module not available`, 'warning');
return false;
}
// Security: Remove credential-embedded remote URL after clone
if (isCI && (appToken || repoToken) && !isDryRun) {
scrubGitRemoteUrl(localPath, repoName);
}
// Validate the setup
if (!isDryRun) {
validateSetup(localPath, validationFiles, repoName);
}
log(`${repoName} setup complete!`, 'success');
return true;
} }
function setupAndroidPassportReader() { function setupAndroidPassportReader() {
log(`Starting setup of ${REPO_NAME}...`, 'info');
// Ensure android directory exists // Ensure android directory exists
if (!fs.existsSync(ANDROID_DIR)) { if (!fs.existsSync(ANDROID_DIR)) {
throw new Error(`Android directory not found: ${ANDROID_DIR}`); throw new Error(`Android directory not found: ${ANDROID_DIR}`);
} }
// Remove existing module log(
removeExistingModule(); `Starting setup of ${PRIVATE_MODULES.length} private module(s)...`,
'info',
);
// Clone the private repository let successCount = 0;
const cloneSuccessful = clonePrivateRepo(); for (const module of PRIVATE_MODULES) {
try {
// If clone was skipped (e.g., in forked PRs), exit gracefully const success = setupPrivateModule(module);
if (cloneSuccessful === false) { if (success) {
log(`${REPO_NAME} setup skipped - private module not available`, 'warning'); successCount++;
return; }
} catch (error) {
log(`Failed to setup ${module.repoName}: ${error.message}`, 'error');
throw error;
}
} }
// Security: Remove credential-embedded remote URL after clone if (successCount === PRIVATE_MODULES.length) {
if (isCI && repoToken && !isDryRun) { log('All private modules setup complete!', 'success');
scrubGitRemoteUrl(); } else if (successCount > 0) {
log(
`Setup complete: ${successCount}/${PRIVATE_MODULES.length} modules cloned`,
'warning',
);
} else {
log(
'No private modules were cloned - this is expected for forked PRs',
'info',
);
} }
// Validate the setup
if (!isDryRun) {
validateSetup();
}
log(`${REPO_NAME} setup complete!`, 'success');
} }
function scrubGitRemoteUrl() { function scrubGitRemoteUrl(modulePath, repoName) {
try { try {
const cleanUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`; const cleanUrl = `https://github.com/${GITHUB_ORG}/${repoName}.git`;
const scrubCommand = `cd "${PRIVATE_MODULE_PATH}" && git remote set-url origin "${cleanUrl}"`; const scrubCommand = `cd "${modulePath}" && git remote set-url origin "${cleanUrl}"`;
log('Scrubbing credential from git remote URL...', 'info'); log(`Scrubbing credential from git remote URL for ${repoName}...`, 'info');
runCommand(scrubCommand, { stdio: 'pipe' }); runCommand(scrubCommand, { stdio: 'pipe' });
log('Git remote URL cleaned', 'success'); log(`Git remote URL cleaned for ${repoName}`, 'success');
} catch (error) { } catch (error) {
log(`Warning: Failed to scrub git remote URL: ${error.message}`, 'warning'); log(
`Warning: Failed to scrub git remote URL for ${repoName}: ${error.message}`,
'warning',
);
// Non-fatal error - continue execution // Non-fatal error - continue execution
} }
} }
@@ -274,5 +323,5 @@ if (require.main === module) {
module.exports = { module.exports = {
setupAndroidPassportReader, setupAndroidPassportReader,
removeExistingModule, removeExistingModule,
PRIVATE_MODULE_PATH, PRIVATE_MODULES,
}; };

View File

@@ -179,14 +179,14 @@ describe('alias-imports transform', () => {
const appRoot = tempRoot; const appRoot = tempRoot;
const srcDir = join(appRoot, 'src'); const srcDir = join(appRoot, 'src');
const testsSrcDir = join(appRoot, 'tests', 'src'); const testsSrcDir = join(appRoot, 'tests', 'src');
const fileHaptic = join(srcDir, 'utils', 'haptic.ts'); const fileHaptic = join(srcDir, 'integrations', 'haptics.ts');
const deepSpecDir = join(testsSrcDir, 'deep'); const deepSpecDir = join(testsSrcDir, 'deep');
const deepSpecFile = join(deepSpecDir, 'spec.ts'); const deepSpecFile = join(deepSpecDir, 'spec.ts');
writeFileEnsured(fileHaptic, 'export const h = 1;\n'); writeFileEnsured(fileHaptic, 'export const h = 1;\n');
writeFileEnsured( writeFileEnsured(
deepSpecFile, deepSpecFile,
"import { h } from '../../../src/utils/haptic';\nexport const v = h;\n", "import { h } from '../../../src/integrations/haptics';\nexport const v = h;\n",
); );
const project = new Project({ const project = new Project({
@@ -203,21 +203,24 @@ describe('alias-imports transform', () => {
const specFile = project.getSourceFileOrThrow(deepSpecFile); const specFile = project.getSourceFileOrThrow(deepSpecFile);
const imports = specFile.getImportDeclarations(); const imports = specFile.getImportDeclarations();
assert.strictEqual(imports.length, 1); assert.strictEqual(imports.length, 1);
assert.strictEqual(imports[0].getModuleSpecifierValue(), '@/utils/haptic'); assert.strictEqual(
imports[0].getModuleSpecifierValue(),
'@/integrations/haptics',
);
}); });
it("transforms deep relative require '../../../src/...' to @src alias from tests", () => { it("transforms deep relative require '../../../src/...' to @src alias from tests", () => {
const appRoot = tempRoot; const appRoot = tempRoot;
const srcDir = join(appRoot, 'src'); const srcDir = join(appRoot, 'src');
const testsSrcDir = join(appRoot, 'tests', 'src'); const testsSrcDir = join(appRoot, 'tests', 'src');
const fileHaptic = join(srcDir, 'utils', 'haptic.ts'); const fileHaptic = join(srcDir, 'integrations', 'haptics.ts');
const deepSpecDir = join(testsSrcDir, 'deep'); const deepSpecDir = join(testsSrcDir, 'deep');
const deepSpecFile = join(deepSpecDir, 'req.ts'); const deepSpecFile = join(deepSpecDir, 'req.ts');
writeFileEnsured(fileHaptic, 'module.exports = { h: 1 };\n'); writeFileEnsured(fileHaptic, 'module.exports = { h: 1 };\n');
writeFileEnsured( writeFileEnsured(
deepSpecFile, deepSpecFile,
"const h = require('../../../src/utils/haptic');\nexport const v = h;\n", "const h = require('../../../src/integrations/haptics');\nexport const v = h;\n",
); );
const project = new Project({ const project = new Project({
@@ -232,7 +235,7 @@ describe('alias-imports transform', () => {
transformProjectToAliasImports(project, appRoot); transformProjectToAliasImports(project, appRoot);
const specFile = project.getSourceFileOrThrow(deepSpecFile); const specFile = project.getSourceFileOrThrow(deepSpecFile);
assert.ok(specFile.getText().includes("require('@/utils/haptic')")); assert.ok(specFile.getText().includes("require('@/integrations/haptics')"));
}); });
it('aliases export star re-exports with ../ from sibling directory', () => { it('aliases export star re-exports with ../ from sibling directory', () => {

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 8.88281C0 8.58594 0.121094 8.32422 0.363281 8.09766L8.12109 0.351562C8.24609 0.226562 8.37109 0.136719 8.49609 0.0820312C8.62891 0.0273437 8.76562 0 8.90625 0C9.19531 0 9.4375 0.0976562 9.63281 0.292969C9.83594 0.480469 9.9375 0.71875 9.9375 1.00781C9.9375 1.14844 9.91016 1.28516 9.85547 1.41797C9.80859 1.54297 9.73828 1.65234 9.64453 1.74609L7.01953 4.41797L2.37891 8.66016L2.13281 8.07422L5.90625 7.83984H20.7305C21.0352 7.83984 21.2812 7.9375 21.4688 8.13281C21.6641 8.32812 21.7617 8.57812 21.7617 8.88281C21.7617 9.1875 21.6641 9.4375 21.4688 9.63281C21.2812 9.82812 21.0352 9.92578 20.7305 9.92578H5.90625L2.13281 9.69141L2.37891 9.11719L7.01953 13.3477L9.64453 16.0195C9.73828 16.1133 9.80859 16.2266 9.85547 16.3594C9.91016 16.4844 9.9375 16.6172 9.9375 16.7578C9.9375 17.0469 9.83594 17.2852 9.63281 17.4727C9.4375 17.668 9.19531 17.7656 8.90625 17.7656C8.625 17.7656 8.37109 17.6562 8.14453 17.4375L0.363281 9.66797C0.121094 9.44141 0 9.17969 0 8.88281Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="22" height="24" viewBox="0 0 22 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.04004 18.7129C1.41699 18.7129 0.919271 18.5446 0.546875 18.208C0.181641 17.8714 -0.000976562 17.4274 -0.000976562 16.876C-0.000976562 16.4678 0.0921224 16.0846 0.27832 15.7266C0.464518 15.3613 0.708008 15.0212 1.00879 14.7061C1.30957 14.3838 1.62826 14.0723 1.96484 13.7715C2.24414 13.5352 2.4554 13.2129 2.59863 12.8047C2.74902 12.3965 2.86361 11.9346 2.94238 11.4189C3.02116 10.9033 3.09993 10.3626 3.17871 9.79688C3.26465 8.57943 3.46875 7.48014 3.79102 6.49902C4.11328 5.51074 4.56803 4.67643 5.15527 3.99609C5.74967 3.30859 6.49089 2.80729 7.37891 2.49219C7.62956 1.76888 8.05208 1.17448 8.64648 0.708984C9.24089 0.236328 9.93555 0 10.7305 0C11.3607 0 11.9193 0.14681 12.4062 0.44043C11.9622 0.905924 11.6077 1.4502 11.3428 2.07324C11.085 2.68913 10.9561 3.34798 10.9561 4.0498C10.9561 5.00944 11.1924 5.8903 11.665 6.69238C12.1449 7.4873 12.7822 8.12467 13.5771 8.60449C14.3792 9.08431 15.2637 9.32422 16.2305 9.32422C16.5885 9.32422 16.9359 9.28841 17.2725 9.2168C17.6162 9.13802 17.9421 9.02702 18.25 8.88379C18.3001 9.20605 18.3431 9.53906 18.3789 9.88281C18.4147 10.2194 18.4469 10.5596 18.4756 10.9033C18.5186 11.2829 18.5794 11.6553 18.6582 12.0205C18.737 12.3857 18.8408 12.7223 18.9697 13.0303C19.1058 13.3382 19.2812 13.5853 19.4961 13.7715C19.8327 14.0723 20.1514 14.3838 20.4521 14.7061C20.7529 15.0212 20.9964 15.3613 21.1826 15.7266C21.376 16.0846 21.4727 16.4678 21.4727 16.876C21.4727 17.4274 21.2865 17.8714 20.9141 18.208C20.5417 18.5446 20.0439 18.7129 19.4209 18.7129H2.04004ZM10.7412 23.3428C10.068 23.3428 9.46289 23.1995 8.92578 22.9131C8.39583 22.6338 7.96973 22.2614 7.64746 21.7959C7.3252 21.3304 7.14258 20.8219 7.09961 20.2705H14.3828C14.3398 20.8219 14.1572 21.3304 13.835 21.7959C13.5127 22.2614 13.083 22.6338 12.5459 22.9131C12.016 23.1995 11.4144 23.3428 10.7412 23.3428ZM16.2412 7.73438C15.568 7.73438 14.9521 7.56608 14.3936 7.22949C13.835 6.8929 13.3874 6.44531 13.0508 5.88672C12.7214 5.32812 12.5566 4.71582 12.5566 4.0498C12.5566 3.37663 12.7214 2.76074 13.0508 2.20215C13.3874 1.64355 13.835 1.19954 14.3936 0.870117C14.9521 0.533529 15.568 0.365234 16.2412 0.365234C16.9072 0.365234 17.5195 0.533529 18.0781 0.870117C18.6367 1.19954 19.0843 1.64355 19.4209 2.20215C19.7575 2.76074 19.9258 3.37663 19.9258 4.0498C19.9258 4.71582 19.7575 5.32812 19.4209 5.88672C19.0843 6.44531 18.6367 6.8929 18.0781 7.22949C17.5195 7.56608 16.9072 7.73438 16.2412 7.73438Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 845 B

After

Width:  |  Height:  |  Size: 845 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.71289 20.2451C0.589844 19.1318 0.0234375 18.0088 0.0136719 16.876C0.00390625 15.7334 0.555664 14.6006 1.66895 13.4775L13.4902 1.6709C14.6035 0.557617 15.7266 0.00585937 16.8594 0.015625C18.002 0.0253906 19.1348 0.591797 20.2578 1.71484L32.0059 13.4629C33.1289 14.5859 33.6904 15.7188 33.6904 16.8613C33.7002 18.0039 33.1484 19.127 32.0352 20.2305L20.2285 32.0518C19.1152 33.1553 17.9873 33.7021 16.8447 33.6924C15.7119 33.6924 14.584 33.1309 13.4609 32.0078L1.71289 20.2451ZM15.2334 24.083C15.5068 24.083 15.7559 24.0195 15.9805 23.8926C16.2148 23.7656 16.4199 23.5752 16.5957 23.3213L23.4512 12.6279C23.5488 12.4717 23.6367 12.3057 23.7148 12.1299C23.793 11.9443 23.832 11.7686 23.832 11.6025C23.832 11.2217 23.6855 10.9141 23.3926 10.6797C23.1094 10.4453 22.7871 10.3281 22.4258 10.3281C21.9473 10.3281 21.5518 10.582 21.2393 11.0898L15.1748 20.7871L12.3623 17.2129C12.167 16.9688 11.9766 16.7979 11.791 16.7002C11.6055 16.6025 11.3955 16.5537 11.1611 16.5537C10.79 16.5537 10.4727 16.6904 10.209 16.9639C9.95508 17.2275 9.82812 17.5449 9.82812 17.916C9.82812 18.1016 9.8623 18.2822 9.93066 18.458C9.99902 18.6338 10.0967 18.8047 10.2236 18.9707L13.8125 23.3359C14.0273 23.5996 14.2471 23.79 14.4717 23.9072C14.6963 24.0244 14.9502 24.083 15.2334 24.083Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 4V8L10.5 9.5" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 444 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="38" height="26" viewBox="0 0 38 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.2539 26.002H9.00977C7.73047 26.002 6.54395 25.7822 5.4502 25.3428C4.36621 24.9131 3.41406 24.3271 2.59375 23.585C1.77344 22.833 1.13379 21.9639 0.674805 20.9775C0.225586 19.9912 0.000976562 18.9414 0.000976562 17.8281C0.000976562 16.6074 0.220703 15.4844 0.660156 14.459C1.09961 13.4238 1.72461 12.5645 2.53516 11.8809C3.3457 11.1973 4.30762 10.7578 5.4209 10.5625C5.45996 9.53711 5.70898 8.60938 6.16797 7.7793C6.62695 6.93945 7.22266 6.24609 7.95508 5.69922C8.69727 5.14258 9.52246 4.77148 10.4307 4.58594C11.3389 4.39063 12.252 4.41992 13.1699 4.67383C13.7852 3.78516 14.5176 2.98926 15.3672 2.28613C16.2168 1.58301 17.1787 1.02637 18.2529 0.616211C19.3369 0.206055 20.5332 0.000976562 21.8418 0.000976562C23.375 0.000976562 24.8008 0.279297 26.1191 0.835938C27.4375 1.39258 28.5898 2.18359 29.5762 3.20898C30.5723 4.23438 31.3438 5.44043 31.8906 6.82715C32.4473 8.21387 32.7256 9.7373 32.7256 11.3975C33.6924 11.7979 34.5273 12.3545 35.2305 13.0674C35.9336 13.7803 36.4707 14.6006 36.8418 15.5283C37.2129 16.4561 37.3984 17.4375 37.3984 18.4727C37.3984 19.5176 37.1885 20.4941 36.7686 21.4023C36.3584 22.3105 35.7822 23.1113 35.04 23.8047C34.2979 24.4883 33.4287 25.0254 32.4326 25.416C31.4463 25.8066 30.3867 26.002 29.2539 26.002ZM17.6377 19.9961C18.1748 19.9961 18.5947 19.7568 18.8975 19.2783L24.6396 10.0498C24.7178 9.92285 24.791 9.78125 24.8594 9.625C24.9375 9.45898 24.9766 9.28809 24.9766 9.1123C24.9766 8.76074 24.8447 8.46777 24.5811 8.2334C24.3174 7.99902 24.0049 7.88184 23.6436 7.88184C23.1553 7.88184 22.7695 8.12109 22.4863 8.59961L17.5791 16.7588L15.0889 13.5654C14.8057 13.165 14.4346 12.9648 13.9756 12.9648C13.624 12.9648 13.3164 13.0918 13.0527 13.3457C12.7988 13.5898 12.6719 13.9023 12.6719 14.2832C12.6719 14.6055 12.7939 14.9229 13.0381 15.2354L16.3193 19.3076C16.5049 19.542 16.7051 19.7178 16.9199 19.835C17.1348 19.9424 17.374 19.9961 17.6377 19.9961Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.79297 18.6377C2.56836 18.6377 1.63021 18.3154 0.978516 17.6709C0.326823 17.0192 0.000976562 16.0846 0.000976562 14.8672V3.77051C0.000976562 2.55306 0.326823 1.62207 0.978516 0.977539C1.63021 0.325846 2.56836 0 3.79297 0H14.8467C16.0641 0 16.9987 0.325846 17.6504 0.977539C18.3021 1.62207 18.6279 2.55306 18.6279 3.77051V6.41309H15.8564V4.11426C15.8564 3.65592 15.7419 3.31934 15.5127 3.10449C15.2907 2.88249 14.9648 2.77148 14.5352 2.77148H4.09375C3.66406 2.77148 3.33464 2.88249 3.10547 3.10449C2.88346 3.31934 2.77246 3.65592 2.77246 4.11426V14.5234C2.77246 14.9818 2.88346 15.3219 3.10547 15.5439C3.33464 15.7588 3.66406 15.8662 4.09375 15.8662H6.92969V18.6377H3.79297ZM9.38965 23.9766C8.15788 23.9766 7.21615 23.6507 6.56445 22.999C5.91276 22.3545 5.58691 21.4235 5.58691 20.2061V9.10938C5.58691 7.89193 5.91276 6.96094 6.56445 6.31641C7.21615 5.66471 8.15788 5.33887 9.38965 5.33887H20.4326C21.6572 5.33887 22.5918 5.66471 23.2363 6.31641C23.888 6.9681 24.2139 7.89909 24.2139 9.10938V20.2061C24.2139 21.4235 23.888 22.3545 23.2363 22.999C22.5918 23.6507 21.6572 23.9766 20.4326 23.9766H9.38965ZM9.69043 21.2051H20.1211C20.5508 21.2051 20.8766 21.0941 21.0986 20.8721C21.3278 20.6572 21.4424 20.3206 21.4424 19.8623V9.45312C21.4424 8.99479 21.3278 8.6582 21.0986 8.44336C20.8766 8.22852 20.5508 8.12109 20.1211 8.12109H9.69043C9.25358 8.12109 8.92057 8.22852 8.69141 8.44336C8.4694 8.6582 8.3584 8.99479 8.3584 9.45312V19.8623C8.3584 20.3206 8.4694 20.6572 8.69141 20.8721C8.92057 21.0941 9.25358 21.2051 9.69043 21.2051Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36">
<path fill="currentColor" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/>
</svg>

After

Width:  |  Height:  |  Size: 774 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Some files were not shown because too many files have changed in this diff Show More