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
9 changes: 7 additions & 2 deletions cli/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ npx code-push release [options]
이름의 full 번들과, 바이너리에 포함된 번들과의 차이만 담은 `<packageHash>-patch.zip` patch
번들입니다. patch 번들에는 업데이트 복원 방법을 담은 `codepush-binary-patch.json` manifest가
포함되어, patch를 적용하면 full 번들과 동일한 `packageHash`가 됩니다. 두 artifact의 크기와
절감량은 업로드 전에 출력됩니다.
절감량은 업로드 전에 출력됩니다. 릴리스 히스토리 항목에는 full 번들 URL과 함께 patch 번들을
내려받을 수 있는 URL이 기록됩니다.

patch는 대체하려는 archive보다 작을 때만 배포할 가치가 있습니다. CLI는 사용자에게 묻지
않으므로, patch 크기가 full 이상일 때의 동작을 `--on-oversized-patch`로 미리 정합니다.
Expand Down Expand Up @@ -248,11 +249,15 @@ npx code-push show-history -b 1.0.0 -p ios
"enabled": true,
"mandatory": true,
"downloadUrl": "https://storage.example.com/bundles/ios/staging/d4e5f6...",
"packageHash": "d4e5f6..."
"packageHash": "d4e5f6...",
"binaryPatchDownloadUrl": "https://storage.example.com/bundles/ios/staging/d4e5f6...-patch.zip"
}
}
```

`binaryPatchDownloadUrl`은 `--binary-bundle-path`로 배포한 릴리스에만 기록됩니다. 그 외의
릴리스에는 이 필드가 없으며, binary patch 이전에 작성된 히스토리도 그대로 유효합니다.

## 일반적인 워크플로우

```
Expand Down
10 changes: 8 additions & 2 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ bundle named after its `packageHash`, and a patch bundle named `<packageHash>-pa
that carries only the difference from the bundle inside the binary. The patch bundle
holds a `codepush-binary-patch.json` manifest describing how to rebuild the update, so
applying it yields the same `packageHash` as the full bundle. Both sizes and the saving
are printed before either artifact is uploaded.
are printed before either artifact is uploaded. The release history entry records where
the patch bundle can be downloaded, next to the full bundle URL.

A patch is only worth publishing when it is smaller than the archive it replaces. The CLI
never prompts, so `--on-oversized-patch` decides in advance what happens when the patch
Expand Down Expand Up @@ -240,11 +241,16 @@ The release history is a JSON object keyed by app version. For example, the hist
"enabled": true,
"mandatory": true,
"downloadUrl": "https://storage.example.com/bundles/ios/staging/d4e5f6...",
"packageHash": "d4e5f6..."
"packageHash": "d4e5f6...",
"binaryPatchDownloadUrl": "https://storage.example.com/bundles/ios/staging/d4e5f6...-patch.zip"
}
}
```

`binaryPatchDownloadUrl` is only written for a release published with
`--binary-bundle-path`. Every other release leaves the field out, and a history written
before binary patches existed stays valid as it is.

## Typical Workflow

