mirror of
https://gitcode.com/github-mirrors/react-native-update-cli.git
synced 2025-09-18 10:20:39 +08:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
814a9d10fb | ||
![]() |
c08c5c0b07 | ||
![]() |
dc8c134ff0 | ||
![]() |
1d1e6cde0f | ||
![]() |
f16aff5674 | ||
![]() |
d7da311c5e | ||
![]() |
abef760f43 | ||
![]() |
467ef0c60d | ||
![]() |
0b08c7760d | ||
![]() |
b24b27d100 | ||
![]() |
4f0784172f |
@@ -8,7 +8,8 @@
|
|||||||
"recommended": true,
|
"recommended": true,
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noExplicitAny": "off",
|
"noExplicitAny": "off",
|
||||||
"noAssignInExpressions": "off"
|
"noAssignInExpressions": "off",
|
||||||
|
"noDoubleEquals": "off"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"noNonNullAssertion": "off"
|
"noNonNullAssertion": "off"
|
||||||
|
9
cli.json
9
cli.json
@@ -145,6 +145,15 @@
|
|||||||
},
|
},
|
||||||
"sourcemap": {
|
"sourcemap": {
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"taro": {
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"expo": {
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"rncli": {
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-native-update-cli",
|
"name": "react-native-update-cli",
|
||||||
"version": "1.38.1",
|
"version": "1.40.0",
|
||||||
"description": "Command tools for javaScript updater with `pushy` service for react native apps.",
|
"description": "Command tools for javaScript updater with `pushy` service for react native apps.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|||||||
import Table from 'tty-table';
|
import Table from 'tty-table';
|
||||||
|
|
||||||
import { post, get, doDelete } from './api';
|
import { post, get, doDelete } from './api';
|
||||||
|
import type { Platform } from './types';
|
||||||
|
|
||||||
const validPlatforms = {
|
const validPlatforms = {
|
||||||
ios: 1,
|
ios: 1,
|
||||||
@@ -10,14 +11,14 @@ const validPlatforms = {
|
|||||||
harmony: 1,
|
harmony: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function checkPlatform(platform) {
|
export function checkPlatform(platform: Platform) {
|
||||||
if (!validPlatforms[platform]) {
|
if (!validPlatforms[platform]) {
|
||||||
throw new Error(`无法识别的平台 '${platform}'`);
|
throw new Error(`无法识别的平台 '${platform}'`);
|
||||||
}
|
}
|
||||||
return platform;
|
return platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSelectedApp(platform) {
|
export function getSelectedApp(platform: Platform) {
|
||||||
checkPlatform(platform);
|
checkPlatform(platform);
|
||||||
|
|
||||||
if (!fs.existsSync('update.json')) {
|
if (!fs.existsSync('update.json')) {
|
||||||
@@ -34,7 +35,7 @@ export function getSelectedApp(platform) {
|
|||||||
return updateInfo[platform];
|
return updateInfo[platform];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listApp(platform) {
|
export async function listApp(platform: Platform) {
|
||||||
const { data } = await get('/app/list');
|
const { data } = await get('/app/list');
|
||||||
const list = platform ? data.filter((v) => v.platform === platform) : data;
|
const list = platform ? data.filter((v) => v.platform === platform) : data;
|
||||||
|
|
||||||
@@ -58,12 +59,12 @@ export async function listApp(platform) {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function chooseApp(platform) {
|
export async function chooseApp(platform: Platform) {
|
||||||
const list = await listApp(platform);
|
const list = await listApp(platform);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const id = await question('输入应用 id:');
|
const id = await question('输入应用 id:');
|
||||||
const app = list.find((v) => v.id === (id | 0));
|
const app = list.find((v) => v.id === Number(id));
|
||||||
if (app) {
|
if (app) {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
@@ -3,15 +3,17 @@ import { getRNVersion, translateOptions } from './utils';
|
|||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import { ZipFile } from 'yazl';
|
import { ZipFile } from 'yazl';
|
||||||
import { open as openZipFile } from 'yauzl';
|
import { open as openZipFile } from 'yauzl';
|
||||||
import { question, printVersionCommand } 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';
|
||||||
|
import semverSatisfies from 'semver/functions/satisfies';
|
||||||
const g2js = require('gradle-to-js/lib/parser');
|
const g2js = require('gradle-to-js/lib/parser');
|
||||||
import os from 'os';
|
import os from 'node:os';
|
||||||
const properties = require('properties');
|
const properties = require('properties');
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
let bsdiff, hdiff, diff;
|
let bsdiff;
|
||||||
|
let hdiff;
|
||||||
|
let diff;
|
||||||
try {
|
try {
|
||||||
bsdiff = require('node-bsdiff').diff;
|
bsdiff = require('node-bsdiff').diff;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -20,16 +22,33 @@ try {
|
|||||||
hdiff = require('node-hdiffpatch').diff;
|
hdiff = require('node-hdiffpatch').diff;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
async function runReactNativeBundleCommand(
|
async function runReactNativeBundleCommand({
|
||||||
bundleName,
|
bundleName,
|
||||||
development,
|
dev,
|
||||||
entryFile,
|
entryFile,
|
||||||
outputFolder,
|
outputFolder,
|
||||||
platform,
|
platform,
|
||||||
sourcemapOutput,
|
sourcemapOutput,
|
||||||
config,
|
config,
|
||||||
) {
|
cli,
|
||||||
let gradleConfig = {};
|
}: {
|
||||||
|
bundleName: string;
|
||||||
|
dev: string;
|
||||||
|
entryFile: string;
|
||||||
|
outputFolder: string;
|
||||||
|
platform: string;
|
||||||
|
sourcemapOutput: string;
|
||||||
|
config?: string;
|
||||||
|
cli: {
|
||||||
|
taro?: boolean;
|
||||||
|
expo?: boolean;
|
||||||
|
rncli?: boolean;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
let gradleConfig: {
|
||||||
|
crunchPngs?: boolean;
|
||||||
|
enableHermes?: boolean;
|
||||||
|
} = {};
|
||||||
if (platform === 'android') {
|
if (platform === 'android') {
|
||||||
gradleConfig = await checkGradleConfig();
|
gradleConfig = await checkGradleConfig();
|
||||||
if (gradleConfig.crunchPngs !== false) {
|
if (gradleConfig.crunchPngs !== false) {
|
||||||
@@ -39,9 +58,9 @@ async function runReactNativeBundleCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let reactNativeBundleArgs = [];
|
const reactNativeBundleArgs: string[] = [];
|
||||||
|
|
||||||
let envArgs = process.env.PUSHY_ENV_ARGS;
|
const envArgs = process.env.PUSHY_ENV_ARGS;
|
||||||
|
|
||||||
if (envArgs) {
|
if (envArgs) {
|
||||||
Array.prototype.push.apply(
|
Array.prototype.push.apply(
|
||||||
@@ -52,15 +71,31 @@ async function runReactNativeBundleCommand(
|
|||||||
|
|
||||||
fs.emptyDirSync(outputFolder);
|
fs.emptyDirSync(outputFolder);
|
||||||
|
|
||||||
let cliPath;
|
let cliPath: string | undefined;
|
||||||
|
|
||||||
let usingExpo = false;
|
let usingExpo = false;
|
||||||
try {
|
|
||||||
cliPath = require.resolve('@expo/cli', {
|
const getExpoCli = () => {
|
||||||
paths: [process.cwd()],
|
try {
|
||||||
});
|
cliPath = require.resolve('@expo/cli', {
|
||||||
usingExpo = true;
|
paths: [process.cwd()],
|
||||||
} catch (e) {
|
});
|
||||||
|
const expoCliVersion = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
require.resolve('@expo/cli/package.json', {
|
||||||
|
paths: [process.cwd()],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).version;
|
||||||
|
// expo cli 0.10.17 (expo 49) 开始支持 bundle:embed
|
||||||
|
if (semverSatisfies(expoCliVersion, '>= 0.10.17')) {
|
||||||
|
usingExpo = true;
|
||||||
|
} else {
|
||||||
|
cliPath = undefined;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRnCli = () => {
|
||||||
try {
|
try {
|
||||||
// rn >= 0.75
|
// rn >= 0.75
|
||||||
cliPath = require.resolve('@react-native-community/cli/build/bin.js', {
|
cliPath = require.resolve('@react-native-community/cli/build/bin.js', {
|
||||||
@@ -72,14 +107,49 @@ async function runReactNativeBundleCommand(
|
|||||||
paths: [process.cwd()],
|
paths: [process.cwd()],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTaroCli = () => {
|
||||||
|
try {
|
||||||
|
cliPath = require.resolve('@tarojs/cli/bin/taro', {
|
||||||
|
paths: [process.cwd()],
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cli.expo) {
|
||||||
|
getExpoCli();
|
||||||
|
} else if (cli.taro) {
|
||||||
|
getTaroCli();
|
||||||
|
} else if (cli.rncli) {
|
||||||
|
getRnCli();
|
||||||
}
|
}
|
||||||
const bundleCommand = usingExpo ? 'export:embed' : platform === 'harmony' ? 'bundle-harmony' : 'bundle';
|
|
||||||
if (platform == 'harmony') {
|
if (!cliPath) {
|
||||||
|
getExpoCli();
|
||||||
|
if (!usingExpo) {
|
||||||
|
getRnCli();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleParams = await checkPlugins();
|
||||||
|
const isSentry = bundleParams.sentry;
|
||||||
|
|
||||||
|
let bundleCommand = 'bundle';
|
||||||
|
if (usingExpo) {
|
||||||
|
bundleCommand = 'export:embed';
|
||||||
|
} else if (platform === 'harmony') {
|
||||||
|
bundleCommand = 'bundle-harmony';
|
||||||
|
} else if (cli.taro) {
|
||||||
|
bundleCommand = 'build';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform === 'harmony') {
|
||||||
Array.prototype.push.apply(reactNativeBundleArgs, [
|
Array.prototype.push.apply(reactNativeBundleArgs, [
|
||||||
cliPath,
|
cliPath,
|
||||||
bundleCommand,
|
bundleCommand,
|
||||||
'--dev',
|
'--dev',
|
||||||
development,
|
dev,
|
||||||
'--entry-file',
|
'--entry-file',
|
||||||
entryFile,
|
entryFile,
|
||||||
]);
|
]);
|
||||||
@@ -91,8 +161,7 @@ async function runReactNativeBundleCommand(
|
|||||||
if (config) {
|
if (config) {
|
||||||
reactNativeBundleArgs.push('--config', config);
|
reactNativeBundleArgs.push('--config', config);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
Array.prototype.push.apply(reactNativeBundleArgs, [
|
Array.prototype.push.apply(reactNativeBundleArgs, [
|
||||||
cliPath,
|
cliPath,
|
||||||
bundleCommand,
|
bundleCommand,
|
||||||
@@ -100,15 +169,25 @@ async function runReactNativeBundleCommand(
|
|||||||
outputFolder,
|
outputFolder,
|
||||||
'--bundle-output',
|
'--bundle-output',
|
||||||
path.join(outputFolder, bundleName),
|
path.join(outputFolder, bundleName),
|
||||||
'--dev',
|
|
||||||
development,
|
|
||||||
'--entry-file',
|
|
||||||
entryFile,
|
|
||||||
'--platform',
|
'--platform',
|
||||||
platform,
|
platform,
|
||||||
'--reset-cache',
|
'--reset-cache',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (cli.taro) {
|
||||||
|
reactNativeBundleArgs.push(...[
|
||||||
|
'--type',
|
||||||
|
'rn',
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
reactNativeBundleArgs.push(...[
|
||||||
|
'--dev',
|
||||||
|
dev,
|
||||||
|
'--entry-file',
|
||||||
|
entryFile,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
if (sourcemapOutput) {
|
if (sourcemapOutput) {
|
||||||
reactNativeBundleArgs.push('--sourcemap-output', sourcemapOutput);
|
reactNativeBundleArgs.push('--sourcemap-output', sourcemapOutput);
|
||||||
}
|
}
|
||||||
@@ -140,17 +219,19 @@ async function runReactNativeBundleCommand(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let hermesEnabled = false;
|
let hermesEnabled: boolean | undefined = false;
|
||||||
|
|
||||||
if (platform === 'android') {
|
if (platform === 'android') {
|
||||||
const gradlePropeties = await new Promise((resolve) => {
|
const gradlePropeties = await new Promise<{
|
||||||
|
hermesEnabled?: boolean;
|
||||||
|
}>((resolve) => {
|
||||||
properties.parse(
|
properties.parse(
|
||||||
'./android/gradle.properties',
|
'./android/gradle.properties',
|
||||||
{ path: true },
|
{ path: true },
|
||||||
function (error, props) {
|
(error: any, props: { hermesEnabled?: boolean }) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
resolve(null);
|
resolve({});
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(props);
|
resolve(props);
|
||||||
@@ -166,7 +247,7 @@ async function runReactNativeBundleCommand(
|
|||||||
fs.existsSync('ios/Pods/hermes-engine')
|
fs.existsSync('ios/Pods/hermes-engine')
|
||||||
) {
|
) {
|
||||||
hermesEnabled = true;
|
hermesEnabled = true;
|
||||||
}else if (platform === 'harmony') {
|
} else if (platform === 'harmony') {
|
||||||
await copyHarmonyBundle(outputFolder);
|
await copyHarmonyBundle(outputFolder);
|
||||||
}
|
}
|
||||||
if (hermesEnabled) {
|
if (hermesEnabled) {
|
||||||
@@ -174,6 +255,7 @@ async function runReactNativeBundleCommand(
|
|||||||
bundleName,
|
bundleName,
|
||||||
outputFolder,
|
outputFolder,
|
||||||
sourcemapOutput,
|
sourcemapOutput,
|
||||||
|
!isSentry,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
@@ -182,7 +264,7 @@ async function runReactNativeBundleCommand(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyHarmonyBundle(outputFolder) {
|
async function copyHarmonyBundle(outputFolder: string) {
|
||||||
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
|
const harmonyRawPath = 'harmony/entry/src/main/resources/rawfile';
|
||||||
try {
|
try {
|
||||||
await fs.ensureDir(harmonyRawPath);
|
await fs.ensureDir(harmonyRawPath);
|
||||||
@@ -196,7 +278,7 @@ async function copyHarmonyBundle(outputFolder) {
|
|||||||
|
|
||||||
await fs.ensureDir(outputFolder);
|
await fs.ensureDir(outputFolder);
|
||||||
await fs.copy(harmonyRawPath, outputFolder);
|
await fs.copy(harmonyRawPath, outputFolder);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('copyHarmonyBundle 错误:', error);
|
console.error('copyHarmonyBundle 错误:', error);
|
||||||
throw new Error(`复制文件失败: ${error.message}`);
|
throw new Error(`复制文件失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -234,11 +316,12 @@ async function checkGradleConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function compileHermesByteCode(
|
async function compileHermesByteCode(
|
||||||
bundleName,
|
bundleName: string,
|
||||||
outputFolder,
|
outputFolder: string,
|
||||||
sourcemapOutput,
|
sourcemapOutput: string,
|
||||||
|
shouldCleanSourcemap: boolean,
|
||||||
) {
|
) {
|
||||||
console.log(`Hermes enabled, now compiling to hermes bytecode:\n`);
|
console.log('Hermes enabled, now compiling to hermes bytecode:\n');
|
||||||
// >= rn 0.69
|
// >= rn 0.69
|
||||||
const rnDir = path.dirname(
|
const rnDir = path.dirname(
|
||||||
require.resolve('react-native', {
|
require.resolve('react-native', {
|
||||||
@@ -264,13 +347,11 @@ async function compileHermesByteCode(
|
|||||||
if (sourcemapOutput) {
|
if (sourcemapOutput) {
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
sourcemapOutput,
|
sourcemapOutput,
|
||||||
path.join(outputFolder, bundleName + '.txt.map'),
|
path.join(outputFolder, `${bundleName}.txt.map`),
|
||||||
);
|
);
|
||||||
args.push('-output-source-map');
|
args.push('-output-source-map');
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(`Running hermesc: ${hermesCommand} ${args.join(' ')}`);
|
||||||
'Running hermesc: ' + hermesCommand + ' ' + args.join(' ') + '\n',
|
|
||||||
);
|
|
||||||
spawnSync(hermesCommand, args, {
|
spawnSync(hermesCommand, args, {
|
||||||
stdio: 'ignore',
|
stdio: 'ignore',
|
||||||
});
|
});
|
||||||
@@ -280,13 +361,13 @@ async function compileHermesByteCode(
|
|||||||
if (!fs.existsSync(composerPath)) {
|
if (!fs.existsSync(composerPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`Composing source map`);
|
console.log('Composing source map');
|
||||||
spawnSync(
|
spawnSync(
|
||||||
'node',
|
'node',
|
||||||
[
|
[
|
||||||
composerPath,
|
composerPath,
|
||||||
path.join(outputFolder, bundleName + '.txt.map'),
|
path.join(outputFolder, `${bundleName}.txt.map`),
|
||||||
path.join(outputFolder, bundleName + '.map'),
|
path.join(outputFolder, `${bundleName}.map`),
|
||||||
'-o',
|
'-o',
|
||||||
sourcemapOutput,
|
sourcemapOutput,
|
||||||
],
|
],
|
||||||
@@ -295,22 +376,120 @@ async function compileHermesByteCode(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fs.removeSync(path.join(outputFolder, bundleName + '.txt.map'));
|
if (shouldCleanSourcemap) {
|
||||||
|
fs.removeSync(path.join(outputFolder, `${bundleName}.txt.map`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pack(dir, output) {
|
async function copyDebugidForSentry(
|
||||||
|
bundleName: string,
|
||||||
|
outputFolder: string,
|
||||||
|
sourcemapOutput: string,
|
||||||
|
) {
|
||||||
|
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: string,
|
||||||
|
outputFolder: string,
|
||||||
|
sourcemapOutput: string,
|
||||||
|
version: string,
|
||||||
|
) {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ignorePackingFileNames = ['.', '..', 'index.bundlejs.map'];
|
||||||
|
const ignorePackingExtensions = ['DS_Store'];
|
||||||
|
async function pack(dir: string, output: string) {
|
||||||
console.log('Packing');
|
console.log('Packing');
|
||||||
fs.ensureDirSync(path.dirname(output));
|
fs.ensureDirSync(path.dirname(output));
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const zipfile = new ZipFile();
|
const zipfile = new ZipFile();
|
||||||
|
|
||||||
function addDirectory(root, rel) {
|
function addDirectory(root: string, rel: string) {
|
||||||
if (rel) {
|
if (rel) {
|
||||||
zipfile.addEmptyDirectory(rel);
|
zipfile.addEmptyDirectory(rel);
|
||||||
}
|
}
|
||||||
const childs = fs.readdirSync(root);
|
const childs = fs.readdirSync(root);
|
||||||
for (const name of childs) {
|
for (const name of childs) {
|
||||||
if (name === '.' || name === '..' || name === 'index.bundlejs.map') {
|
if (
|
||||||
|
ignorePackingFileNames.includes(name) ||
|
||||||
|
ignorePackingExtensions.some((ext) => name.endsWith(`.${ext}`))
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const fullPath = path.join(root, name);
|
const fullPath = path.join(root, name);
|
||||||
@@ -320,30 +499,28 @@ async function pack(dir, output) {
|
|||||||
zipfile.addFile(fullPath, rel + name);
|
zipfile.addFile(fullPath, rel + name);
|
||||||
} else if (stat.isDirectory()) {
|
} else if (stat.isDirectory()) {
|
||||||
//console.log('adding: ' + rel+name+'/');
|
//console.log('adding: ' + rel+name+'/');
|
||||||
addDirectory(fullPath, rel + name + '/');
|
addDirectory(fullPath, `${rel}${name}/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addDirectory(dir, '');
|
addDirectory(dir, '');
|
||||||
|
|
||||||
zipfile.outputStream.on('error', (err) => reject(err));
|
zipfile.outputStream.on('error', (err: any) => reject(err));
|
||||||
zipfile.outputStream
|
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', () => {
|
||||||
.pipe(fs.createWriteStream(output))
|
resolve();
|
||||||
.on('close', function () {
|
});
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
zipfile.end();
|
zipfile.end();
|
||||||
});
|
});
|
||||||
console.log('ppk热更包已生成并保存到: ' + output);
|
console.log(`ppk热更包已生成并保存到: ${output}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readEntire(entry, zipFile) {
|
export function readEntire(entry: string, zipFile: ZipFile) {
|
||||||
const buffers = [];
|
const buffers: Buffer[] = [];
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
zipFile.openReadStream(entry, (err, stream) => {
|
zipFile.openReadStream(entry, (err: any, stream: any) => {
|
||||||
stream.pipe({
|
stream.pipe({
|
||||||
write(chunk) {
|
write(chunk: Buffer) {
|
||||||
buffers.push(chunk);
|
buffers.push(chunk);
|
||||||
},
|
},
|
||||||
end() {
|
end() {
|
||||||
@@ -358,12 +535,12 @@ export function readEntire(entry, zipFile) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function basename(fn) {
|
function basename(fn: string) {
|
||||||
const m = /^(.+\/)[^\/]+\/?$/.exec(fn);
|
const m = /^(.+\/)[^\/]+\/?$/.exec(fn);
|
||||||
return m && m[1];
|
return m?.[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function diffFromPPK(origin, next, output) {
|
async function diffFromPPK(origin: string, next: string, output: string) {
|
||||||
fs.ensureDirSync(path.dirname(output));
|
fs.ensureDirSync(path.dirname(output));
|
||||||
|
|
||||||
const originEntries = {};
|
const originEntries = {};
|
||||||
@@ -377,7 +554,10 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
// isFile
|
// isFile
|
||||||
originMap[entry.crc32] = entry.fileName;
|
originMap[entry.crc32] = entry.fileName;
|
||||||
|
|
||||||
if (entry.fileName === 'index.bundlejs' || entry.fileName === 'bundle.harmony.js') {
|
if (
|
||||||
|
entry.fileName === 'index.bundlejs' ||
|
||||||
|
entry.fileName === 'bundle.harmony.js'
|
||||||
|
) {
|
||||||
// This is source.
|
// This is source.
|
||||||
return readEntire(entry, zipFile).then((v) => (originSource = v));
|
return readEntire(entry, zipFile).then((v) => (originSource = v));
|
||||||
}
|
}
|
||||||
@@ -386,7 +566,7 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
|
|
||||||
if (!originSource) {
|
if (!originSource) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Bundle file not found! Please use default bundle file name and path.`,
|
'Bundle file not found! Please use default bundle file name and path.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,16 +578,14 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
zipfile.outputStream.on('error', (err) => {
|
zipfile.outputStream.on('error', (err) => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
zipfile.outputStream
|
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', () => {
|
||||||
.pipe(fs.createWriteStream(output))
|
resolve();
|
||||||
.on('close', function () {
|
});
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const addedEntry = {};
|
const addedEntry = {};
|
||||||
|
|
||||||
function addEntry(fn) {
|
function addEntry(fn: string) {
|
||||||
//console.log(fn);
|
//console.log(fn);
|
||||||
if (!fn || addedEntry[fn]) {
|
if (!fn || addedEntry[fn]) {
|
||||||
return;
|
return;
|
||||||
@@ -439,7 +617,7 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
);
|
);
|
||||||
//console.log('End diff');
|
//console.log('End diff');
|
||||||
});
|
});
|
||||||
}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 readEntire(entry, nextZipfile).then((newSource) => {
|
||||||
//console.log('Begin diff');
|
//console.log('Begin diff');
|
||||||
@@ -471,7 +649,7 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
addEntry(basename(entry.fileName));
|
addEntry(basename(entry.fileName));
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
nextZipfile.openReadStream(entry, function (err, readStream) {
|
nextZipfile.openReadStream(entry, (err, readStream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
@@ -487,9 +665,9 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
|
|
||||||
const deletes = {};
|
const deletes = {};
|
||||||
|
|
||||||
for (let k in originEntries) {
|
for (const k in originEntries) {
|
||||||
if (!newEntries[k]) {
|
if (!newEntries[k]) {
|
||||||
console.log('Delete ' + k);
|
console.log(`Delete ${k}`);
|
||||||
deletes[k] = 1;
|
deletes[k] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,11 +682,11 @@ async function diffFromPPK(origin, next, output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function diffFromPackage(
|
async function diffFromPackage(
|
||||||
origin,
|
origin: string,
|
||||||
next,
|
next: string,
|
||||||
output,
|
output: string,
|
||||||
originBundleName,
|
originBundleName: string,
|
||||||
transformPackagePath = (v) => v,
|
transformPackagePath = (v: string) => v,
|
||||||
) {
|
) {
|
||||||
fs.ensureDirSync(path.dirname(output));
|
fs.ensureDirSync(path.dirname(output));
|
||||||
|
|
||||||
@@ -517,7 +695,7 @@ async function diffFromPackage(
|
|||||||
|
|
||||||
let originSource;
|
let originSource;
|
||||||
|
|
||||||
await enumZipEntries(origin, (entry, zipFile) => {
|
await enumZipEntries(origin, (entry: any, zipFile: any) => {
|
||||||
if (!/\/$/.test(entry.fileName)) {
|
if (!/\/$/.test(entry.fileName)) {
|
||||||
const fn = transformPackagePath(entry.fileName);
|
const fn = transformPackagePath(entry.fileName);
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
@@ -538,7 +716,7 @@ async function diffFromPackage(
|
|||||||
|
|
||||||
if (!originSource) {
|
if (!originSource) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Bundle file not found! Please use default bundle file name and path.`,
|
'Bundle file not found! Please use default bundle file name and path.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,11 +728,9 @@ async function diffFromPackage(
|
|||||||
zipfile.outputStream.on('error', (err) => {
|
zipfile.outputStream.on('error', (err) => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
zipfile.outputStream
|
zipfile.outputStream.pipe(fs.createWriteStream(output)).on('close', () => {
|
||||||
.pipe(fs.createWriteStream(output))
|
resolve();
|
||||||
.on('close', function () {
|
});
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await enumZipEntries(next, (entry, nextZipfile) => {
|
await enumZipEntries(next, (entry, nextZipfile) => {
|
||||||
@@ -581,7 +757,7 @@ async function diffFromPackage(
|
|||||||
);
|
);
|
||||||
//console.log('End diff');
|
//console.log('End diff');
|
||||||
});
|
});
|
||||||
}else {
|
} else {
|
||||||
// If same file.
|
// If same file.
|
||||||
if (originEntries[entry.fileName] === entry.crc32) {
|
if (originEntries[entry.fileName] === entry.crc32) {
|
||||||
copies[entry.fileName] = '';
|
copies[entry.fileName] = '';
|
||||||
@@ -594,7 +770,7 @@ async function diffFromPackage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
nextZipfile.openReadStream(entry, function (err, readStream) {
|
nextZipfile.openReadStream(entry, (err, readStream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
@@ -613,55 +789,66 @@ async function diffFromPackage(
|
|||||||
await writePromise;
|
await writePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function enumZipEntries(zipFn, callback, nestedPath = '') {
|
export async function enumZipEntries(
|
||||||
|
zipFn: string,
|
||||||
|
callback: (entry: any, zipFile: any) => void,
|
||||||
|
nestedPath = '',
|
||||||
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
openZipFile(zipFn, { lazyEntries: true }, async (err, zipfile) => {
|
openZipFile(
|
||||||
if (err) {
|
zipFn,
|
||||||
return reject(err);
|
{ lazyEntries: true },
|
||||||
}
|
async (err: any, zipfile: ZipFile) => {
|
||||||
|
if (err) {
|
||||||
zipfile.on('end', resolve);
|
return reject(err);
|
||||||
zipfile.on('error', reject);
|
|
||||||
zipfile.on('entry', async (entry) => {
|
|
||||||
const fullPath = nestedPath + entry.fileName;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
!entry.fileName.endsWith('/') &&
|
|
||||||
entry.fileName.toLowerCase().endsWith('.hap')
|
|
||||||
) {
|
|
||||||
const tempDir = path.join(os.tmpdir(), 'nested_zip_' + Date.now());
|
|
||||||
await fs.ensureDir(tempDir);
|
|
||||||
const tempZipPath = path.join(tempDir, 'temp.zip');
|
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
|
||||||
zipfile.openReadStream(entry, async (err, readStream) => {
|
|
||||||
if (err) return rej(err);
|
|
||||||
const writeStream = fs.createWriteStream(tempZipPath);
|
|
||||||
readStream.pipe(writeStream);
|
|
||||||
writeStream.on('finish', res);
|
|
||||||
writeStream.on('error', rej);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await enumZipEntries(tempZipPath, callback, fullPath + '/');
|
|
||||||
|
|
||||||
await fs.remove(tempDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = callback(entry, zipfile, fullPath);
|
|
||||||
if (result && typeof result.then === 'function') {
|
|
||||||
await result;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('处理文件时出错:', error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
zipfile.readEntry();
|
zipfile.on('end', resolve);
|
||||||
});
|
zipfile.on('error', reject);
|
||||||
|
zipfile.on('entry', async (entry) => {
|
||||||
|
const fullPath = nestedPath + entry.fileName;
|
||||||
|
|
||||||
zipfile.readEntry();
|
try {
|
||||||
});
|
if (
|
||||||
|
!entry.fileName.endsWith('/') &&
|
||||||
|
entry.fileName.toLowerCase().endsWith('.hap')
|
||||||
|
) {
|
||||||
|
const tempDir = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`nested_zip_${Date.now()}`,
|
||||||
|
);
|
||||||
|
await fs.ensureDir(tempDir);
|
||||||
|
const tempZipPath = path.join(tempDir, 'temp.zip');
|
||||||
|
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
zipfile.openReadStream(entry, async (err, readStream) => {
|
||||||
|
if (err) return rej(err);
|
||||||
|
const writeStream = fs.createWriteStream(tempZipPath);
|
||||||
|
readStream.pipe(writeStream);
|
||||||
|
writeStream.on('finish', res);
|
||||||
|
writeStream.on('error', rej);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await enumZipEntries(tempZipPath, callback, `${fullPath}/`);
|
||||||
|
|
||||||
|
await fs.remove(tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = callback(entry, zipfile, fullPath);
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
await result;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理文件时出错:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
zipfile.readEntry();
|
||||||
|
});
|
||||||
|
|
||||||
|
zipfile.readEntry();
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +884,7 @@ function diffArgsCheck(args, options, diffFn) {
|
|||||||
return {
|
return {
|
||||||
origin,
|
origin,
|
||||||
next,
|
next,
|
||||||
realOutput: output.replace(/\$\{time\}/g, '' + Date.now()),
|
realOutput: output.replace(/\$\{time\}/g, `${Date.now()}`),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,15 +894,28 @@ export const commands = {
|
|||||||
options.platform || (await question('平台(ios/android/harmony):')),
|
options.platform || (await question('平台(ios/android/harmony):')),
|
||||||
);
|
);
|
||||||
|
|
||||||
let { bundleName, entryFile, intermediaDir, output, dev, sourcemap } =
|
const {
|
||||||
translateOptions({
|
bundleName,
|
||||||
...options,
|
entryFile,
|
||||||
platform,
|
intermediaDir,
|
||||||
});
|
output,
|
||||||
|
dev,
|
||||||
|
sourcemap,
|
||||||
|
taro,
|
||||||
|
expo,
|
||||||
|
rncli,
|
||||||
|
} = translateOptions({
|
||||||
|
...options,
|
||||||
|
platform,
|
||||||
|
});
|
||||||
|
|
||||||
const sourcemapOutput = path.join(intermediaDir, bundleName + '.map');
|
const bundleParams = await checkPlugins();
|
||||||
|
const sourcemapPlugin = bundleParams.sourcemap;
|
||||||
|
const isSentry = bundleParams.sentry;
|
||||||
|
|
||||||
const realOutput = output.replace(/\$\{time\}/g, '' + Date.now());
|
const sourcemapOutput = path.join(intermediaDir, `${bundleName}.map`);
|
||||||
|
|
||||||
|
const realOutput = output.replace(/\$\{time\}/g, `${Date.now()}`);
|
||||||
|
|
||||||
if (!platform) {
|
if (!platform) {
|
||||||
throw new Error('Platform must be specified.');
|
throw new Error('Platform must be specified.');
|
||||||
@@ -723,27 +923,41 @@ export const commands = {
|
|||||||
|
|
||||||
const { version, major, minor } = getRNVersion();
|
const { version, major, minor } = getRNVersion();
|
||||||
|
|
||||||
console.log('Bundling with react-native: ', version);
|
console.log(`Bundling with react-native: ${version}`);
|
||||||
|
|
||||||
await runReactNativeBundleCommand(
|
await runReactNativeBundleCommand({
|
||||||
bundleName,
|
bundleName,
|
||||||
dev,
|
dev,
|
||||||
entryFile,
|
entryFile,
|
||||||
intermediaDir,
|
outputFolder: intermediaDir,
|
||||||
platform,
|
platform,
|
||||||
sourcemap ? sourcemapOutput : '',
|
sourcemapOutput: sourcemap || sourcemapPlugin ? sourcemapOutput : '',
|
||||||
);
|
cli: {
|
||||||
|
taro,
|
||||||
|
expo,
|
||||||
|
rncli,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await pack(path.resolve(intermediaDir), realOutput);
|
await pack(path.resolve(intermediaDir), realOutput);
|
||||||
|
|
||||||
const v = await question('是否现在上传此热更包?(Y/N)');
|
const v = await question('是否现在上传此热更包?(Y/N)');
|
||||||
if (v.toLowerCase() === 'y') {
|
if (v.toLowerCase() === 'y') {
|
||||||
await this.publish({
|
const versionName = await this.publish({
|
||||||
args: [realOutput],
|
args: [realOutput],
|
||||||
options: {
|
options: {
|
||||||
platform,
|
platform,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (isSentry) {
|
||||||
|
await copyDebugidForSentry(bundleName, intermediaDir, sourcemapOutput);
|
||||||
|
await uploadSourcemapForSentry(
|
||||||
|
bundleName,
|
||||||
|
intermediaDir,
|
||||||
|
sourcemapOutput,
|
||||||
|
versionName,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -817,7 +1031,7 @@ export const commands = {
|
|||||||
|
|
||||||
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
|
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
|
||||||
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
|
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
|
||||||
return m && m[1];
|
return m?.[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`${realOutput} generated.`);
|
console.log(`${realOutput} generated.`);
|
||||||
@@ -832,7 +1046,7 @@ export const commands = {
|
|||||||
|
|
||||||
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
|
await diffFromPackage(origin, next, realOutput, 'main.jsbundle', (v) => {
|
||||||
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
|
const m = /^Payload\/[^/]+\/(.+)$/.exec(v);
|
||||||
return m && m[1];
|
return m?.[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`${realOutput} generated.`);
|
console.log(`${realOutput} generated.`);
|
@@ -6,7 +6,7 @@ import { checkPlatform, getSelectedApp } from './app';
|
|||||||
import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
|
import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
|
||||||
import Table from 'tty-table';
|
import Table from 'tty-table';
|
||||||
|
|
||||||
export async function listPackage(appId) {
|
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: '原生包 Id' }, { value: '原生版本' }];
|
||||||
@@ -35,12 +35,12 @@ export async function listPackage(appId) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function choosePackage(appId) {
|
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('输入原生包 id:');
|
||||||
const app = list.find((v) => v.id === (id | 0));
|
const app = list.find((v) => v.id === Number(id));
|
||||||
if (app) {
|
if (app) {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ export async function choosePackage(appId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
uploadIpa: async function ({ args }) {
|
uploadIpa: async ({ args }: { args: string[] }) => {
|
||||||
const fn = args[0];
|
const fn = args[0];
|
||||||
if (!fn || !fn.endsWith('.ipa')) {
|
if (!fn || !fn.endsWith('.ipa')) {
|
||||||
throw new Error('使用方法: pushy uploadIpa ipa后缀文件');
|
throw new Error('使用方法: pushy uploadIpa ipa后缀文件');
|
||||||
@@ -85,7 +85,7 @@ export const commands = {
|
|||||||
`已成功上传ipa原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
`已成功上传ipa原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
uploadApk: async function ({ args }) {
|
uploadApk: async ({ args }) => {
|
||||||
const fn = args[0];
|
const fn = args[0];
|
||||||
if (!fn || !fn.endsWith('.apk')) {
|
if (!fn || !fn.endsWith('.apk')) {
|
||||||
throw new Error('使用方法: pushy uploadApk apk后缀文件');
|
throw new Error('使用方法: pushy uploadApk apk后缀文件');
|
||||||
@@ -122,7 +122,7 @@ export const commands = {
|
|||||||
`已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
`已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
uploadApp: async function ({ args }) {
|
uploadApp: async ({ args }) => {
|
||||||
const fn = args[0];
|
const fn = args[0];
|
||||||
if (!fn || !fn.endsWith('.app')) {
|
if (!fn || !fn.endsWith('.app')) {
|
||||||
throw new Error('使用方法: pushy uploadApp app后缀文件');
|
throw new Error('使用方法: pushy uploadApp app后缀文件');
|
||||||
@@ -135,7 +135,6 @@ export const commands = {
|
|||||||
} = await getAppInfo(fn);
|
} = await getAppInfo(fn);
|
||||||
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(
|
||||||
`appId不匹配!当前app: ${appIdInPkg}, 当前update.json: ${appId}`,
|
`appId不匹配!当前app: ${appIdInPkg}, 当前update.json: ${appId}`,
|
||||||
@@ -160,28 +159,28 @@ export const commands = {
|
|||||||
`已成功上传app原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
`已成功上传app原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
parseApp: async function ({ args }) {
|
parseApp: async ({ args }) => {
|
||||||
const fn = args[0];
|
const fn = args[0];
|
||||||
if (!fn || !fn.endsWith('.app')) {
|
if (!fn || !fn.endsWith('.app')) {
|
||||||
throw new Error('使用方法: pushy parseApp app后缀文件');
|
throw new Error('使用方法: pushy parseApp app后缀文件');
|
||||||
}
|
}
|
||||||
console.log(await getAppInfo(fn));
|
console.log(await getAppInfo(fn));
|
||||||
},
|
},
|
||||||
parseIpa: async function ({ args }) {
|
parseIpa: async ({ args }) => {
|
||||||
const fn = args[0];
|
const fn = args[0];
|
||||||
if (!fn || !fn.endsWith('.ipa')) {
|
if (!fn || !fn.endsWith('.ipa')) {
|
||||||
throw new Error('使用方法: pushy parseIpa ipa后缀文件');
|
throw new Error('使用方法: pushy parseIpa ipa后缀文件');
|
||||||
}
|
}
|
||||||
console.log(await getIpaInfo(fn));
|
console.log(await getIpaInfo(fn));
|
||||||
},
|
},
|
||||||
parseApk: async function ({ args }) {
|
parseApk: async ({ args }) => {
|
||||||
const fn = args[0];
|
const fn = args[0];
|
||||||
if (!fn || !fn.endsWith('.apk')) {
|
if (!fn || !fn.endsWith('.apk')) {
|
||||||
throw new Error('使用方法: pushy parseApk apk后缀文件');
|
throw new Error('使用方法: pushy parseApk apk后缀文件');
|
||||||
}
|
}
|
||||||
console.log(await getApkInfo(fn));
|
console.log(await getApkInfo(fn));
|
||||||
},
|
},
|
||||||
packages: async function ({ options }) {
|
packages: async ({ options }) => {
|
||||||
const platform = checkPlatform(
|
const platform = checkPlatform(
|
||||||
options.platform || (await question('平台(ios/android/harmony):')),
|
options.platform || (await question('平台(ios/android/harmony):')),
|
||||||
);
|
);
|
@@ -6,3 +6,5 @@ declare global {
|
|||||||
export interface Session {
|
export interface Session {
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Platform = 'ios' | 'android' | 'harmony';
|
||||||
|
@@ -2,12 +2,12 @@ import { question } from './utils';
|
|||||||
import { post, get, replaceSession, saveSession, closeSession } from './api';
|
import { post, get, replaceSession, saveSession, closeSession } from './api';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
|
|
||||||
function md5(str) {
|
function md5(str: string) {
|
||||||
return crypto.createHash('md5').update(str).digest('hex');
|
return crypto.createHash('md5').update(str).digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
login: async ({ args }) => {
|
login: async ({ args }: { args: string[] }) => {
|
||||||
const email = args[0] || (await question('email:'));
|
const email = args[0] || (await question('email:'));
|
||||||
const pwd = args[1] || (await question('password:', true));
|
const pwd = args[1] || (await question('password:', true));
|
||||||
const { token, info } = await post('/user/login', {
|
const { token, info } = await post('/user/login', {
|
28
src/utils/check-plugin.ts
Normal file
28
src/utils/check-plugin.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { plugins } from './plugin-config';
|
||||||
|
|
||||||
|
interface BundleParams {
|
||||||
|
sentry: boolean;
|
||||||
|
sourcemap: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkPlugins(): Promise<BundleParams> {
|
||||||
|
const params: BundleParams = {
|
||||||
|
sentry: false,
|
||||||
|
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;
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import AppInfoParser from './app-info-parser';
|
|||||||
import semverSatisfies from 'semver/functions/satisfies';
|
import semverSatisfies from 'semver/functions/satisfies';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import latestVersion from '@badisi/latest-version';
|
import latestVersion from '@badisi/latest-version';
|
||||||
|
import { checkPlugins } from './check-plugin';
|
||||||
|
|
||||||
import { read } from 'read';
|
import { read } from 'read';
|
||||||
|
|
||||||
@@ -22,12 +23,13 @@ export async function question(query, password) {
|
|||||||
|
|
||||||
export function translateOptions(options) {
|
export function translateOptions(options) {
|
||||||
const ret = {};
|
const ret = {};
|
||||||
for (let key in options) {
|
for (const key in options) {
|
||||||
const v = options[key];
|
const v = options[key];
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') {
|
||||||
ret[key] = v.replace(/\$\{(\w+)\}/g, function (v, n) {
|
ret[key] = v.replace(
|
||||||
return options[n] || process.env[n] || v;
|
/\$\{(\w+)\}/g,
|
||||||
});
|
(v, n) => options[n] || process.env[n] || v,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
ret[key] = v;
|
ret[key] = v;
|
||||||
}
|
}
|
||||||
@@ -124,7 +126,7 @@ export async function getAppInfo(fn) {
|
|||||||
return { versionName, buildTime, ...appCredential };
|
return { versionName, buildTime, ...appCredential };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getIpaInfo(fn) {
|
export async function getIpaInfo(fn: string) {
|
||||||
const appInfoParser = new AppInfoParser(fn);
|
const appInfoParser = new AppInfoParser(fn);
|
||||||
const bundleFile = await appInfoParser.parser.getEntry(
|
const bundleFile = await appInfoParser.parser.getEntry(
|
||||||
/payload\/.+?\.app\/main.jsbundle/,
|
/payload\/.+?\.app\/main.jsbundle/,
|
||||||
@@ -217,10 +219,12 @@ export async function printVersionCommand() {
|
|||||||
);
|
);
|
||||||
} else if (semverSatisfies(pushyVersion, '10.0.0 - 10.17.0')) {
|
} else if (semverSatisfies(pushyVersion, '10.0.0 - 10.17.0')) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`当前版本已不再支持,请升级到 v10 的最新小版本(代码无需改动,可直接热更): npm i react-native-update@10`,
|
'当前版本已不再支持,请升级到 v10 的最新小版本(代码无需改动,可直接热更): npm i react-native-update@10',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pricingPageUrl = 'https://pushy.reactnative.cn/pricing.html';
|
export const pricingPageUrl = 'https://pushy.reactnative.cn/pricing.html';
|
||||||
|
|
||||||
|
export { checkPlugins };
|
32
src/utils/plugin-config.ts
Normal file
32
src/utils/plugin-config.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
interface PluginConfig {
|
||||||
|
name: string;
|
||||||
|
bundleParams?: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
detect: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const plugins: PluginConfig[] = [
|
||||||
|
{
|
||||||
|
name: 'sentry',
|
||||||
|
bundleParams: {
|
||||||
|
sentry: true,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
@@ -97,8 +97,9 @@ export const commands = {
|
|||||||
|
|
||||||
const { hash } = await uploadFile(fn);
|
const { hash } = await uploadFile(fn);
|
||||||
|
|
||||||
|
const versionName = name || (await question('输入版本名称: ')) || '(未命名)';
|
||||||
const { id } = await post(`/app/${appId}/version/create`, {
|
const { id } = await post(`/app/${appId}/version/create`, {
|
||||||
name: name || (await question('输入版本名称: ')) || '(未命名)',
|
name: versionName,
|
||||||
hash,
|
hash,
|
||||||
description: description || (await question('输入版本描述:')),
|
description: description || (await question('输入版本描述:')),
|
||||||
metaInfo: metaInfo || (await question('输入自定义的 meta info:')),
|
metaInfo: metaInfo || (await question('输入自定义的 meta info:')),
|
||||||
@@ -111,6 +112,7 @@ export const commands = {
|
|||||||
if (v.toLowerCase() === 'y') {
|
if (v.toLowerCase() === 'y') {
|
||||||
await this.update({ args: [], options: { versionId: id, platform } });
|
await this.update({ args: [], options: { versionId: id, platform } });
|
||||||
}
|
}
|
||||||
|
return versionName;
|
||||||
},
|
},
|
||||||
versions: async ({ options }) => {
|
versions: async ({ options }) => {
|
||||||
const platform = checkPlatform(
|
const platform = checkPlatform(
|
||||||
|
Reference in New Issue
Block a user