diff --git a/.github/workflows/build-sample-apps.yml b/.github/workflows/build-sample-apps.yml index c24e7d13..fad92820 100644 --- a/.github/workflows/build-sample-apps.yml +++ b/.github/workflows/build-sample-apps.yml @@ -58,16 +58,21 @@ jobs: sample-app: # List all sample apps you want to have compiled. # List item is name of directory inside of "Apps" directory for the corresponding app to compile. - - "APN" - - "FCM" + - name: "APN" + cio-workspace-name: "Mobile: React Native" + - name: "FCM" + cio-workspace-name: "Mobile: xReact Native FCM workspace" defaults: run: - working-directory: apps/${{ matrix.sample-app }} + working-directory: apps/${{ matrix.sample-app.name }} runs-on: macos-14 - name: Building sample app ${{ matrix.sample-app }} + name: Building sample app ${{ matrix.sample-app.name }} steps: - - uses: actions/checkout@v4 + - name: Check out code with conditional fetch-depth + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Workaround for bug https://github.com/actions/checkout/issues/1471 # Install CLI tools, Ruby, and Ruby dependencies for Fastlane @@ -80,7 +85,7 @@ jobs: NEXT_BUILDS_GROUP: next PUBLIC_BUILDS_GROUP: public # Input variables - IS_PRIMARY_APP: ${{ matrix.sample-app == 'APN' }} + IS_PRIMARY_APP: ${{ matrix.sample-app.name == 'APN' }} CURRENT_BRANCH: ${{ github.ref }} run: | # Initialize with the default distribution group @@ -107,21 +112,21 @@ jobs: with: ruby-version: ${{ env.RUBY_VERSION }} bundler-cache: true # cache tools to make builds faster in future - working-directory: apps/${{ matrix.sample-app }} + working-directory: apps/${{ matrix.sample-app.name }} # Update version numbers and workspace credentials before building the app - name: Generate New Version uses: maierj/fastlane-action@v3.1.0 with: - subdirectory: Apps/${{ matrix.sample-app }} + subdirectory: Apps/${{ matrix.sample-app.name }} lane: "generate_new_version" options: '{"branch_name":"${{ github.ref_name }}", "pull_request_number":"${{ github.event.pull_request.number }}"}' - name: Update React Native SDK Version uses: maierj/fastlane-action@v3.1.0 with: - subdirectory: Apps/${{ matrix.sample-app }} + subdirectory: Apps/${{ matrix.sample-app.name }} lane: "update_react_native_sdk_version" env: SDK_VERSION_NAME: ${{ env.SDK_VERSION_NAME }} @@ -131,7 +136,7 @@ jobs: - name: Update Sample App Version uses: maierj/fastlane-action@v3.1.0 with: - subdirectory: Apps/${{ matrix.sample-app }} + subdirectory: Apps/${{ matrix.sample-app.name }} lane: "update_react_native_app_version" env: SDK_VERSION_NAME: ${{ env.SDK_VERSION_NAME }} @@ -139,6 +144,9 @@ jobs: APP_VERSION_CODE: ${{ env.APP_VERSION_CODE }} - name: Setup workspace credentials in React Native environment files + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + COMMIT_HASH: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} run: | # Determine the correct file extension (.js or .ts) if [ -f "env.sample.js" ]; then @@ -153,15 +161,22 @@ jobs: fi # Update keys in the environment file - sd "siteId: '.*'" "siteId: '${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_SITE_ID', matrix.sample-app)] }}'" "$ENV_FILE" - sd "cdpApiKey: '.*'" "cdpApiKey: '${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_CDP_API_KEY', matrix.sample-app)] }}'" "$ENV_FILE" + sd "buildTimestamp: .*" "buildTimestamp: $(date +%s)," "$ENV_FILE" + sd "cdpApiKey: '.*'" "cdpApiKey: '${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_CDP_API_KEY', matrix.sample-app.name)] }}'" "$ENV_FILE" + sd "siteId: '.*'" "siteId: '${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_SITE_ID', matrix.sample-app.name)] }}'" "$ENV_FILE" + sd "workspaceName: '.*'" "workspaceName: '${{ matrix.sample-app.cio-workspace-name }}'" "$ENV_FILE" + sd "branchName: '.*'" "branchName: '$BRANCH_NAME'" "$ENV_FILE" + sd "commitHash: '.*'" "commitHash: '${COMMIT_HASH:0:7}'" "$ENV_FILE" + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "untagged") + COMMITS_AHEAD=$(git rev-list $LAST_TAG..HEAD --count 2>/dev/null || echo "untracked") + sd "commitsAheadCount: '.*'" "commitsAheadCount: '$COMMITS_AHEAD'" "$ENV_FILE" - name: Setup workspace credentials in iOS environment files - working-directory: Apps/${{ matrix.sample-app }}/ios + working-directory: Apps/${{ matrix.sample-app.name }}/ios run: | cp "Env.swift.example" "Env.swift" - sd 'siteId: String = ".*"' "siteId: String = \"${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_SITE_ID', matrix.sample-app)] }}\"" "Env.swift" - sd 'cdpApiKey: String = ".*"' "cdpApiKey: String = \"${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_CDP_API_KEY', matrix.sample-app)] }}\"" "Env.swift" + sd 'siteId: String = ".*"' "siteId: String = \"${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_SITE_ID', matrix.sample-app.name)] }}\"" "Env.swift" + sd 'cdpApiKey: String = ".*"' "cdpApiKey: String = \"${{ secrets[format('CUSTOMERIO_{0}_WORKSPACE_CDP_API_KEY', matrix.sample-app.name)] }}\"" "Env.swift" # Make sure to fetch dependencies only after updating version numbers and workspace credentials @@ -175,10 +190,10 @@ jobs: - name: Cache CocoaPods downloaded dependencies for faster builds in the future uses: actions/cache@v4 with: - path: Apps/${{ matrix.sample-app }}/Pods - key: ${{ runner.os }}-${{ matrix.sample-app }}-Pods-${{ github.ref }} + path: Apps/${{ matrix.sample-app.name }}/Pods + key: ${{ runner.os }}-${{ matrix.sample-app.name }}-Pods-${{ github.ref }} restore-keys: | - ${{ runner.os }}-${{ matrix.sample-app }}-Pods + ${{ runner.os }}-${{ matrix.sample-app.name }}-Pods - name: Install dependencies to build SDK run: npm ci @@ -196,7 +211,7 @@ jobs: id: android_build uses: maierj/fastlane-action@v3.1.0 with: - subdirectory: Apps/${{ matrix.sample-app }} + subdirectory: Apps/${{ matrix.sample-app.name }} lane: 'android build' options: '{"distribution_groups": "${{ env.firebase_distribution_groups }}"}' env: @@ -214,7 +229,7 @@ jobs: id: ios_build uses: maierj/fastlane-action@v3.1.0 with: - subdirectory: Apps/${{ matrix.sample-app }} + subdirectory: Apps/${{ matrix.sample-app.name }} lane: "ios build" options: '{"distribution_groups": "${{ env.firebase_distribution_groups }}"}' env: @@ -238,7 +253,7 @@ jobs: issue-number: ${{ github.event.pull_request.number }} # the variables APP_VERSION_NAME, APP_VERSION_CODE are generated above in generate_new_version lane body: | - * ${{ matrix.sample-app }}: `${{ env.APP_VERSION_NAME }} (${{ env.APP_VERSION_CODE }})` + * ${{ matrix.sample-app.name }}: `${{ env.APP_VERSION_NAME }} (${{ env.APP_VERSION_CODE }})` edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds. - name: Update sample builds PR comment with build failure message @@ -248,5 +263,5 @@ jobs: comment-id: ${{ needs.update-pr-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | - * ${{ matrix.sample-app }}: Build failed. See [CI job logs](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}) to determine the issue and try re-building. + * ${{ matrix.sample-app.name }}: Build failed. See [CI job logs](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}) to determine the issue and try re-building. edit-mode: append # append new line to the existing PR comment to build a list of all sample app builds. \ No newline at end of file diff --git a/Apps/APN/env.sample.js b/Apps/APN/env.sample.js index a04fde00..b77a9b12 100644 --- a/Apps/APN/env.sample.js +++ b/Apps/APN/env.sample.js @@ -1,6 +1,11 @@ const Env = { - siteId: 'YourSiteId', - cdpApiKey: 'YourCdpApiKey', + buildTimestamp: Math.floor(Date.now() / 1000), + cdpApiKey: 'CDP_API_KEY', + siteId: 'SITE_ID', + workspaceName: 'WORKSPACE_NAME', + branchName: 'BRANCH_NAME', + commitHash: 'COMMIT_HASH', + commitsAheadCount: 'COMMITS_AHEAD_COUNT', }; export default Env; diff --git a/Apps/APN/src/components/BuildInfoText.js b/Apps/APN/src/components/BuildInfoText.js index 4bb5289a..6a5248b2 100644 --- a/Apps/APN/src/components/BuildInfoText.js +++ b/Apps/APN/src/components/BuildInfoText.js @@ -1,24 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { getBuildNumber, getVersion } from 'react-native-device-info'; +import { BuildMetadata } from '../utils/build'; import { Caption } from './Text'; const BuildInfoText = () => { - const [buildInfo, setBuildInfo] = useState(''); - - useEffect(() => { - const sdkPackageJson = require('customerio-reactnative/package.json'); - - const value = - `Customer.io` + - ` React Native SDK ${sdkPackageJson.version}` + - ` APN Sample ${getVersion()} (${getBuildNumber()})`; - setBuildInfo(value); - }, [buildInfo]); + const metadata = BuildMetadata.toString(); return ( - {buildInfo} + {metadata} ); }; @@ -27,9 +17,13 @@ const styles = StyleSheet.create({ buildInfoContainer: { alignItems: 'center', justifyContent: 'center', - marginBottom: 32, + marginBottom: 16, paddingHorizontal: 16, }, + buildInfoText: { + width: '100%', + textAlign: 'left', + }, }); export default BuildInfoText; diff --git a/Apps/APN/src/utils/build.js b/Apps/APN/src/utils/build.js new file mode 100644 index 00000000..f55dfb2b --- /dev/null +++ b/Apps/APN/src/utils/build.js @@ -0,0 +1,91 @@ +import { getVersion } from 'react-native-device-info'; +import Env from '../../env'; + +const BuildMetadata = { + sdkVersion: getSdkVersion(), + appVersion: resolveValidOrElse(getVersion()), + buildDate: formatBuildDateWithRelativeTime(Env.buildTimestamp), + gitMetadata: `${resolveValidOrElse( + Env.branchName, + () => 'development build' + )}-${resolveValidOrElse(Env.commitHash, () => 'untracked')}`, + defaultWorkspace: resolveValidOrElse(Env.workspaceName), + language: 'JavaScript', + uiFramework: 'React Native', + sdkIntegration: 'npm', + + toString() { + return ` + SDK Version: ${this.sdkVersion} \tApp Version: ${this.appVersion} + Build Date: ${this.buildDate} + Branch: ${this.gitMetadata} + Default Workspace: ${this.defaultWorkspace} + Language: ${this.language} \tUI Framework: ${this.uiFramework} + SDK Integration: ${this.sdkIntegration} + `; + }, +}; + +function resolveValidOrElse(value, fallback = () => 'unknown') { + return value && value.trim() ? value : fallback(); +} + +function formatBuildDateWithRelativeTime(timestamp) { + if (!timestamp) return 'unavailable'; + const parsedTimestamp = parseInt(timestamp, 10); + if (isNaN(parsedTimestamp)) return 'invalid timestamp'; + + const buildDate = new Date(parsedTimestamp * 1000); + const now = new Date(); + const daysAgo = Math.floor((now - buildDate) / (1000 * 60 * 60 * 24)); + + return `${buildDate.toLocaleString()} ${ + daysAgo === 0 ? '(Today)' : `(${daysAgo} days ago)` + }`; +} + +function getSdkVersion() { + const sdkPackageName = 'customerio-reactnative'; + try { + const sdkPackage = getSdkMetadataFromPackageLock(sdkPackageName); + if (!sdkPackage) { + console.warn(`${sdkPackageName} not found in package-lock.json`); + return undefined; + } + + const version = resolveValidOrElse(sdkPackage.version); + const isPathDependency = + sdkPackage.resolved && sdkPackage.resolved.startsWith('file:'); + if (isPathDependency) { + return `${version}-${resolveValidOrElse( + Env.commitsAheadCount, + () => 'as-source' + )}`; + } + + return version; + } catch (error) { + console.warn( + `Failed to read ${sdkPackageName} sdk version: ${error.message}` + ); + return undefined; + } +} + +function getSdkMetadataFromPackageLock(packageName) { + const packageLockPath = '../../package-lock.json'; + try { + const packageLock = require(packageLockPath); + const packages = packageLock.packages || {}; + const resolvedPackageName = `node_modules/${packageName}`; + const sdkPackage = packages[resolvedPackageName]; + if (sdkPackage) { + return sdkPackage; + } + } catch (error) { + console.warn(`Failed to read ${packageLockPath}: ${error.message}`); + } + return undefined; +} + +export { BuildMetadata };