Skip to content
Merged
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
30 changes: 5 additions & 25 deletions script/binary-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as crypto from "crypto";
import * as chalk from "chalk";
import { log } from "./ui/log";
import * as os from "os";
import * as yazl from "yazl";
import * as yauzl from "yauzl";
import { createZip, ZipEntry } from "./utils/file-utils";
import { readFile } from "node:fs/promises";
import { buffer as readStreamToBuffer } from "node:stream/consumers";
import * as protobuf from "protobufjs";
Expand Down Expand Up @@ -140,30 +140,10 @@ async function takeHashesFromMetadata(metadataPath: string): Promise<BinaryHashe
}

function createZipArchive(sourceFolder: string, zipPath: string, filesToInclude: string[]): Promise<void> {
return new Promise<void>((resolve, reject) => {
const zipFile = new yazl.ZipFile();
const writeStream = fs.createWriteStream(zipPath);

// yazl reports unreadable input files on the ZipFile itself
zipFile.on("error", reject);
zipFile.outputStream
.pipe(writeStream)
.on("error", (error: Error) => {
reject(error);
})
.on("close", () => {
resolve();
});

for (const file of filesToInclude) {
const filePath = path.join(sourceFolder, file);
if (fs.existsSync(filePath)) {
zipFile.addFile(filePath, file);
}
}

zipFile.end();
});
const entries: ZipEntry[] = filesToInclude
.map((file): ZipEntry => [path.join(sourceFolder, file), file])
.filter(([filePath]) => fs.existsSync(filePath));
return createZip(zipPath, entries);
}

function parsePlistBuffer(buf: Buffer): any {
Expand Down
115 changes: 30 additions & 85 deletions script/command-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -843,73 +843,29 @@ function printReleaseSizeWarnings(analysis: ReleaseSizeAnalysis | null, force: b
}
}

export const releaseExpo = async (command: cli.IReleaseReactCommand): Promise<void> => {
const { platform, bundleName, projectName } = validateReactReleaseCommand(command, {
resolveEntryFile: false,
});

const outputFolder: string = command.outputDir || path.join(os.tmpdir(), "CodePush");
const sourcemapOutputFolder: string = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
const baseReleaseTmpFolder: string = path.join(os.tmpdir(), "CodePushBaseRelease");
// releaseCommand below is `command` itself, and it overwrites command.outputDir with the
// resolved path -- so capture whether the user actually passed one before that happens.
const isTempOutputFolder: boolean = !command.outputDir;

const releaseCommand: cli.IReleaseReactCommand = command;
releaseCommand.package = outputFolder;
releaseCommand.outputDir = outputFolder;
releaseCommand.bundleName = bundleName;
await sdk.getDeployment(command.appName, command.deploymentName);

// For release-expo, buildNumber is NOT auto-detected — it must be passed explicitly
// via --buildNumber. Auto-detection only applies to release-native where the binary
// build number is the natural targeting key.
const appVersion: string = command.appStoreVersion || (await getReactNativeProjectVersionInfo(command, projectName)).appVersion;

if (!command.sourcemapOutput) {
await createEmptyTempReleaseFolder(sourcemapOutputFolder);
}

throwForInvalidSemverRange(appVersion);
releaseCommand.appStoreVersion = appVersion;

await createEmptyTempReleaseFolder(outputFolder);
await deleteReactTempFolders();

const { isHermes, sourceMaps, bundleOptions } = await planBundle(command, outputFolder, bundleName, sourcemapOutputFolder);
await runExpoExportEmbedCommand(bundleOptions);

if (isHermes) {
await compileHermesBundle(command, outputFolder, bundleName, sourceMaps, baseReleaseTmpFolder);
}
// The only steps where release-react and release-expo differ
interface ReactBundler {
resolveEntryFile: boolean;
bundle(options: BundleOptions, entryFile: string, isHermes: boolean): Promise<void>;
}

const releaseSizeAnalysis: ReleaseSizeAnalysis | null = validateGeneratedReleaseSizes(command, outputFolder, bundleName, platform);
const reactNativeBundler: ReactBundler = {
resolveEntryFile: true,
bundle: (options, entryFile, isHermes) => runReactNativeBundleCommand(options, entryFile, isHermes),
};

if (command.privateKeyPath) {
log(chalk.cyan("\nSigning the bundle:\n"));
await sign(command.privateKeyPath, outputFolder);
} else {
console.log("private key was not provided");
}
// Expo finds the JS entry file itself
const expoBundler: ReactBundler = {
resolveEntryFile: false,
bundle: (options) => runExpoExportEmbedCommand(options),
};

log(chalk.cyan("\nReleasing update contents to CodePush:\n"));
await releaseReactNative(releaseCommand, () => printReleaseSizeWarnings(releaseSizeAnalysis, command.force || false));
export const releaseReact = (command: cli.IReleaseReactCommand): Promise<void> => releaseReactBundle(command, reactNativeBundler);

if (isTempOutputFolder) {
await deleteFolder(outputFolder);
}
export const releaseExpo = (command: cli.IReleaseReactCommand): Promise<void> => releaseReactBundle(command, expoBundler);

if (!command.sourcemapOutput) {
await deleteFolder(sourcemapOutputFolder);
}

await deleteFolder(baseReleaseTmpFolder);
};

