react-native-update-cli 1.41.0 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/app.js CHANGED
@@ -103,7 +103,8 @@ const commands = {
103
103
  const platform = checkPlatform(options.platform || await (0, _utils.question)('平台(ios/android/harmony):'));
104
104
  const { id } = await (0, _api.post)('/app/create', {
105
105
  name,
106
- platform
106
+ platform,
107
+ downloadUrl
107
108
  });
108
109
  console.log(`已成功创建应用(id: ${id})`);
109
110
  await this.selectApp({
@@ -111,8 +112,7 @@ const commands = {
111
112
  id
112
113
  ],
113
114
  options: {
114
- platform,
115
- downloadUrl
115
+ platform
116
116
  }
117
117
  });
118
118
  },
package/lib/bundle.js CHANGED
@@ -28,6 +28,7 @@ const _app = require("./app");
28
28
  const _nodechild_process = require("node:child_process");
29
29
  const _satisfies = /*#__PURE__*/ _interop_require_default(require("semver/functions/satisfies"));
30
30
  const _nodeos = /*#__PURE__*/ _interop_require_default(require("node:os"));
31
+ const _depversions = require("./utils/dep-versions");
31
32
  function _interop_require_default(obj) {
32
33
  return obj && obj.__esModule ? obj : {
33
34
  default: obj
@@ -766,8 +767,7 @@ const commands = {
766
767
  if (!platform) {
767
768
  throw new Error('Platform must be specified.');
768
769
  }
769
- const { version, major, minor } = (0, _utils.getRNVersion)();
770
- console.log(`Bundling with react-native: ${version}`);
770
+ console.log(`Bundling with react-native: ${_depversions.depVersions['react-native']}`);
771
771
  await runReactNativeBundleCommand({
772
772
  bundleName,
773
773
  dev,
package/lib/index.js CHANGED
@@ -7,34 +7,23 @@ const _api = require("./api");
7
7
  const _updatenotifier = /*#__PURE__*/ _interop_require_default(require("update-notifier"));
8
8
  const _utils = require("./utils");
9
9
  const _packagejson = /*#__PURE__*/ _interop_require_default(require("../package.json"));
10
- const _i18next = /*#__PURE__*/ _interop_require_default(require("i18next"));
11
- const _en = /*#__PURE__*/ _interop_require_default(require("./locales/en"));
12
- const _zh = /*#__PURE__*/ _interop_require_default(require("./locales/zh"));
13
- const _constants = require("./utils/constants");
10
+ const _i18n = require("./utils/i18n");
14
11
  function _interop_require_default(obj) {
15
12
  return obj && obj.__esModule ? obj : {
16
13
  default: obj
17
14
  };
18
15
  }
19
- _i18next.default.init({
20
- lng: _constants.IS_CRESC ? 'en' : 'zh',
21
- // debug: process.env.NODE_ENV !== 'production',
22
- resources: {
23
- en: _en.default,
24
- zh: _zh.default
25
- }
26
- });
27
16
  (0, _updatenotifier.default)({
28
17
  pkg: _packagejson.default
29
18
  }).notify({
30
19
  isGlobal: true,
31
- message: '建议运行 `{updateCommand}` 来更新命令行工具以获得功能、性能和安全性的持续改进'
20
+ message: (0, _i18n.t)('updateNotifier')
32
21
  });
33
22
  function printUsage() {
34
23
  // const commandName = args[0];
35
24
  // TODO: print usage of commandName, or print global usage.
36
25
  console.log('Usage is under development now.');
37
- console.log('Visit `https://github.com/reactnativecn/react-native-pushy` for early document.');
26
+ console.log('Visit `https://github.com/reactnativecn/react-native-update` for document.');
38
27
  process.exit(1);
39
28
  }
40
29
  const commands = {
@@ -55,7 +44,7 @@ async function run() {
55
44
  global.USE_ACC_OSS = argv.options.acc;
56
45
  (0, _api.loadSession)().then(()=>commands[argv.command](argv)).catch((err)=>{
57
46
  if (err.status === 401) {
58
- console.log('尚未登录。\n请在项目目录中运行`pushy login`命令来登录');
47
+ console.log((0, _i18n.t)('loginFirst'));
59
48
  return;
60
49
  }
61
50
  console.error(err.stack);
package/lib/locales/en.js CHANGED
@@ -8,4 +8,16 @@ Object.defineProperty(exports, "default", {
8
8
  return _default;
9
9
  }
10
10
  });
11
- const _default = {};
11
+ const _default = {
12
+ updateNotifier: 'Run `{updateCommand}` to update the CLI to get continuous improvements in features, performance, and security.',
13
+ loginFirst: 'Not logged in.\nPlease run `cresc login` in the project directory to login.',
14
+ lockNotFound: 'No lock file detected, which may cause inconsistent dependencies and hot-updating issues.',
15
+ multipleLocksFound: 'Multiple lock files detected ({lockFiles}), which may cause inconsistent dependencies and hot-updating issues.',
16
+ lockBestPractice: `
17
+ Best practices for lock files:
18
+ 1. All members of the development team should use the same package manager to maintain a single lock file.
19
+ 2. Add the lock file to version control (but do not commit multiple lock files of different formats).
20
+ 3. Pay attention to changes in the lock file during code review.
21
+ This can reduce the risk of inconsistent dependencies and supply chain attacks.
22
+ `
23
+ };
package/lib/locales/zh.js CHANGED
@@ -8,4 +8,16 @@ Object.defineProperty(exports, "default", {
8
8
  return _default;
9
9
  }
10
10
  });
11
- const _default = {};
11
+ const _default = {
12
+ updateNotifier: '建议运行 `{updateCommand}` 来更新命令行工具以获得功能、性能和安全性的持续改进',
13
+ loginFirst: '尚未登录。\n请在项目目录中运行`pushy login`命令来登录',
14
+ lockNotFound: '没有检测到任何 lock 文件,这可能导致依赖关系不一致而使热更异常。',
15
+ lockBestPractice: `
16
+ 关于 lock 文件的最佳实践:
17
+ 1. 开发团队中的所有成员应该使用相同的包管理器,维护同一份 lock 文件。
18
+ 2. 将 lock 文件添加到版本控制中(但不要同时提交多种不同格式的 lock 文件)。
19
+ 3. 代码审核时应关注 lock 文件的变化。
20
+ 这样可以最大限度避免因依赖关系不一致而导致的热更异常,也降低供应链攻击等安全隐患。
21
+ `,
22
+ multipleLocksFound: '检测到多种不同格式的锁文件({lockFiles}),这可能导致依赖关系不一致而使热更异常。'
23
+ };
package/lib/package.js CHANGED
@@ -23,6 +23,8 @@ const _api = require("./api");
23
23
  const _utils = require("./utils");
24
24
  const _app = require("./app");
25
25
  const _ttytable = /*#__PURE__*/ _interop_require_default(require("tty-table"));
26
+ const _depversions = require("./utils/dep-versions");
27
+ const _git = require("./utils/git");
26
28
  function _interop_require_default(obj) {
27
29
  return obj && obj.__esModule ? obj : {
28
30
  default: obj
@@ -92,7 +94,9 @@ const commands = {
92
94
  const { id } = await (0, _api.post)(`/app/${appId}/package/create`, {
93
95
  name: versionName,
94
96
  hash,
95
- buildTime
97
+ buildTime,
98
+ deps: _depversions.depVersions,
99
+ commit: await (0, _git.getCommitInfo)()
96
100
  });
97
101
  (0, _utils.saveToLocal)(fn, `${appId}/package/${id}.ipa`);
98
102
  console.log(`已成功上传ipa原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`);
@@ -114,7 +118,9 @@ const commands = {
114
118
  const { id } = await (0, _api.post)(`/app/${appId}/package/create`, {
115
119
  name: versionName,
116
120
  hash,
117
- buildTime
121
+ buildTime,
122
+ deps: _depversions.depVersions,
123
+ commit: await (0, _git.getCommitInfo)()
118
124
  });
119
125
  (0, _utils.saveToLocal)(fn, `${appId}/package/${id}.apk`);
120
126
  console.log(`已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`);
@@ -136,7 +142,9 @@ const commands = {
136
142
  const { id } = await (0, _api.post)(`/app/${appId}/package/create`, {
137
143
  name: versionName,
138
144
  hash,
139
- buildTime
145
+ buildTime,
146
+ deps: _depversions.depVersions,
147
+ commit: await (0, _git.getCommitInfo)()
140
148
  });
141
149
  (0, _utils.saveToLocal)(fn, `${appId}/package/${id}.app`);
142
150
  console.log(`已成功上传app原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`);
@@ -27,7 +27,7 @@ function isBrowser() {
27
27
  iteratorObj(apkInfo);
28
28
  return apkInfo;
29
29
  function iteratorObj(obj) {
30
- for(var i in obj){
30
+ for(const i in obj){
31
31
  if (isArray(obj[i])) {
32
32
  iteratorArray(obj[i]);
33
33
  } else if (isObject(obj[i])) {
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "depVersions", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return depVersions;
9
+ }
10
+ });
11
+ const currentPackage = require(`${process.cwd()}/package.json`);
12
+ const depKeys = Object.keys(currentPackage.dependencies);
13
+ const devDepKeys = Object.keys(currentPackage.devDependencies);
14
+ const dedupedDeps = [
15
+ ...new Set([
16
+ ...depKeys,
17
+ ...devDepKeys
18
+ ])
19
+ ];
20
+ const _depVersions = {};
21
+ for (const dep of dedupedDeps){
22
+ try {
23
+ const packageJsonPath = require.resolve(`${dep}/package.json`, {
24
+ paths: [
25
+ process.cwd()
26
+ ]
27
+ });
28
+ const version = require(packageJsonPath).version;
29
+ _depVersions[dep] = version;
30
+ } catch (e) {}
31
+ }
32
+ const depVersions = Object.keys(_depVersions).sort() // Sort the keys alphabetically
33
+ .reduce((obj, key)=>{
34
+ obj[key] = _depVersions[key]; // Rebuild the object with sorted keys
35
+ return obj;
36
+ }, {}); // console.log({ depVersions });
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "getCommitInfo", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return getCommitInfo;
9
+ }
10
+ });
11
+ const _isomorphicgit = /*#__PURE__*/ _interop_require_default(require("isomorphic-git"));
12
+ const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
13
+ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
14
+ function _interop_require_default(obj) {
15
+ return obj && obj.__esModule ? obj : {
16
+ default: obj
17
+ };
18
+ }
19
+ function findGitRoot(dir = process.cwd()) {
20
+ const gitRoot = _nodefs.default.readdirSync(dir).find((dir)=>dir === '.git');
21
+ if (gitRoot) {
22
+ // console.log({ gitRoot });
23
+ return _nodepath.default.join(dir, gitRoot);
24
+ }
25
+ const parentDir = _nodepath.default.dirname(dir);
26
+ if (parentDir === dir) {
27
+ return null;
28
+ }
29
+ return findGitRoot(parentDir);
30
+ }
31
+ const gitRoot = findGitRoot();
32
+ async function getCommitInfo() {
33
+ if (!gitRoot) {
34
+ return;
35
+ }
36
+ try {
37
+ const remotes = await _isomorphicgit.default.listRemotes({
38
+ fs: _nodefs.default,
39
+ gitdir: gitRoot
40
+ });
41
+ const origin = remotes.find((remote)=>remote.remote === 'origin') || remotes[0];
42
+ const { commit, oid } = (await _isomorphicgit.default.log({
43
+ fs: _nodefs.default,
44
+ gitdir: gitRoot,
45
+ depth: 1
46
+ }))[0];
47
+ return {
48
+ hash: oid,
49
+ message: commit.message,
50
+ author: commit.author.name || commit.committer.name,
51
+ timestamp: String(commit.committer.timestamp),
52
+ origin: origin.url
53
+ };
54
+ } catch (error) {
55
+ console.error(error);
56
+ return;
57
+ }
58
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "t", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return t;
9
+ }
10
+ });
11
+ const _i18next = /*#__PURE__*/ _interop_require_default(require("i18next"));
12
+ const _en = /*#__PURE__*/ _interop_require_default(require("../locales/en"));
13
+ const _zh = /*#__PURE__*/ _interop_require_default(require("../locales/zh"));
14
+ const _constants = require("./constants");
15
+ function _interop_require_default(obj) {
16
+ return obj && obj.__esModule ? obj : {
17
+ default: obj
18
+ };
19
+ }
20
+ _i18next.default.init({
21
+ lng: _constants.IS_CRESC ? 'en' : 'zh',
22
+ // debug: process.env.NODE_ENV !== 'production',
23
+ resources: {
24
+ en: _en.default,
25
+ zh: _zh.default
26
+ }
27
+ });
28
+ const t = _i18next.default.t;
@@ -21,9 +21,6 @@ _export(exports, {
21
21
  getIpaInfo: function() {
22
22
  return getIpaInfo;
23
23
  },
24
- getRNVersion: function() {
25
- return getRNVersion;
26
- },
27
24
  printVersionCommand: function() {
28
25
  return printVersionCommand;
29
26
  },
@@ -48,6 +45,7 @@ const _latestversion = /*#__PURE__*/ _interop_require_default(require("@badisi/l
48
45
  const _checkplugin = require("./check-plugin");
49
46
  const _read = require("read");
50
47
  const _constants = require("./constants");
48
+ const _depversions = require("./dep-versions");
51
49
  function _interop_require_default(obj) {
52
50
  return obj && obj.__esModule ? obj : {
53
51
  default: obj
@@ -75,19 +73,6 @@ function translateOptions(options) {
75
73
  }
76
74
  return ret;
77
75
  }
78
- function getRNVersion() {
79
- const version = JSON.parse(_fsextra.default.readFileSync(require.resolve('react-native/package.json', {
80
- paths: [
81
- process.cwd()
82
- ]
83
- })).toString()).version;
84
- const [, major, minor] = /^(\d+)\.(\d+)\./.exec(version) || [];
85
- return {
86
- version,
87
- major: Number(major),
88
- minor: Number(minor)
89
- };
90
- }
91
76
  async function getApkInfo(fn) {
92
77
  const appInfoParser = new _appinfoparser.default(fn);
93
78
  const bundleFile = await appInfoParser.parser.getEntry(/assets\/index.android.bundle/);
@@ -198,18 +183,9 @@ async function printVersionCommand() {
198
183
  latestPushyCliVersion = latestPushyCliVersion ? ` (最新:${_chalk.default.green(latestPushyCliVersion)})` : '';
199
184
  console.log(`react-native-update-cli: ${_packagejson.default.version}${latestPushyCliVersion}`);
200
185
  let pushyVersion = '';
201
- try {
202
- const PACKAGE_JSON_PATH = require.resolve('react-native-update/package.json', {
203
- paths: [
204
- process.cwd()
205
- ]
206
- });
207
- pushyVersion = require(PACKAGE_JSON_PATH).version;
208
- latestPushyVersion = latestPushyVersion ? ` (最新:${_chalk.default.green(latestPushyVersion)})` : '';
209
- console.log(`react-native-update: ${pushyVersion}${latestPushyVersion}`);
210
- } catch (e) {
211
- console.log('react-native-update: 无法获取版本号,请在项目目录中运行命令');
212
- }
186
+ pushyVersion = _depversions.depVersions['react-native-update'];
187
+ latestPushyVersion = latestPushyVersion ? ` (最新:${_chalk.default.green(latestPushyVersion)})` : '';
188
+ console.log(`react-native-update: ${pushyVersion}${latestPushyVersion}`);
213
189
  if (pushyVersion) {
214
190
  if ((0, _satisfies.default)(pushyVersion, '<8.5.2')) {
215
191
  console.warn(`当前版本已不再支持,请至少升级到 v8 的最新小版本后重新打包(代码无需改动): npm i react-native-update@8 .
@@ -220,5 +196,7 @@ async function printVersionCommand() {
220
196
  } else if ((0, _satisfies.default)(pushyVersion, '10.0.0 - 10.17.0')) {
221
197
  console.warn('当前版本已不再支持,请升级到 v10 的最新小版本(代码无需改动,可直接热更): npm i react-native-update@10');
222
198
  }
199
+ } else {
200
+ console.log('react-native-update: 无法获取版本号,请在项目目录中运行命令');
223
201
  }
224
202
  }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ const lockFiles = [
3
+ 'package-lock.json',
4
+ 'yarn.lock',
5
+ 'pnpm-lock.yaml',
6
+ 'bun.lockb',
7
+ 'bun.lock'
8
+ ];
package/lib/versions.js CHANGED
@@ -13,16 +13,18 @@ const _utils = require("./utils");
13
13
  const _app = require("./app");
14
14
  const _package = require("./package");
15
15
  const _compareversions = require("compare-versions");
16
+ const _depversions = require("./utils/dep-versions");
17
+ const _git = require("./utils/git");
16
18
  async function showVersion(appId, offset) {
17
19
  const { data, count } = await (0, _api.get)(`/app/${appId}/version/list`);
18
20
  console.log(`Offset ${offset}`);
19
21
  for (const version of data){
20
22
  let packageInfo = version.packages.slice(0, 3).map((v)=>v.name).join(', ');
21
- const count = version.packages.length;
22
- if (count > 3) {
23
- packageInfo += `...and ${count - 3} more`;
23
+ const pkgCount = version.packages.length;
24
+ if (pkgCount > 3) {
25
+ packageInfo += `...and ${pkgCount - 3} more`;
24
26
  }
25
- if (count === 0) {
27
+ if (pkgCount === 0) {
26
28
  packageInfo = 'no package';
27
29
  } else {
28
30
  packageInfo = `[${packageInfo}]`;
@@ -91,7 +93,9 @@ const commands = {
91
93
  name: versionName,
92
94
  hash,
93
95
  description: description || await (0, _utils.question)('输入版本描述:'),
94
- metaInfo: metaInfo || await (0, _utils.question)('输入自定义的 meta info:')
96
+ metaInfo: metaInfo || await (0, _utils.question)('输入自定义的 meta info:'),
97
+ deps: _depversions.depVersions,
98
+ commit: await (0, _git.getCommitInfo)()
95
99
  });
96
100
  // TODO local diff
97
101
  (0, _utils.saveToLocal)(fn, `${appId}/ppk/${id}.ppk`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update-cli",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "description": "Command tools for javaScript updater with `pushy` service for react native apps.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -35,26 +35,27 @@
35
35
  },
36
36
  "homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli",
37
37
  "dependencies": {
38
- "@badisi/latest-version": "^7.0.10",
38
+ "@badisi/latest-version": "^7.0.12",
39
39
  "bplist-parser": "^0.3.2",
40
40
  "bytebuffer": "^5.0.1",
41
41
  "cgbi-to-png": "^1.0.7",
42
42
  "chalk": "4",
43
43
  "cli-arguments": "^0.2.1",
44
- "commander": "^12.1.0",
44
+ "commander": "^13.1.0",
45
45
  "compare-versions": "^6.1.1",
46
46
  "filesize-parser": "^1.5.1",
47
- "form-data": "^4.0.1",
47
+ "form-data": "^4.0.2",
48
48
  "fs-extra": "8",
49
49
  "gradle-to-js": "^2.0.1",
50
- "i18next": "^24.2.2",
50
+ "i18next": "^24.2.3",
51
+ "isomorphic-git": "^1.29.0",
51
52
  "isomorphic-unzip": "^1.1.5",
52
53
  "node-fetch": "^2.6.1",
53
54
  "plist": "^3.1.0",
54
55
  "progress": "^2.0.3",
55
56
  "properties": "^1.2.1",
56
- "read": "^4.0.0",
57
- "semver": "^7.6.3",
57
+ "read": "^4.1.0",
58
+ "semver": "^7.7.1",
58
59
  "tcp-ping": "^0.1.1",
59
60
  "tty-table": "4.2",
60
61
  "update-notifier": "^5.1.0",
@@ -66,11 +67,11 @@
66
67
  },
67
68
  "devDependencies": {
68
69
  "@biomejs/biome": "^1.9.4",
69
- "@swc/cli": "^0.5.1",
70
- "@swc/core": "^1.9.3",
70
+ "@swc/cli": "^0.6.0",
71
+ "@swc/core": "^1.11.9",
71
72
  "@types/filesize-parser": "^1.5.3",
72
73
  "@types/fs-extra": "^11.0.4",
73
- "@types/node": "^22.9.3",
74
+ "@types/node": "^22.13.10",
74
75
  "@types/node-fetch": "^2.6.12",
75
76
  "@types/progress": "^2.0.7",
76
77
  "@types/semver": "^7.5.8",
@@ -78,6 +79,6 @@
78
79
  "@types/update-notifier": "^6.0.8",
79
80
  "@types/yauzl": "^2.10.3",
80
81
  "@types/yazl": "^2.4.6",
81
- "typescript": "^5.7.2"
82
+ "typescript": "^5.8.2"
82
83
  }
83
84
  }
package/src/api.ts CHANGED
@@ -92,7 +92,7 @@ function queryWithoutBody(method: string) {
92
92
  }
93
93
 
94
94
  function queryWithBody(method: string) {
95
- return (api: string, body: Record<string, any>) =>
95
+ return (api: string, body?: Record<string, any>) =>
96
96
  query(host + api, {
97
97
  method,
98
98
  headers: {
package/src/app.ts CHANGED
@@ -72,20 +72,24 @@ export async function chooseApp(platform: Platform) {
72
72
  }
73
73
 
74
74
  export const commands = {
75
- createApp: async function ({ options }) {
75
+ createApp: async function ({
76
+ options,
77
+ }: {
78
+ options: { name: string; downloadUrl: string; platform: Platform };
79
+ }) {
76
80
  const name = options.name || (await question('应用名称:'));
77
81
  const { downloadUrl } = options;
78
82
  const platform = checkPlatform(
79
83
  options.platform || (await question('平台(ios/android/harmony):')),
80
84
  );
81
- const { id } = await post('/app/create', { name, platform });
85
+ const { id } = await post('/app/create', { name, platform, downloadUrl });
82
86
  console.log(`已成功创建应用(id: ${id})`);
83
87
  await this.selectApp({
84
88
  args: [id],
85
- options: { platform, downloadUrl },
89
+ options: { platform },
86
90
  });
87
91
  },
88
- deleteApp: async ({ args, options }) => {
92
+ deleteApp: async ({ args, options }: { args: string[]; options: { platform: Platform } }) => {
89
93
  const { platform } = options;
90
94
  const id = args[0] || chooseApp(platform);
91
95
  if (!id) {
@@ -94,11 +98,11 @@ export const commands = {
94
98
  await doDelete(`/app/${id}`);
95
99
  console.log('操作成功');
96
100
  },
97
- apps: async ({ options }) => {
101
+ apps: async ({ options }: { options: { platform: Platform } }) => {
98
102
  const { platform } = options;
99
103
  listApp(platform);
100
104
  },
101
- selectApp: async ({ args, options }) => {
105
+ selectApp: async ({ args, options }: { args: string[]; options: { platform: Platform } }) => {
102
106
  const platform = checkPlatform(
103
107
  options.platform || (await question('平台(ios/android/harmony):')),
104
108
  );
@@ -106,7 +110,7 @@ export const commands = {
106
110
  ? Number.parseInt(args[0])
107
111
  : (await chooseApp(platform)).id;
108
112
 
109
- let updateInfo = {};
113
+ let updateInfo: Partial<Record<Platform, { appId: number; appKey: string }>> = {};
110
114
  if (fs.existsSync('update.json')) {
111
115
  try {
112
116
  updateInfo = JSON.parse(fs.readFileSync('update.json', 'utf8'));
package/src/bundle.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { getRNVersion, translateOptions } from './utils';
2
+ import { translateOptions } from './utils';
3
3
  import * as fs from 'fs-extra';
4
4
  import { ZipFile } from 'yazl';
5
5
  import { open as openZipFile } from 'yauzl';
@@ -10,6 +10,7 @@ import semverSatisfies from 'semver/functions/satisfies';
10
10
  const g2js = require('gradle-to-js/lib/parser');
11
11
  import os from 'node:os';
12
12
  const properties = require('properties');
13
+ import { depVersions } from './utils/dep-versions';
13
14
 
14
15
  let bsdiff;
15
16
  let hdiff;
@@ -82,11 +83,13 @@ async function runReactNativeBundleCommand({
82
83
  paths: [process.cwd()],
83
84
  });
84
85
  const expoCliVersion = JSON.parse(
85
- fs.readFileSync(
86
- require.resolve('@expo/cli/package.json', {
87
- paths: [process.cwd()],
88
- }),
89
- ).toString(),
86
+ fs
87
+ .readFileSync(
88
+ require.resolve('@expo/cli/package.json', {
89
+ paths: [process.cwd()],
90
+ }),
91
+ )
92
+ .toString(),
90
93
  ).version;
91
94
  // expo cli 0.10.17 (expo 49) 开始支持 bundle:embed
92
95
  if (semverSatisfies(expoCliVersion, '>= 0.10.17')) {
@@ -175,19 +178,11 @@ async function runReactNativeBundleCommand({
175
178
  platform,
176
179
  '--reset-cache',
177
180
  ]);
178
-
181
+
179
182
  if (cli.taro) {
180
- reactNativeBundleArgs.push(...[
181
- '--type',
182
- 'rn',
183
- ])
183
+ reactNativeBundleArgs.push(...['--type', 'rn']);
184
184
  } else {
185
- reactNativeBundleArgs.push(...[
186
- '--dev',
187
- dev,
188
- '--entry-file',
189
- entryFile,
190
- ])
185
+ reactNativeBundleArgs.push(...['--dev', dev, '--entry-file', entryFile]);
191
186
  }
192
187
 
193
188
  if (sourcemapOutput) {
@@ -927,9 +922,7 @@ export const commands = {
927
922
  throw new Error('Platform must be specified.');
928
923
  }
929
924
 
930
- const { version, major, minor } = getRNVersion();
931
-
932
- console.log(`Bundling with react-native: ${version}`);
925
+ console.log(`Bundling with react-native: ${depVersions['react-native']}`);
933
926
 
934
927
  await runReactNativeBundleCommand({
935
928
  bundleName,
package/src/index.ts CHANGED
@@ -4,24 +4,11 @@ import { loadSession } from './api';
4
4
  import updateNotifier from 'update-notifier';
5
5
  import { printVersionCommand } from './utils';
6
6
  import pkg from '../package.json';
7
- import i18next from 'i18next';
8
- import en from './locales/en';
9
- import zh from './locales/zh';
10
- import { IS_CRESC } from './utils/constants';
11
-
12
- i18next.init({
13
- lng: IS_CRESC ? 'en' : 'zh',
14
- // debug: process.env.NODE_ENV !== 'production',
15
- resources: {
16
- en,
17
- zh,
18
- },
19
- });
7
+ import { t } from './utils/i18n';
20
8
 
21
9
  updateNotifier({ pkg }).notify({
22
10
  isGlobal: true,
23
- message:
24
- '建议运行 `{updateCommand}` 来更新命令行工具以获得功能、性能和安全性的持续改进',
11
+ message: t('updateNotifier'),
25
12
  });
26
13
 
27
14
  function printUsage() {
@@ -30,7 +17,7 @@ function printUsage() {
30
17
 
31
18
  console.log('Usage is under development now.');
32
19
  console.log(
33
- 'Visit `https://github.com/reactnativecn/react-native-pushy` for early document.',
20
+ 'Visit `https://github.com/reactnativecn/react-native-update` for document.',
34
21
  );
35
22
  process.exit(1);
36
23
  }
@@ -58,7 +45,7 @@ async function run() {
58
45
  .then(() => commands[argv.command](argv))
59
46
  .catch((err) => {
60
47
  if (err.status === 401) {
61
- console.log('尚未登录。\n请在项目目录中运行`pushy login`命令来登录');
48
+ console.log(t('loginFirst'));
62
49
  return;
63
50
  }
64
51
  console.error(err.stack);
package/src/locales/en.ts CHANGED
@@ -1 +1,17 @@
1
- export default {};
1
+ export default {
2
+ updateNotifier:
3
+ 'Run `{updateCommand}` to update the CLI to get continuous improvements in features, performance, and security.',
4
+ loginFirst:
5
+ 'Not logged in.\nPlease run `cresc login` in the project directory to login.',
6
+ lockNotFound:
7
+ 'No lock file detected, which may cause inconsistent dependencies and hot-updating issues.',
8
+ multipleLocksFound:
9
+ 'Multiple lock files detected ({lockFiles}), which may cause inconsistent dependencies and hot-updating issues.',
10
+ lockBestPractice: `
11
+ Best practices for lock files:
12
+ 1. All members of the development team should use the same package manager to maintain a single lock file.
13
+ 2. Add the lock file to version control (but do not commit multiple lock files of different formats).
14
+ 3. Pay attention to changes in the lock file during code review.
15
+ This can reduce the risk of inconsistent dependencies and supply chain attacks.
16
+ `,
17
+ };
package/src/locales/zh.ts CHANGED
@@ -1 +1,16 @@
1
- export default {};
1
+ export default {
2
+ updateNotifier:
3
+ '建议运行 `{updateCommand}` 来更新命令行工具以获得功能、性能和安全性的持续改进',
4
+ loginFirst: '尚未登录。\n请在项目目录中运行`pushy login`命令来登录',
5
+ lockNotFound:
6
+ '没有检测到任何 lock 文件,这可能导致依赖关系不一致而使热更异常。',
7
+ lockBestPractice: `
8
+ 关于 lock 文件的最佳实践:
9
+ 1. 开发团队中的所有成员应该使用相同的包管理器,维护同一份 lock 文件。
10
+ 2. 将 lock 文件添加到版本控制中(但不要同时提交多种不同格式的 lock 文件)。
11
+ 3. 代码审核时应关注 lock 文件的变化。
12
+ 这样可以最大限度避免因依赖关系不一致而导致的热更异常,也降低供应链攻击等安全隐患。
13
+ `,
14
+ multipleLocksFound:
15
+ '检测到多种不同格式的锁文件({lockFiles}),这可能导致依赖关系不一致而使热更异常。',
16
+ };
package/src/package.ts CHANGED
@@ -5,6 +5,9 @@ import { checkPlatform, getSelectedApp } from './app';
5
5
 
6
6
  import { getApkInfo, getIpaInfo, getAppInfo } from './utils';
7
7
  import Table from 'tty-table';
8
+ import { depVersions } from './utils/dep-versions';
9
+ import { getCommitInfo } from './utils/git';
10
+ import type { Platform } from 'types';
8
11
 
9
12
  export async function listPackage(appId: string) {
10
13
  const { data } = await get(`/app/${appId}/package/list?limit=1000`);
@@ -79,13 +82,15 @@ export const commands = {
79
82
  name: versionName,
80
83
  hash,
81
84
  buildTime,
85
+ deps: depVersions,
86
+ commit: await getCommitInfo(),
82
87
  });
83
88
  saveToLocal(fn, `${appId}/package/${id}.ipa`);
84
89
  console.log(
85
90
  `已成功上传ipa原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
86
91
  );
87
92
  },
88
- uploadApk: async ({ args }) => {
93
+ uploadApk: async ({ args }: { args: string[] }) => {
89
94
  const fn = args[0];
90
95
  if (!fn || !fn.endsWith('.apk')) {
91
96
  throw new Error('使用方法: pushy uploadApk apk后缀文件');
@@ -116,13 +121,15 @@ export const commands = {
116
121
  name: versionName,
117
122
  hash,
118
123
  buildTime,
124
+ deps: depVersions,
125
+ commit: await getCommitInfo(),
119
126
  });
120
127
  saveToLocal(fn, `${appId}/package/${id}.apk`);
121
128
  console.log(
122
129
  `已成功上传apk原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
123
130
  );
124
131
  },
125
- uploadApp: async ({ args }) => {
132
+ uploadApp: async ({ args }: { args: string[] }) => {
126
133
  const fn = args[0];
127
134
  if (!fn || !fn.endsWith('.app')) {
128
135
  throw new Error('使用方法: pushy uploadApp app后缀文件');
@@ -153,34 +160,36 @@ export const commands = {
153
160
  name: versionName,
154
161
  hash,
155
162
  buildTime,
163
+ deps: depVersions,
164
+ commit: await getCommitInfo(),
156
165
  });
157
166
  saveToLocal(fn, `${appId}/package/${id}.app`);
158
167
  console.log(
159
168
  `已成功上传app原生包(id: ${id}, version: ${versionName}, buildTime: ${buildTime})`,
160
169
  );
161
170
  },
162
- parseApp: async ({ args }) => {
171
+ parseApp: async ({ args }: { args: string[] }) => {
163
172
  const fn = args[0];
164
173
  if (!fn || !fn.endsWith('.app')) {
165
174
  throw new Error('使用方法: pushy parseApp app后缀文件');
166
175
  }
167
176
  console.log(await getAppInfo(fn));
168
177
  },
169
- parseIpa: async ({ args }) => {
178
+ parseIpa: async ({ args }: { args: string[] }) => {
170
179
  const fn = args[0];
171
180
  if (!fn || !fn.endsWith('.ipa')) {
172
181
  throw new Error('使用方法: pushy parseIpa ipa后缀文件');
173
182
  }
174
183
  console.log(await getIpaInfo(fn));
175
184
  },
176
- parseApk: async ({ args }) => {
185
+ parseApk: async ({ args }: { args: string[] }) => {
177
186
  const fn = args[0];
178
187
  if (!fn || !fn.endsWith('.apk')) {
179
188
  throw new Error('使用方法: pushy parseApk apk后缀文件');
180
189
  }
181
190
  console.log(await getApkInfo(fn));
182
191
  },
183
- packages: async ({ options }) => {
192
+ packages: async ({ options }: { options: { platform: Platform } }) => {
184
193
  const platform = checkPlatform(
185
194
  options.platform || (await question('平台(ios/android/harmony):')),
186
195
  );
@@ -30,7 +30,7 @@ function mapInfoResource (apkInfo, resourceMap) {
30
30
  iteratorObj(apkInfo)
31
31
  return apkInfo
32
32
  function iteratorObj (obj) {
33
- for (var i in obj) {
33
+ for (const i in obj) {
34
34
  if (isArray(obj[i])) {
35
35
  iteratorArray(obj[i])
36
36
  } else if (isObject(obj[i])) {
@@ -0,0 +1,26 @@
1
+ const currentPackage = require(`${process.cwd()}/package.json`);
2
+
3
+ const depKeys = Object.keys(currentPackage.dependencies);
4
+ const devDepKeys = Object.keys(currentPackage.devDependencies);
5
+ const dedupedDeps = [...new Set([...depKeys, ...devDepKeys])];
6
+
7
+ const _depVersions: Record<string, string> = {};
8
+
9
+ for (const dep of dedupedDeps) {
10
+ try {
11
+ const packageJsonPath = require.resolve(`${dep}/package.json`, {
12
+ paths: [process.cwd()],
13
+ });
14
+ const version = require(packageJsonPath).version;
15
+ _depVersions[dep] = version;
16
+ } catch (e) {}
17
+ }
18
+
19
+ export const depVersions = Object.keys(_depVersions)
20
+ .sort() // Sort the keys alphabetically
21
+ .reduce((obj, key) => {
22
+ obj[key] = _depVersions[key]; // Rebuild the object with sorted keys
23
+ return obj;
24
+ }, {} as Record<string, string>);
25
+
26
+ // console.log({ depVersions });
@@ -0,0 +1,50 @@
1
+ import git from 'isomorphic-git';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export interface CommitInfo {
6
+ hash: string;
7
+ message: string;
8
+ author: string;
9
+ timestamp: string;
10
+ origin: string;
11
+ }
12
+
13
+ function findGitRoot(dir = process.cwd()) {
14
+ const gitRoot = fs.readdirSync(dir).find((dir) => dir === '.git');
15
+ if (gitRoot) {
16
+ // console.log({ gitRoot });
17
+ return path.join(dir, gitRoot);
18
+ }
19
+ const parentDir = path.dirname(dir);
20
+ if (parentDir === dir) {
21
+ return null;
22
+ }
23
+ return findGitRoot(parentDir);
24
+ }
25
+
26
+ const gitRoot = findGitRoot();
27
+
28
+ export async function getCommitInfo(): Promise<CommitInfo | undefined> {
29
+ if (!gitRoot) {
30
+ return;
31
+ }
32
+ try {
33
+ const remotes = await git.listRemotes({ fs, gitdir: gitRoot });
34
+ const origin =
35
+ remotes.find((remote) => remote.remote === 'origin') || remotes[0];
36
+ const { commit, oid } = (
37
+ await git.log({ fs, gitdir: gitRoot, depth: 1 })
38
+ )[0];
39
+ return {
40
+ hash: oid,
41
+ message: commit.message,
42
+ author: commit.author.name || commit.committer.name,
43
+ timestamp: String(commit.committer.timestamp),
44
+ origin: origin.url,
45
+ };
46
+ } catch (error) {
47
+ console.error(error);
48
+ return;
49
+ }
50
+ }
@@ -0,0 +1,28 @@
1
+ import i18next from 'i18next';
2
+ import en from '../locales/en';
3
+ import zh from '../locales/zh';
4
+ import { IS_CRESC } from './constants';
5
+ i18next.init({
6
+ lng: IS_CRESC ? 'en' : 'zh',
7
+ // debug: process.env.NODE_ENV !== 'production',
8
+ resources: {
9
+ en,
10
+ zh,
11
+ },
12
+ });
13
+
14
+ declare module 'i18next' {
15
+ // Extend CustomTypeOptions
16
+ interface CustomTypeOptions {
17
+ // custom namespace type, if you changed it
18
+ defaultNS: 'en';
19
+ // custom resources type
20
+ resources: {
21
+ en: typeof en;
22
+ zh: typeof zh;
23
+ };
24
+ // other
25
+ }
26
+ }
27
+
28
+ export const t = i18next.t;
@@ -10,6 +10,7 @@ import { checkPlugins } from './check-plugin';
10
10
 
11
11
  import { read } from 'read';
12
12
  import { tempDir } from './constants';
13
+ import { depVersions } from './dep-versions';
13
14
 
14
15
  export async function question(query: string, password?: boolean) {
15
16
  if (NO_INTERACTIVE) {
@@ -38,26 +39,6 @@ export function translateOptions(options: Record<string, string>) {
38
39
  return ret;
39
40
  }
40
41
 
41
- export function getRNVersion() {
42
- const version = JSON.parse(
43
- fs
44
- .readFileSync(
45
- require.resolve('react-native/package.json', {
46
- paths: [process.cwd()],
47
- }),
48
- )
49
- .toString(),
50
- ).version;
51
-
52
- const [, major, minor] = /^(\d+)\.(\d+)\./.exec(version) || [];
53
-
54
- return {
55
- version,
56
- major: Number(major),
57
- minor: Number(minor),
58
- };
59
- }
60
-
61
42
  export async function getApkInfo(fn: string) {
62
43
  const appInfoParser = new AppInfoParser(fn);
63
44
  const bundleFile = await appInfoParser.parser.getEntry(
@@ -198,21 +179,11 @@ export async function printVersionCommand() {
198
179
  `react-native-update-cli: ${pkg.version}${latestPushyCliVersion}`,
199
180
  );
200
181
  let pushyVersion = '';
201
- try {
202
- const PACKAGE_JSON_PATH = require.resolve(
203
- 'react-native-update/package.json',
204
- {
205
- paths: [process.cwd()],
206
- },
207
- );
208
- pushyVersion = require(PACKAGE_JSON_PATH).version;
209
- latestPushyVersion = latestPushyVersion
210
- ? ` (最新:${chalk.green(latestPushyVersion)})`
211
- : '';
212
- console.log(`react-native-update: ${pushyVersion}${latestPushyVersion}`);
213
- } catch (e) {
214
- console.log('react-native-update: 无法获取版本号,请在项目目录中运行命令');
215
- }
182
+ pushyVersion = depVersions['react-native-update'];
183
+ latestPushyVersion = latestPushyVersion
184
+ ? ` (最新:${chalk.green(latestPushyVersion)})`
185
+ : '';
186
+ console.log(`react-native-update: ${pushyVersion}${latestPushyVersion}`);
216
187
  if (pushyVersion) {
217
188
  if (semverSatisfies(pushyVersion, '<8.5.2')) {
218
189
  console.warn(
@@ -229,6 +200,8 @@ export async function printVersionCommand() {
229
200
  '当前版本已不再支持,请升级到 v10 的最新小版本(代码无需改动,可直接热更): npm i react-native-update@10',
230
201
  );
231
202
  }
203
+ } else {
204
+ console.log('react-native-update: 无法获取版本号,请在项目目录中运行命令');
232
205
  }
233
206
  }
234
207
 
@@ -0,0 +1,7 @@
1
+ const lockFiles = [
2
+ 'package-lock.json',
3
+ 'yarn.lock',
4
+ 'pnpm-lock.yaml',
5
+ 'bun.lockb',
6
+ 'bun.lock',
7
+ ];
package/src/versions.ts CHANGED
@@ -4,6 +4,8 @@ import { question, saveToLocal } from './utils';
4
4
  import { checkPlatform, getSelectedApp } from './app';
5
5
  import { choosePackage } from './package';
6
6
  import { compare } from 'compare-versions';
7
+ import { depVersions } from './utils/dep-versions';
8
+ import { getCommitInfo } from './utils/git';
7
9
 
8
10
  async function showVersion(appId: string, offset: number) {
9
11
  const { data, count } = await get(`/app/${appId}/version/list`);
@@ -13,11 +15,11 @@ async function showVersion(appId: string, offset: number) {
13
15
  .slice(0, 3)
14
16
  .map((v) => v.name)
15
17
  .join(', ');
16
- const count = version.packages.length;
17
- if (count > 3) {
18
- packageInfo += `...and ${count - 3} more`;
18
+ const pkgCount = version.packages.length;
19
+ if (pkgCount > 3) {
20
+ packageInfo += `...and ${pkgCount - 3} more`;
19
21
  }
20
- if (count === 0) {
22
+ if (pkgCount === 0) {
21
23
  packageInfo = 'no package';
22
24
  } else {
23
25
  packageInfo = `[${packageInfo}]`;
@@ -31,7 +33,7 @@ async function showVersion(appId: string, offset: number) {
31
33
  return data;
32
34
  }
33
35
 
34
- async function listVersions(appId) {
36
+ async function listVersions(appId: string) {
35
37
  let offset = 0;
36
38
  while (true) {
37
39
  await showVersion(appId, offset);
@@ -52,7 +54,7 @@ async function listVersions(appId) {
52
54
  }
53
55
  }
54
56
 
55
- async function chooseVersion(appId) {
57
+ async function chooseVersion(appId: string) {
56
58
  let offset = 0;
57
59
  while (true) {
58
60
  const data = await showVersion(appId, offset);
@@ -97,12 +99,15 @@ export const commands = {
97
99
 
98
100
  const { hash } = await uploadFile(fn);
99
101
 
100
- const versionName = name || (await question('输入版本名称: ')) || '(未命名)';
102
+ const versionName =
103
+ name || (await question('输入版本名称: ')) || '(未命名)';
101
104
  const { id } = await post(`/app/${appId}/version/create`, {
102
105
  name: versionName,
103
106
  hash,
104
107
  description: description || (await question('输入版本描述:')),
105
108
  metaInfo: metaInfo || (await question('输入自定义的 meta info:')),
109
+ deps: depVersions,
110
+ commit: await getCommitInfo(),
106
111
  });
107
112
  // TODO local diff
108
113
  saveToLocal(fn, `${appId}/ppk/${id}.ppk`);