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
8 changes: 0 additions & 8 deletions .babelrc

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

actions/setup-node v6 release breaking changes

💡 Result:

Short summary — breaking changes in actions/setup-node v6:

  • Automatic caching behavior narrowed: automatic cache is now enabled only for npm projects when package.json sets packageManager (or devEngines.packageManager); Yarn and pnpm caching is disabled by default and must be opted into via the cache input. [1][2]

  • The always-auth input was removed (deprecated; handling was removed in v6/v6.1.x). Remove any references to always-auth in your workflows. [1][2]

Sources:
[1] actions/setup-node README — “Breaking changes in V6”.
[2] actions/setup-node GitHub releases (v6.0.0 / v6.1.0 release notes).


Verify workflow compatibility with setup-node v6 breaking changes.

The upgrade to actions/setup-node@v6 introduces breaking changes:

  • Automatic caching narrowed: Automatic cache is now enabled only for npm projects with packageManager (or devEngines.packageManager) specified in package.json. Yarn and pnpm caching are disabled by default and must be explicitly opted into via the cache input.
  • always-auth input removed: This deprecated input was removed in v6/v6.1.x. Ensure the workflow does not reference it.

Check the workflow and package.json to confirm caching behavior is still correct and that always-auth is not used.

🤖 Prompt for AI Agents
.github/workflows/publish.yml around line 20: the workflow was updated to
actions/setup-node@v6 which changed caching and removed the always-auth input;
to fix, inspect package.json for a packageManager (or devEngines.packageManager)
entry and add one if missing so automatic npm caching works, or explicitly set
the setup-node cache input (cache: 'yarn' or 'pnpm' or 'npm') to enable caching
for non-npm managers; also search the workflow for any use of always-auth and
remove it or replace with the appropriate npmrc/auth configuration supported by
v6; finally run the workflow locally or in a test branch to confirm caching and
auth behavior are correct.

with:
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,12 @@ Each workflow step contains:

- `uploadIpa`: Upload IPA files (supports `--version` to override extracted version)
- `uploadApk`: Upload APK files (supports `--version` to override extracted version)
- `uploadAab`: Upload AAB files (converted to APK, supports `--version`, `--includeAllSplits`, `--splits`)
- `uploadApp`: Upload APP files (supports `--version` to override extracted version)
- `parseApp`: Parse APP file information
- `parseIpa`: Parse IPA file information
- `parseApk`: Parse APK file information
- `extractApk`: Extract a universal APK from an AAB (supports `--output`, `--includeAllSplits`, `--splits`)
- `packages`: List packages

