diff --git a/package.json b/package.json index 99796cf..d1067e1 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/bundle.ts b/src/bundle.ts index 418806c..5c3d1cb 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -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((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 { 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, 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], diff --git a/src/index.ts b/src/index.ts index 9587ca9..0be27e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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.', ); diff --git a/src/locales/en.ts b/src/locales/en.ts index a2d3841..19d7f68 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -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}} ', - 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 ', - usageUploadApk: 'Usage: cresc uploadApk ', - usageUploadApp: 'Usage: cresc uploadApp ', - usageParseApp: 'Usage: cresc parseApp ', - usageParseIpa: 'Usage: cresc parseIpa ', - usageParseApk: 'Usage: cresc parseApk ', - 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 --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}} ', + usageParseApk: 'Usage: cresc parseApk ', + usageParseApp: 'Usage: cresc parseApp ', + usageParseIpa: 'Usage: cresc parseIpa ', + usageUnderDevelopment: 'Usage is under development now.', + usageUploadApk: 'Usage: cresc uploadApk ', + usageUploadApp: 'Usage: cresc uploadApp ', + usageUploadIpa: 'Usage: cresc uploadIpa ', versionBind: 'Bound version {{version}} to native version {{nativeVersion}} (id: {{id}})', + welcomeMessage: 'Welcome to Cresc hot update service, {{name}}.', }; diff --git a/src/locales/zh.ts b/src/locales/zh.ts index e88f363..8108ec5 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -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}} ', - 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}} ', + 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}}。', }; diff --git a/src/package.ts b/src/package.ts index b7bc8cf..213f858 100644 --- a/src/package.ts +++ b/src/package.ts @@ -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); diff --git a/src/versions.ts b/src/versions.ts index 732f108..92925e3 100644 --- a/src/versions.ts +++ b/src/versions.ts @@ -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 = {}; @@ -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) {