diff --git a/src/api.ts b/src/api.ts index 3b4edfb..454ae29 100644 --- a/src/api.ts +++ b/src/api.ts @@ -6,19 +6,20 @@ import ProgressBar from 'progress'; import packageJson from '../package.json'; import tcpp from 'tcp-ping'; import filesizeParser from 'filesize-parser'; -import { pricingPageUrl, credentialFile, IS_CRESC } from './utils/constants'; +import { + pricingPageUrl, + credentialFile, + defaultEndpoint, +} from './utils/constants'; import type { Session } from 'types'; import FormData from 'form-data'; +import { t } from './utils/i18n'; const tcpPing = util.promisify(tcpp.ping); let session: Session | undefined; let savedSession: Session | undefined; -const defaultEndpoint = IS_CRESC - ? 'https://api.cresc.dev' - : 'https://update.reactnative.cn/api'; - const host = process.env.PUSHY_REGISTRY || process.env.RNU_API || defaultEndpoint; @@ -73,7 +74,7 @@ async function query(url: string, options: fetch.RequestInit) { if (resp.status !== 200) { const message = json?.message || resp.statusText; if (resp.status === 401) { - throw new Error('登录信息已过期,请使用 pushy login 命令重新登录'); + throw new Error(t('loginExpired')); } throw new Error(message); } @@ -133,10 +134,13 @@ export async function uploadFile(fn: string, key?: string) { const fileSize = fs.statSync(fn).size; if (maxSize && fileSize > filesizeParser(maxSize)) { + const readableFileSize = `${(fileSize / 1048576).toFixed(1)}m`; throw new Error( - `此文件大小 ${(fileSize / 1048576).toFixed( - 1, - )}m , 超出当前额度 ${maxSize} 。您可以考虑升级付费业务以提升此额度。详情请访问: ${pricingPageUrl}`, + t('fileSizeExceeded', { + fileSize: readableFileSize, + maxSize, + pricingPageUrl, + }), ); } diff --git a/src/locales/en.ts b/src/locales/en.ts index a1022f7..a447246 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -14,4 +14,15 @@ 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. `, + 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', }; diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 69187da..384c3a7 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -13,4 +13,14 @@ export default { `, 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: 无法获取版本号。请在项目目录中运行命令', }; diff --git a/src/utils/app-info-parser/index.js b/src/utils/app-info-parser/index.ts similarity index 76% rename from src/utils/app-info-parser/index.js rename to src/utils/app-info-parser/index.ts index 90f78b3..b94e515 100644 --- a/src/utils/app-info-parser/index.js +++ b/src/utils/app-info-parser/index.ts @@ -4,17 +4,19 @@ const AppParser = require('./app'); const supportFileTypes = ['ipa', 'apk', 'app']; class AppInfoParser { + file: string | File; + parser: any; /** * parser for parsing .ipa or .apk file - * @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser + * @param {String | File} file // file's path in Node, instance of File in Browser */ - constructor(file) { + constructor(file: string | File) { if (!file) { throw new Error( - "Param miss: file(file's path in Node, instance of File or Blob in browser).", + "Param miss: file(file's path in Node, instance of File in browser).", ); } - const splits = (file.name || file).split('.'); + const splits = (typeof file === 'string' ? file : file.name).split('.'); const fileType = splits[splits.length - 1].toLowerCase(); if (!supportFileTypes.includes(fileType)) { throw new Error( @@ -40,4 +42,4 @@ class AppInfoParser { } } -module.exports = AppInfoParser; +export default AppInfoParser; diff --git a/src/utils/check-plugin.ts b/src/utils/check-plugin.ts index ee72fa3..fa42c4e 100644 --- a/src/utils/check-plugin.ts +++ b/src/utils/check-plugin.ts @@ -17,10 +17,10 @@ export async function checkPlugins(): Promise { const isEnabled = await plugin.detect(); if (isEnabled && plugin.bundleParams) { Object.assign(params, plugin.bundleParams); - console.log(`检测到 ${plugin.name} 插件,应用相应打包配置`); + console.log(`detected ${plugin.name} plugin`); } } catch (err) { - console.warn(`检测 ${plugin.name} 插件时出错:`, err); + console.warn(`error while detecting ${plugin.name} plugin:`, err); } } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b04c9d8..0c4398a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,8 +1,6 @@ import path from 'node:path'; -const scriptName: 'cresc' | 'pushy' = path.basename(process.argv[1]) as - | 'cresc' - | 'pushy'; +const scriptName = path.basename(process.argv[1]) as 'cresc' | 'pushy'; export const IS_CRESC = scriptName === 'cresc'; export const credentialFile = IS_CRESC ? '.cresc.token' : '.update'; @@ -11,3 +9,7 @@ export const tempDir = IS_CRESC ? '.cresc.temp' : '.pushy'; export const pricingPageUrl = IS_CRESC ? 'https://cresc.dev/pricing' : 'https://pushy.reactnative.cn/pricing.html'; + +export const defaultEndpoint = IS_CRESC + ? 'https://api.cresc.dev' + : 'https://update.reactnative.cn/api'; diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts index 57b7423..e2b09e9 100644 --- a/src/utils/i18n.ts +++ b/src/utils/i18n.ts @@ -2,12 +2,18 @@ import i18next from 'i18next'; import en from '../locales/en'; import zh from '../locales/zh'; import { IS_CRESC } from './constants'; + i18next.init({ lng: IS_CRESC ? 'en' : 'zh', // debug: process.env.NODE_ENV !== 'production', + // debug: true, resources: { - en, - zh, + en: { + translation: en, + }, + zh: { + translation: zh, + }, }, }); diff --git a/src/utils/index.ts b/src/utils/index.ts index 7f636e1..27b3c66 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -9,8 +9,9 @@ import latestVersion from '@badisi/latest-version'; import { checkPlugins } from './check-plugin'; import { read } from 'read'; -import { tempDir } from './constants'; +import { IS_CRESC, tempDir } from './constants'; import { depVersions } from './dep-versions'; +import { t } from './i18n'; export async function question(query: string, password?: boolean) { if (NO_INTERACTIVE) { @@ -46,7 +47,10 @@ export async function getApkInfo(fn: string) { ); if (!bundleFile) { throw new Error( - '找不到bundle文件。请确保此apk为release版本,且bundle文件名为默认的index.android.bundle', + t('bundleNotFound', { + packageType: 'apk', + entryFile: 'index.android.bundle', + }), ); } const updateJsonFile = await appInfoParser.parser.getEntry( @@ -66,21 +70,22 @@ export async function getApkInfo(fn: string) { } } if (buildTime == 0) { - throw new Error( - '无法获取此包的编译时间戳。请更新 react-native-update 到最新版本后重新打包上传。', - ); + throw new Error(t('buildTimeNotFound')); } return { versionName, buildTime, ...appCredential }; } -export async function getAppInfo(fn) { +export async function getAppInfo(fn: string) { const appInfoParser = new AppInfoParser(fn); const bundleFile = await appInfoParser.parser.getEntryFromHarmonyApp( /rawfile\/bundle.harmony.js/, ); if (!bundleFile) { throw new Error( - '找不到bundle文件。请确保此app为release版本,且bundle文件名为默认的bundle.harmony.js', + t('bundleNotFound', { + packageType: 'app', + entryFile: 'bundle.harmony.js', + }), ); } const updateJsonFile = await appInfoParser.parser.getEntryFromHarmonyApp( @@ -103,9 +108,7 @@ export async function getAppInfo(fn) { buildTime = pushy_build_time; } if (buildTime == 0) { - throw new Error( - '无法获取此包的编译时间戳。请更新 react-native-update 到最新版本后重新打包上传。', - ); + throw new Error(t('buildTimeNotFound')); } return { versionName, buildTime, ...appCredential }; } @@ -117,7 +120,10 @@ export async function getIpaInfo(fn: string) { ); if (!bundleFile) { throw new Error( - '找不到bundle文件。请确保此ipa为release版本,且bundle文件名为默认的main.jsbundle', + t('bundleNotFound', { + packageType: 'ipa', + entryFile: 'main.jsbundle', + }), ); } const updateJsonFile = await appInfoParser.parser.getEntry( @@ -139,9 +145,7 @@ export async function getIpaInfo(fn: string) { ); } if (!buildTimeTxtBuffer) { - throw new Error( - '无法获取此包的编译时间戳。请更新 react-native-update 到最新版本后重新打包上传。', - ); + throw new Error(t('buildTimeNotFound')); } const buildTime = buildTimeTxtBuffer.toString().replace('\n', ''); return { versionName, buildTime, ...appCredential }; @@ -168,40 +172,51 @@ async function getLatestVersion(pkgNames: string[]) { } export async function printVersionCommand() { - let [latestPushyCliVersion, latestPushyVersion] = await getLatestVersion([ + let [latestRnuCliVersion, latestRnuVersion] = await getLatestVersion([ 'react-native-update-cli', 'react-native-update', ]); - latestPushyCliVersion = latestPushyCliVersion - ? ` (最新:${chalk.green(latestPushyCliVersion)})` + latestRnuCliVersion = latestRnuCliVersion + ? ` ${t('latestVersionTag', { + version: chalk.green(latestRnuCliVersion), + })}` : ''; console.log( - `react-native-update-cli: ${pkg.version}${latestPushyCliVersion}`, + `react-native-update-cli: ${pkg.version}${latestRnuCliVersion}`, ); - let pushyVersion = ''; - pushyVersion = depVersions['react-native-update']; - latestPushyVersion = latestPushyVersion - ? ` (最新:${chalk.green(latestPushyVersion)})` + let rnuVersion = ''; + rnuVersion = depVersions['react-native-update']; + latestRnuVersion = latestRnuVersion + ? ` ${t('latestVersionTag', { version: chalk.green(latestRnuVersion) })}` : ''; - console.log(`react-native-update: ${pushyVersion}${latestPushyVersion}`); - if (pushyVersion) { - if (semverSatisfies(pushyVersion, '<8.5.2')) { - console.warn( - `当前版本已不再支持,请至少升级到 v8 的最新小版本后重新打包(代码无需改动): npm i react-native-update@8 . - 如有使用安装 apk 的功能,请注意添加所需权限 https://pushy.reactnative.cn/docs/api#async-function-downloadandinstallapkurl`, - ); - } else if (semverSatisfies(pushyVersion, '9.0.0 - 9.2.1')) { - console.warn( - `当前版本已不再支持,请至少升级到 v9 的最新小版本后重新打包(代码无需改动,可直接热更): npm i react-native-update@9 . - 如有使用安装 apk 的功能,请注意添加所需权限 https://pushy.reactnative.cn/docs/api#async-function-downloadandinstallapkurl`, - ); - } else if (semverSatisfies(pushyVersion, '10.0.0 - 10.17.0')) { - console.warn( - '当前版本已不再支持,请升级到 v10 的最新小版本(代码无需改动,可直接热更): npm i react-native-update@10', - ); + console.log(`react-native-update: ${rnuVersion}${latestRnuVersion}`); + if (rnuVersion) { + if (IS_CRESC) { + if (semverSatisfies(rnuVersion, '<10.27.0')) { + console.error( + 'Unsupported version, please update to the latest version: npm i react-native-update@latest', + ); + process.exit(1); + } + } else { + if (semverSatisfies(rnuVersion, '<8.5.2')) { + console.warn( + `当前版本已不再支持,请至少升级到 v8 的最新小版本后重新打包(代码无需改动): npm i react-native-update@8 . + 如有使用安装 apk 的功能,请注意添加所需权限 https://pushy.reactnative.cn/docs/api#async-function-downloadandinstallapkurl`, + ); + } else if (semverSatisfies(rnuVersion, '9.0.0 - 9.2.1')) { + console.warn( + `当前版本已不再支持,请至少升级到 v9 的最新小版本后重新打包(代码无需改动,可直接热更): npm i react-native-update@9 . + 如有使用安装 apk 的功能,请注意添加所需权限 https://pushy.reactnative.cn/docs/api#async-function-downloadandinstallapkurl`, + ); + } else if (semverSatisfies(rnuVersion, '10.0.0 - 10.17.0')) { + console.warn( + '当前版本已不再支持,请升级到 v10 的最新小版本(代码无需改动,可直接热更): npm i react-native-update@10', + ); + } } } else { - console.log('react-native-update: 无法获取版本号,请在项目目录中运行命令'); + console.log(t('rnuVersionNotFound')); } } diff --git a/src/versions.ts b/src/versions.ts index e7c9f1a..4b6d2b9 100644 --- a/src/versions.ts +++ b/src/versions.ts @@ -6,6 +6,7 @@ import { choosePackage } from './package'; import { compare } from 'compare-versions'; import { depVersions } from './utils/dep-versions'; import { getCommitInfo } from './utils/git'; +import { Platform } from 'types'; async function showVersion(appId: string, offset: number) { const { data, count } = await get(`/app/${appId}/version/list`); @@ -61,7 +62,7 @@ async function chooseVersion(appId: string) { const cmd = await question( 'Enter versionId or page Up/page Down/Begin(U/D/B)', ); - switch (cmd.toLowerCase()) { + switch (cmd.toUpperCase()) { case 'U': offset = Math.max(0, offset - 10); break; @@ -82,7 +83,12 @@ async function chooseVersion(appId: string) { } export const commands = { - publish: async function ({ args, options }) { + publish: async function ({ args, options }: { args: string[]; options: { + name: string; + description?: string; + metaInfo?: string; + platform?: Platform; + } }) { const fn = args[0]; const { name, description, metaInfo } = options; @@ -136,7 +142,7 @@ export const commands = { versionId = null; } - let pkgId; + let pkgId: string | undefined; let pkgVersion = options.packageVersion; let minPkgVersion = options.minPackageVersion; let maxPkgVersion = options.maxPackageVersion;