### User Module (`user`)
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,12 @@ const workflowResult = await moduleManager.executeWorkflow('my-workflow', {

- `uploadIpa`: 上传 IPA 文件(支持 `--version` 参数覆盖提取的版本)
- `uploadApk`: 上传 APK 文件(支持 `--version` 参数覆盖提取的版本)
- `uploadAab`: 上传 AAB 文件(转换为 APK,支持 `--version`、`--includeAllSplits`、`--splits`)
- `uploadApp`: 上传 APP 文件(支持 `--version` 参数覆盖提取的版本)
- `parseApp`: 解析 APP 文件信息
- `parseIpa`: 解析 IPA 文件信息
- `parseApk`: 解析 APK 文件信息
- `extractApk`: 从 AAB 提取通用 APK(支持 `--output`、`--includeAllSplits`、`--splits`)
- `packages`: 列出包

### User 模块 (`user`)
Expand Down
26 changes: 26 additions & 0 deletions cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@
}
}
},
"uploadAab": {
"options": {
"version": {
"hasValue": true
},
"includeAllSplits": {
"default": false
},
"splits": {
"hasValue": true
}
}
},
"uploadApp": {
"options": {
"version": {
Expand All @@ -56,6 +69,19 @@
"parseIpa": {},
"parseApk": {},
"parseAab": {},
"extractApk": {
"options": {
"output": {
"hasValue": true
},
"includeAllSplits": {
"default": false
},
"splits": {
"hasValue": true
}
}
},
"packages": {
"options": {
"platform": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-update-cli",
"version": "2.5.0",
"version": "2.6.0",
"description": "command line tool for react-native-update (remote updates for react native)",
"main": "index.js",
"bin": {
Expand Down
8 changes: 2 additions & 6 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@ import FormData from 'form-data';
import fetch from 'node-fetch';
import ProgressBar from 'progress';
import tcpp from 'tcp-ping';
import { getBaseUrl } from 'utils/http-helper';
import packageJson from '../package.json';
import type { Package, Session } from './types';
import {
credentialFile,
pricingPageUrl,
} from './utils/constants';
import { credentialFile, pricingPageUrl } from './utils/constants';
import { t } from './utils/i18n';
import { getBaseUrl } from 'utils/http-helper';

const tcpPing = util.promisify(tcpp.ping);

let session: Session | undefined;
let savedSession: Session | undefined;


const userAgent = `react-native-update-cli/${packageJson.version}`;

export const getSession = () => session;
Expand Down
17 changes: 17 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ export default {
addedToGitignore: 'Added {{line}} to .gitignore',
androidCrunchPngsWarning:
'The crunchPngs option of android seems not disabled (Please ignore this warning if already disabled), which may cause abnormal consumption of mobile network traffic. Please refer to https://cresc.dev/docs/getting-started#disable-crunchpngs-on-android \n',
aabOpenApksFailed: 'Failed to open generated .apks file',
aabReadUniversalApkFailed: 'Failed to read universal.apk',
aabUniversalApkNotFound: 'universal.apk not found in generated .apks',
aabBundletoolDownloadHint:
'bundletool not found. Downloading node-bundletool via npx (first run may take a while).',
aabManifestNotFound:
"AndroidManifest.xml can't be found in AAB base/manifest/",
aabParseResourcesWarning:
'[Warning] Failed to parse resources.arsc: {{error}}',
aabParseFailed: 'Failed to parse AAB: {{error}}',
aabParseManifestError: 'Parse AndroidManifest.xml error: {{error}}',
aabParseResourcesError: 'Parser resources.arsc error: {{error}}',
appId: 'App ID',
appIdMismatchApk:
'App ID mismatch! Current APK: {{appIdInPkg}}, current update.json: {{appId}}',
Expand Down Expand Up @@ -116,10 +128,14 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
usageDiff: 'Usage: cresc {{command}} <origin> <next>',
usageParseApk: 'Usage: cresc parseApk <apk file>',
usageParseAab: 'Usage: cresc parseAab <aab file>',
usageExtractApk:
'Usage: cresc extractApk <aab file> [--output <apk file>] [--includeAllSplits] [--splits <split names>]',
usageParseApp: 'Usage: cresc parseApp <app file>',
usageParseIpa: 'Usage: cresc parseIpa <ipa file>',
usageUnderDevelopment: 'Usage is under development now.',
usageUploadApk: 'Usage: cresc uploadApk <apk file>',
usageUploadAab:
'Usage: cresc uploadAab <aab file> [--includeAllSplits] [--splits <split names>]',
usageUploadApp: 'Usage: cresc uploadApp <app file>',
usageUploadIpa: 'Usage: cresc uploadIpa <ipa file>',
versionBind:
Expand Down Expand Up @@ -147,4 +163,5 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
'This function needs "node-bsdiff". Please run "{{scriptName}} install node-bsdiff" to install',
nodeHdiffpatchRequired:
'This function needs "node-hdiffpatch". Please run "{{scriptName}} install node-hdiffpatch" to install',
apkExtracted: 'APK extracted to {{output}}',
};
15 changes: 15 additions & 0 deletions src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ export default {
addedToGitignore: '已将 {{line}} 添加到 .gitignore',
androidCrunchPngsWarning:
'android 的 crunchPngs 选项似乎尚未禁用(如已禁用则请忽略此提示),这可能导致热更包体积异常增大,具体请参考 https://pushy.reactnative.cn/docs/getting-started.html#%E7%A6%81%E7%94%A8-android-%E7%9A%84-crunch-%E4%BC%98%E5%8C%96 \n',
aabOpenApksFailed: '无法打开生成的 .apks 文件',
aabReadUniversalApkFailed: '无法读取 universal.apk',
aabUniversalApkNotFound: '在生成的 .apks 中未找到 universal.apk',
aabBundletoolDownloadHint:
'未找到 bundletool,正在通过 npx 下载 node-bundletool(首次下载可能需要一些时间)。',
aabManifestNotFound: '在 AAB 的 base/manifest/ 中找不到 AndroidManifest.xml',
aabParseResourcesWarning: '[警告] 解析 resources.arsc 失败:{{error}}',
aabParseFailed: '解析 AAB 失败:{{error}}',
aabParseManifestError: '解析 AndroidManifest.xml 出错:{{error}}',
aabParseResourcesError: '解析 resources.arsc 出错:{{error}}',
appId: '应用 id',
appIdMismatchApk:
'appId不匹配!当前apk: {{appIdInPkg}}, 当前update.json: {{appId}}',
Expand Down Expand Up @@ -110,9 +120,13 @@ export default {
usageDiff: '用法:pushy {{command}} <origin> <next>',
usageParseApk: '使用方法: pushy parseApk apk后缀文件',
usageParseAab: '使用方法: pushy parseAab aab后缀文件',
usageExtractApk:
'使用方法: pushy extractApk aab后缀文件 [--output apk文件] [--includeAllSplits] [--splits 分包名列表]',
usageParseApp: '使用方法: pushy parseApp app后缀文件',
usageParseIpa: '使用方法: pushy parseIpa ipa后缀文件',
usageUploadApk: '使用方法: pushy uploadApk apk后缀文件',
usageUploadAab:
'使用方法: pushy uploadAab aab后缀文件 [--includeAllSplits] [--splits 分包名列表]',
usageUploadApp: '使用方法: pushy uploadApp app后缀文件',
usageUploadIpa: '使用方法: pushy uploadIpa ipa后缀文件',
versionBind:
Expand All @@ -138,4 +152,5 @@ export default {
'此功能需要 "node-bsdiff"。请运行 "{{scriptName}} install node-bsdiff" 来安装',
nodeHdiffpatchRequired:
'此功能需要 "node-hdiffpatch"。请运行 "{{scriptName}} install node-hdiffpatch" 来安装',
apkExtracted: 'APK 已提取到 {{output}}',
};
2 changes: 1 addition & 1 deletion src/modules/version-module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CLIModule} from '../types';
import type { CLIModule } from '../types';

export const versionModule: CLIModule = {
name: 'version',
Expand Down
111 changes: 101 additions & 10 deletions src/package.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { get, getAllPackages, post, uploadFile, doDelete } from './api';
import { question, saveToLocal } from './utils';
import { t } from './utils/i18n';

import { getPlatform, getSelectedApp } from './app';

import os from 'os';
import path from 'path';
import fs from 'fs-extra';
import Table from 'tty-table';
import { doDelete, getAllPackages, post, uploadFile } from './api';
import { getPlatform, getSelectedApp } from './app';
import type { Platform } from './types';
import { getApkInfo, getAppInfo, getIpaInfo, getAabInfo } from './utils';
import {
getAabInfo,
getApkInfo,
getAppInfo,
getIpaInfo,
question,
saveToLocal,
} from './utils';
import { AabParser } from './utils/app-info-parser/aab';
import { depVersions } from './utils/dep-versions';
import { getCommitInfo } from './utils/git';
import { t } from './utils/i18n';

export async function listPackage(appId: string) {
const allPkgs = await getAllPackages(appId);
const allPkgs = (await getAllPackages(appId)) || [];

const header = [
{ value: t('nativePackageId') },
Expand Down Expand Up @@ -49,7 +57,7 @@ export async function choosePackage(appId: string) {

while (true) {
const id = await question(t('enterNativePackageId'));
const app = list.find((v) => v.id.toString() === id);
const app = list?.find((v) => v.id.toString() === id);
if (app) {
return app;
}
Expand Down Expand Up @@ -143,6 +151,48 @@ export const packageCommands = {
saveToLocal(fn, `${appId}/package/${id}.apk`);
console.log(t('apkUploadSuccess', { id, version: versionName, buildTime }));
},
uploadAab: async ({
args,
options,
}: {
args: string[];
options: Record<string, any>;
}) => {
const source = args[0];
if (!source || !source.endsWith('.aab')) {
throw new Error(t('usageUploadAab'));
}

const output = path.join(
os.tmpdir(),
`${path.basename(source, path.extname(source))}-${Date.now()}.apk`,
);

const includeAllSplits =
options.includeAllSplits === true || options.includeAllSplits === 'true';
const splits = options.splits
? String(options.splits)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: null;

const parser = new AabParser(source);
try {
await parser.extractApk(output, {
includeAllSplits,
splits,
});
await packageCommands.uploadApk({
args: [output],
options,
});
} finally {
if (await fs.pathExists(output)) {
await fs.remove(output);
}
}
},
uploadApp: async ({
args,
options,
Expand Down Expand Up @@ -214,6 +264,42 @@ export const packageCommands = {
}
console.log(await getAabInfo(fn));
},
extractApk: async ({
args,
options,
}: {
args: string[];
options: Record<string, any>;
}) => {
const source = args[0];
if (!source || !source.endsWith('.aab')) {
throw new Error(t('usageExtractApk'));
}

const output =
options.output ||
path.join(
path.dirname(source),
`${path.basename(source, path.extname(source))}.apk`,
);

const includeAllSplits =
options.includeAllSplits === true || options.includeAllSplits === 'true';
const splits = options.splits
? String(options.splits)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: null;

const parser = new AabParser(source);
await parser.extractApk(output, {
includeAllSplits,
splits,
});

console.log(t('apkExtracted', { output }));
},
Comment on lines +267 to +302
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Check README for bundletool mention
echo "=== Checking README for bundletool ==="
rg -i "bundletool" README.md || echo "No bundletool found in README.md"

# Check for any documentation of extractApk command
echo -e "\n=== Checking for extractApk documentation ==="
rg -i "extractapk|extract-apk" README.md docs/ 2>/dev/null || echo "No extractApk docs found"

# List documentation files
echo -e "\n=== Available documentation files ==="
git ls-files | grep -E "(README|docs/|\.md$)" | head -20

Repository: reactnativecn/react-native-update-cli

Length of output: 511


🏁 Script executed:

#!/bin/bash

# Find and examine the AabParser class to understand bundletool usage
echo "=== Finding AabParser implementation ==="
fd -t f "AabParser" --type f

# Search for AabParser in the codebase
echo -e "\n=== Searching for AabParser class definition ==="
rg "class AabParser|export.*AabParser" -A 5

# Search for extractApk method in AabParser
echo -e "\n=== Searching for extractApk method ==="
rg "extractApk" -B 2 -A 10

Repository: reactnativecn/react-native-update-cli

Length of output: 5520


🏁 Script executed:

#!/bin/bash

# Check how bundletool is invoked
echo "=== Checking for bundletool invocations ==="
rg "bundletool|node-bundletool" -A 3 -B 3

# Check for error messages related to bundletool or missing dependencies
echo -e "\n=== Checking for dependency error messages ==="
rg -i "bundletool.*not.*found|bundletool.*install|bundletool.*require" -A 2

Repository: reactnativecn/react-native-update-cli

Length of output: 1580


🏁 Script executed:

#!/bin/bash

# Get the full AabParser.extractApk method
cat -n src/utils/app-info-parser/aab.ts | sed -n '1,200p'

Repository: reactnativecn/react-native-update-cli

Length of output: 6837


🏁 Script executed:

#!/bin/bash

# Find the exact line numbers for extractApk method
rg -n "async extractApk" src/utils/app-info-parser/aab.ts

# Then read the full method
wc -l src/utils/app-info-parser/aab.ts

Repository: reactnativecn/react-native-update-cli

Length of output: 140


🏁 Script executed:

#!/bin/bash

# Check the localization strings for bundletool/dependency error messages
echo "=== English error messages ==="
rg "aabOpenApksFailed|aabReadUniversalApkFailed|aabUniversalApkNotFound" src/locales/en.ts -A 1

echo -e "\n=== Chinese error messages ==="
rg "aabOpenApksFailed|aabReadUniversalApkFailed|aabUniversalApkNotFound" src/locales/zh.ts -A 1

# Check if there are any other error message keys for bundletool
echo -e "\n=== All aab-related error messages ==="
rg "aab[A-Z]" src/locales/en.ts | grep -E ":\s*['\"]"

Repository: reactnativecn/react-native-update-cli

Length of output: 1094


🏁 Script executed:

#!/bin/bash

# Check if there's any documentation about bundletool in package.json or setup guides
echo "=== Checking package.json for bundletool reference ==="
rg "bundletool" package.json

# Check if there's a setup guide or install guide
echo -e "\n=== Looking for setup/install documentation ==="
fd -t f -e md | xargs grep -l -i "install\|setup\|depend" 2>/dev/null | head -10

# Check cli.json for extractApk full definition
echo -e "\n=== Checking cli.json extractApk definition ==="
rg -A 10 '"extractApk"' cli.json

Repository: reactnativecn/react-native-update-cli

Length of output: 499


Document bundletool dependency and improve error messages for missing binary.

The extractApk command depends on bundletool being available, with a fallback to npx node-bundletool, but this dependency is not documented in the README or CLI help. Additionally, if both the direct bundletool command and the npx fallback fail, users see a raw shell error without guidance on installing bundletool.

Add bundletool to the README installation/dependencies section, and improve the error handling in AabParser.extractApk to catch bundletool failures and provide clear installation instructions.

🤖 Prompt for AI Agents
In src/package.ts around lines 218-253, the extractApk command currently assumes
bundletool is available but does not document that dependency or provide a
friendly error when the bundletool binary (or the npx fallback) fails; update
the README installation/dependencies section and the CLI help text to explicitly
state that bundletool (or node-bundletool via npx) is required and how to
install it, and modify the AabParser.extractApk implementation (the file
implementing AabParser, e.g., src/aab-parser.ts) to catch failures when invoking
bundletool or the npx fallback, surface a clear, user-friendly error that
includes the stderr/exception details and actionable install commands (e.g.,
install via Homebrew, download jar, or run `npx node-bundletool`), and ensure
the code first attempts the direct bundletool command, then the npx fallback,
and logs the underlying error when both fail.

packages: async ({ options }: { options: { platform: Platform } }) => {
const platform = await getPlatform(options.platform);
const { appId } = await getSelectedApp(platform);
Expand All @@ -224,7 +310,12 @@ export const packageCommands = {
options,
}: {
args: string[];
options: { appId?: string; packageId?: string; packageVersion?: string; platform?: Platform };
options: {
appId?: string;
packageId?: string;
packageVersion?: string;
platform?: Platform;
};
}) => {
let { appId, packageId, packageVersion } = options;

Expand Down
3 changes: 3 additions & 0 deletions src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export class CLIProviderImpl implements CLIProvider {
case 'apk':
await packageCommands.uploadApk(context);
break;
case 'aab':
await packageCommands.uploadAab(context);
break;
case 'app':
await packageCommands.uploadApp(context);
break;
Expand Down
Loading
Loading