diff --git a/src/bundle.js b/src/bundle.js index 429f982..f243a0d 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -3,7 +3,7 @@ import { getRNVersion, translateOptions } from './utils'; import * as fs from 'fs-extra'; import { ZipFile } from 'yazl'; import { open as openZipFile } from 'yauzl'; -import { question, printVersionCommand } from './utils'; +import { question, checkPlugins } from './utils'; import { checkPlatform } from './app'; import { spawn, spawnSync } from 'node:child_process'; import semverSatisfies from 'semver/functions/satisfies'; @@ -86,6 +86,9 @@ async function runReactNativeBundleCommand( }); } } + const bundleParams = await checkPlugins(); + const minifyOption = bundleParams.minify; + const isSentry = bundleParams.sentry; const bundleCommand = usingExpo ? 'export:embed' : platform === 'harmony' @@ -123,6 +126,8 @@ async function runReactNativeBundleCommand( '--platform', platform, '--reset-cache', + '--minify', + minifyOption, ]); if (sourcemapOutput) { @@ -190,6 +195,7 @@ async function runReactNativeBundleCommand( bundleName, outputFolder, sourcemapOutput, + !isSentry, ); } resolve(null); @@ -253,6 +259,7 @@ async function compileHermesByteCode( bundleName, outputFolder, sourcemapOutput, + shouldCleanSourcemap, ) { console.log('Hermes enabled, now compiling to hermes bytecode:\n'); // >= rn 0.69 @@ -309,9 +316,98 @@ async function compileHermesByteCode( }, ); } + if (shouldCleanSourcemap) { + fs.removeSync(path.join(outputFolder, `${bundleName}.txt.map`)); + } +} + +async function copyDebugidForSentry(bundleName, outputFolder, sourcemapOutput) { + if (sourcemapOutput) { + let copyDebugidPath; + try { + copyDebugidPath = require.resolve( + '@sentry/react-native/scripts/copy-debugid.js', + { + paths: [process.cwd()], + }, + ); + } catch (error) { + console.error( + '无法找到 Sentry copy-debugid.js 脚本文件,请确保已正确安装 @sentry/react-native', + ); + return; + } + + if (!fs.existsSync(copyDebugidPath)) { + return; + } + console.log('Copying debugid'); + spawnSync( + 'node', + [ + copyDebugidPath, + path.join(outputFolder, `${bundleName}.txt.map`), + path.join(outputFolder, `${bundleName}.map`), + ], + { + stdio: 'ignore', + }, + ); + } fs.removeSync(path.join(outputFolder, `${bundleName}.txt.map`)); } +async function uploadSourcemapForSentry( + bundleName, + outputFolder, + sourcemapOutput, + version, +) { + if (sourcemapOutput) { + let sentryCliPath; + try { + sentryCliPath = require.resolve('@sentry/cli/bin/sentry-cli', { + paths: [process.cwd()], + }); + } catch (error) { + console.error('无法找到 Sentry CLI 工具,请确保已正确安装 @sentry/cli'); + return; + } + + if (!fs.existsSync(sentryCliPath)) { + return; + } + + spawnSync( + 'node', + [sentryCliPath, 'releases', 'set-commits', version, '--auto'], + { + stdio: 'inherit', + }, + ); + console.log(`Sentry release created for version: ${version}`); + + console.log('Uploading sourcemap'); + spawnSync( + 'node', + [ + sentryCliPath, + 'releases', + 'files', + version, + 'upload-sourcemaps', + '--strip-prefix', + path.join(process.cwd(), outputFolder), + path.join(outputFolder, bundleName), + path.join(outputFolder, `${bundleName}.map`), + ], + { + stdio: 'inherit', + }, + ); + } +} + async function pack(dir, output) { console.log('Packing'); fs.ensureDirSync(path.dirname(output)); @@ -718,12 +814,16 @@ export const commands = { options.platform || (await question('平台(ios/android/harmony):')), ); - const { bundleName, entryFile, intermediaDir, output, dev, sourcemap } = + const { bundleName, entryFile, intermediaDir, output, dev } = translateOptions({ ...options, platform, }); + const bundleParams = await checkPlugins(); + const sourcemap = bundleParams.sourcemap; + const isSentry = bundleParams.sentry; + const sourcemapOutput = path.join(intermediaDir, `${bundleName}.map`); const realOutput = output.replace(/\$\{time\}/g, `${Date.now()}`); @@ -749,12 +849,21 @@ export const commands = { const v = await question('是否现在上传此热更包?(Y/N)'); if (v.toLowerCase() === 'y') { - await this.publish({ + const versionName = await this.publish({ args: [realOutput], options: { platform, }, }); + if (isSentry) { + await copyDebugidForSentry(bundleName, intermediaDir, sourcemapOutput); + await uploadSourcemapForSentry( + bundleName, + intermediaDir, + sourcemapOutput, + versionName, + ); + } } }, diff --git a/src/utils/check-plugin.ts b/src/utils/check-plugin.ts new file mode 100644 index 0000000..347f2a8 --- /dev/null +++ b/src/utils/check-plugin.ts @@ -0,0 +1,30 @@ +import { plugins } from './plugin-config'; + +interface BundleParams { + sentry: boolean; + minify: boolean; + sourcemap: boolean; + [key: string]: any; +} + +export async function checkPlugins(): Promise { + const params: BundleParams = { + sentry: false, + minify: true, + sourcemap: false, + }; + + for (const plugin of plugins) { + try { + const isEnabled = await plugin.detect(); + if (isEnabled && plugin.bundleParams) { + Object.assign(params, plugin.bundleParams); + console.log(`检测到 ${plugin.name} 插件,应用相应打包配置`); + } + } catch (err) { + console.warn(`检测 ${plugin.name} 插件时出错:`, err); + } + } + + return params; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index c191263..282ed9c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,6 +6,7 @@ import AppInfoParser from './app-info-parser'; import semverSatisfies from 'semver/functions/satisfies'; import chalk from 'chalk'; import latestVersion from '@badisi/latest-version'; +import { checkPlugins } from './check-plugin'; import { read } from 'read'; @@ -225,3 +226,5 @@ export async function printVersionCommand() { } export const pricingPageUrl = 'https://pushy.reactnative.cn/pricing.html'; + +export { checkPlugins }; diff --git a/src/utils/plugin-config.ts b/src/utils/plugin-config.ts new file mode 100644 index 0000000..cf60a20 --- /dev/null +++ b/src/utils/plugin-config.ts @@ -0,0 +1,34 @@ +import fs from 'fs-extra'; + +interface PluginConfig { + name: string; + bundleParams?: { + minify?: boolean; + [key: string]: any; + }; + detect: () => Promise; +} + +export const plugins: PluginConfig[] = [ + { + name: 'sentry', + bundleParams: { + sentry: true, + minify: false, + sourcemap: true, + }, + detect: async () => { + try { + await fs.access('ios/sentry.properties'); + return true; + } catch { + try { + await fs.access('android/sentry.properties'); + return true; + } catch { + return false; + } + } + } + } +]; \ No newline at end of file diff --git a/src/versions.js b/src/versions.js index 4c32607..9622eb8 100644 --- a/src/versions.js +++ b/src/versions.js @@ -97,8 +97,9 @@ export const commands = { const { hash } = await uploadFile(fn); + const versionName = name || (await question('输入版本名称: ')) || '(未命名)'; const { id } = await post(`/app/${appId}/version/create`, { - name: name || (await question('输入版本名称: ')) || '(未命名)', + name: versionName, hash, description: description || (await question('输入版本描述:')), metaInfo: metaInfo || (await question('输入自定义的 meta info:')), @@ -111,6 +112,7 @@ export const commands = { if (v.toLowerCase() === 'y') { await this.update({ args: [], options: { versionId: id, platform } }); } + return versionName; }, versions: async ({ options }) => { const platform = checkPlatform(