Skip to content

Commit 75f8fdb

Browse files
committed
add deps warning
1 parent 3a754fb commit 75f8fdb

File tree

5 files changed

+296
-3
lines changed

5 files changed

+296
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-update-cli",
3-
"version": "2.7.3",
3+
"version": "2.8.5",
44
"description": "command line tool for react-native-update (remote updates for react native)",
55
"main": "index.js",
66
"bin": {

src/locales/en.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
9696
packageIdRequired: 'Please provide packageId or packageVersion parameter',
9797
packageUploadSuccess:
9898
'Successfully uploaded new hot update package (id: {{id}})',
99+
depsChangeSummary:
100+
'Dependency changes: added {{added}}, removed {{removed}}, changed {{changed}}.',
101+
depsChangeTargetPackage:
102+
'Target native package: {{packageName}} (id: {{packageId}})',
103+
depsChangeDependencyHeader: 'Dependency',
104+
depsChangeVersionHeader: 'Version change',
105+
depsChangeAddedLabel: 'Added',
106+
depsChangeRemovedLabel: 'Removed',
107+
depsChangeChangedLabel: 'Changed',
108+
depsChangeArrow: '->',
109+
depsChangeRiskWarning:
110+
'Warning: if changed dependencies are pure JS modules, impact is usually low; if native code is newly introduced or changed, OTA update may cause abnormal behavior or even crashes. Test thoroughly before production release.',
111+
depsChangeFetchFailed: 'Failed to fetch OTA dependency info: {{error}}',
112+
depsChangeNonBlockingHint:
113+
'This is only a dependency change warning and will not block publishing.',
99114
packing: 'Packing',
100115
pausedStatus: '(Paused)',
101116
platform: 'Platform',

src/locales/zh.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ export default {
8989
operationSuccess: '操作成功',
9090
packageIdRequired: '请提供 packageId 或 packageVersion 参数',
9191
packageUploadSuccess: '已成功上传新热更包(id: {{id}})',
92+
depsChangeSummary:
93+
'依赖变化:新增 {{added}} 项,移除 {{removed}} 项,版本变更 {{changed}} 项。',
94+
depsChangeTargetPackage: '目标原生包:{{packageName}} (id: {{packageId}})',
95+
depsChangeDependencyHeader: '依赖',
96+
depsChangeVersionHeader: '版本变化',
97+
depsChangeAddedLabel: '新增',
98+
depsChangeRemovedLabel: '移除',
99+
depsChangeChangedLabel: '变更',
100+
depsChangeArrow: '->',
101+
depsChangeRiskWarning:
102+
'警告:如果变更依赖是纯 JS 模块,则一般没有影响;若包含原生代码新增或变化,热更可能导致功能不正常甚至闪退,建议发布前使用扫码功能完整测试。',
103+
depsChangeFetchFailed: '获取热更依赖信息失败:{{error}}',
104+
depsChangeNonBlockingHint: '以上仅为依赖变化提示,不会阻止本次发布。',
92105
packing: '正在打包',
93106
pausedStatus: '(已暂停)',
94107
platform: '平台',

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ export interface Package {
1818
appKey?: string;
1919
versionName?: any;
2020
buildTime?: any;
21+
deps?: Record<string, string> | string | null;
2122
}
2223

2324
export interface Version {
2425
id: string;
2526
hash: string;
2627
name: string;
2728
packages?: Package[];
29+
deps?: Record<string, string> | string | null;
2830
}
2931

3032
export interface CommandContext {

src/versions.ts

Lines changed: 265 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { get, getAllPackages, post, put, uploadFile, doDelete } from './api';
1+
import { doDelete, get, getAllPackages, post, put, uploadFile } from './api';
22
import { question, saveToLocal } from './utils';
33
import { t } from './utils/i18n';
44

55
import chalk from 'chalk';
66
import { satisfies } from 'compare-versions';
7+
import Table from 'tty-table';
78
import { getPlatform, getSelectedApp } from './app';
89
import { choosePackage } from './package';
910
import type { Package, Platform, Version } from './types';
@@ -24,6 +25,258 @@ interface VersionCommandOptions {
2425
packageVersionRange?: string;
2526
rollout?: string;
2627
dryRun?: boolean;
28+
versionDeps?: Record<string, string>;
29+
}
30+
31+
type Deps = Record<string, string>;
32+
type DepChangeType = 'added' | 'removed' | 'changed';
33+
34+
interface DepChange {
35+
dependency: string;
36+
oldVersion: string;
37+
newVersion: string;
38+
type: DepChangeType;
39+
}
40+
41+
interface DepsChangeSummary {
42+
added: number;
43+
removed: number;
44+
changed: number;
45+
}
46+
47+
function normalizeDeps(input: unknown): Deps | undefined {
48+
if (!input) {
49+
return undefined;
50+
}
51+
52+
let raw: unknown = input;
53+
if (typeof input === 'string') {
54+
try {
55+
raw = JSON.parse(input);
56+
} catch (_e) {
57+
return undefined;
58+
}
59+
}
60+
61+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
62+
return undefined;
63+
}
64+
65+
const deps: Deps = {};
66+
for (const [name, version] of Object.entries(
67+
raw as Record<string, unknown>,
68+
)) {
69+
if (typeof version === 'string' && version) {
70+
deps[name] = version;
71+
}
72+
}
73+
74+
return Object.keys(deps).length > 0 ? deps : undefined;
75+
}
76+
77+
function getDepsChanges(oldDeps?: Deps, newDeps?: Deps): DepChange[] {
78+
if (!oldDeps || !newDeps) {
79+
return [];
80+
}
81+
82+
const rows: DepChange[] = [];
83+
const keys = Object.keys({ ...oldDeps, ...newDeps }).sort((a, b) =>
84+
a.localeCompare(b),
85+
);
86+
87+
for (const key of keys) {
88+
const oldVersion = oldDeps[key];
89+
const newVersion = newDeps[key];
90+
91+
if (oldVersion === undefined && newVersion !== undefined) {
92+
rows.push({
93+
dependency: key,
94+
oldVersion: '-',
95+
newVersion,
96+
type: 'added',
97+
});
98+
continue;
99+
}
100+
101+
if (oldVersion !== undefined && newVersion === undefined) {
102+
rows.push({
103+
dependency: key,
104+
oldVersion,
105+
newVersion: '-',
106+
type: 'removed',
107+
});
108+
continue;
109+
}
110+
111+
if (
112+
oldVersion !== undefined &&
113+
newVersion !== undefined &&
114+
oldVersion !== newVersion
115+
) {
116+
rows.push({
117+
dependency: key,
118+
oldVersion,
119+
newVersion,
120+
type: 'changed',
121+
});
122+
}
123+
}
124+
125+
return rows;
126+
}
127+
128+
function getDepsChangeSummary(changes: DepChange[]): DepsChangeSummary {
129+
return changes.reduce(
130+
(acc, item) => {
131+
if (item.type === 'added') {
132+
acc.added += 1;
133+
} else if (item.type === 'removed') {
134+
acc.removed += 1;
135+
} else {
136+
acc.changed += 1;
137+
}
138+
return acc;
139+
},
140+
{ added: 0, removed: 0, changed: 0 },
141+
);
142+
}
143+
144+
function renderVersionChange(change: DepChange) {
145+
const arrow = chalk.gray(` ${t('depsChangeArrow')} `);
146+
147+
if (change.type === 'added') {
148+
return `${chalk.red(t('depsChangeAddedLabel'))} | ${chalk.gray(
149+
change.oldVersion,
150+
)}${arrow}${chalk.red(change.newVersion)}`;
151+
}
152+
153+
if (change.type === 'removed') {
154+
return `${chalk.green(t('depsChangeRemovedLabel'))} | ${chalk.green(
155+
change.oldVersion,
156+
)}${arrow}${chalk.gray(change.newVersion)}`;
157+
}
158+
159+
return `${chalk.yellow(t('depsChangeChangedLabel'))} | ${chalk.yellow(
160+
change.oldVersion,
161+
)}${arrow}${chalk.yellow(change.newVersion)}`;
162+
}
163+
164+
function printDepsChangesForPackage({
165+
pkg,
166+
versionDeps,
167+
}: {
168+
pkg: Package;
169+
versionDeps: Deps;
170+
}) {
171+
const pkgDeps = normalizeDeps(pkg.deps);
172+
if (!pkgDeps) {
173+
return false;
174+
}
175+
176+
const changes = getDepsChanges(pkgDeps, versionDeps);
177+
if (changes.length === 0) {
178+
return false;
179+
}
180+
181+
const summary = getDepsChangeSummary(changes);
182+
const summaryText = t('depsChangeSummary', {
183+
added: chalk.red(String(summary.added)),
184+
removed: chalk.green(String(summary.removed)),
185+
changed: chalk.yellow(String(summary.changed)),
186+
});
187+
const header = [
188+
{ value: t('depsChangeDependencyHeader') },
189+
{ value: t('depsChangeVersionHeader') },
190+
];
191+
const rows = changes.map((change) => [
192+
change.dependency,
193+
renderVersionChange(change),
194+
]);
195+
196+
console.log('');
197+
console.log(
198+
chalk.yellow(
199+
t('depsChangeTargetPackage', {
200+
packageName: pkg.name,
201+
packageId: pkg.id,
202+
}),
203+
),
204+
);
205+
console.log(summaryText);
206+
console.log(Table(header, rows).render());
207+
console.log(chalk.yellow(t('depsChangeRiskWarning')));
208+
return true;
209+
}
210+
211+
async function findVersionDeps(appId: string, versionId: string) {
212+
const targetId = String(versionId);
213+
const limit = 100;
214+
let offset = 0;
215+
216+
while (true) {
217+
const { data, count } = await get(
218+
`/app/${appId}/version/list?offset=${offset}&limit=${limit}`,
219+
);
220+
const versions: Version[] = Array.isArray(data) ? data : [];
221+
const version = versions.find((item) => String(item.id) === targetId);
222+
223+
if (version) {
224+
return normalizeDeps(version.deps);
225+
}
226+
227+
offset += versions.length;
228+
if (versions.length === 0 || offset >= Number(count || 0)) {
229+
break;
230+
}
231+
}
232+
233+
return undefined;
234+
}
235+
236+
async function printDepsChangesForPublish({
237+
appId,
238+
versionId,
239+
pkgs,
240+
providedVersionDeps,
241+
}: {
242+
appId: string;
243+
versionId?: string;
244+
pkgs: Package[];
245+
providedVersionDeps?: Deps;
246+
}) {
247+
if (!versionId || pkgs.length === 0) {
248+
return;
249+
}
250+
251+
let versionDeps = normalizeDeps(providedVersionDeps);
252+
if (!versionDeps) {
253+
try {
254+
versionDeps = await findVersionDeps(appId, versionId);
255+
} catch (error: any) {
256+
console.warn(
257+
chalk.yellow(
258+
t('depsChangeFetchFailed', {
259+
error: error?.message || String(error),
260+
}),
261+
),
262+
);
263+
return;
264+
}
265+
}
266+
267+
if (!versionDeps) {
268+
return;
269+
}
270+
271+
let hasChanges = false;
272+
for (const pkg of pkgs) {
273+
const printed = printDepsChangesForPackage({ pkg, versionDeps });
274+
hasChanges = hasChanges || printed;
275+
}
276+
277+
if (hasChanges) {
278+
console.log(chalk.yellow(t('depsChangeNonBlockingHint')));
279+
}
27280
}
28281

29282
async function showVersion(appId: string, offset: number) {
@@ -211,12 +464,15 @@ export const versionCommands = {
211464
maxPackageVersion,
212465
rollout,
213466
dryRun,
467+
versionDeps: depVersions,
214468
},
215469
});
216470
} else {
217471
const q = await question(t('updateNativePackageQuestion'));
218472
if (q.toLowerCase() === 'y') {
219-
await this.update({ options: { versionId: id, platform } });
473+
await this.update({
474+
options: { versionId: id, platform, versionDeps: depVersions },
475+
});
220476
}
221477
}
222478
return versionName;
@@ -318,6 +574,13 @@ export const versionCommands = {
318574
}
319575
}
320576

577+
await printDepsChangesForPublish({
578+
appId,
579+
versionId,
580+
pkgs: pkgsToBind,
581+
providedVersionDeps: options.versionDeps,
582+
});
583+
321584
await bindVersionToPackages({
322585
appId,
323586
versionId,

0 commit comments

Comments
 (0)