export const releaseReact = async (command: cli.IReleaseReactCommand): Promise<void> => {
const { platform, bundleName, entryFile, projectName } = validateReactReleaseCommand(command, {
resolveEntryFile: true,
});
async function releaseReactBundle(command: cli.IReleaseReactCommand, bundler: ReactBundler): Promise<void> {
const { platform, bundleName, entryFile, projectName } = validateReactReleaseCommand(command, bundler);

const outputFolder: string = command.outputDir || path.join(os.tmpdir(), "CodePush");
const sourcemapOutputFolder: string = command.sourcemapOutput || path.join(os.tmpdir(), "CodePushSourceMap");
Expand All @@ -926,9 +882,8 @@ export const releaseReact = async (command: cli.IReleaseReactCommand): Promise<v
// This validation helps to save about 1 minute or more in case user has typed wrong app or deployment name.
await sdk.getDeployment(command.appName, command.deploymentName);

// For release-react, buildNumber is NOT auto-detected — it must be passed explicitly
// via --buildNumber. Auto-detection only applies to release-native where the binary
// build number is the natural targeting key.
// buildNumber is NOT auto-detected here — it must be passed explicitly via --buildNumber.
// Auto-detection only applies to release-native where the binary build number is the natural targeting key.
const appVersion: string = command.appStoreVersion || (await getReactNativeProjectVersionInfo(command, projectName)).appVersion;

if (!command.sourcemapOutput) {
Expand All @@ -944,7 +899,7 @@ export const releaseReact = async (command: cli.IReleaseReactCommand): Promise<v
await deleteReactTempFolders();

const { isHermes, sourceMaps, bundleOptions } = await planBundle(command, outputFolder, bundleName, sourcemapOutputFolder);
await runReactNativeBundleCommand(bundleOptions, entryFile, isHermes);
await bundler.bundle(bundleOptions, entryFile, isHermes);

if (isHermes) {
await compileHermesBundle(command, outputFolder, bundleName, sourceMaps, baseReleaseTmpFolder);
Expand All @@ -960,7 +915,9 @@ export const releaseReact = async (command: cli.IReleaseReactCommand): Promise<v
}

log(chalk.cyan("\nReleasing update contents to CodePush:\n"));
await releaseReactNative(releaseCommand, () => printReleaseSizeWarnings(releaseSizeAnalysis, command.force || false));
await doRelease(releaseCommand, getReactNativePackageInfo(releaseCommand), () =>
printReleaseSizeWarnings(releaseSizeAnalysis, command.force || false)
);

if (isTempOutputFolder) {
await deleteFolder(outputFolder);
Expand All @@ -971,7 +928,7 @@ export const releaseReact = async (command: cli.IReleaseReactCommand): Promise<v
}

await deleteFolder(baseReleaseTmpFolder);
};
}

export const releaseNative = async (command: cli.IReleaseNativeCommand): Promise<void> => {
const platform: string = command.platform.toLowerCase();
Expand Down Expand Up @@ -1076,9 +1033,9 @@ export const releaseNative = async (command: cli.IReleaseNativeCommand): Promise
}
};

const releaseReactNative = async (command: cli.IReleaseReactCommand, onReleaseCompleted?: () => void): Promise<void> => {
// for initial release we explicitly define release as optional, disabled, without rollout, with a special description
const updateMetadata: ReactNativePackageInfo = {
// For an initial release we explicitly define it as optional, disabled, without rollout, with a special description
function getReactNativePackageInfo(command: cli.IReleaseReactCommand): ReactNativePackageInfo {
return {
description: command.initial ? `Zero release for v${command.appStoreVersion}` : command.description,
isDisabled: command.initial ? true : command.disabled,
isMandatory: command.initial ? false : command.mandatory,
Expand All @@ -1089,9 +1046,7 @@ const releaseReactNative = async (command: cli.IReleaseReactCommand, onReleaseCo
appVersion: command.appStoreVersion,
buildNumber: command.buildNumber,
};

return doRelease(command, updateMetadata, onReleaseCompleted);
};
}

const doRelease = async (
command: cli.IReleaseCommand | cli.IReleaseReactCommand,
Expand Down Expand Up @@ -1144,17 +1099,7 @@ const doNativeRelease = async (releaseCommand: cli.IReleaseReactCommand): Promis

const filePath: string = releaseCommand.package;

const updateMetadata: ReactNativePackageInfo = {
description: releaseCommand.initial ? `Zero release for v${releaseCommand.appStoreVersion}` : releaseCommand.description,
isDisabled: releaseCommand.initial ? true : releaseCommand.disabled,
isMandatory: releaseCommand.initial ? false : releaseCommand.mandatory,
isInitial: releaseCommand.initial,
bundleName: releaseCommand.bundleName,
outputDir: releaseCommand.outputDir,
rollout: releaseCommand.initial ? undefined : releaseCommand.rollout,
appVersion: releaseCommand.appStoreVersion,
buildNumber: releaseCommand.buildNumber,
};
const updateMetadata: ReactNativePackageInfo = getReactNativePackageInfo(releaseCommand);

const uploadProgress = createUploadProgress();

Expand Down
Loading
Loading