@fancode/react-native-codepush-joystick
v0.0.2
Published
A flexible CodePush Joystick for React Native apps
Readme
React Native CodePush Joystick
A flexible and powerful CodePush management library for React Native applications that integrates seamlessly with GitHub and CI/CD pipelines. This library provides a comprehensive solution for managing CodePush updates, building apps from pull requests, and providing developers with a convenient "joystick" interface to test different versions of their app.
Features
- 🚀 CodePush Integration: Seamless integration with Microsoft CodePush for over-the-air updates
- 🔧 GitHub Integration: Fetch pull requests and trigger builds directly from GitHub
- ⚙️ CI/CD Support: Built-in support for GitHub Actions and custom CI/CD providers
- 📱 React Native Hook: Easy-to-use React hook for state management
- 🔄 Real-time Updates: Monitor build status and download progress in real-time
- 🎯 Flexible Versioning: Support for PR-based and custom versioning strategies
- 🛠️ Build Management: Trigger, monitor, and cancel builds programmatically
- 📊 Comprehensive Callbacks: Extensive callback system for lifecycle events
Installation
npm install @fancode/react-native-codepush-joystick
# or
yarn add @fancode/react-native-codepush-joystickPeer Dependencies
Make sure you have the required peer dependencies installed:
npm install react react-native
# or
yarn add react react-nativeQuick Start
1. Basic Setup with GitHub Actions
import React, { useEffect, useState } from "react";
import { View, Text, TouchableOpacity, FlatList } from "react-native";
import {
useCodePushManager,
createGitHubActionsCICDProvider,
CodePushActionButtonState,
} from "@fancode/react-native-codepush-joystick";
export default function CodePushJoystick() {
const [pullRequests, setPullRequests] = useState([]);
// Configure the CodePush manager with GitHub Actions CI/CD provider
const config = {
sourceControl: {
config: {
owner: "your-github-username",
repo: "your-repo-name",
token: "your-github-token",
},
},
cicdProvider: createGitHubActionsCICDProvider({
owner: "your-github-username",
repo: "your-repo-name",
token: "your-github-token",
workflowFile: "codepush-build.yml", // Your GitHub workflow file
workflowInputs: {
DEPLOYMENT_NAME: "Staging",
},
}),
codepush: {
deploymentKey: "YOUR_DEPLOYMENT_KEY", // Your CodePush deployment key (from App Center)
},
appVersion: "1.0.0", // Your current app version
callbacks: {
onPullRequestsFetched: (prs) => setPullRequests(prs),
onError: (error, context) => console.error(`Error in ${context}:`, error),
onCodePushAvailable: (pr, packageInfo) => {
console.log(`CodePush available for PR #${pr.number}`);
},
onDownloadStarted: (pullRequest, packageInfo) => {
console.log(
`Starting download for PR #${pullRequest.number}: ${packageInfo.label}`
);
},
onDownloadComplete: (pullRequest, localPackage) => {
// Store version information after successful download
const formattedVersion = manager.formatVersionForStorage(localPackage);
AsyncStorage.setItem("app_center_version", formattedVersion);
},
},
};
const { manager, stateMap, fetchPullRequests } = useCodePushManager(config);
useEffect(() => {
fetchPullRequests({ state: "open", per_page: 10 });
}, [fetchPullRequests]);
const renderPullRequest = ({ item: pr }) => {
const state = stateMap[pr.id] || {};
const buttonText = CodePushActionButtonState[state.status] || "Status";
return (
<View style={{ padding: 16, borderBottomWidth: 1 }}>
<Text style={{ fontSize: 16, fontWeight: "bold" }}>
#{pr.number} - {pr.title}
</Text>
<Text style={{ color: "#666" }}>
Branch: {pr.head?.ref} | Author: {pr.user.login}
</Text>
{state.message && (
<Text style={{ color: "#888", marginTop: 4 }}>{state.message}</Text>
)}
{state.progress !== null && (
<Text style={{ color: "#007AFF" }}>
Download Progress: {state.progress}%
</Text>
)}
<TouchableOpacity
style={{
backgroundColor: state.loading ? "#ccc" : "#007AFF",
padding: 12,
borderRadius: 8,
marginTop: 8,
}}
disabled={state.loading}
onPress={() => manager?.processWorkflow(pr)}
>
<Text style={{ color: "white", textAlign: "center" }}>
{state.loading ? "Loading..." : buttonText}
</Text>
</TouchableOpacity>
</View>
);
};
return (
<View style={{ flex: 1 }}>
<Text style={{ fontSize: 20, fontWeight: "bold", padding: 16 }}>
CodePush Joystick
</Text>
<FlatList
data={pullRequests}
keyExtractor={(item) => item.id.toString()}
renderItem={renderPullRequest}
/>
</View>
);
}2. Custom CI/CD Provider Setup
import { createCustomCICDProvider } from "@fancode/react-native-codepush-joystick";
const customProvider = createCustomCICDProvider({
triggerBuild: async (params) => {
// Your custom build trigger logic
const response = await fetch("https://your-ci-cd-api.com/trigger", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
branch: params.branch,
version: params.version,
}),
});
const data = await response.json();
return {
buildId: data.buildId,
status: {
status: data.status,
conclusion: data.conclusion,
},
startedAt: data.startedAt,
};
},
getWorkflowRuns: async (branchName) => {
// Fetch workflow runs for the branch
const response = await fetch(
`https://your-ci-cd-api.com/runs?branch=${branchName}`
);
return response.json();
},
cancelBuild: async (buildId) => {
// Cancel a specific build
await fetch(`https://your-ci-cd-api.com/builds/${buildId}/cancel`, {
method: "POST",
});
},
findWorkflowStatus: (workflowRun) => {
if (!workflowRun) return null;
return {
workflowStatus: {
isRunning: workflowRun.status === "running",
isFailed: workflowRun.status === "failed",
isCancelled: workflowRun.status === "cancelled",
isCompleted: workflowRun.status === "completed",
rawStatus: workflowRun.status,
id: workflowRun.id,
startedAt: new Date(workflowRun.startedAt),
},
buildInfo: {
buildId: workflowRun.id,
status: {
status: workflowRun.status,
},
startedAt: workflowRun.startedAt,
},
};
},
});
// Use the custom provider in your config
const config = {
// ... other config
cicdProvider: customProvider,
};Simple Usage Example
import {
useCodePushManager,
createGitHubActionsCICDProvider,
} from "@fancode/react-native-codepush-joystick";
function MyComponent() {
const { manager, stateMap, fetchPullRequests, resetState } =
useCodePushManager({
sourceControl: {
config: { owner: "user", repo: "repo", token: "token" },
},
cicdProvider: createGitHubActionsCICDProvider({
/* config */
}),
codepush: { deploymentKey: "key" },
appVersion: "1.0.0",
});
useEffect(() => {
fetchPullRequests({ state: "open", per_page: 10 });
}, [fetchPullRequests]);
return (
<FlatList
data={pullRequests}
renderItem={({ item }) => {
const state = stateMap[item.id];
return (
<View>
<TouchableOpacity onPress={() => handleAction(item)}>
<Text>
{item.title} - {state?.status}
</Text>
</TouchableOpacity>
{state?.status === "ERROR" && (
<TouchableOpacity onPress={() => resetState(item.id)}>
<Text>Reset</Text>
</TouchableOpacity>
)}
</View>
);
}}
/>
);
}Configuration
CI/CD Provider Configuration
The library supports two types of CI/CD providers. You must choose one and configure it properly:
1. GitHub Actions CI/CD Provider
Use createGitHubActionsCICDProvider when your builds are handled by GitHub Actions:
import { createGitHubActionsCICDProvider } from "@fancode/react-native-codepush-joystick";
const githubActionsProvider = createGitHubActionsCICDProvider({
owner: "your-github-username",
repo: "your-repo-name",
token: "your-github-token", // GitHub personal access token
workflowFile: "codepush-build.yml", // Your workflow file name
workflowInputs: {
// Optional: additional inputs for your workflow
DEPLOYMENT_NAME: "Staging",
BUILD_TYPE: "release",
},
});Required Permissions for GitHub Token:
repo(full control of private repositories)workflow(update GitHub Action workflows)
2. Custom CI/CD Provider
Use createCustomCICDProvider when using other CI/CD services (Jenkins, Azure DevOps, CircleCI, etc.):
import { createCustomCICDProvider } from "@fancode/react-native-codepush-joystick";
const customCICDProvider = createCustomCICDProvider({
// Trigger a new build
triggerBuild: async (params) => {
const response = await fetch("https://your-ci-api.com/trigger", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_CI_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
branch: params.branch,
version: params.version,
// Add your CI-specific parameters
}),
});
const data = await response.json();
return {
buildId: data.buildId,
status: {
status: data.status,
conclusion: data.conclusion,
},
startedAt: data.startedAt,
};
},
// Get workflow runs for a branch
getWorkflowRuns: async (branchName) => {
const response = await fetch(
`https://your-ci-api.com/runs?branch=${branchName}`
);
const runs = await response.json();
return runs.map((run) => ({
id: run.id,
status: run.status,
startedAt: run.startedAt,
extra: run.metadata, // Optional additional data
}));
},
// Cancel a running build
cancelBuild: async (buildId) => {
await fetch(`https://your-ci-api.com/builds/${buildId}/cancel`, {
method: "POST",
headers: { Authorization: "Bearer YOUR_CI_TOKEN" },
});
},
// Determine workflow status from run data
findWorkflowStatus: (workflowRun) => {
if (!workflowRun) return null;
return {
workflowStatus: {
isRunning: workflowRun.status === "running",
isFailed: workflowRun.status === "failed",
isCancelled: workflowRun.status === "cancelled",
isCompleted: workflowRun.status === "completed",
rawStatus: workflowRun.status,
id: workflowRun.id,
startedAt: new Date(workflowRun.startedAt),
},
buildInfo: {
buildId: workflowRun.id,
status: {
status: workflowRun.status,
},
startedAt: workflowRun.startedAt,
},
};
},
});CodePushManagerConfig
The main configuration object for the CodePush manager:
interface CodePushManagerConfig {
sourceControl: {
config: GitHubConfig;
};
cicdProvider: CICDProvider; // Use either createGitHubActionsCICDProvider or createCustomCICDProvider
codepush: {
deploymentKey?: string;
};
appVersion: string;
versioning?: {
strategy?: "pr-based" | "custom";
customCalculator?: (pr: GithubPullRequest, baseVersion: string) => string;
};
callbacks?: CodePushCallbacks;
}Complete Configuration Examples
Using GitHub Actions Provider:
import {
useCodePushManager,
createGitHubActionsCICDProvider,
} from "@fancode/react-native-codepush-joystick";
const config = {
sourceControl: {
config: {
owner: "your-github-username",
repo: "your-repo-name",
token: "your-github-token",
},
},
cicdProvider: createGitHubActionsCICDProvider({
owner: "your-github-username",
repo: "your-repo-name",
token: "your-github-token",
workflowFile: "codepush-build.yml",
workflowInputs: {
DEPLOYMENT_NAME: "Staging",
},
}),
codepush: {
deploymentKey: "YOUR_DEPLOYMENT_KEY",
},
appVersion: "1.0.0",
callbacks: {
onError: (error, context) => console.error(`Error in ${context}:`, error),
onDownloadStarted: (pullRequest, packageInfo) => {
console.log(
`Starting download for PR #${pullRequest.number}: ${packageInfo.label}`
);
},
onDownloadComplete: (pullRequest, localPackage) => {
// Store version information after successful download
const formattedVersion = manager.formatVersionForStorage(localPackage);
AsyncStorage.setItem("app_center_version", formattedVersion);
},
},
};
const { manager, stateMap } = useCodePushManager(config);Using Custom CI/CD Provider:
import {
useCodePushManager,
createCustomCICDProvider,
} from "@fancode/react-native-codepush-joystick";
const config = {
sourceControl: {
config: {
owner: "your-github-username",
repo: "your-repo-name",
token: "your-github-token",
},
},
cicdProvider: createCustomCICDProvider({
triggerBuild: async (params) => {
// Your custom build logic here
return await yourCustomBuildService.trigger(params);
},
getWorkflowRuns: async (branchName) => {
return await yourCustomBuildService.getRuns(branchName);
},
cancelBuild: async (buildId) => {
await yourCustomBuildService.cancel(buildId);
},
findWorkflowStatus: (workflowRun) => {
return yourCustomBuildService.parseStatus(workflowRun);
},
}),
codepush: {
deploymentKey: "YOUR_DEPLOYMENT_KEY",
},
appVersion: "1.0.0",
callbacks: {
onDownloadStarted: (pullRequest, packageInfo) => {
console.log(
`Starting download for PR #${pullRequest.number}: ${packageInfo.label}`
);
},
onDownloadComplete: (pullRequest, localPackage) => {
// Store version information after successful download
const formattedVersion = manager.formatVersionForStorage(localPackage);
AsyncStorage.setItem("app_center_version", formattedVersion);
},
},
};
const { manager, stateMap } = useCodePushManager(config);GitHubConfig
interface GitHubConfig {
owner: string; // GitHub repository owner
repo: string; // Repository name
token: string; // GitHub personal access token
}GitHubActionsConfig
interface GitHubActionsConfig {
owner: string;
repo: string;
token: string;
workflowFile: string; // e.g., 'codepush-build.yml'
workflowInputs?: Record<string, string>; // Additional workflow inputs
}Custom Versioning
You can implement custom versioning strategies:
const config = {
// ... other config
versioning: {
strategy: "custom",
customCalculator: (pr, baseVersion) => {
// Example: Use PR number and branch name for versioning
const [major, minor, patch] = baseVersion.split(".");
const newPatch = parseInt(patch) + pr.number;
return `${major}.${minor}.${newPatch}-${pr.head?.ref}`;
},
},
};Version Storage After Download
The library provides a callback to handle version storage after a CodePush update is downloaded. This is useful for tracking which version is currently installed:
const config = {
// ... other config
callbacks: {
onDownloadComplete: (pullRequest, localPackage) => {
// Format version similar to your existing pattern
const formattedVersion = manager.formatVersionForStorage(localPackage);
AsyncStorage.setItem("app_center_version", formattedVersion);
},
onDownloadProgress: (pullRequest, progress) => {
console.log(`Download progress: ${progress}%`);
},
// ... other callbacks
},
};Manual Version Formatting
You can also manually format the version string:
const config = {
// ... other config
callbacks: {
onDownloadComplete: (pullRequest, localPackage) => {
// Custom version formatting
let version = localPackage.label.replace("v", "");
if (isInternalBuild && localPackage.description) {
version += "/" + localPackage.description;
}
// Store the version using your preferred storage mechanism
AsyncStorage.setItem("app_center_version", version);
},
},
};CodePush Workflow & Lifecycle
The library provides both a convenience method for automatic workflow management and individual methods for granular control.
Automated Workflow with processWorkflow (Recommended)
The processWorkflow method automatically determines the next action based on the current state:
// Simple automated approach. Use this function in your action button
const newState = await manager.processWorkflow(pullRequest);State-based Actions:
NOT_CHECKED/UN_AVAILABLE/ERROR→ Check for CodePush updatesAVAILABLE→ Download the updateDOWNLOADED→ Restart the appALREADY_RUNNING→ Cancel the running buildNOT_RUNNING→ Trigger a new build
Manual Workflow Control
For advanced use cases, you can control each step manually:
1. Check for Updates
// Step 1: Check if CodePush update is available
const state = await manager.checkCodePushUpdate(pullRequest);
// Callbacks triggered:
// - onCodePushAvailable (if update found)
// - onStateChange2. Handle Build Management
If no CodePush update is found, the system checks for running builds:
// This is called automatically by checkCodePushUpdate
// But you can call it manually:
await manager.checkGitHubWorkflows(pullRequest);
// Possible outcomes:
// - Build is running → Status: ALREADY_RUNNING
// - No build found → Status: NOT_RUNNING3. Trigger New Build
// Trigger a new build for the pull request
const buildInfo = await manager.triggerBuild(pullRequest);
// Callbacks triggered:
// - onBuildTriggered
// - onStateChange4. Download Update
// Download the CodePush update
const state = await manager.downloadCodePushUpdate(pullRequest);
// Callbacks triggered in sequence:
// - onDownloadStarted
// - onDownloadProgress (multiple times)
// - onDownloadComplete
// - onStateChange5. Install & Restart
// Restart the app to apply the update
await manager.restartApp();Complete Manual Workflow Example
const handleManualWorkflow = async (pullRequest) => {
try {
// Step 1: Check for existing CodePush update
console.log("Checking for CodePush update...");
let state = await manager.checkCodePushUpdate(pullRequest);
if (state.status === CodePushStatus.AVAILABLE) {
// Step 2: Download the update
console.log("CodePush update available, downloading...");
state = await manager.downloadCodePushUpdate(pullRequest);
if (state.status === CodePushStatus.DOWNLOADED) {
// Step 3: Restart app
console.log("Download complete, restarting app...");
await manager.restartApp();
}
} else if (state.status === CodePushStatus.NOT_RUNNING) {
// Step 2: Trigger new build
console.log("No update found, triggering new build...");
const buildInfo = await manager.triggerBuild(pullRequest);
console.log("Build triggered:", buildInfo.buildId);
// Step 3: Monitor build status (you'd need to poll this)
// Once build completes, repeat from Step 1
} else if (state.status === CodePushStatus.ALREADY_RUNNING) {
// Option: Cancel running build
console.log("Build already running, canceling...");
await manager.cancelBuild(pullRequest);
}
} catch (error) {
console.error("Workflow error:", error);
}
};Lifecycle Callbacks
Each step in the workflow triggers specific callbacks:
const config = {
// ... other config
callbacks: {
// Pull Request Management
onPullRequestsFetched: (prs) => {
console.log(`Fetched ${prs.length} pull requests`);
},
// Build Lifecycle
onBuildTriggered: (pr, buildInfo) => {
console.log(`Build ${buildInfo.buildId} triggered for PR #${pr.number}`);
showNotification(`Building ${pr.title}...`);
},
// CodePush Discovery
onCodePushAvailable: (pr, packageInfo) => {
console.log(
`CodePush available for PR #${pr.number}: ${packageInfo.label}`
);
updateUI(`Update available: ${packageInfo.label}`);
},
// Download Lifecycle
onDownloadStarted: (pr, packageInfo) => {
console.log(`Starting download: ${packageInfo.label}`);
showProgressBar(0);
},
onDownloadProgress: (pr, progress) => {
console.log(`Download progress: ${progress}%`);
updateProgressBar(progress);
},
onDownloadComplete: (pr, localPackage) => {
console.log(`Download complete: ${localPackage.label}`);
hideProgressBar();
// Store version information
const version = manager.formatVersionForStorage(localPackage);
AsyncStorage.setItem("app_center_version", version);
showNotification("Update downloaded! Tap to restart.");
},
// State Management
onStateChange: (pr, newState, oldState) => {
console.log(
`PR #${pr.number} state: ${oldState.status} → ${newState.status}`
);
updateButtonText(getButtonText(newState.status));
},
// Error Handling
onError: (error, context, metadata) => {
console.error(`Error in ${context}:`, error);
showErrorNotification(`Failed to ${context}: ${error.message}`);
},
},
};State-Based UI Updates
Use the state to update your UI appropriately:
const getButtonText = (status) => {
switch (status) {
case CodePushStatus.NOT_CHECKED:
return "Check Update";
case CodePushStatus.AVAILABLE:
return "Download";
case CodePushStatus.DOWNLOADED:
return "Restart App";
case CodePushStatus.ALREADY_RUNNING:
return "Cancel Build";
case CodePushStatus.NOT_RUNNING:
return "Trigger Build";
case CodePushStatus.ERROR:
return "Retry";
default:
return "Status";
}
};
const getStatusColor = (status) => {
switch (status) {
case CodePushStatus.AVAILABLE:
return "#4CAF50"; // Green
case CodePushStatus.DOWNLOADED:
return "#2196F3"; // Blue
case CodePushStatus.ALREADY_RUNNING:
return "#FF9800"; // Orange
case CodePushStatus.ERROR:
return "#F44336"; // Red
default:
return "#757575"; // Gray
}
};This gives you complete control over the CodePush lifecycle, whether you prefer the automated approach or need fine-grained control over each step.
API Reference
CodePushManager
The main class that handles CodePush operations.
Methods
fetchPullRequests(options?: FetchPROptions): Promise<GithubPullRequest[]>
Fetches pull requests from GitHub.
const prs = await manager.fetchPullRequests({
state: "open",
per_page: 20,
sort: "updated",
direction: "desc",
});checkCodePushUpdate(pullRequest: GithubPullRequest): Promise<CodePushOptionState>
Checks if a CodePush update is available for a specific pull request.
const state = await manager.checkCodePushUpdate(pullRequest);
console.log("Update available:", state.status === "AVAILABLE");downloadCodePushUpdate(pullRequest: GithubPullRequest): Promise<CodePushOptionState>
Downloads the CodePush update for a pull request.
const state = await manager.downloadCodePushUpdate(pullRequest);
// Monitor progress through callbackstriggerBuild(pullRequest: GithubPullRequest): Promise<BuildInfo>
Triggers a build for the pull request branch.
const buildInfo = await manager.triggerBuild(pullRequest);
console.log("Build triggered:", buildInfo.buildId);cancelBuild(pullRequest: GithubPullRequest): Promise<void>
Cancels a running build.
await manager.cancelBuild(pullRequest);processWorkflow(pullRequest: GithubPullRequest): Promise<CodePushOptionState>
Automatically determines and executes the next logical action in the CodePush workflow based on the current state. This is the recommended method for most use cases.
const newState = await manager.processWorkflow(pullRequest);getState(pullRequestId: number): CodePushOptionState
Gets the current state for a pull request.
const state = manager.getState(pullRequest.id);useCodePushManager Hook
React hook for managing CodePush state.
const { manager, stateMap, fetchPullRequests, resetState } =
useCodePushManager(config);Returns
manager: CodePushManager instance or nullstateMap: Record of pull request states keyed by PR IDfetchPullRequests: Self-contained function to fetch pull requests with guaranteed callback setupresetState: Function to reset state for a specific PR or all PRs
fetchPullRequests Function
The fetchPullRequests function returned by the hook is the recommended way to fetch pull requests as it ensures callbacks are properly set up before making the API call:
const { fetchPullRequests } = useCodePushManager(config);
// Automatically ensures callbacks are ready before fetching
const pullRequests = await fetchPullRequests({
state: "open",
per_page: 20,
sort: "updated",
direction: "desc",
});Benefits over calling manager.fetchPullRequests() directly:
- ✅ No timing issues - callbacks are guaranteed to be ready
- ✅ No need to check if manager exists
- ✅ Self-healing - automatically sets up callbacks if needed
- ✅ Cleaner API - no race conditions
State Management Functions
The hook provides convenient functions for managing PR states:
const { resetState } = useCodePushManager(config);
// Reset a specific PR's state
resetState(pullRequest.id);
// Reset all PR states
resetState();Callbacks
The library provides extensive callbacks for monitoring operations:
interface CodePushCallbacks {
onPullRequestsFetched?: (prs: GithubPullRequest[]) => void;
onBuildTriggered?: (pr: GithubPullRequest, buildInfo: BuildInfo) => void;
onCodePushAvailable?: (
pr: GithubPullRequest,
packageInfo: RemotePackage
) => void;
onDownloadStarted?: (
pr: GithubPullRequest,
packageInfo: RemotePackage
) => void;
onDownloadProgress?: (pr: GithubPullRequest, progress: number) => void;
onDownloadComplete?: (
pr: GithubPullRequest,
localPackage: LocalPackage
) => void;
onError?: (error: Error, context: string, metadata?: any) => void;
onStateChange?: (
pr: GithubPullRequest,
newState: CodePushOptionState,
oldState: CodePushOptionState
) => void;
}Common CI/CD Integrations
Jenkins Integration
const jenkinsProvider = createCustomCICDProvider({
triggerBuild: async (params) => {
const response = await fetch(
`${JENKINS_URL}/job/${JOB_NAME}/buildWithParameters`,
{
method: "POST",
headers: {
Authorization: `Basic ${btoa(`${JENKINS_USER}:${JENKINS_TOKEN}`)}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
BRANCH: params.branch,
APP_VERSION: params.version,
}),
}
);
// Get build number from Location header
const location = response.headers.get("Location");
const buildNumber = location?.split("/").pop();
return {
buildId: buildNumber,
status: { status: "queued" },
startedAt: new Date().toISOString(),
};
},
// ... other methods
});Azure DevOps Integration
const azureDevOpsProvider = createCustomCICDProvider({
triggerBuild: async (params) => {
const response = await fetch(
`https://dev.azure.com/${ORGANIZATION}/${PROJECT}/_apis/build/builds?api-version=7.0`,
{
method: "POST",
headers: {
Authorization: `Basic ${btoa(`:${AZURE_PAT}`)}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
definition: { id: BUILD_DEFINITION_ID },
sourceBranch: `refs/heads/${params.branch}`,
parameters: JSON.stringify({
appVersion: params.version,
}),
}),
}
);
const build = await response.json();
return {
buildId: build.id.toString(),
status: { status: build.status },
startedAt: build.queueTime,
};
},
// ... other methods
});CircleCI Integration
const circleCIProvider = createCustomCICDProvider({
triggerBuild: async (params) => {
const response = await fetch(
`https://circleci.com/api/v2/project/github/${OWNER}/${REPO}/pipeline`,
{
method: "POST",
headers: {
Authorization: `Basic ${btoa(`${CIRCLE_TOKEN}:`)}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
branch: params.branch,
parameters: {
app_version: params.version,
},
}),
}
);
const pipeline = await response.json();
return {
buildId: pipeline.id,
status: { status: "running" },
startedAt: new Date().toISOString(),
};
},
// ... other methods
});Error Handling
The library provides comprehensive error handling:
const config = {
// ... other config
callbacks: {
onError: (error, context, metadata) => {
console.error(`Error in ${context}:`, error);
// Handle specific contexts
switch (context) {
case "fetchPullRequests":
// Handle PR fetch errors
break;
case "triggerBuild":
// Handle build trigger errors
break;
case "downloadCodePushUpdate":
// Handle download errors
break;
}
// Log to crash reporting service
crashlytics().recordError(error);
},
},
};TypeScript Support
The library is written in TypeScript and provides full type definitions:
import type {
CodePushManagerConfig,
GithubPullRequest,
CodePushStatus,
CodePushOptionState,
} from "@fancode/react-native-codepush-joystick";Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
