From e0c599b73873aa50e24bd0e4f8abca0efc380169 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 10 Oct 2025 11:12:25 -0700 Subject: [PATCH 1/6] code rabbit feedback for staging auto deploy logic --- .github/workflows/mobile-deploy.yml | 56 ++++- app/fastlane/Fastfile | 20 +- app/fastlane/helpers/version_manager.rb | 20 +- app/scripts/version-manager.cjs | 31 ++- app/scripts/version-manager.test.cjs | 297 ++++++++++++++++++++++++ 5 files changed, 404 insertions(+), 20 deletions(-) create mode 100644 app/scripts/version-manager.test.cjs diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 70ac4b95c..082606409 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -56,8 +56,7 @@ env: IOS_PROV_PROFILE_DIRECTORY: "~/Library/MobileDevice/Provisioning\ Profiles/" permissions: - contents: write - pull-requests: write + contents: read id-token: write on: @@ -150,6 +149,8 @@ jobs: # NOTE: Checks out the triggering branch (staging for PR merges, or the branch where manually triggered) bump-version: runs-on: ubuntu-latest + permissions: + contents: read if: | (github.event_name != 'pull_request' || github.event.pull_request.merged == true) && !contains(github.event.pull_request.labels.*.name, 'skip-deploy') @@ -241,9 +242,26 @@ jobs: echo "✅ Version bump calculated successfully" echo "⚠️ Note: Changes are local only. Will be committed in PR after successful builds." + - name: Verify bump outputs were set + run: | + VERSION="${{ steps.bump.outputs.version }}" + IOS_BUILD="${{ steps.bump.outputs.ios_build }}" + ANDROID_BUILD="${{ steps.bump.outputs.android_build }}" + + if [ -z "$VERSION" ] || [ -z "$IOS_BUILD" ] || [ -z "$ANDROID_BUILD" ]; then + echo "❌ Version bump failed to set required outputs" + echo "version='$VERSION', ios_build='$IOS_BUILD', android_build='$ANDROID_BUILD'" + exit 1 + fi + + echo "✅ All version outputs verified" + build-ios: needs: [bump-version] runs-on: macos-latest-large + permissions: + contents: read + id-token: write if: | (github.event_name != 'pull_request' || github.event.pull_request.merged == true) && ( @@ -804,6 +822,9 @@ jobs: build-android: needs: [bump-version] runs-on: ubuntu-latest + permissions: + contents: read + id-token: write if: | (github.event_name != 'pull_request' || github.event.pull_request.merged == true) && ( @@ -1228,6 +1249,9 @@ jobs: # but create the version bump PR to dev so it can be reviewed before merging to staging create-version-bump-pr: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write needs: [bump-version, build-ios, build-android] if: | always() && @@ -1278,6 +1302,20 @@ jobs: echo "✅ Versions applied successfully" + - name: Verify version changes + run: | + cd ${{ env.APP_PATH }} + + # Check that version files actually changed + if ! git diff --quiet package.json version.json; then + echo "✅ Version changes detected" + git diff package.json version.json + else + echo "⚠️ No version changes detected in package.json or version.json" + echo "This may indicate a problem with version application" + exit 1 + fi + - name: Determine platforms that succeeded and PR title id: platforms run: | @@ -1327,7 +1365,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # Check if branch already exists (idempotent PR creation) + # Check if branch already exists (prevents race condition) if git ls-remote --heads origin "${BRANCH_NAME}" | grep -q "${BRANCH_NAME}"; then echo "⚠️ Branch ${BRANCH_NAME} already exists" echo "ℹ️ Version bump PR may already exist for version ${VERSION}" @@ -1335,6 +1373,14 @@ jobs: exit 0 fi + # Check if PR already exists for this version (additional safety) + EXISTING_PR=$(gh pr list --base ${TARGET_BRANCH} --head ${BRANCH_NAME} --json number --jq '.[0].number' || echo "") + if [ -n "$EXISTING_PR" ]; then + echo "⚠️ PR #${EXISTING_PR} already exists for version ${VERSION}" + echo "ℹ️ Skipping PR creation" + exit 0 + fi + # Commit the version changes cd ${{ env.APP_PATH }} git add package.json version.json @@ -1374,6 +1420,9 @@ jobs: # Create git tags after successful deployment create-release-tags: + runs-on: ubuntu-latest + permissions: + contents: write needs: [bump-version, build-ios, build-android, create-version-bump-pr] if: | always() && @@ -1381,7 +1430,6 @@ jobs: needs.create-version-bump-pr.result == 'success' && (needs.build-ios.result == 'success' || needs.build-android.result == 'success') && (inputs.deployment_track == 'production') - runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index aff78d7b5..7cb68f582 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -49,12 +49,12 @@ ios_xcode_profile_path = "../ios/#{PROJECT_NAME}.xcodeproj" default_platform(:ios) platform :ios do - desc "Sync ios version" + desc "Sync ios version (DEPRECATED)" lane :sync_version do - increment_version_number( - xcodeproj: "ios/#{PROJECT_NAME}.xcodeproj", - version_number: package_version, - ) + UI.error("⛔ This lane is deprecated!") + UI.error("Version management is now centralized in CI.") + UI.error("Use: node scripts/version-manager.cjs apply ") + UI.user_error!("sync_version lane is deprecated - use version-manager.cjs instead") end desc "Push a new build to TestFlight Internal Testing" @@ -247,12 +247,12 @@ platform :ios do end platform :android do - desc "Sync android version" + desc "Sync android version (DEPRECATED)" lane :sync_version do - android_set_version_name( - version_name: package_version, - gradle_file: android_gradle_file_path.gsub("../", ""), - ) + UI.error("⛔ This lane is deprecated!") + UI.error("Version management is now centralized in CI.") + UI.error("Use: node scripts/version-manager.cjs apply ") + UI.user_error!("sync_version lane is deprecated - use version-manager.cjs instead") end desc "Push a new build to Google Play Internal Testing" diff --git a/app/fastlane/helpers/version_manager.rb b/app/fastlane/helpers/version_manager.rb index 5c4f9d6d3..3bb97a6cb 100644 --- a/app/fastlane/helpers/version_manager.rb +++ b/app/fastlane/helpers/version_manager.rb @@ -63,10 +63,26 @@ def verify_ci_version_match android_matches = android_build == expected_android_build unless version_matches && ios_matches && android_matches - UI.error("Version mismatch detected!") + UI.error("❌ Version mismatch detected!") UI.error("Expected: v#{expected_version} (iOS: #{expected_ios_build}, Android: #{expected_android_build})") UI.error("Actual: v#{pkg_version} (iOS: #{ios_build}, Android: #{android_build})") - UI.user_error!("Version mismatch! CI version-manager script should have set these correctly.") + UI.error("") + + # Add specific diagnostics + UI.error("Mismatched fields:") + UI.error(" • package.json version") unless version_matches + UI.error(" • version.json iOS build") unless ios_matches + UI.error(" • version.json Android build") unless android_matches + UI.error("") + + UI.error("💡 Common causes:") + UI.error(" 1. version-manager.cjs 'apply' command didn't run in workflow") + UI.error(" 2. Files were modified after version bump was applied") + UI.error(" 3. CI_VERSION, CI_IOS_BUILD, or CI_ANDROID_BUILD env vars are incorrect") + UI.error("") + UI.error("🔍 Debug: Check workflow logs for 'Apply version bump' step") + + UI.user_error!("Version verification failed") end UI.success("✅ Version verification passed:") diff --git a/app/scripts/version-manager.cjs b/app/scripts/version-manager.cjs index a3317d6bc..b8c9ec0cb 100755 --- a/app/scripts/version-manager.cjs +++ b/app/scripts/version-manager.cjs @@ -204,10 +204,33 @@ function bumpVersion(bumpType, platform = 'both') { * Apply version changes to files */ function applyVersions(version, iosBuild, androidBuild) { + // Validate version format (semver X.Y.Z) + if ( + !version || + typeof version !== 'string' || + !/^\d+\.\d+\.\d+$/.test(version) + ) { + throw new Error(`Invalid version format: ${version}. Expected X.Y.Z`); + } + + // Validate and coerce build numbers + const iosNum = Number(iosBuild); + const androidNum = Number(androidBuild); + + if (!Number.isInteger(iosNum) || iosNum < 1) { + throw new Error(`Invalid iOS build: ${iosBuild}. Must be positive integer`); + } + + if (!Number.isInteger(androidNum) || androidNum < 1) { + throw new Error( + `Invalid Android build: ${androidBuild}. Must be positive integer`, + ); + } + console.log(`📝 Applying versions to files...`); console.log(` Version: ${version}`); - console.log(` iOS Build: ${iosBuild}`); - console.log(` Android Build: ${androidBuild}`); + console.log(` iOS Build: ${iosNum}`); + console.log(` Android Build: ${androidNum}`); // Update package.json const pkg = readPackageJson(); @@ -217,8 +240,8 @@ function applyVersions(version, iosBuild, androidBuild) { // Update version.json const versionData = readVersionJson(); - versionData.ios.build = iosBuild; - versionData.android.build = androidBuild; + versionData.ios.build = iosNum; + versionData.android.build = androidNum; writeVersionJson(versionData); console.log(`✅ Updated version.json`); } diff --git a/app/scripts/version-manager.test.cjs b/app/scripts/version-manager.test.cjs new file mode 100644 index 000000000..20d42ed3c --- /dev/null +++ b/app/scripts/version-manager.test.cjs @@ -0,0 +1,297 @@ +#!/usr/bin/env node + +// 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. + +/** + * Unit tests for version-manager.cjs + */ + +const fs = require('fs'); +const path = require('path'); + +// Mock file system operations +const mockPackageJson = { version: '1.2.3' }; +const mockVersionJson = { + ios: { build: 100, lastDeployed: '2024-01-01T00:00:00Z' }, + android: { build: 200, lastDeployed: '2024-01-01T00:00:00Z' }, +}; + +// Store original functions +const originalReadFileSync = fs.readFileSync; +const originalWriteFileSync = fs.writeFileSync; +const originalExistsSync = fs.existsSync; + +// Mock fs functions +function setupMocks() { + fs.readFileSync = jest.fn(filePath => { + if (filePath.includes('package.json')) { + return JSON.stringify(mockPackageJson); + } + if (filePath.includes('version.json')) { + return JSON.stringify(mockVersionJson); + } + return originalReadFileSync(filePath); + }); + + fs.writeFileSync = jest.fn(); + fs.existsSync = jest.fn(() => true); +} + +function restoreMocks() { + fs.readFileSync = originalReadFileSync; + fs.writeFileSync = originalWriteFileSync; + fs.existsSync = originalExistsSync; +} + +// Import module after setting up mocks +let versionManager; + +describe('version-manager', () => { + beforeAll(() => { + setupMocks(); + versionManager = require('./version-manager.cjs'); + }); + + afterAll(() => { + restoreMocks(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + // Reset mock data + mockPackageJson.version = '1.2.3'; + mockVersionJson.ios.build = 100; + mockVersionJson.android.build = 200; + }); + + describe('getVersionInfo', () => { + it('should return current version information', () => { + const info = versionManager.getVersionInfo(); + expect(info.version).toBe('1.2.3'); + expect(info.iosBuild).toBe(100); + expect(info.androidBuild).toBe(200); + }); + }); + + describe('bumpVersion', () => { + it('should bump major version correctly', () => { + const result = versionManager.bumpVersion('major', 'both'); + expect(result.version).toBe('2.0.0'); + expect(result.iosBuild).toBe(101); + expect(result.androidBuild).toBe(201); + }); + + it('should bump minor version correctly', () => { + const result = versionManager.bumpVersion('minor', 'both'); + expect(result.version).toBe('1.3.0'); + expect(result.iosBuild).toBe(101); + expect(result.androidBuild).toBe(201); + }); + + it('should bump patch version correctly', () => { + const result = versionManager.bumpVersion('patch', 'both'); + expect(result.version).toBe('1.2.4'); + expect(result.iosBuild).toBe(101); + expect(result.androidBuild).toBe(201); + }); + + it('should bump build numbers only', () => { + const result = versionManager.bumpVersion('build', 'both'); + expect(result.version).toBe('1.2.3'); + expect(result.iosBuild).toBe(101); + expect(result.androidBuild).toBe(201); + }); + + it('should respect platform parameter (ios only)', () => { + const result = versionManager.bumpVersion('build', 'ios'); + expect(result.version).toBe('1.2.3'); + expect(result.iosBuild).toBe(101); + expect(result.androidBuild).toBe(200); // unchanged + }); + + it('should respect platform parameter (android only)', () => { + const result = versionManager.bumpVersion('build', 'android'); + expect(result.version).toBe('1.2.3'); + expect(result.iosBuild).toBe(100); // unchanged + expect(result.androidBuild).toBe(201); + }); + + it('should throw on invalid bump type', () => { + expect(() => versionManager.bumpVersion('invalid', 'both')).toThrow( + /Invalid bump type/, + ); + }); + + it('should throw on invalid platform', () => { + expect(() => versionManager.bumpVersion('build', 'invalid')).toThrow( + /Invalid platform/, + ); + }); + + it('should handle version with major bump resetting minor and patch', () => { + mockPackageJson.version = '2.5.8'; + const result = versionManager.bumpVersion('major', 'both'); + expect(result.version).toBe('3.0.0'); + }); + + it('should handle version with minor bump resetting patch', () => { + mockPackageJson.version = '2.5.8'; + const result = versionManager.bumpVersion('minor', 'both'); + expect(result.version).toBe('2.6.0'); + }); + }); + + describe('applyVersions', () => { + it('should reject invalid version format - not semver', () => { + expect(() => versionManager.applyVersions('invalid', 1, 1)).toThrow( + /Invalid version format/, + ); + }); + + it('should reject invalid version format - two parts', () => { + expect(() => versionManager.applyVersions('1.2', 1, 1)).toThrow( + /Invalid version format/, + ); + }); + + it('should reject invalid version format - four parts', () => { + expect(() => versionManager.applyVersions('1.2.3.4', 1, 1)).toThrow( + /Invalid version format/, + ); + }); + + it('should reject invalid version format - empty string', () => { + expect(() => versionManager.applyVersions('', 1, 1)).toThrow( + /Invalid version format/, + ); + }); + + it('should reject invalid version format - null', () => { + expect(() => versionManager.applyVersions(null, 1, 1)).toThrow( + /Invalid version format/, + ); + }); + + it('should reject invalid iOS build number - zero', () => { + expect(() => versionManager.applyVersions('1.2.3', 0, 1)).toThrow( + /Invalid iOS build/, + ); + }); + + it('should reject invalid iOS build number - negative', () => { + expect(() => versionManager.applyVersions('1.2.3', -1, 1)).toThrow( + /Invalid iOS build/, + ); + }); + + it('should reject invalid iOS build number - non-numeric string', () => { + expect(() => versionManager.applyVersions('1.2.3', 'abc', 1)).toThrow( + /Invalid iOS build/, + ); + }); + + it('should reject invalid iOS build number - float', () => { + expect(() => versionManager.applyVersions('1.2.3', 1.5, 1)).toThrow( + /Invalid iOS build/, + ); + }); + + it('should reject invalid Android build number - zero', () => { + expect(() => versionManager.applyVersions('1.2.3', 1, 0)).toThrow( + /Invalid Android build/, + ); + }); + + it('should reject invalid Android build number - negative', () => { + expect(() => versionManager.applyVersions('1.2.3', 1, -1)).toThrow( + /Invalid Android build/, + ); + }); + + it('should reject invalid Android build number - non-numeric string', () => { + expect(() => versionManager.applyVersions('1.2.3', 1, 'xyz')).toThrow( + /Invalid Android build/, + ); + }); + + it('should reject invalid Android build number - float', () => { + expect(() => versionManager.applyVersions('1.2.3', 1, 2.5)).toThrow( + /Invalid Android build/, + ); + }); + + it('should accept string build numbers that parse to integers', () => { + expect(() => + versionManager.applyVersions('1.2.3', '100', '200'), + ).not.toThrow(); + expect(fs.writeFileSync).toHaveBeenCalled(); + }); + + it('should accept large build numbers', () => { + expect(() => + versionManager.applyVersions('1.2.3', 99999, 88888), + ).not.toThrow(); + }); + + it('should write correct values to files', () => { + versionManager.applyVersions('2.0.0', 150, 250); + + // Check that writeFileSync was called + expect(fs.writeFileSync).toHaveBeenCalledTimes(2); + + // Verify the writes contain correct data + const calls = fs.writeFileSync.mock.calls; + const packageJsonCall = calls.find(call => + call[0].includes('package.json'), + ); + const versionJsonCall = calls.find(call => + call[0].includes('version.json'), + ); + + expect(packageJsonCall).toBeDefined(); + expect(versionJsonCall).toBeDefined(); + + // Parse and verify package.json update + const updatedPackage = JSON.parse(packageJsonCall[1]); + expect(updatedPackage.version).toBe('2.0.0'); + + // Parse and verify version.json update + const updatedVersion = JSON.parse(versionJsonCall[1]); + expect(updatedVersion.ios.build).toBe(150); + expect(updatedVersion.android.build).toBe(250); + }); + }); + + describe('readPackageJson', () => { + it('should read and parse package.json', () => { + const pkg = versionManager.readPackageJson(); + expect(pkg.version).toBe('1.2.3'); + }); + + it('should throw error if file does not exist', () => { + fs.existsSync = jest.fn(() => false); + expect(() => versionManager.readPackageJson()).toThrow( + /package.json not found/, + ); + fs.existsSync = jest.fn(() => true); // restore + }); + }); + + describe('readVersionJson', () => { + it('should read and parse version.json', () => { + const version = versionManager.readVersionJson(); + expect(version.ios.build).toBe(100); + expect(version.android.build).toBe(200); + }); + + it('should throw error if file does not exist', () => { + fs.existsSync = jest.fn(() => false); + expect(() => versionManager.readVersionJson()).toThrow( + /version.json not found/, + ); + fs.existsSync = jest.fn(() => true); // restore + }); + }); +}); From af787b1da6131a1dbed0f4d84969e8f4337a696d Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 10 Oct 2025 11:24:03 -0700 Subject: [PATCH 2/6] fix jest test --- app/jest.config.cjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/jest.config.cjs b/app/jest.config.cjs index 7d552df0d..b61b4744e 100644 --- a/app/jest.config.cjs +++ b/app/jest.config.cjs @@ -4,12 +4,15 @@ module.exports = { preset: 'react-native', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'cjs', 'json', 'node'], 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)/)', ], setupFiles: ['/jest.setup.js'], - testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$', + testMatch: [ + '**/__tests__/**/*.(js|jsx|ts|tsx|cjs)', + '**/*.(test|spec).(js|jsx|ts|tsx|cjs)', + ], moduleNameMapper: { '^@env$': '/tests/__setup__/@env.js', '\\.svg$': '/tests/__setup__/svgMock.js', From 68588542f0bf84b8878393c4ee2062c706161a0e Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 10 Oct 2025 16:16:41 -0700 Subject: [PATCH 3/6] cr feedback --- .github/workflows/mobile-deploy.yml | 2 -- app/jest.config.cjs | 8 ++++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 082606409..184a70b1a 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -57,7 +57,6 @@ env: permissions: contents: read - id-token: write on: workflow_dispatch: @@ -261,7 +260,6 @@ jobs: runs-on: macos-latest-large permissions: contents: read - id-token: write if: | (github.event_name != 'pull_request' || github.event.pull_request.merged == true) && ( diff --git a/app/jest.config.cjs b/app/jest.config.cjs index b61b4744e..fd54e51a5 100644 --- a/app/jest.config.cjs +++ b/app/jest.config.cjs @@ -10,8 +10,12 @@ module.exports = { ], setupFiles: ['/jest.setup.js'], testMatch: [ - '**/__tests__/**/*.(js|jsx|ts|tsx|cjs)', - '**/*.(test|spec).(js|jsx|ts|tsx|cjs)', + '/**/__tests__/**/*.{js,jsx,ts,tsx,cjs}', + '/**/?(*.)+(spec|test).{js,jsx,ts,tsx,cjs}', + ], + testPathIgnorePatterns: [ + '/node_modules/', + '/scripts/tests/', // Node.js native test runner tests ], moduleNameMapper: { '^@env$': '/tests/__setup__/@env.js', From c072478986bde010b13999b116756c93795f1234 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Sun, 12 Oct 2025 21:59:48 -0700 Subject: [PATCH 4/6] workflow fixes --- .github/workflows/mobile-deploy.yml | 8 ++++---- .github/workflows/release-calendar.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 08fb99556..0cfb53a07 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -301,7 +301,7 @@ jobs: echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" - name: Verify branch and commit (iOS) - if: inputs.platform != 'android' + if: needs.bump-version.outputs.platform != 'android' run: | echo "🔍 Verifying we're building from the correct branch and commit..." echo "Current branch: $(git branch --show-current || git symbolic-ref --short HEAD 2>/dev/null || echo 'detached')" @@ -325,7 +325,7 @@ jobs: fi - name: Apply version bump for build - if: inputs.platform != 'android' + if: needs.bump-version.outputs.platform != 'android' run: | cd ${{ env.APP_PATH }} @@ -929,7 +929,7 @@ jobs: echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV" - name: Verify branch and commit (Android) - if: inputs.platform != 'ios' + if: needs.bump-version.outputs.platform != 'ios' run: | echo "🔍 Verifying we're building from the correct branch and commit..." echo "Current branch: $(git branch --show-current || git symbolic-ref --short HEAD 2>/dev/null || echo 'detached')" @@ -949,7 +949,7 @@ jobs: fi - name: Apply version bump for build - if: inputs.platform != 'ios' + if: needs.bump-version.outputs.platform != 'ios' run: | cd ${{ env.APP_PATH }} diff --git a/.github/workflows/release-calendar.yml b/.github/workflows/release-calendar.yml index f63e24cab..6cf54cc08 100644 --- a/.github/workflows/release-calendar.yml +++ b/.github/workflows/release-calendar.yml @@ -160,7 +160,7 @@ jobs: - name: Create dev to staging release PR if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.check_dev_staging.outputs.existing_pr == '' }} env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} PR_DATE: ${{ steps.check_dev_staging.outputs.date }} BRANCH_NAME: ${{ steps.check_dev_staging.outputs.branch_name }} shell: bash @@ -328,7 +328,7 @@ jobs: - name: Create staging to main release PR if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.production_status.outputs.staging_not_ahead != 'true' && steps.production_status.outputs.existing_pr == '' }} env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }} PR_DATE: ${{ steps.production_status.outputs.date }} COMMITS_AHEAD: ${{ steps.production_status.outputs.commits }} shell: bash From 3a09ad4e676218863541c1c89a20bb6ada66349c Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Sun, 12 Oct 2025 22:57:13 -0700 Subject: [PATCH 5/6] fix tests --- app/scripts/version-manager.test.cjs | 111 ++++++++++++++++----------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/app/scripts/version-manager.test.cjs b/app/scripts/version-manager.test.cjs index 20d42ed3c..8f09e60c2 100644 --- a/app/scripts/version-manager.test.cjs +++ b/app/scripts/version-manager.test.cjs @@ -1,71 +1,83 @@ -#!/usr/bin/env node - // 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. +/** + * @jest-environment node + * + * 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. + */ + /** * Unit tests for version-manager.cjs + * + * This file is only meant to be run with Jest. */ -const fs = require('fs'); const path = require('path'); -// Mock file system operations +// Mock file system operations - data const mockPackageJson = { version: '1.2.3' }; const mockVersionJson = { ios: { build: 100, lastDeployed: '2024-01-01T00:00:00Z' }, android: { build: 200, lastDeployed: '2024-01-01T00:00:00Z' }, }; -// Store original functions +// Use manual mocking instead of jest.mock to avoid hoisting issues +const fs = require('fs'); + +// Store originals for restore const originalReadFileSync = fs.readFileSync; const originalWriteFileSync = fs.writeFileSync; const originalExistsSync = fs.existsSync; +const originalAppendFileSync = fs.appendFileSync; -// Mock fs functions +// Setup mocks before importing the module function setupMocks() { - fs.readFileSync = jest.fn(filePath => { + fs.readFileSync = function (filePath, encoding) { if (filePath.includes('package.json')) { return JSON.stringify(mockPackageJson); } if (filePath.includes('version.json')) { return JSON.stringify(mockVersionJson); } - return originalReadFileSync(filePath); - }); - - fs.writeFileSync = jest.fn(); - fs.existsSync = jest.fn(() => true); + return originalReadFileSync(filePath, encoding); + }; + + fs.writeFileSync = function () {}; + fs.existsSync = function () { + return true; + }; + fs.appendFileSync = function () {}; } function restoreMocks() { fs.readFileSync = originalReadFileSync; fs.writeFileSync = originalWriteFileSync; fs.existsSync = originalExistsSync; + fs.appendFileSync = originalAppendFileSync; } -// Import module after setting up mocks -let versionManager; +// Setup mocks before requiring the module +setupMocks(); -describe('version-manager', () => { - beforeAll(() => { - setupMocks(); - versionManager = require('./version-manager.cjs'); - }); - - afterAll(() => { - restoreMocks(); - }); +// Import module after mocks are set up +const versionManager = require('./version-manager.cjs'); +describe('version-manager', () => { beforeEach(() => { - jest.clearAllMocks(); // Reset mock data mockPackageJson.version = '1.2.3'; mockVersionJson.ios.build = 100; mockVersionJson.android.build = 200; }); + afterAll(() => { + restoreMocks(); + }); + describe('getVersionInfo', () => { it('should return current version information', () => { const info = versionManager.getVersionInfo(); @@ -226,7 +238,6 @@ describe('version-manager', () => { expect(() => versionManager.applyVersions('1.2.3', '100', '200'), ).not.toThrow(); - expect(fs.writeFileSync).toHaveBeenCalled(); }); it('should accept large build numbers', () => { @@ -236,29 +247,31 @@ describe('version-manager', () => { }); it('should write correct values to files', () => { + // Track write calls + const writeCalls = []; + fs.writeFileSync = function (filePath, content) { + writeCalls.push({ filePath, content }); + }; + versionManager.applyVersions('2.0.0', 150, 250); - // Check that writeFileSync was called - expect(fs.writeFileSync).toHaveBeenCalledTimes(2); + // Verify writes occurred + expect(writeCalls.length).toBe(2); - // Verify the writes contain correct data - const calls = fs.writeFileSync.mock.calls; - const packageJsonCall = calls.find(call => - call[0].includes('package.json'), - ); - const versionJsonCall = calls.find(call => - call[0].includes('version.json'), + // Find and verify package.json write + const packageWrite = writeCalls.find(call => + call.filePath.includes('package.json'), ); - - expect(packageJsonCall).toBeDefined(); - expect(versionJsonCall).toBeDefined(); - - // Parse and verify package.json update - const updatedPackage = JSON.parse(packageJsonCall[1]); + expect(packageWrite).toBeDefined(); + const updatedPackage = JSON.parse(packageWrite.content); expect(updatedPackage.version).toBe('2.0.0'); - // Parse and verify version.json update - const updatedVersion = JSON.parse(versionJsonCall[1]); + // Find and verify version.json write + const versionWrite = writeCalls.find(call => + call.filePath.includes('version.json'), + ); + expect(versionWrite).toBeDefined(); + const updatedVersion = JSON.parse(versionWrite.content); expect(updatedVersion.ios.build).toBe(150); expect(updatedVersion.android.build).toBe(250); }); @@ -271,11 +284,14 @@ describe('version-manager', () => { }); it('should throw error if file does not exist', () => { - fs.existsSync = jest.fn(() => false); + const originalExists = fs.existsSync; + fs.existsSync = function () { + return false; + }; expect(() => versionManager.readPackageJson()).toThrow( /package.json not found/, ); - fs.existsSync = jest.fn(() => true); // restore + fs.existsSync = originalExists; }); }); @@ -287,11 +303,14 @@ describe('version-manager', () => { }); it('should throw error if file does not exist', () => { - fs.existsSync = jest.fn(() => false); + const originalExists = fs.existsSync; + fs.existsSync = function () { + return false; + }; expect(() => versionManager.readVersionJson()).toThrow( /version.json not found/, ); - fs.existsSync = jest.fn(() => true); // restore + fs.existsSync = originalExists; }); }); }); From 73db86a6f81a0ef2d2560dd9e7f851a2b5154ebe Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Fri, 17 Oct 2025 05:46:56 -0700 Subject: [PATCH 6/6] restore cache --- .github/workflows/mobile-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 0cfb53a07..f21b8782e 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -260,6 +260,7 @@ jobs: runs-on: macos-latest-large permissions: contents: read + actions: write if: | (github.event_name != 'pull_request' || github.event.pull_request.merged == true) && ( @@ -822,6 +823,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + actions: write id-token: write if: | (github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&