```
Expand Down
7 changes: 7 additions & 0 deletions cli/commands/releaseCommand/addToReleaseHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export async function addToReleaseHistory(
appVersion: string,
binaryVersion: string,
bundleDownloadUrl: string,
binaryPatchDownloadUrl: string | undefined,
packageHash: string,
getReleaseHistory: CliConfigInterface['getReleaseHistory'],
setReleaseHistory: CliConfigInterface['setReleaseHistory'],
Expand All @@ -32,6 +33,12 @@ export async function addToReleaseHistory(
packageHash: packageHash,
};

// A release without a binary patch says nothing about one, so that a client reading
// this history downloads the full bundle exactly as it did before patches existed.
if (binaryPatchDownloadUrl) {
newReleaseHistory[appVersion].binaryPatchDownloadUrl = binaryPatchDownloadUrl;
}

if (typeof rollout === 'number') {
newReleaseHistory[appVersion].rollout = rollout;
}
Expand Down
10 changes: 8 additions & 2 deletions cli/commands/releaseCommand/release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ describe("release without --binary-bundle-path", () => {
downloadUrl: uploads[0].downloadUrl,
packageHash: staged.bundleFileName,
});
// A release without a patch says nothing about one, so a client reading this
// history behaves exactly as it did before binary patches existed.
expect(releaseHistories[0][APP_VERSION]).not.toHaveProperty('binaryPatchDownloadUrl');
});
});

Expand All @@ -217,9 +220,10 @@ describe("release --skip-bundle --binary-bundle-path", () => {
expect(uploads.map(({ filePath }) => path.basename(filePath))).toEqual([staged.bundleFileName, patchFileName]);
expect(fs.existsSync(path.join(staged.bundleDirectory, patchFileName))).toBe(true);

// Carrying the patch URL in the release history is a separate concern; for now
// the history keeps describing the full bundle only.
// Both artifacts are described in the same entry: the full bundle every client can
// download, and the patch a client holding the matching binary can apply instead.
expect(releaseHistories[0][APP_VERSION].downloadUrl).toBe(uploads[0].downloadUrl);
expect(releaseHistories[0][APP_VERSION].binaryPatchDownloadUrl).toBe(uploads[1].downloadUrl);
expect(releaseHistories[0][APP_VERSION].packageHash).toBe(staged.bundleFileName);
});

Expand Down Expand Up @@ -424,6 +428,8 @@ describe("release --on-oversized-patch", () => {
expect(logs.filter((line) => line.startsWith('warn:')).join('\n')).toMatch(/not smaller than the full archive/);
expect(logs.join('\n')).toContain('Patch skipped:');
expect(releaseHistories[0][APP_VERSION].downloadUrl).toBe(uploads[0].downloadUrl);
// Nothing was uploaded to patch from, so the entry must not point at one.
expect(releaseHistories[0][APP_VERSION]).not.toHaveProperty('binaryPatchDownloadUrl');
});

it("fails before any upload when the policy is fail", async () => {
Expand Down
4 changes: 3 additions & 1 deletion cli/commands/releaseCommand/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,17 @@ export async function release(
// Every artifact is uploaded before the release history is touched, so a failed
// upload leaves the history describing only updates that can actually be downloaded.
const downloadUrl = await uploadArtifact(bundleUploader, bundleFilePath, platform, identifier, 'bundle');
let patchDownloadUrl: string | undefined;
if (binaryPatch) {
const patchDownloadUrl = await uploadArtifact(bundleUploader, binaryPatch.patchBundleFilePath, platform, identifier, 'binary patch bundle');
patchDownloadUrl = await uploadArtifact(bundleUploader, binaryPatch.patchBundleFilePath, platform, identifier, 'binary patch bundle');
console.log(`log: Binary patch archive uploaded (download url: ${patchDownloadUrl})`);
}

await addToReleaseHistory(
appVersion,
binaryVersion,
downloadUrl,
patchDownloadUrl,
packageHash,
getReleaseHistory,
setReleaseHistory,
Expand Down
3 changes: 2 additions & 1 deletion docs/api-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,10 @@ Contains details about an update that is available for download from the CodePus

###### Properties

The `RemotePackage` inherits all of the same properties as the `LocalPackage`, but includes one additional one:
The `RemotePackage` inherits all of the same properties as the `LocalPackage`, but includes the following additional ones:

- __downloadUrl__: The URL at which the package is available for download. This property is only needed for advanced usage, since the `download` method will automatically handle the acquisition of updates for you. *(String)*
- __binaryPatchDownloadUrl__: The URL at which a binary patch archive of the package is available for download. It is only present when the release was published together with such a patch, and the package is downloaded in full from `downloadUrl` otherwise. *(String, optional)*

###### Methods

Expand Down
11 changes: 11 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('jest').Config} */
module.exports = {
rootDir: __dirname,
testEnvironment: 'node',
transform: {
// The only source file with JSX, which Babel is not set up to transform here.
'src/CodePush\\.js$': '<rootDir>/jest.jsxTransformer.cjs',
'^.+\\.(t|j)sx?$': 'babel-jest',
},
watchman: false,
};
41 changes: 41 additions & 0 deletions jest.jsxTransformer.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const crypto = require('crypto');
const ts = require('typescript');

/**
* `src/CodePush.js` carries the JSX of the `codePush` decorator, and the Babel config of
* this repository has no JSX transform - an app bundling the library transforms it with
* the React Native preset. Tests still have to load the module, so it is compiled with
* TypeScript instead, which turns the JSX into `React.createElement` calls and leaves the
* rest of the file to the same downlevelling Babel would have applied.
*/
const COMPILER_OPTIONS = {
allowJs: true,
esModuleInterop: true,
jsx: ts.JsxEmit.React,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
};

module.exports = {
process(sourceText, sourcePath) {
const { outputText } = ts.transpileModule(sourceText, {
fileName: sourcePath,
compilerOptions: COMPILER_OPTIONS,
});

return { code: outputText };
},

getCacheKey(sourceText, sourcePath) {
return crypto
.createHash('sha1')
.update(ts.version)
.update('\0', 'utf8')
.update(JSON.stringify(COMPILER_OPTIONS))
.update('\0', 'utf8')
.update(sourcePath)
.update('\0', 'utf8')
.update(sourceText)
.digest('hex');
},
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"build:cli": "npm run --workspace cli clean && npm run --workspace cli build",
"prepack": "npm run build:cli",
"eslint": "eslint --quiet .",
"jest": "jest src/versioning/* expo/* && npm run --workspace cli test",
"jest": "jest src/CodePush.test.js src/versioning/* expo/* && npm run --workspace cli test",
"e2e": "ts-node --project e2e/tsconfig.json e2e/run.ts"
},
"publishConfig": {
Expand Down
10 changes: 10 additions & 0 deletions src/CodePush.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
*/
const updateInfo = {
download_url: latestReleaseInfo.downloadUrl,
/**
* Only released updates that were published with a binary patch carry this.
* When it is missing, the update is downloaded in full from `download_url`.
*/
binary_patch_download_url: latestReleaseInfo.binaryPatchDownloadUrl,
// (`enabled` will always be true in the release information obtained from the previous process.)
is_available: latestReleaseInfo.enabled,
package_hash: latestReleaseInfo.packageHash,
Expand Down Expand Up @@ -281,6 +286,11 @@ function mapToRemotePackageMetadata(updateInfo) {
packageHash: updateInfo.package_hash ?? '',
packageSize: updateInfo.package_size ?? 0,
downloadUrl: updateInfo.download_url ?? '',
// The field stays out of the package unless the update really has a binary patch,
// so that the native side sees exactly what it saw before patches existed.
...(updateInfo.binary_patch_download_url
? { binaryPatchDownloadUrl: updateInfo.binary_patch_download_url }
: {}),
};
}

Expand Down
Loading
Loading