Skip to content

Commit 9d5c8a2

Browse files
committed
feat(CLI): add --hash-calc option to release command for computing hash of existing bundle file
1 parent 314b86e commit 9d5c8a2

File tree

6 files changed

+134
-4
lines changed

6 files changed

+134
-4
lines changed

cli/commands/releaseCommand/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Options = {
1919
skipBundle: boolean;
2020
skipCleanup: boolean;
2121
outputBundleDir: string;
22+
hashCalc?: boolean;
2223
}
2324

2425
program.command('release')
@@ -36,6 +37,7 @@ program.command('release')
3637
.option('--enable <bool>', 'make the release to be enabled', parseBoolean, true)
3738
.option('--rollout <number>', 'rollout percentage (0-100)', parseFloat)
3839
.option('--skip-bundle <bool>', 'skip bundle process', parseBoolean, false)
40+
.option('--hash-calc <bool>', 'calculates the bundle file hash used for packageHash in the release history (Requires setting --skip-bundle to true)', parseBoolean)
3941
.option('--skip-cleanup <bool>', 'skip cleanup process', parseBoolean, false)
4042
.option('--output-bundle-dir <string>', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR)
4143
.action(async (options: Options) => {
@@ -46,6 +48,11 @@ program.command('release')
4648
process.exit(1);
4749
}
4850

51+
if (options.hashCalc && !options.skipBundle) {
52+
console.error('--hash-calc option can be used only when --skip-bundle is set to true.');
53+
process.exit(1);
54+
}
55+
4956
await release(
5057
config.bundleUploader,
5158
config.getReleaseHistory,
@@ -64,6 +71,7 @@ program.command('release')
6471
options.skipBundle,
6572
options.skipCleanup,
6673
`${options.outputPath}/${options.outputBundleDir}`,
74+
options.hashCalc,
6775
)
6876

6977
console.log('🚀 Release completed.')

cli/commands/releaseCommand/release.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from "path";
33
import { bundleCodePush } from "../bundleCommand/bundleCodePush.js";
44
import { addToReleaseHistory } from "./addToReleaseHistory.js";
55
import type { CliConfigInterface } from "../../../typings/react-native-code-push.d.ts";
6+
import { generatePackageHashFromDirectory } from "../../utils/hash-utils.js";
7+
import { unzip } from "../../utils/unzip.js";
68

79
export async function release(
810
bundleUploader: CliConfigInterface['bundleUploader'],
@@ -22,12 +24,21 @@ export async function release(
2224
skipBundle: boolean,
2325
skipCleanup: boolean,
2426
bundleDirectory: string,
27+
hashCalc?: boolean,
2528
): Promise<void> {
2629
const bundleFileName = skipBundle
2730
? readBundleFileNameFrom(bundleDirectory)
2831
: await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory);
2932
const bundleFilePath = `${bundleDirectory}/${bundleFileName}`;
3033

34+
const packageHash = await (() => {
35+
if (skipBundle && hashCalc) {
36+
return calcHashFromBundleFile(bundleFilePath);
37+
}
38+
// If not using --skip-bundle, the bundleFileName represents package hash already.
39+
return bundleFileName;
40+
})();
41+
3142
const downloadUrl = await (async () => {
3243
try {
3344
const { downloadUrl } = await bundleUploader(bundleFilePath, platform, identifier);
@@ -42,7 +53,7 @@ export async function release(
4253
appVersion,
4354
binaryVersion,
4455
downloadUrl,
45-
bundleFileName,
56+
packageHash,
4657
getReleaseHistory,
4758
setReleaseHistory,
4859
platform,
@@ -70,3 +81,22 @@ function readBundleFileNameFrom(bundleDirectory: string): string {
7081
const bundleFilePath = path.join(bundleDirectory, files[0]);
7182
return path.basename(bundleFilePath);
7283
}
84+
85+
async function calcHashFromBundleFile(bundleFilePath: string): Promise<string> {
86+
const tempDir = path.resolve(path.join(path.dirname(bundleFilePath), 'temp_contents_for_hash_calc'));
87+
const zipFilePath = path.resolve(bundleFilePath);
88+
89+
if (fs.existsSync(tempDir)) {
90+
fs.rmSync(tempDir, { recursive: true, force: true });
91+
}
92+
fs.mkdirSync(tempDir, { recursive: true });
93+
94+
try {
95+
await unzip(zipFilePath, tempDir);
96+
const hash = await generatePackageHashFromDirectory(tempDir, tempDir);
97+
console.log(`log: Calculated package hash from existing bundle file: ${hash}`);
98+
return hash;
99+
} finally {
100+
fs.rmSync(tempDir, { recursive: true, force: true });
101+
}
102+
}

cli/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"commander": "^12.1.0",
1515
"shelljs": "^0.10.0",
1616
"xcode": "^3.0.1",
17-
"yazl": "^3.3.1"
17+
"yazl": "^3.3.1",
18+
"yauzl": "^3.2.0"
1819
},
1920
"peerDependencies": {
2021
"ts-node": ">=10"
@@ -28,6 +29,7 @@
2829
"node": ">=18"
2930
},
3031
"devDependencies": {
31-
"@types/yazl": "^3.3.0"
32+
"@types/yazl": "^3.3.0",
33+
"@types/yauzl": "^2.10.3"
3234
}
3335
}

cli/utils/unzip.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import yauzl from "yauzl";
4+
5+
export function unzip(zipPath: string, outputDir: string): Promise<void> {
6+
return new Promise<void>((resolve, reject) => {
7+
yauzl.open(zipPath, { lazyEntries: true }, (err, zipFile) => {
8+
if (err) return reject(err);
9+
10+
zipFile.readEntry();
11+
12+
zipFile.on("entry", (entry) => {
13+
const fullPath = path.join(outputDir, entry.fileName);
14+
15+
// Handle directory entry
16+
if (/\/$/.test(entry.fileName)) {
17+
fs.mkdir(fullPath, { recursive: true }, (err) => {
18+
if (err) return reject(err);
19+
zipFile.readEntry();
20+
});
21+
return;
22+
}
23+
24+
// Handle file entry
25+
zipFile.openReadStream(entry, (err, readStream) => {
26+
if (err) return reject(err);
27+
28+
fs.mkdir(path.dirname(fullPath), { recursive: true }, (err) => {
29+
if (err) return reject(err);
30+
31+
const writeStream = fs.createWriteStream(fullPath);
32+
readStream.pipe(writeStream);
33+
34+
// Continue to the next entry after writing
35+
writeStream.on("close", () => {
36+
zipFile.readEntry();
37+
});
38+
});
39+
});
40+
});
41+
42+
zipFile.on("end", resolve);
43+
zipFile.on("error", reject);
44+
});
45+
});
46+
}

package-lock.json

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@
8181
"semver": "^7.3.5",
8282
"shelljs": "^0.10.0",
8383
"xcode": "^3.0.1",
84-
"yazl": "^3.3.1"
84+
"yazl": "^3.3.1",
85+
"yauzl": "^3.2.0"
8586
},
8687
"peerDependencies": {
8788
"expo": ">=50.0.0",
@@ -104,6 +105,7 @@
104105
"@types/q": "^1.5.4",
105106
"@types/semver": "^7.5.8",
106107
"@types/shelljs": "^0.8.15",
108+
"@types/yauzl": "^2.10.3",
107109
"archiver": "latest",
108110
"babel-jest": "^29.7.0",
109111
"body-parser": "latest",

0 commit comments

Comments
 (0)