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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ build/Release
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules

# Compiled output of src/ (built via `npm run build:ts`, generated fresh at publish time)
lib/

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
Expand Down
12 changes: 12 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ Recipes/
bin/
test/

# Don't publish TypeScript source - only the compiled lib/ output (built via `npm run build:ts`
# as part of `prepack`) ships
# Anchored to repo root (leading slash) - an unanchored `src/` also matches android/app/src/,
# which stripped the native Android module's Java sources from every published tarball.
/src/

# Remove after this framework is published on NPM
code-push-plugin-testing-framework/

Expand Down Expand Up @@ -101,3 +107,9 @@ packages/

.watchmanconfig

# Dev tooling, not relevant to consumers
.claude/
.config/
CLAUDE.md
mise.toml

6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ React Native CodePush is a native module that enables over-the-air updates for R
### Build
- `npm run build` - Build TypeScript tests to bin/ directory
- `npm run tsc` - TypeScript compilation
- `npm run build:ts` - Compiles `src/` (currently just the vendored `acquisition-sdk`) to `lib/`, which is what ships to consumers instead of raw `.ts`. Wired into `setup` (so local dev/tests have `lib/` available) and `prepack` (so `npm publish`/`npm pack` always ship a freshly built `lib/`).
- This split (a separate `tsconfig.build.json` and `tsconfig.json` for `test/` -> `bin/`) is temporary. Once `CodePush.js` and the rest of this repo's runtime JS are migrated to TypeScript, these should be unified into a single build, and adopting `react-native-builder-bob` (or some other common tool) is worth considering at that point instead of hand-rolled `tsc` + npm script wiring.

### Platform Testing
- Tests run on actual emulators/simulators with real React Native apps
Expand All @@ -28,7 +30,7 @@ React Native CodePush is a native module that enables over-the-air updates for R
- **JavaScript Bridge** (`CodePush.js`): Main API layer exposing update methods
- **Native Modules**: Platform-specific implementations handling file operations, bundle management
- **Update Manager**: Handles download, installation, and rollback logic
- **Acquisition SDK**: Manages server communication and update metadata
- **Acquisition SDK** (`src/acquisition-sdk/`): Manages server communication and update metadata

### Platform Structure
- **iOS**: `ios/` - Objective-C implementation with CocoaPods integration
Expand All @@ -46,6 +48,7 @@ React Native CodePush is a native module that enables over-the-air updates for R
- **Custom Test Runner**: TypeScript-based test framework in `test/`
- **Real App Testing**: Creates actual React Native apps for integration testing
- **Scenario Testing**: Update, rollback, and error scenarios
- **No unit test infra yet**: this repo only has the mocha-based integration suite above. `src/acquisition-sdk/__tests__/` contains tests ported from upstream `microsoft/code-push`, kept for future reference - they are deliberately not wired into `npm test` or any runner. Don't assume they're dead/forgotten code, and don't wire them in without setting up real unit test infra first.
- **Templates**: `test/template/` holds native files (Podfile, AppDelegate, Android app files) and JS scenarios copied over top of a freshly generated RN/Expo app during test setup, overwriting its defaults — edit files here, not the generated project, for changes to persist
- **`test:ios` vs `test:setup:ios` vs `test:fast:ios`**: `test:ios` is just `test:setup:ios` followed by `test:fast:ios` — the two are meant to be split apart for local iteration.
- `test:setup:ios` (mocha `--ios --setup`) boots the simulator and provisions the test app once: copies templates, runs `pod install`, patches Info.plist/AppDelegate. It never builds or runs any test scenario.
Expand All @@ -61,3 +64,4 @@ React Native CodePush is a native module that enables over-the-air updates for R
- **Android Gradle Plugin**: Automatically generates bundle hashes and processes assets
- **iOS CocoaPods**: Manages native dependencies and build configuration
- **Bundle Processing**: Automated zip creation and hash calculation for OTA updates
- **`.npmignore` is a blocklist, not an allowlist**: `package.json` has no `files` field, so any new top-level file/dir ships to npm by default unless explicitly excluded. When adding new repo tooling/config, check whether it needs a `.npmignore` entry. Verify with `npm pack --dry-run`.
2 changes: 1 addition & 1 deletion CodePush.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AcquisitionManager as Sdk } from "code-push/script/acquisition-sdk";
import { AcquisitionManager as Sdk } from "./lib/acquisition-sdk/acquisition-sdk";
import { Alert } from "./AlertAdapter";
import requestFetchAdapter from "./request-fetch-adapter";
import { AppState, Platform } from "react-native";
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"author": "Bitrise",
"license": "MIT",
"scripts": {
"clean": "shx rm -rf bin",
"setup": "npm install --quiet --no-progress",
"clean": "shx rm -rf bin lib",
"setup": "npm install --quiet --no-progress && npm run build:ts",
"build:ts": "tsc -p tsconfig.build.json",
"prepack": "npm run build:ts",
"prebuild:tests": "npm run clean && npm run tslint",
"build:tests": "tsc",
"test": "npm run build:tests && npm run test:setup && npm run test:fast",
Expand All @@ -42,7 +44,6 @@
"url": "https://github.com/bitrise-io/react-native-code-push"
},
"dependencies": {
"code-push": "4.2.3",
"glob": "^7.1.7",
"hoist-non-react-statics": "^3.3.2",
"inquirer": "^8.1.5",
Expand Down
4 changes: 2 additions & 2 deletions request-fetch-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
"Content-Type": "application/json",
"X-CodePush-Plugin-Name": packageJson.name,
"X-CodePush-Plugin-Version": packageJson.version,
"X-CodePush-SDK-Version": packageJson.dependencies["code-push"]
"X-CodePush-SDK-Version": packageJson.version
};

