1
0
mirror of https://gitcode.com/github-mirrors/react-native-update-cli.git synced 2025-09-16 09:41:38 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee
This commit is contained in:
sunnylqm
2025-04-19 22:15:35 +08:00
parent 4613da1fbe
commit 573ab60e44
7 changed files with 277 additions and 221 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-native-update-cli", "name": "react-native-update-cli",
"version": "1.44.1", "version": "1.44.2",
"description": "command line tool for react-native-update (remote updates for react native)", "description": "command line tool for react-native-update (remote updates for react native)",
"main": "index.js", "main": "index.js",
"bin": { "bin": {

View File

@@ -1,8 +1,12 @@
import path from 'node:path'; import path from 'node:path';
import { translateOptions } from './utils'; import { translateOptions } from './utils';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import { ZipFile } from 'yazl'; import { ZipFile as YazlZipFile } from 'yazl';
import { open as openZipFile } from 'yauzl'; import {
type Entry,
open as openZipFile,
type ZipFile as YauzlZipFile,
} from 'yauzl';
import { question, checkPlugins } from './utils'; import { question, checkPlugins } from './utils';
import { checkPlatform } from './app'; import { checkPlatform } from './app';
import { spawn, spawnSync } from 'node:child_process'; import { spawn, spawnSync } from 'node:child_process';
@@ -16,9 +20,11 @@ import { tempDir } from './utils/constants';
import { checkLockFiles } from './utils/check-lockfile'; import { checkLockFiles } from './utils/check-lockfile';
import { addGitIgnore } from './utils/add-gitignore'; import { addGitIgnore } from './utils/add-gitignore';
let bsdiff; type Diff = (oldSource?: Buffer, newSource?: Buffer) => Buffer;
let hdiff;
let diff; let bsdiff: Diff;
let hdiff: Diff;
let diff: Diff;
try { try {
bsdiff = require('node-bsdiff').diff; bsdiff = require('node-bsdiff').diff;
} catch (e) {} } catch (e) {}
@@ -59,9 +65,7 @@ async function runReactNativeBundleCommand({
if (platform === 'android') { if (platform === 'android') {
gradleConfig = await checkGradleConfig(); gradleConfig = await checkGradleConfig();
if (gradleConfig.crunchPngs !== false) { if (gradleConfig.crunchPngs !== false) {
console.warn( console.warn(t('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',
);
} }
} }
@@ -321,7 +325,7 @@ async function compileHermesByteCode(
sourcemapOutput: string, sourcemapOutput: string,
shouldCleanSourcemap: boolean, shouldCleanSourcemap: boolean,
) { ) {
console.log('Hermes enabled, now compiling to hermes bytecode:\n'); console.log(t('hermesEnabledCompiling'));
// >= rn 0.69 // >= rn 0.69
const rnDir = path.dirname( const rnDir = path.dirname(
require.resolve('react-native', { require.resolve('react-native', {
@@ -351,7 +355,9 @@ async function compileHermesByteCode(
); );
args.push('-output-source-map'); args.push('-output-source-map');
} }
console.log(t('runningHermesc', { command: hermesCommand, args: args.join(' ') })); console.log(
t('runningHermesc', { command: hermesCommand, args: args.join(' ') }),
);
spawnSync(hermesCommand, args, { spawnSync(hermesCommand, args, {
stdio: 'ignore', stdio: 'ignore',
}); });
@@ -387,7 +393,7 @@ async function copyDebugidForSentry(
sourcemapOutput: string, sourcemapOutput: string,
) { ) {
if (sourcemapOutput) { if (sourcemapOutput) {
let copyDebugidPath; let copyDebugidPath: string | undefined;
try { try {
copyDebugidPath = require.resolve( copyDebugidPath = require.resolve(
'@sentry/react-native/scripts/copy-debugid.js', '@sentry/react-native/scripts/copy-debugid.js',
@@ -426,13 +432,13 @@ async function uploadSourcemapForSentry(
version: string, version: string,
) { ) {
if (sourcemapOutput) { if (sourcemapOutput) {
let sentryCliPath; let sentryCliPath: string | undefined;
try { try {
sentryCliPath = require.resolve('@sentry/cli/bin/sentry-cli', { sentryCliPath = require.resolve('@sentry/cli/bin/sentry-cli', {
paths: [process.cwd()], paths: [process.cwd()],
}); });
} catch (error) { } catch (error) {
console.error('无法找到 Sentry CLI 工具,请确保已正确安装 @sentry/cli'); console.error(t('sentryCliNotFound'));
return; return;
} }
@@ -476,7 +482,7 @@ async function pack(dir: string, output: string) {
console.log(t('packing')); console.log(t('packing'));
fs.ensureDirSync(path.dirname(output)); fs.ensureDirSync(path.dirname(output));
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const zipfile = new ZipFile(); const zipfile = new YazlZipFile();
function addDirectory(root: string, rel: string) { function addDirectory(root: string, rel: string) {
if (rel) { if (rel) {
@@ -513,10 +519,13 @@ async function pack(dir: string, output: string) {
console.log(t('fileGenerated', { file: output })); console.log(t('fileGenerated', { file: output }));
} }
export function readEntire(entry: string, zipFile: ZipFile) { export function readEntry(
entry: Entry,
zipFile: YauzlZipFile,
): Promise<Buffer> {
const buffers: Buffer[] = []; const buffers: Buffer[] = [];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
zipFile.openReadStream(entry, (err: any, stream: any) => { zipFile.openReadStream(entry, (err, stream) => {
stream.pipe({ stream.pipe({
write(chunk: Buffer) { write(chunk: Buffer) {
buffers.push(chunk); buffers.push(chunk);
@@ -544,7 +553,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
const originEntries = {}; const originEntries = {};
const originMap = {}; const originMap = {};
let originSource; let originSource: Buffer | undefined;
await enumZipEntries(origin, (entry, zipFile) => { await enumZipEntries(origin, (entry, zipFile) => {
originEntries[entry.fileName] = entry; originEntries[entry.fileName] = entry;
@@ -557,7 +566,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
entry.fileName === 'bundle.harmony.js' entry.fileName === 'bundle.harmony.js'
) { ) {
// This is source. // This is source.
return readEntire(entry, zipFile).then((v) => (originSource = v)); return readEntry(entry, zipFile).then((v) => (originSource = v));
} }
} }
}); });
@@ -570,7 +579,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
const copies = {}; const copies = {};
const zipfile = new ZipFile(); const zipfile = new YazlZipFile();
const writePromise = new Promise((resolve, reject) => { const writePromise = new Promise((resolve, reject) => {
zipfile.outputStream.on('error', (err) => { zipfile.outputStream.on('error', (err) => {
@@ -607,7 +616,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
} }
} else if (entry.fileName === 'index.bundlejs') { } else if (entry.fileName === 'index.bundlejs') {
//console.log('Found bundle'); //console.log('Found bundle');
return readEntire(entry, nextZipfile).then((newSource) => { return readEntry(entry, nextZipfile).then((newSource) => {
//console.log('Begin diff'); //console.log('Begin diff');
zipfile.addBuffer( zipfile.addBuffer(
diff(originSource, newSource), diff(originSource, newSource),
@@ -617,7 +626,7 @@ async function diffFromPPK(origin: string, next: string, output: string) {
}); });
} else if (entry.fileName === 'bundle.harmony.js') { } else if (entry.fileName === 'bundle.harmony.js') {
//console.log('Found bundle'); //console.log('Found bundle');
return readEntire(entry, nextZipfile).then((newSource) => { return readEntry(entry, nextZipfile).then((newSource) => {
//console.log('Begin diff'); //console.log('Begin diff');
zipfile.addBuffer( zipfile.addBuffer(
diff(originSource, newSource), diff(originSource, newSource),
@@ -691,9 +700,9 @@ async function diffFromPackage(
const originEntries = {}; const originEntries = {};
const originMap = {}; const originMap = {};
let originSource; let originSource: Buffer | undefined;
await enumZipEntries(origin, (entry: any, zipFile: any) => { await enumZipEntries(origin, (entry, zipFile) => {
if (!/\/$/.test(entry.fileName)) { if (!/\/$/.test(entry.fileName)) {
const fn = transformPackagePath(entry.fileName); const fn = transformPackagePath(entry.fileName);
if (!fn) { if (!fn) {
@@ -707,7 +716,7 @@ async function diffFromPackage(
if (fn === originBundleName) { if (fn === originBundleName) {
// This is source. // This is source.
return readEntire(entry, zipFile).then((v) => (originSource = v)); return readEntry(entry, zipFile).then((v) => (originSource = v));
} }
} }
}); });
@@ -720,7 +729,7 @@ async function diffFromPackage(
const copies = {}; const copies = {};
const zipfile = new ZipFile(); const zipfile = new YazlZipFile();
const writePromise = new Promise((resolve, reject) => { const writePromise = new Promise((resolve, reject) => {
zipfile.outputStream.on('error', (err) => { zipfile.outputStream.on('error', (err) => {
@@ -737,7 +746,7 @@ async function diffFromPackage(
zipfile.addEmptyDirectory(entry.fileName); zipfile.addEmptyDirectory(entry.fileName);
} else if (entry.fileName === 'index.bundlejs') { } else if (entry.fileName === 'index.bundlejs') {
//console.log('Found bundle'); //console.log('Found bundle');
return readEntire(entry, nextZipfile).then((newSource) => { return readEntry(entry, nextZipfile).then((newSource) => {
//console.log('Begin diff'); //console.log('Begin diff');
zipfile.addBuffer( zipfile.addBuffer(
diff(originSource, newSource), diff(originSource, newSource),
@@ -747,7 +756,7 @@ async function diffFromPackage(
}); });
} else if (entry.fileName === 'bundle.harmony.js') { } else if (entry.fileName === 'bundle.harmony.js') {
//console.log('Found bundle'); //console.log('Found bundle');
return readEntire(entry, nextZipfile).then((newSource) => { return readEntry(entry, nextZipfile).then((newSource) => {
//console.log('Begin diff'); //console.log('Begin diff');
zipfile.addBuffer( zipfile.addBuffer(
diff(originSource, newSource), diff(originSource, newSource),
@@ -789,14 +798,18 @@ async function diffFromPackage(
export async function enumZipEntries( export async function enumZipEntries(
zipFn: string, zipFn: string,
callback: (entry: any, zipFile: any) => void, callback: (
entry: Entry,
zipFile: YauzlZipFile,
nestedPath?: string,
) => Promise<any>,
nestedPath = '', nestedPath = '',
) { ) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
openZipFile( openZipFile(
zipFn, zipFn,
{ lazyEntries: true }, { lazyEntries: true },
async (err: any, zipfile: ZipFile) => { async (err: any, zipfile: YauzlZipFile) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
@@ -850,7 +863,7 @@ export async function enumZipEntries(
}); });
} }
function diffArgsCheck(args, options, diffFn) { function diffArgsCheck(args: string[], options: any, diffFn: string) {
const [origin, next] = args; const [origin, next] = args;
if (!origin || !next) { if (!origin || !next) {
@@ -889,7 +902,7 @@ function diffArgsCheck(args, options, diffFn) {
export const commands = { export const commands = {
bundle: async function ({ options }) { bundle: async function ({ options }) {
const platform = checkPlatform( const platform = checkPlatform(
options.platform || (await question('平台(ios/android/harmony):')), options.platform || (await question(t('platformPrompt'))),
); );
const { const {
@@ -943,7 +956,7 @@ export const commands = {
await pack(path.resolve(intermediaDir), realOutput); await pack(path.resolve(intermediaDir), realOutput);
const v = await question('是否现在上传此热更包?(Y/N)'); const v = await question(t('uploadBundlePrompt'));
if (v.toLowerCase() === 'y') { if (v.toLowerCase() === 'y') {
const versionName = await this.publish({ const versionName = await this.publish({
args: [realOutput], args: [realOutput],

View File

@@ -8,7 +8,6 @@ function printUsage() {
// const commandName = args[0]; // const commandName = args[0];
// TODO: print usage of commandName, or print global usage. // TODO: print usage of commandName, or print global usage.
console.log('Usage is under development now.');
console.log( console.log(
'Visit `https://github.com/reactnativecn/react-native-update` for document.', 'Visit `https://github.com/reactnativecn/react-native-update` for document.',
); );

View File

@@ -1,10 +1,60 @@
export default { export default {
loginFirst: addedToGitignore: 'Added {{line}} to .gitignore',
'Not logged in.\nPlease run `cresc login` in the project directory to login.', androidCrunchPngsWarning:
lockNotFound: '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',
'No lock file detected, which may cause inconsistent dependencies and hot-updating issues.', appId: 'App ID',
multipleLocksFound: appIdMismatchApk:
'Multiple lock files detected ({{- lockFiles}}), which may cause inconsistent dependencies and hot-updating issues.', 'App ID mismatch! Current APK: {{appIdInPkg}}, current update.json: {{appId}}',
appIdMismatchApp:
'App ID mismatch! Current APP: {{appIdInPkg}}, current update.json: {{appId}}',
appIdMismatchIpa:
'App ID mismatch! Current IPA: {{appIdInPkg}}, current update.json: {{appId}}',
appKeyMismatchApk:
'App Key mismatch! Current APK: {{appKeyInPkg}}, current update.json: {{appKey}}',
appKeyMismatchApp:
'App Key mismatch! Current APP: {{appKeyInPkg}}, current update.json: {{appKey}}',
appKeyMismatchIpa:
'App Key mismatch! Current IPA: {{appKeyInPkg}}, current update.json: {{appKey}}',
appName: 'App Name',
appNameQuestion: 'App Name:',
appNotSelected:
'App not selected. run `cresc selectApp --platform {{platform}}` first!',
appUploadSuccess:
'Successfully uploaded APP native package (id: {{id}}, version: {{version}}, buildTime: {{buildTime}})',
apkUploadSuccess:
'Successfully uploaded APK native package (id: {{id}}, version: {{version}}, buildTime: {{buildTime}})',
boundTo: ', bound to: {{name}} ({{id}})',
buildTimeNotFound:
'Cannot get the build timestamp of this package. Please update `react-native-update` to the latest version and re-package and upload.',
bundleCommandError:
'"react-native bundle" command exited with code {{code}}.',
bundleNotFound:
'Bundle file not found. Please ensure that this {{packageType}} is a release version and the bundle file name is the default `{{entryFile}}`',
bundlingWithRN: 'Bundling with react-native: {{version}}',
cancelled: 'Cancelled',
composingSourceMap: 'Composing source map',
copyFileFailed: 'Failed to copy file: {{error}}',
copyHarmonyBundleError: 'Error copying Harmony bundle: {{error}}',
copyingDebugId: 'Copying debugid',
createAppSuccess: 'App created successfully (id: {{id}})',
deleteFile: 'Delete {{- file}}',
deletingFile: 'Delete {{- file}}',
enterAppIdQuestion: 'Enter AppId:',
enterNativePackageId: 'Enter native package ID:',
errorInHarmonyApp: 'Error in getEntryFromHarmonyApp: {{error}}',
expiredStatus: '(Expired)',
failedToParseIcon: '[Warning] failed to parse icon: {{error}}',
failedToParseUpdateJson:
'Failed to parse file `update.json`. Try to remove it manually.',
fileGenerated: '{{- file}} generated.',
fileSizeExceeded:
'This file size is {{fileSize}} , exceeding the current quota {{maxSize}} . You may consider upgrading to a higher plan to increase this quota. Details can be found at: {{pricingPageUrl}}',
hermesDisabled: 'Hermes disabled',
hermesEnabledCompiling: 'Hermes enabled, now compiling to hermes bytecode:\n',
ipaUploadSuccess:
'Successfully uploaded IPA native package (id: {{id}}, version: {{version}}, buildTime: {{buildTime}})',
keyStrings: 'Key strings:',
latestVersionTag: '(latest: {{version}})',
lockBestPractice: ` lockBestPractice: `
Best practices for lock files: Best practices for lock files:
1. All members of the development team should use the same package manager to maintain a single lock file. 1. All members of the development team should use the same package manager to maintain a single lock file.
@@ -12,84 +62,64 @@ Best practices for lock files:
3. Pay attention to changes in the lock file during code review. 3. Pay attention to changes in the lock file during code review.
This can reduce the risk of inconsistent dependencies and supply chain attacks. This can reduce the risk of inconsistent dependencies and supply chain attacks.
`, `,
lockNotFound:
'No lock file detected, which may cause inconsistent dependencies and hot-updating issues.',
loggedOut: 'Logged out',
loginExpired: loginExpired:
'Login information has expired. Please use `cresc login` command to re-login', 'Login information has expired. Please use `cresc login` command to re-login',
fileSizeExceeded: loginFirst:
'This file size is {{fileSize}} , exceeding the current quota {{maxSize}} . You may consider upgrading to a higher plan to increase this quota. Details can be found at: {{pricingPageUrl}}', 'Not logged in.\nPlease run `cresc login` in the project directory to login.',
bundleNotFound: multipleLocksFound:
'Bundle file not found. Please ensure that this {{packageType}} is a release version and the bundle file name is the default `{{entryFile}}`', 'Multiple lock files detected ({{- lockFiles}}), which may cause inconsistent dependencies and hot-updating issues.',
buildTimeNotFound: nativePackageId: 'Native Package ID',
'Cannot get the build timestamp of this package. Please update `react-native-update` to the latest version and re-package and upload.', nativeVersion: 'Native Version',
latestVersionTag: '(latest: {{version}})',
rnuVersionNotFound:
'react-native-update: Cannot get the version number. Please run the command in the project directory',
unsupportedPlatform: 'Unsupported platform `{{platform}}`',
appId: 'App ID',
appName: 'App Name',
platform: 'Platform',
totalApps: 'Total {{count}} {{platform}} apps',
appNotSelected:
'App not selected. run `cresc selectApp --platform {{platform}}` first!',
enterAppIdQuestion: 'Enter AppId:',
appNameQuestion: 'App Name:',
platformQuestion: 'Platform(ios/android/harmony):',
createAppSuccess: 'App created successfully (id: {{id}})',
cancelled: 'Cancelled',
operationSuccess: 'Operation successful',
failedToParseUpdateJson:
'Failed to parse file `update.json`. Try to remove it manually.',
ppkPackageGenerated: 'ppk package generated and saved to: {{- output}}',
Message: 'Welcome to Cresc hot update service, {{name}}.',
loggedOut: 'Logged out',
usageUnderDevelopment: 'Usage is under development now.',
hermesDisabled: 'Hermes disabled',
hermesEnabled: 'Hermes enabled, now compiling to hermes bytecode:\n',
runningHermesc: 'Running hermesc: {{command}} {{args}}',
composingSourceMap: 'Composing source map',
copyingDebugId: 'Copying debugid',
sentryCliNotFound:
'Cannot find Sentry CLI tool, please make sure @sentry/cli is properly installed',
sentryReleaseCreated: 'Sentry release created for version: {{version}}',
uploadingSourcemap: 'Uploading sourcemap',
packing: 'Packing',
deletingFile: 'Delete {{- file}}',
bundlingWithRN: 'Bundling with react-native: {{version}}',
fileGenerated: '{{- file}} generated.',
processingError: 'Error processing file: {{error}}',
usageDiff: 'Usage: cresc {{command}} <origin> <next>',
pluginDetected: 'detected {{name}} plugin',
pluginDetectionError: 'error while detecting {{name}} plugin: {{error}}',
addedToGitignore: 'Added {{line}} to .gitignore',
processingStringPool: 'Processing the string pool ...',
processingPackage: 'Processing the package {{count}} ...',
typeStrings: 'Type strings:',
keyStrings: 'Key strings:',
failedToParseIcon: '[Warning] failed to parse icon: {{error}}',
errorInHarmonyApp: 'Error in getEntryFromHarmonyApp: {{error}}',
totalPackages: 'Total {{count}} packages',
usageUploadIpa: 'Usage: cresc uploadIpa <ipa file>',
usageUploadApk: 'Usage: cresc uploadApk <apk file>',
usageUploadApp: 'Usage: cresc uploadApp <app file>',
usageParseApp: 'Usage: cresc parseApp <app file>',
usageParseIpa: 'Usage: cresc parseIpa <ipa file>',
usageParseApk: 'Usage: cresc parseApk <apk file>',
offset: 'Offset {{offset}}',
packageUploadSuccess:
'Successfully uploaded new hot update package (id: {{id}})',
rolloutRangeError: 'rollout must be an integer between 1-100',
nativeVersionNotFound: 'No native version found >= {{version}}', nativeVersionNotFound: 'No native version found >= {{version}}',
nativeVersionNotFoundLess: 'No native version found <= {{version}}', nativeVersionNotFoundLess: 'No native version found <= {{version}}',
nativeVersionNotFoundMatch: 'No matching native version found: {{version}}', nativeVersionNotFoundMatch: 'No matching native version found: {{version}}',
packageIdRequired: 'Please provide packageId or packageVersion parameter', offset: 'Offset {{offset}}',
operationComplete: 'Operation complete, bound to {{count}} native versions', operationComplete: 'Operation complete, bound to {{count}} native versions',
operationSuccess: 'Operation successful',
packageIdRequired: 'Please provide packageId or packageVersion parameter',
packageUploadSuccess:
'Successfully uploaded new hot update package (id: {{id}})',
packing: 'Packing',
pausedStatus: '(Paused)',
platform: 'Platform',
platformPrompt: 'Platform (ios/android/harmony):',
platformQuestion: 'Platform(ios/android/harmony):',
platformRequired: 'Platform must be specified.', platformRequired: 'Platform must be specified.',
bundleCommandError: pluginDetectionError: 'error while detecting {{name}} plugin: {{error}}',
'"react-native bundle" command exited with code {{code}}.', pluginDetected: 'detected {{name}} plugin',
copyHarmonyBundleError: 'Error copying Harmony bundle: {{error}}', ppkPackageGenerated: 'ppk package generated and saved to: {{- output}}',
copyFileFailed: 'Failed to copy file: {{error}}', processingError: 'Error processing file: {{error}}',
deleteFile: 'Delete {{- file}}', processingPackage: 'Processing the package {{count}} ...',
processingStringPool: 'Processing the string pool ...',
publishUsage:
'Usage: pushy publish <ppk file> --platform ios|android|harmony',
rnuVersionNotFound:
'react-native-update: Cannot get the version number. Please run the command in the project directory',
rolloutConfigSet: rolloutConfigSet:
'Set {{rollout}}% rollout for version {{version}} on native version(s) {{versions}}', 'Set {{rollout}}% rollout for version {{version}} on native version(s) {{versions}}',
rolloutRangeError: 'rollout must be an integer between 1-100',
runningHermesc: 'Running hermesc: {{- command}} {{- args}}',
sentryCliNotFound:
'Cannot find Sentry CLI tool, please make sure @sentry/cli is properly installed',
sentryReleaseCreated: 'Sentry release created for version: {{version}}',
totalApps: 'Total {{count}} {{platform}} apps',
totalPackages: 'Total {{count}} packages',
typeStrings: 'Type strings:',
unsupportedPlatform: 'Unsupported platform `{{platform}}`',
uploadBundlePrompt: 'Upload this bundle now?(Y/N)',
uploadingSourcemap: 'Uploading sourcemap',
usageDiff: 'Usage: cresc {{command}} <origin> <next>',
usageParseApk: 'Usage: cresc parseApk <apk file>',
usageParseApp: 'Usage: cresc parseApp <app file>',
usageParseIpa: 'Usage: cresc parseIpa <ipa file>',
usageUnderDevelopment: 'Usage is under development now.',
usageUploadApk: 'Usage: cresc uploadApk <apk file>',
usageUploadApp: 'Usage: cresc uploadApp <app file>',
usageUploadIpa: 'Usage: cresc uploadIpa <ipa file>',
versionBind: versionBind:
'Bound version {{version}} to native version {{nativeVersion}} (id: {{id}})', 'Bound version {{version}} to native version {{nativeVersion}} (id: {{id}})',
welcomeMessage: 'Welcome to Cresc hot update service, {{name}}.',
}; };

View File

@@ -1,7 +1,58 @@
export default { export default {
loginFirst: '尚未登录。\n请在项目目录中运行`pushy login`命令来登录', addedToGitignore: '已将 {{line}} 添加到 .gitignore',
lockNotFound: androidCrunchPngsWarning:
'没有检测到任何 lock 文件,这可能导致依赖关系不一致而使热更异常。', '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',
appId: '应用 id',
appIdMismatchApk:
'appId不匹配当前apk: {{appIdInPkg}}, 当前update.json: {{appId}}',
appIdMismatchApp:
'appId不匹配当前app: {{appIdInPkg}}, 当前update.json: {{appId}}',
appIdMismatchIpa:
'appId不匹配当前ipa: {{appIdInPkg}}, 当前update.json: {{appId}}',
appKeyMismatchApk:
'appKey不匹配当前apk: {{appKeyInPkg}}, 当前update.json: {{appKey}}',
appKeyMismatchApp:
'appKey不匹配当前app: {{appKeyInPkg}}, 当前update.json: {{appKey}}',
appKeyMismatchIpa:
'appKey不匹配当前ipa: {{appKeyInPkg}}, 当前update.json: {{appKey}}',
appName: '应用名称',
appNameQuestion: '应用名称:',
appNotSelected:
'尚未选择应用。请先运行 `pushy selectApp --platform {{platform}}` 来选择应用',
appUploadSuccess:
'已成功上传app原生包id: {{id}}, version: {{version}}, buildTime: {{buildTime}}',
apkUploadSuccess:
'已成功上传apk原生包id: {{id}}, version: {{version}}, buildTime: {{buildTime}}',
boundTo: ', 已绑定:{{name}} ({{id}})',
buildTimeNotFound:
'无法获取此包的编译时间戳。请更新 `react-native-update` 到最新版本后重新打包上传。',
bundleCommandError: '"react-native bundle" 命令退出,代码为 {{code}}。',
bundleNotFound:
'找不到 bundle 文件。请确保此 {{packageType}} 为 release 版本,且 bundle 文件名为默认的 `{{entryFile}}`',
bundlingWithRN: '正在使用 react-native {{version}} 打包',
cancelled: '已取消',
composingSourceMap: '正在生成 source map',
copyFileFailed: '复制文件失败:{{error}}',
copyHarmonyBundleError: '复制 Harmony bundle 错误:{{error}}',
copyingDebugId: '正在复制 debugid',
createAppSuccess: '已成功创建应用id: {{id}}',
deleteFile: '删除 {{- file}}',
deletingFile: '删除 {{- file}}',
enterAppIdQuestion: '输入应用 id:',
enterNativePackageId: '输入原生包 id:',
errorInHarmonyApp: '获取 Harmony 应用入口时出错:{{error}}',
expiredStatus: '(已过期)',
failedToParseIcon: '[警告] 解析图标失败:{{error}}',
failedToParseUpdateJson: '无法解析文件 `update.json`。请手动删除它。',
fileGenerated: '已生成 {{- file}}',
fileSizeExceeded:
'此文件大小 {{fileSize}} , 超出当前额度 {{maxSize}} 。您可以考虑升级付费业务以提升此额度。详情请访问: {{pricingPageUrl}}',
hermesDisabled: 'Hermes 已禁用',
hermesEnabledCompiling: 'Hermes 已启用,正在编译为 hermes 字节码:\n',
ipaUploadSuccess:
'已成功上传ipa原生包id: {{id}}, version: {{version}}, buildTime: {{buildTime}}',
keyStrings: '键字符串:',
latestVersionTag: '(最新:{{version}}',
lockBestPractice: ` lockBestPractice: `
关于 lock 文件的最佳实践: 关于 lock 文件的最佳实践:
1. 开发团队中的所有成员应该使用相同的包管理器,维护同一份 lock 文件。 1. 开发团队中的所有成员应该使用相同的包管理器,维护同一份 lock 文件。
@@ -9,81 +60,59 @@ export default {
3. 代码审核时应关注 lock 文件的变化。 3. 代码审核时应关注 lock 文件的变化。
这样可以最大限度避免因依赖关系不一致而导致的热更异常,也降低供应链攻击等安全隐患。 这样可以最大限度避免因依赖关系不一致而导致的热更异常,也降低供应链攻击等安全隐患。
`, `,
lockNotFound:
'没有检测到任何 lock 文件,这可能导致依赖关系不一致而使热更异常。',
loggedOut: '已退出登录',
loginExpired: '登录信息已过期,请使用 `pushy login` 命令重新登录',
loginFirst: '尚未登录。\n请在项目目录中运行`pushy login`命令来登录',
multipleLocksFound: multipleLocksFound:
'检测到多种不同格式的锁文件({{- lockFiles}}),这可能导致依赖关系不一致而使热更异常。', '检测到多种不同格式的锁文件({{- lockFiles}}),这可能导致依赖关系不一致而使热更异常。',
loginExpired: '登录信息已过期,请使用 `pushy login` 命令重新登录', nativePackageId: '原生包 Id',
fileSizeExceeded: nativeVersion: '原生版本',
'此文件大小 {{fileSize}} , 超出当前额度 {{maxSize}} 。您可以考虑升级付费业务以提升此额度。详情请访问: {{pricingPageUrl}}',
bundleNotFound:
'找不到 bundle 文件。请确保此 {{packageType}} 为 release 版本,且 bundle 文件名为默认的 `{{entryFile}}`',
buildTimeNotFound:
'无法获取此包的编译时间戳。请更新 `react-native-update` 到最新版本后重新打包上传。',
latestVersionTag: '(最新:{{version}}',
rnuVersionNotFound:
'react-native-update: 无法获取版本号。请在项目目录中运行命令',
unsupportedPlatform: '无法识别的平台 `{{platform}}`',
appId: '应用 id',
appName: '应用名称',
platform: '平台',
totalApps: '共 {{count}} 个 {{platform}} 应用',
appNotSelected:
'尚未选择应用。请先运行 `pushy selectApp --platform {{platform}}` 来选择应用',
enterAppIdQuestion: '输入应用 id:',
appNameQuestion: '应用名称:',
platformQuestion: '平台(ios/android/harmony):',
createAppSuccess: '已成功创建应用id: {{id}}',
cancelled: '已取消',
operationSuccess: '操作成功',
failedToParseUpdateJson: '无法解析文件 `update.json`。请手动删除它。',
ppkPackageGenerated: 'ppk 热更包已生成并保存到: {{- output}}',
welcomeMessage: '欢迎使用 pushy 热更新服务,{{name}}。',
loggedOut: '已退出登录',
usageUnderDevelopment: '使用说明正在开发中。',
hermesDisabled: 'Hermes 已禁用',
hermesEnabled: 'Hermes 已启用,正在编译为 hermes 字节码:\n',
runningHermesc: '运行 hermesc{{command}} {{args}}',
composingSourceMap: '正在生成 source map',
copyingDebugId: '正在复制 debugid',
sentryCliNotFound: '无法找到 Sentry CLI 工具,请确保已正确安装 @sentry/cli',
sentryReleaseCreated: '已为版本 {{version}} 创建 Sentry release',
uploadingSourcemap: '正在上传 sourcemap',
packing: '正在打包',
deletingFile: '删除 {{- file}}',
bundlingWithRN: '正在使用 react-native {{version}} 打包',
fileGenerated: '已生成 {{- file}}',
processingError: '处理文件时出错:{{error}}',
usageDiff: '用法pushy {{command}} <origin> <next>',
pluginDetected: '检测到 {{name}} 插件',
pluginDetectionError: '检测 {{name}} 插件时出错:{{error}}',
addedToGitignore: '已将 {{line}} 添加到 .gitignore',
processingStringPool: '正在处理字符串池...',
processingPackage: '正在处理包 {{count}}...',
typeStrings: '类型字符串:',
keyStrings: '键字符串:',
failedToParseIcon: '[警告] 解析图标失败:{{error}}',
errorInHarmonyApp: '获取 Harmony 应用入口时出错:{{error}}',
totalPackages: '共 {{count}} 个包',
usageUploadIpa: '使用方法: pushy uploadIpa ipa后缀文件',
usageUploadApk: '使用方法: pushy uploadApk apk后缀文件',
usageUploadApp: '使用方法: pushy uploadApp app后缀文件',
usageParseApp: '使用方法: pushy parseApp app后缀文件',
usageParseIpa: '使用方法: pushy parseIpa ipa后缀文件',
usageParseApk: '使用方法: pushy parseApk apk后缀文件',
offset: '偏移量 {{offset}}',
packageUploadSuccess: '已成功上传新热更包id: {{id}}',
rolloutRangeError: 'rollout 必须是 1-100 的整数',
nativeVersionNotFound: '未查询到 >= {{version}} 的原生版本', nativeVersionNotFound: '未查询到 >= {{version}} 的原生版本',
nativeVersionNotFoundLess: '未查询到 <= {{version}} 的原生版本', nativeVersionNotFoundLess: '未查询到 <= {{version}} 的原生版本',
nativeVersionNotFoundMatch: '未查询到匹配原生版本:{{version}}', nativeVersionNotFoundMatch: '未查询到匹配原生版本:{{version}}',
packageIdRequired: '请提供 packageId 或 packageVersion 参数', offset: '偏移量 {{offset}}',
operationComplete: '操作完成,共已绑定 {{count}} 个原生版本', operationComplete: '操作完成,共已绑定 {{count}} 个原生版本',
operationSuccess: '操作成功',
packageIdRequired: '请提供 packageId 或 packageVersion 参数',
packageUploadSuccess: '已成功上传新热更包id: {{id}}',
packing: '正在打包',
pausedStatus: '(已暂停)',
platform: '平台',
platformPrompt: '平台(ios/android/harmony):',
platformQuestion: '平台(ios/android/harmony):',
platformRequired: '必须指定平台。', platformRequired: '必须指定平台。',
bundleCommandError: '"react-native bundle" 命令退出,代码为 {{code}}', pluginDetectionError: '检测 {{name}} 插件时出错:{{error}}',
copyHarmonyBundleError: '复制 Harmony bundle 错误:{{error}}', pluginDetected: '检测到 {{name}} 插件',
copyFileFailed: '复制文件失败:{{error}}', ppkPackageGenerated: 'ppk 热更包已生成并保存到: {{- output}}',
deleteFile: '删除 {{- file}}', processingError: '处理文件时出错:{{error}}',
processingPackage: '正在处理包 {{count}}...',
processingStringPool: '正在处理字符串池...',
publishUsage:
'使用方法: pushy publish ppk后缀文件 --platform ios|android|harmony',
rnuVersionNotFound:
'react-native-update: 无法获取版本号。请在项目目录中运行命令',
rolloutConfigSet: rolloutConfigSet:
'已在原生版本 {{versions}} 上设置灰度发布 {{rollout}}% 热更版本 {{version}}', '已在原生版本 {{versions}} 上设置灰度发布 {{rollout}}% 热更版本 {{version}}',
rolloutRangeError: 'rollout 必须是 1-100 的整数',
runningHermesc: '运行 hermesc{{- command}} {{- args}}',
sentryCliNotFound: '无法找到 Sentry CLI 工具,请确保已正确安装 @sentry/cli',
sentryReleaseCreated: '已为版本 {{version}} 创建 Sentry release',
totalApps: '共 {{count}} 个 {{platform}} 应用',
totalPackages: '共 {{count}} 个包',
typeStrings: '类型字符串:',
unsupportedPlatform: '无法识别的平台 `{{platform}}`',
uploadBundlePrompt: '是否现在上传此热更包?(Y/N)',
uploadingSourcemap: '正在上传 sourcemap',
usageDiff: '用法pushy {{command}} <origin> <next>',
usageParseApk: '使用方法: pushy parseApk apk后缀文件',
usageParseApp: '使用方法: pushy parseApp app后缀文件',
usageParseIpa: '使用方法: pushy parseIpa ipa后缀文件',
usageUploadApk: '使用方法: pushy uploadApk apk后缀文件',
usageUploadApp: '使用方法: pushy uploadApp app后缀文件',
usageUploadIpa: '使用方法: pushy uploadIpa ipa后缀文件',
versionBind: versionBind:
'已将热更版本 {{version}} 绑定到原生版本 {{nativeVersion}} (id: {{id}})', '已将热更版本 {{version}} 绑定到原生版本 {{nativeVersion}} (id: {{id}})',
welcomeMessage: '欢迎使用 pushy 热更新服务,{{name}}。',
}; };

View File

@@ -13,22 +13,23 @@ import type { Platform } from 'types';
export async function listPackage(appId: string) { export async function listPackage(appId: string) {
const { data } = await get(`/app/${appId}/package/list?limit=1000`); const { data } = await get(`/app/${appId}/package/list?limit=1000`);
const header = [{ value: '原生包 Id' }, { value: '原生版本' }]; const header = [
{ value: t('nativePackageId') },
{ value: t('nativeVersion') },
];
const rows = []; const rows = [];
for (const pkg of data) { for (const pkg of data) {
const { version } = pkg; const { version } = pkg;
let versionInfo = ''; let versionInfo = '';
if (version) { if (version) {
versionInfo = `, 已绑定:${version.name} (${version.id})`; versionInfo = t('boundTo', { name: version.name, id: version.id });
} else {
// versionInfo = ' (newest)';
} }
let output = pkg.name; let output = pkg.name;
if (pkg.status === 'paused') { if (pkg.status === 'paused') {
output += '(已暂停)'; output += t('pausedStatus');
} }
if (pkg.status === 'expired') { if (pkg.status === 'expired') {
output += '(已过期)'; output += t('expiredStatus');
} }
output += versionInfo; output += versionInfo;
rows.push([pkg.id, output]); rows.push([pkg.id, output]);
@@ -43,7 +44,7 @@ export async function choosePackage(appId: string) {
const list = await listPackage(appId); const list = await listPackage(appId);
while (true) { while (true) {
const id = await question('输入原生包 id:'); const id = await question(t('enterNativePackageId'));
const app = list.find((v) => v.id === Number(id)); const app = list.find((v) => v.id === Number(id));
if (app) { if (app) {
return app; return app;
@@ -66,15 +67,11 @@ export const commands = {
const { appId, appKey } = await getSelectedApp('ios'); const { appId, appKey } = await getSelectedApp('ios');
if (appIdInPkg && appIdInPkg != appId) { if (appIdInPkg && appIdInPkg != appId) {
throw new Error( throw new Error(t('appIdMismatchIpa', { appIdInPkg, appId }));
`appId不匹配当前ipa: ${appIdInPkg}, 当前update.json: ${appId}`,
);
} }
if (appKeyInPkg && appKeyInPkg !== appKey) { if (appKeyInPkg && appKeyInPkg !== appKey) {
throw new Error( throw new Error(t('appKeyMismatchIpa', { appKeyInPkg, appKey }));
`appKey不匹配当前ipa: ${appKeyInPkg}, 当前update.json: ${appKey}`,
);
} }
const { hash } = await uploadFile(fn); const { hash } = await uploadFile(fn);
@@ -87,9 +84,7 @@ export const commands = {
commit: await getCommitInfo(), commit: await getCommitInfo(),
}); });
saveToLocal(fn, `${appId}/package/${id}.ipa`); saveToLocal(fn, `${appId}/package/${id}.ipa`);
console.log( console.log(t('ipaUploadSuccess', { id, version: versionName, buildTime }));
`已成功上传ipa原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
);
}, },
uploadApk: async ({ args }: { args: string[] }) => { uploadApk: async ({ args }: { args: string[] }) => {
const fn = args[0]; const fn = args[0];
@@ -105,15 +100,11 @@ export const commands = {
const { appId, appKey } = await getSelectedApp('android'); const { appId, appKey } = await getSelectedApp('android');
if (appIdInPkg && appIdInPkg != appId) { if (appIdInPkg && appIdInPkg != appId) {
throw new Error( throw new Error(t('appIdMismatchApk', { appIdInPkg, appId }));
`appId不匹配当前apk: ${appIdInPkg}, 当前update.json: ${appId}`,
);
} }
if (appKeyInPkg && appKeyInPkg !== appKey) { if (appKeyInPkg && appKeyInPkg !== appKey) {
throw new Error( throw new Error(t('appKeyMismatchApk', { appKeyInPkg, appKey }));
`appKey不匹配当前apk: ${appKeyInPkg}, 当前update.json: ${appKey}`,
);
} }
const { hash } = await uploadFile(fn); const { hash } = await uploadFile(fn);
@@ -126,9 +117,7 @@ export const commands = {
commit: await getCommitInfo(), commit: await getCommitInfo(),
}); });
saveToLocal(fn, `${appId}/package/${id}.apk`); saveToLocal(fn, `${appId}/package/${id}.apk`);
console.log( console.log(t('apkUploadSuccess', { id, version: versionName, buildTime }));
`已成功上传apk原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
);
}, },
uploadApp: async ({ args }: { args: string[] }) => { uploadApp: async ({ args }: { args: string[] }) => {
const fn = args[0]; const fn = args[0];
@@ -144,15 +133,11 @@ export const commands = {
const { appId, appKey } = await getSelectedApp('harmony'); const { appId, appKey } = await getSelectedApp('harmony');
if (appIdInPkg && appIdInPkg != appId) { if (appIdInPkg && appIdInPkg != appId) {
throw new Error( throw new Error(t('appIdMismatchApp', { appIdInPkg, appId }));
`appId不匹配当前app: ${appIdInPkg}, 当前update.json: ${appId}`,
);
} }
if (appKeyInPkg && appKeyInPkg !== appKey) { if (appKeyInPkg && appKeyInPkg !== appKey) {
throw new Error( throw new Error(t('appKeyMismatchApp', { appKeyInPkg, appKey }));
`appKey不匹配当前app: ${appKeyInPkg}, 当前update.json: ${appKey}`,
);
} }
const { hash } = await uploadFile(fn); const { hash } = await uploadFile(fn);
@@ -165,9 +150,7 @@ export const commands = {
commit: await getCommitInfo(), commit: await getCommitInfo(),
}); });
saveToLocal(fn, `${appId}/package/${id}.app`); saveToLocal(fn, `${appId}/package/${id}.app`);
console.log( console.log(t('appUploadSuccess', { id, version: versionName, buildTime }));
`已成功上传app原生包id: ${id}, version: ${versionName}, buildTime: ${buildTime}`,
);
}, },
parseApp: async ({ args }: { args: string[] }) => { parseApp: async ({ args }: { args: string[] }) => {
const fn = args[0]; const fn = args[0];
@@ -192,7 +175,7 @@ export const commands = {
}, },
packages: async ({ options }: { options: { platform: Platform } }) => { packages: async ({ options }: { options: { platform: Platform } }) => {
const platform = checkPlatform( const platform = checkPlatform(
options.platform || (await question('平台(ios/android/harmony):')), options.platform || (await question(t('platformPrompt'))),
); );
const { appId } = await getSelectedApp(platform); const { appId } = await getSelectedApp(platform);
await listPackage(appId); await listPackage(appId);

View File

@@ -125,9 +125,7 @@ export const commands = {
const { name, description, metaInfo } = options; const { name, description, metaInfo } = options;
if (!fn || !fn.endsWith('.ppk')) { if (!fn || !fn.endsWith('.ppk')) {
throw new Error( throw new Error(t('publishUsage'));
'使用方法: pushy publish ppk后缀文件 --platform ios|android|harmony',
);
} }
const platform = checkPlatform( const platform = checkPlatform(
@@ -204,7 +202,7 @@ export const commands = {
minPkgVersion = String(minPkgVersion).trim(); minPkgVersion = String(minPkgVersion).trim();
const { data } = await get(`/app/${appId}/package/list?limit=1000`); const { data } = await get(`/app/${appId}/package/list?limit=1000`);
const pkgs = data.filter((pkg: Package) => const pkgs = data.filter((pkg: Package) =>
compare(pkg.name, minPkgVersion, '>='), compare(pkg.name, minPkgVersion!, '>='),
); );
if (pkgs.length === 0) { if (pkgs.length === 0) {
throw new Error(t('nativeVersionNotFound', { version: minPkgVersion })); throw new Error(t('nativeVersionNotFound', { version: minPkgVersion }));
@@ -245,10 +243,12 @@ export const commands = {
maxPkgVersion = String(maxPkgVersion).trim(); maxPkgVersion = String(maxPkgVersion).trim();
const { data } = await get(`/app/${appId}/package/list?limit=1000`); const { data } = await get(`/app/${appId}/package/list?limit=1000`);
const pkgs = data.filter((pkg: Package) => const pkgs = data.filter((pkg: Package) =>
compare(pkg.name, maxPkgVersion, '<='), compare(pkg.name, maxPkgVersion!, '<='),
); );
if (pkgs.length === 0) { if (pkgs.length === 0) {
throw new Error(t('nativeVersionNotFoundLess', { version: maxPkgVersion })); throw new Error(
t('nativeVersionNotFoundLess', { version: maxPkgVersion }),
);
} }
if (rollout !== undefined) { if (rollout !== undefined) {
const rolloutConfig: Record<string, number> = {}; const rolloutConfig: Record<string, number> = {};
@@ -290,7 +290,9 @@ export const commands = {
if (pkg) { if (pkg) {
pkgId = pkg.id; pkgId = pkg.id;
} else { } else {
throw new Error(t('nativeVersionNotFoundMatch', { version: pkgVersion })); throw new Error(
t('nativeVersionNotFoundMatch', { version: pkgVersion }),
);
} }
} }
if (!pkgId) { if (!pkgId) {