From d276c46ae6bbad12e24a9576996441e3de349e9c Mon Sep 17 00:00:00 2001 From: saseungmin Date: Tue, 10 Jun 2025 14:36:33 +0900 Subject: [PATCH 1/3] feat(cli): add expo project type support to bundle command --- cli/commands/bundleCommand/bundleCodePush.js | 28 ++++++++--- cli/commands/bundleCommand/index.js | 3 ++ cli/commands/releaseCommand/index.js | 3 ++ cli/commands/releaseCommand/release.js | 4 +- cli/functions/runExpoBundleCommand.js | 53 ++++++++++++++++++++ 5 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 cli/functions/runExpoBundleCommand.js diff --git a/cli/commands/bundleCommand/bundleCodePush.js b/cli/commands/bundleCommand/bundleCodePush.js index e19e9c266..b181f0b0b 100644 --- a/cli/commands/bundleCommand/bundleCodePush.js +++ b/cli/commands/bundleCommand/bundleCodePush.js @@ -1,12 +1,14 @@ const fs = require('fs'); const { prepareToBundleJS } = require('../../functions/prepareToBundleJS'); const { runReactNativeBundleCommand } = require('../../functions/runReactNativeBundleCommand'); +const { runExpoBundleCommand } = require('../../functions/runExpoBundleCommand'); const { getReactTempDir } = require('../../functions/getReactTempDir'); const { runHermesEmitBinaryCommand } = require('../../functions/runHermesEmitBinaryCommand'); const { makeCodePushBundle } = require('../../functions/makeCodePushBundle'); const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant'); /** + * @param type {string} 'react-native' | 'expo' * @param platform {string} 'ios' | 'android' * @param outputRootPath {string} * @param entryFile {string} @@ -15,6 +17,7 @@ const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant'); * @return {Promise} CodePush bundle file name (equals to packageHash) */ async function bundleCodePush( + type = 'react-native', platform = 'ios', outputRootPath = ROOT_OUTPUT_DIR, entryFile = ENTRY_FILE, @@ -32,13 +35,24 @@ async function bundleCodePush( prepareToBundleJS({ deleteDirs: [outputRootPath, getReactTempDir()], makeDir: OUTPUT_CONTENT_PATH }); - runReactNativeBundleCommand( - _jsBundleName, - OUTPUT_CONTENT_PATH, - platform, - SOURCEMAP_OUTPUT, - entryFile, - ); + if (type === 'react-native') { + runReactNativeBundleCommand( + _jsBundleName, + OUTPUT_CONTENT_PATH, + platform, + SOURCEMAP_OUTPUT, + entryFile, + ); + } else if (type === 'expo') { + runExpoBundleCommand( + _jsBundleName, + OUTPUT_CONTENT_PATH, + platform, + SOURCEMAP_OUTPUT, + entryFile, + ); + } + console.log('log: JS bundling complete'); await runHermesEmitBinaryCommand( diff --git a/cli/commands/bundleCommand/index.js b/cli/commands/bundleCommand/index.js index 3b819d94e..593dd7331 100644 --- a/cli/commands/bundleCommand/index.js +++ b/cli/commands/bundleCommand/index.js @@ -4,6 +4,7 @@ const { OUTPUT_BUNDLE_DIR, ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../consta program.command('bundle') .description('Creates a CodePush bundle file (assumes Hermes is enabled).') + .addOption(new Option('-t, --type ', 'project type (react-native | expo)').choices(['react-native', 'expo']).default('react-native')) .addOption(new Option('-p, --platform ', 'platform').choices(['ios', 'android']).default('ios')) .option('-o, --output-path ', 'path to output root directory', ROOT_OUTPUT_DIR) .option('-e, --entry-file ', 'path to JS/TS entry file', ENTRY_FILE) @@ -11,6 +12,7 @@ program.command('bundle') .option('--output-bundle-dir ', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR) /** * @param {Object} options + * @param {string} options.type * @param {string} options.platform * @param {string} options.outputPath * @param {string} options.entryFile @@ -20,6 +22,7 @@ program.command('bundle') */ .action((options) => { bundleCodePush( + options.type, options.platform, options.outputPath, options.entryFile, diff --git a/cli/commands/releaseCommand/index.js b/cli/commands/releaseCommand/index.js index 533f84d90..63a48d547 100644 --- a/cli/commands/releaseCommand/index.js +++ b/cli/commands/releaseCommand/index.js @@ -7,6 +7,7 @@ program.command('release') .description('Deploys a new CodePush update for a target binary app.\nAfter creating the CodePush bundle, it uploads the file and updates the ReleaseHistory information.\n`bundleUploader`, `getReleaseHistory`, and `setReleaseHistory` functions should be implemented in the config file.') .requiredOption('-b, --binary-version ', '(Required) The target binary version') .requiredOption('-v, --app-version ', '(Required) The app version to be released. It must be greater than the binary version.') + .addOption(new Option('-t, --type ', 'project type (react-native | expo)').choices(['react-native', 'expo']).default('react-native')) .addOption(new Option('-p, --platform ', 'platform').choices(['ios', 'android']).default('ios')) .option('-i, --identifier ', 'reserved characters to distinguish the release.') .option('-c, --config ', 'set config file name (JS/TS)', CONFIG_FILE_NAME) @@ -22,6 +23,7 @@ program.command('release') * @param {Object} options * @param {string} options.binaryVersion * @param {string} options.appVersion + * @param {string} options.type * @param {string} options.platform * @param {string} options.identifier * @param {string} options.config @@ -44,6 +46,7 @@ program.command('release') config.setReleaseHistory, options.binaryVersion, options.appVersion, + options.type, options.platform, options.identifier, options.outputPath, diff --git a/cli/commands/releaseCommand/release.js b/cli/commands/releaseCommand/release.js index 7cfd5a9ba..c090d13cf 100644 --- a/cli/commands/releaseCommand/release.js +++ b/cli/commands/releaseCommand/release.js @@ -25,6 +25,7 @@ const { addToReleaseHistory } = require("./addToReleaseHistory"); * ): Promise} * @param binaryVersion {string} * @param appVersion {string} + * @param type {string} 'react-native' | 'expo' * @param platform {"ios" | "android"} * @param identifier {string?} * @param outputPath {string} @@ -43,6 +44,7 @@ async function release( setReleaseHistory, binaryVersion, appVersion, + type = 'react-native', platform, identifier, outputPath, @@ -56,7 +58,7 @@ async function release( ) { const bundleFileName = skipBundle ? readBundleFileNameFrom(bundleDirectory) - : await bundleCodePush(platform, outputPath, entryFile, jsBundleName, bundleDirectory); + : await bundleCodePush(type, platform, outputPath, entryFile, jsBundleName, bundleDirectory); const bundleFilePath = `${bundleDirectory}/${bundleFileName}`; const downloadUrl = await (async () => { diff --git a/cli/functions/runExpoBundleCommand.js b/cli/functions/runExpoBundleCommand.js new file mode 100644 index 000000000..7ed3ab972 --- /dev/null +++ b/cli/functions/runExpoBundleCommand.js @@ -0,0 +1,53 @@ +const path = require('path'); +const shell = require('shelljs'); + +/** + * Run `expo bundle` CLI command + * + * @param bundleName {string} JS bundle file name + * @param entryFile {string} App code entry file name (default: index.ts) + * @param outputPath {string} Path to output JS bundle file and assets + * @param platform {string} Platform (ios | android) + * @param sourcemapOutput {string} Path to output sourcemap file (Warning: if sourcemapOutput points to the outputPath, the sourcemap will be included in the CodePush bundle and increase the deployment size) + * @return {void} + */ +function runExpoBundleCommand( + bundleName, + outputPath, + platform, + sourcemapOutput, + entryFile, +) { + /** + * @return {string} + */ + function getCliPath() { + return path.join('node_modules', '.bin', 'expo'); + } + + /** + * @type {string[]} + */ + const expoBundleArgs = [ + 'export:embed', + '--assets-dest', + outputPath, + '--bundle-output', + path.join(outputPath, bundleName), + '--dev', + 'false', + '--entry-file', + entryFile, + '--platform', + platform, + '--sourcemap-output', + sourcemapOutput, + '--reset-cache', + ]; + + console.log('Running "expo export:embed" command:\n'); + + shell.exec(`${getCliPath()} ${expoBundleArgs.join(' ')}`); +} + +module.exports = { runExpoBundleCommand }; From 3161b87e75b00c157be4538bd6b0d3650a1d2492 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Wed, 11 Jun 2025 14:51:18 +0900 Subject: [PATCH 2/3] docs: add Expo support examples to CLI documentation --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3accc74b3..82eada536 100644 --- a/README.md +++ b/README.md @@ -330,7 +330,7 @@ Create a new release history for a specific binary app version. **Example:** - Create a new release history for the binary app version `1.0.0`. -``` +```bash npx code-push create-history --binary-version 1.0.0 --platform ios --identifier staging ``` @@ -342,7 +342,7 @@ Display the release history for a specific binary app version. **Example:** - Show the release history for the binary app version `1.0.0`. -``` +```bash npx code-push show-history --binary-version 1.0.0 --platform ios --identifier staging ``` @@ -354,11 +354,15 @@ Release a CodePush update for a specific binary app version. **Example:** - Release a CodePush update `1.0.1` targeting the binary app version `1.0.0`. -``` +```bash npx code-push release --binary-version 1.0.0 --app-version 1.0.1 \ --platform ios --identifier staging --entry-file index.js \ --mandatory true + +# Expo project +npx code-push release --type expo --binary-version 1.0.0 --app-version 1.0.1 --platform ios ``` +- `--type`: Project type (react-native (default) | expo) - `--binary-version`: The version of the binary app that the CodePush update is targeting. - `--app-version`: The version of the CodePush update itself. @@ -375,7 +379,7 @@ Update the release history for a specific CodePush update. **Example:** - Rollback the CodePush update `1.0.1` (targeting the binary app version `1.0.0`). -``` +```bash npx code-push update-history --binary-version 1.0.0 --app-version 1.0.1 \ --platform ios --identifier staging \ --enable false @@ -386,10 +390,16 @@ npx code-push update-history --binary-version 1.0.0 --app-version 1.0.1 \ Create a CodePush bundle file. **Example:** -``` +```bash npx code-push bundle --platform android --entry-file index.js + +# Expo project +npx code-push bundle --type expo --platform android --entry-file index.js ``` +- `--type`: Project type (react-native (default) | expo) By default, the bundle file is created in the `/build/bundleOutput` directory. +>[!NOTE] For Expo projects, the CLI uses `expo export:embed` command for bundling instead of React Native's bundle command. + (The file name represents a hash value of the bundle content.) From 22accedeeb4c6a19179afeab9026d03571a92b25 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Mon, 23 Jun 2025 09:28:39 +0900 Subject: [PATCH 3/3] refactor: for suggest review --- README.md | 11 ++++++----- cli/commands/bundleCommand/bundleCodePush.js | 12 ++++++------ cli/commands/bundleCommand/index.js | 6 +++--- cli/commands/releaseCommand/index.js | 6 +++--- cli/commands/releaseCommand/release.js | 6 +++--- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 82eada536..d737cb1f2 100644 --- a/README.md +++ b/README.md @@ -360,9 +360,9 @@ npx code-push release --binary-version 1.0.0 --app-version 1.0.1 \ --mandatory true # Expo project -npx code-push release --type expo --binary-version 1.0.0 --app-version 1.0.1 --platform ios +npx code-push release --framework expo --binary-version 1.0.0 --app-version 1.0.1 --platform ios ``` -- `--type`: Project type (react-native (default) | expo) +- `--framework`(`-f`) : Framework type (expo) - `--binary-version`: The version of the binary app that the CodePush update is targeting. - `--app-version`: The version of the CodePush update itself. @@ -394,12 +394,13 @@ Create a CodePush bundle file. npx code-push bundle --platform android --entry-file index.js # Expo project -npx code-push bundle --type expo --platform android --entry-file index.js +npx code-push bundle --framework expo --platform android --entry-file index.js ``` -- `--type`: Project type (react-native (default) | expo) +- `--framework`(`-f`): Framework type (expo) By default, the bundle file is created in the `/build/bundleOutput` directory. ->[!NOTE] For Expo projects, the CLI uses `expo export:embed` command for bundling instead of React Native's bundle command. +> [!NOTE] +> For Expo projects, the CLI uses `expo export:embed` command for bundling instead of React Native's bundle command. (The file name represents a hash value of the bundle content.) diff --git a/cli/commands/bundleCommand/bundleCodePush.js b/cli/commands/bundleCommand/bundleCodePush.js index b181f0b0b..727a03cd3 100644 --- a/cli/commands/bundleCommand/bundleCodePush.js +++ b/cli/commands/bundleCommand/bundleCodePush.js @@ -8,7 +8,7 @@ const { makeCodePushBundle } = require('../../functions/makeCodePushBundle'); const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant'); /** - * @param type {string} 'react-native' | 'expo' + * @param framework {string|undefined} 'expo' * @param platform {string} 'ios' | 'android' * @param outputRootPath {string} * @param entryFile {string} @@ -17,7 +17,7 @@ const { ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../constant'); * @return {Promise} CodePush bundle file name (equals to packageHash) */ async function bundleCodePush( - type = 'react-native', + framework, platform = 'ios', outputRootPath = ROOT_OUTPUT_DIR, entryFile = ENTRY_FILE, @@ -35,16 +35,16 @@ async function bundleCodePush( prepareToBundleJS({ deleteDirs: [outputRootPath, getReactTempDir()], makeDir: OUTPUT_CONTENT_PATH }); - if (type === 'react-native') { - runReactNativeBundleCommand( + if (framework === 'expo') { + runExpoBundleCommand( _jsBundleName, OUTPUT_CONTENT_PATH, platform, SOURCEMAP_OUTPUT, entryFile, ); - } else if (type === 'expo') { - runExpoBundleCommand( + } else { + runReactNativeBundleCommand( _jsBundleName, OUTPUT_CONTENT_PATH, platform, diff --git a/cli/commands/bundleCommand/index.js b/cli/commands/bundleCommand/index.js index 593dd7331..7cf2d82da 100644 --- a/cli/commands/bundleCommand/index.js +++ b/cli/commands/bundleCommand/index.js @@ -4,7 +4,7 @@ const { OUTPUT_BUNDLE_DIR, ROOT_OUTPUT_DIR, ENTRY_FILE } = require('../../consta program.command('bundle') .description('Creates a CodePush bundle file (assumes Hermes is enabled).') - .addOption(new Option('-t, --type ', 'project type (react-native | expo)').choices(['react-native', 'expo']).default('react-native')) + .addOption(new Option('-f, --framework ', 'framework type (expo)').choices(['expo'])) .addOption(new Option('-p, --platform ', 'platform').choices(['ios', 'android']).default('ios')) .option('-o, --output-path ', 'path to output root directory', ROOT_OUTPUT_DIR) .option('-e, --entry-file ', 'path to JS/TS entry file', ENTRY_FILE) @@ -12,7 +12,7 @@ program.command('bundle') .option('--output-bundle-dir ', 'name of directory containing the bundle file created by the "bundle" command', OUTPUT_BUNDLE_DIR) /** * @param {Object} options - * @param {string} options.type + * @param {string} options.framework * @param {string} options.platform * @param {string} options.outputPath * @param {string} options.entryFile @@ -22,7 +22,7 @@ program.command('bundle') */ .action((options) => { bundleCodePush( - options.type, + options.framework, options.platform, options.outputPath, options.entryFile, diff --git a/cli/commands/releaseCommand/index.js b/cli/commands/releaseCommand/index.js index 63a48d547..6863a885f 100644 --- a/cli/commands/releaseCommand/index.js +++ b/cli/commands/releaseCommand/index.js @@ -7,7 +7,7 @@ program.command('release') .description('Deploys a new CodePush update for a target binary app.\nAfter creating the CodePush bundle, it uploads the file and updates the ReleaseHistory information.\n`bundleUploader`, `getReleaseHistory`, and `setReleaseHistory` functions should be implemented in the config file.') .requiredOption('-b, --binary-version ', '(Required) The target binary version') .requiredOption('-v, --app-version ', '(Required) The app version to be released. It must be greater than the binary version.') - .addOption(new Option('-t, --type ', 'project type (react-native | expo)').choices(['react-native', 'expo']).default('react-native')) + .addOption(new Option('-f, --framework ', 'framework type (expo)').choices(['expo'])) .addOption(new Option('-p, --platform ', 'platform').choices(['ios', 'android']).default('ios')) .option('-i, --identifier ', 'reserved characters to distinguish the release.') .option('-c, --config ', 'set config file name (JS/TS)', CONFIG_FILE_NAME) @@ -23,7 +23,7 @@ program.command('release') * @param {Object} options * @param {string} options.binaryVersion * @param {string} options.appVersion - * @param {string} options.type + * @param {string} options.framework * @param {string} options.platform * @param {string} options.identifier * @param {string} options.config @@ -46,7 +46,7 @@ program.command('release') config.setReleaseHistory, options.binaryVersion, options.appVersion, - options.type, + options.framework, options.platform, options.identifier, options.outputPath, diff --git a/cli/commands/releaseCommand/release.js b/cli/commands/releaseCommand/release.js index c090d13cf..2aa1745fb 100644 --- a/cli/commands/releaseCommand/release.js +++ b/cli/commands/releaseCommand/release.js @@ -25,7 +25,7 @@ const { addToReleaseHistory } = require("./addToReleaseHistory"); * ): Promise} * @param binaryVersion {string} * @param appVersion {string} - * @param type {string} 'react-native' | 'expo' + * @param framework {string|undefined} 'expo' * @param platform {"ios" | "android"} * @param identifier {string?} * @param outputPath {string} @@ -44,7 +44,7 @@ async function release( setReleaseHistory, binaryVersion, appVersion, - type = 'react-native', + framework, platform, identifier, outputPath, @@ -58,7 +58,7 @@ async function release( ) { const bundleFileName = skipBundle ? readBundleFileNameFrom(bundleDirectory) - : await bundleCodePush(type, platform, outputPath, entryFile, jsBundleName, bundleDirectory); + : await bundleCodePush(framework, platform, outputPath, entryFile, jsBundleName, bundleDirectory); const bundleFilePath = `${bundleDirectory}/${bundleFileName}`; const downloadUrl = await (async () => {