1
0
mirror of https://gitcode.com/github-mirrors/react-native-update-cli.git synced 2025-09-16 01:41:37 +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",
"version": "1.44.1",
"version": "1.44.2",
"description": "command line tool for react-native-update (remote updates for react native)",
"main": "index.js",
"bin": {

View File

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

View File

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

View File

@@ -1,10 +1,60 @@
export default {
loginFirst:
'Not logged in.\nPlease run `cresc login` in the project directory to login.',
lockNotFound:
'No lock file detected, which may cause inconsistent dependencies and hot-updating issues.',
multipleLocksFound:
'Multiple lock files detected ({{- lockFiles}}), which may cause inconsistent dependencies and hot-updating issues.',
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',
appId: 'App ID',
appIdMismatchApk:
'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: `
Best practices for lock files:
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.
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:
'Login information has expired. Please use `cresc login` command to re-login',
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}}',
bundleNotFound:
'Bundle file not found. Please ensure that this {{packageType}} is a release version and the bundle file name is the default `{{entryFile}}`',
buildTimeNotFound:
'Cannot get the build timestamp of this package. Please update `react-native-update` to the latest version and re-package and upload.',
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',
loginFirst:
'Not logged in.\nPlease run `cresc login` in the project directory to login.',
multipleLocksFound:
'Multiple lock files detected ({{- lockFiles}}), which may cause inconsistent dependencies and hot-updating issues.',
nativePackageId: 'Native Package ID',
nativeVersion: 'Native Version',
nativeVersionNotFound: 'No native version found >= {{version}}',
nativeVersionNotFoundLess: 'No 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',
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.',
bundleCommandError:
'"react-native bundle" command exited with code {{code}}.',
copyHarmonyBundleError: 'Error copying Harmony bundle: {{error}}',
copyFileFailed: 'Failed to copy file: {{error}}',
deleteFile: 'Delete {{- file}}',
pluginDetectionError: 'error while detecting {{name}} plugin: {{error}}',
pluginDetected: 'detected {{name}} plugin',
ppkPackageGenerated: 'ppk package generated and saved to: {{- output}}',
processingError: 'Error processing file: {{error}}',
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:
'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:
'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 {
loginFirst: '尚未登录。\n请在项目目录中运行`pushy login`命令来登录',
lockNotFound:
'没有检测到任何 lock 文件,这可能导致依赖关系不一致而使热更异常。',
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',
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: `
关于 lock 文件的最佳实践:
1. 开发团队中的所有成员应该使用相同的包管理器,维护同一份 lock 文件。
@@ -9,81 +60,59 @@ export default {
3. 代码审核时应关注 lock 文件的变化。
这样可以最大限度避免因依赖关系不一致而导致的热更异常,也降低供应链攻击等安全隐患。
`,
lockNotFound:
'没有检测到任何 lock 文件,这可能导致依赖关系不一致而使热更异常。',
loggedOut: '已退出登录',
loginExpired: '登录信息已过期,请使用 `pushy login` 命令重新登录',
loginFirst: '尚未登录。\n请在项目目录中运行`pushy login`命令来登录',
multipleLocksFound:
'检测到多种不同格式的锁文件({{- lockFiles}}),这可能导致依赖关系不一致而使热更异常。',
loginExpired: '登录信息已过期,请使用 `pushy login` 命令重新登录',
fileSizeExceeded:
'此文件大小 {{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 的整数',
nativePackageId: '原生包 Id',
nativeVersion: '原生版本',
nativeVersionNotFound: '未查询到 >= {{version}} 的原生版本',
nativeVersionNotFoundLess: '未查询到 <= {{version}} 的原生版本',
nativeVersionNotFoundMatch: '未查询到匹配原生版本:{{version}}',
packageIdRequired: '请提供 packageId 或 packageVersion 参数',
offset: '偏移量 {{offset}}',
operationComplete: '操作完成,共已绑定 {{count}} 个原生版本',
operationSuccess: '操作成功',
packageIdRequired: '请提供 packageId 或 packageVersion 参数',
packageUploadSuccess: '已成功上传新热更包id: {{id}}',
packing: '正在打包',
pausedStatus: '(已暂停)',
platform: '平台',
platformPrompt: '平台(ios/android/harmony):',
platformQuestion: '平台(ios/android/harmony):',
platformRequired: '必须指定平台。',
bundleCommandError: '"react-native bundle" 命令退出,代码为 {{code}}',
copyHarmonyBundleError: '复制 Harmony bundle 错误:{{error}}',
copyFileFailed: '复制文件失败:{{error}}',
deleteFile: '删除 {{- file}}',
pluginDetectionError: '检测 {{name}} 插件时出错:{{error}}',
pluginDetected: '检测到 {{name}} 插件',
ppkPackageGenerated: 'ppk 热更包已生成并保存到: {{- output}}',
processingError: '处理文件时出错:{{error}}',
processingPackage: '正在处理包 {{count}}...',
processingStringPool: '正在处理字符串池...',
publishUsage:
'使用方法: pushy publish ppk后缀文件 --platform ios|android|harmony',
rnuVersionNotFound:
'react-native-update: 无法获取版本号。请在项目目录中运行命令',
rolloutConfigSet:
'已在原生版本 {{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:
'已将热更版本 {{version}} 绑定到原生版本 {{nativeVersion}} (id: {{id}})',
welcomeMessage: '欢迎使用 pushy 热更新服务,{{name}}。',
};

View File

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

View File

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