if (requestBody && typeof requestBody === "object") {
Expand All @@ -37,7 +37,7 @@ module.exports = {

function getHttpMethodName(verb) {
// Note: This should stay in sync with the enum definition in
// https://github.com/microsoft/code-push/blob/master/sdk/script/acquisition-sdk.ts#L6
// https://github.com/microsoft/code-push/blob/master/src/script/acquisition-sdk.ts#L5
return [
"GET",
"HEAD",
Expand Down
124 changes: 124 additions & 0 deletions src/acquisition-sdk/__tests__/acquisition-rest-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Ported from https://github.com/microsoft/code-push/blob/master/src/test/acquisition-rest-mock.ts (archived, MIT licensed)
// Not currently wired into any test runner — this repo has no unit test infra yet.

import * as querystring from "querystring";

import * as acquisitionSdk from "../acquisition-sdk";
import * as types from "../types";

export var validDeploymentKey = "Valid Deployment Key";
export var latestPackage = <types.UpdateCheckResponse>{
download_url: "http://www.windowsazure.com/blobs/awperoiuqpweru",
description: "Angry flappy birds",
target_binary_range: "1.5.0",
label: "2.4.0",
is_mandatory: false,
is_available: true,
update_app_version: false,
package_hash: "hash240",
package_size: 1024
};

export var serverUrl = "http://myurl.com";
var publicPrefixUrl = "/v0.1/public/codepush";
var reportStatusDeployUrl = serverUrl + publicPrefixUrl + "/report_status/deploy";
var reportStatusDownloadUrl = serverUrl + publicPrefixUrl + "/report_status/download";
var updateCheckUrl = serverUrl + publicPrefixUrl + "/update_check?";

export function updateMockUrl() {
reportStatusDeployUrl = serverUrl + publicPrefixUrl + "/report_status/deploy";
reportStatusDownloadUrl = serverUrl + publicPrefixUrl + "/report_status/download";
updateCheckUrl = serverUrl + publicPrefixUrl + "/update_check?";
}

export class HttpRequester implements acquisitionSdk.Http.Requester {
private expectedStatusCode: number;

constructor(expectedStatusCode?: number) {
this.expectedStatusCode = expectedStatusCode;
}

public request(verb: acquisitionSdk.Http.Verb, url: string, requestBodyOrCallback: string | acquisitionSdk.Callback<acquisitionSdk.Http.Response>, callback?: acquisitionSdk.Callback<acquisitionSdk.Http.Response>): void {
if (!callback && typeof requestBodyOrCallback === "function") {
callback = <acquisitionSdk.Callback<acquisitionSdk.Http.Response>>requestBodyOrCallback;
}

if (verb === acquisitionSdk.Http.Verb.GET && url.indexOf(updateCheckUrl) === 0) {
var params = querystring.parse(url.substring(updateCheckUrl.length));
Server.onUpdateCheck(params, callback, this.expectedStatusCode);
} else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDeployUrl) {
Server.onReportStatus(callback, this.expectedStatusCode);
} else if (verb === acquisitionSdk.Http.Verb.POST && url === reportStatusDownloadUrl) {
Server.onReportStatus(callback, this.expectedStatusCode);
} else {
throw new Error("Unexpected call");
}
}
}

export class CustomResponseHttpRequester implements acquisitionSdk.Http.Requester {
response: acquisitionSdk.Http.Response;

constructor(response: acquisitionSdk.Http.Response) {
this.response = response;
}

public request(verb: acquisitionSdk.Http.Verb, url: string, requestBodyOrCallback: string | acquisitionSdk.Callback<acquisitionSdk.Http.Response>, callback?: acquisitionSdk.Callback<acquisitionSdk.Http.Response>): void {
if (typeof requestBodyOrCallback !== "function") {
throw new Error("Unexpected request body");
}

callback = <acquisitionSdk.Callback<acquisitionSdk.Http.Response>>requestBodyOrCallback;
callback(null, this.response);
}
}

class Server {
public static onAcquire(params: any, callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>): void {
if (params.deploymentKey !== validDeploymentKey) {
callback(/*error=*/ null, {
statusCode: 200,
body: JSON.stringify({ update_info: { isAvailable: false } })
});
} else {
callback(/*error=*/ null, {
statusCode: 200,
body: JSON.stringify({ update_info: latestPackage })
});
}
}

public static onUpdateCheck(params: any, callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>, expectedStatusCode?: number): void {
var updateRequest: types.UpdateCheckRequest = {
deployment_key: params.deployment_key,
app_version: params.app_version,
package_hash: params.package_hash,
is_companion: !!(params.is_companion),
label: params.label
};

if (!updateRequest.deployment_key || !updateRequest.app_version) {
callback(/*error=*/ null, { statusCode: 400 });
} else {
var updateInfo = <types.UpdateCheckResponse>{ is_available: false };
if (updateRequest.deployment_key === validDeploymentKey) {
if (updateRequest.is_companion || updateRequest.app_version === latestPackage.target_binary_range) {
if (updateRequest.package_hash !== latestPackage.package_hash) {
updateInfo = latestPackage;
}
} else if (updateRequest.app_version < latestPackage.target_binary_range) {
updateInfo = <types.UpdateCheckResponse><any>{ update_app_version: true, target_binary_range: latestPackage.target_binary_range };
}
}

callback(/*error=*/ null, {
statusCode: expectedStatusCode ? expectedStatusCode : 200,
body: JSON.stringify({ update_info: updateInfo })
});
}
}

public static onReportStatus(callback: acquisitionSdk.Callback<acquisitionSdk.Http.Response>, expectedStatusCode: number): void {
callback(/*error*/ null, /*response*/ { statusCode: expectedStatusCode ? expectedStatusCode : 200 });
}
}
Loading