app.mjs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /**
  2. * 更新发布工具
  3. *
  4. * Copyright © 2025 imengyu.top imengyu-update-server
  5. */
  6. import { program } from 'commander';
  7. import { input, confirm, select } from '@inquirer/prompts';
  8. import { postAppUpdate, postWebUpdate } from './postUpdate.mjs';
  9. import Table from 'cli-table3';
  10. import { config } from './postConfig.mjs';
  11. import {
  12. axiosInstance,
  13. initAuth,
  14. login,
  15. logout,
  16. checkLogged,
  17. } from './auth.mjs';
  18. import consola from 'consola';
  19. import { logError } from './utils.mjs';
  20. // 版本相关
  21. //========================================
  22. export async function viewVersion(type) {
  23. if (type === 'all' || !type) {
  24. const data = await axiosInstance.get('/version/list');
  25. const table = new Table({
  26. head: ['ID', '版本'],
  27. colWidths: [10, 20 ]
  28. });
  29. data.data.forEach((d) => {
  30. table.push([ d.id, d.version ]);
  31. })
  32. console.log(table.toString());
  33. } else {
  34. let data = null;
  35. try {
  36. if (Number.isNaN(Number(type)))
  37. data = await axiosInstance.get('/version/get-by-name?name=' + type);
  38. else
  39. data = await axiosInstance.get('/version/' + type);
  40. }
  41. catch (e) {
  42. <<<<<<< HEAD
  43. logError('Failed to load version info', e);
  44. =======
  45. console.error('Failed to load version info', e);
  46. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  47. return;
  48. }
  49. const table = new Table({
  50. head: ['key', 'data'],
  51. colWidths: [30, 60]
  52. });
  53. table.push(
  54. [ 'ID', data.data.id ],
  55. [ '状态', stateConstant[data.data.status] ],
  56. [ '版本号', data.data.version ],
  57. [ '创建时间', new Date(data.data.createAt).toString() ],
  58. [ '设置', data.data.config ],
  59. [ '激活的Web更新ID', data.data.webUpdateId ],
  60. [ '激活的App更新ID', data.data.appUpdateId ],
  61. [ '激活的下一个App更新ID', data.data.appUpdateNextId ],
  62. );
  63. console.log(table.toString());
  64. }
  65. }
  66. export async function getVersion(action, type) {
  67. switch(action) {
  68. case 'view':
  69. await viewVersion(type);
  70. break;
  71. case 'new': {
  72. const version = await input({ message: 'Enter version name, like (1.0.0)' });
  73. try {
  74. await axiosInstance.post('/version', {
  75. version: version,
  76. status: 1,
  77. config: "{}",
  78. });
  79. <<<<<<< HEAD
  80. consola.success('Add version success');
  81. } catch (e) {
  82. logError('Failed to add version', e);
  83. =======
  84. console.log('Add version success');
  85. } catch (e) {
  86. console.error('Failed to add version', e);
  87. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  88. }
  89. break;
  90. }
  91. case 'delete': {
  92. const versionId = type ? type : await input({ message: '输入版本ID' });
  93. if (!await confirm({ message: `确定删除版本 ${versionId}?`, default: false }))
  94. return;
  95. if (!await confirm({ message: '确认删除版本?此操作会删除所属版本的所有更新项目、存储等,无法恢复,是否确定删除?', default: false }))
  96. return;
  97. try {
  98. await axiosInstance.delete('/version/' + versionId);
  99. <<<<<<< HEAD
  100. consola.success('删除版本成功');
  101. } catch (e) {
  102. logError('删除版本失败', e);
  103. =======
  104. console.log('删除版本成功');
  105. } catch (e) {
  106. console.error('删除版本失败', e);
  107. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  108. }
  109. break;
  110. }
  111. case 'set-state': {
  112. const versionId = type ? type : await input({ message: '输入版本ID' });
  113. const state = await select({
  114. message: '设置状态',
  115. choices: [
  116. {
  117. name: 'NotEnable',
  118. value: 0,
  119. },
  120. {
  121. name: 'Normal',
  122. value: 1,
  123. },
  124. {
  125. name: 'Deprecated',
  126. value: 2,
  127. },
  128. ],
  129. });
  130. try {
  131. await axiosInstance.put('/version/' + versionId, {
  132. status: state
  133. });
  134. <<<<<<< HEAD
  135. consola.success('设置状态成功');
  136. } catch (e) {
  137. logError('设置状态失败', e);
  138. =======
  139. console.log('设置状态成功');
  140. } catch (e) {
  141. console.error('设置状态失败', e);
  142. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  143. }
  144. break;
  145. }
  146. case 'set-config': {
  147. const versionId = type ? type : await input({ message: '输入版本ID' });
  148. const config = await input({ message: '输入配置Json' });
  149. try {
  150. await axiosInstance.put('/version/' + versionId, {
  151. config: config,
  152. });
  153. <<<<<<< HEAD
  154. consola.success('设置配置成功');
  155. } catch (e) {
  156. logError('设置配置失败', e);
  157. =======
  158. console.log('设置配置成功');
  159. } catch (e) {
  160. console.error('设置配置失败', e);
  161. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  162. }
  163. break;
  164. }
  165. case 'set-active-app-update': {
  166. const versionId = await input({ message: '输入版本ID' });
  167. const updateId = await input({ message: '输入更新ID' });
  168. const isNext = await confirm({ message: 'Set as next active?', default: false });
  169. try {
  170. await axiosInstance.post('/update/active/app', { versionId, updateId, isNext });
  171. <<<<<<< HEAD
  172. consola.success('成功');
  173. } catch (e) {
  174. logError('失败', e);
  175. =======
  176. console.log('成功');
  177. } catch (e) {
  178. console.error('失败', e);
  179. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  180. }
  181. break;
  182. }
  183. case 'set-active-web-update': {
  184. const versionId = await input({ message: '输入版本ID' });
  185. const updateId = await input({ message: '输入更新ID' });
  186. try {
  187. await axiosInstance.post('/update/active/web', { versionId, updateId });
  188. <<<<<<< HEAD
  189. consola.success('成功');
  190. } catch (e) {
  191. logError('失败', e);
  192. =======
  193. console.log('成功');
  194. } catch (e) {
  195. console.error('失败', e);
  196. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  197. }
  198. break;
  199. }
  200. default:
  201. <<<<<<< HEAD
  202. logError('未知参数', action);
  203. =======
  204. console.error('未知参数', action);
  205. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  206. break;
  207. }
  208. }
  209. //选择方法
  210. //========================================
  211. export async function selectVersion(defaultVersionId = null) {
  212. const data = (await axiosInstance.get('/version/list?full=true&search=' + JSON.stringify({
  213. appId: config.appId
  214. }))).data;
  215. if (data.length === 0) {
  216. <<<<<<< HEAD
  217. logError('没有版本,请在管理后台创建 appId ' + config.appId + ' 的版本');
  218. =======
  219. console.error('没有版本');
  220. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  221. return;
  222. }
  223. const versionId = (await select({
  224. choices: data.map(p => ({
  225. value: p.id,
  226. name: p.version,
  227. })),
  228. default: defaultVersionId,
  229. message: '选择一个版本',
  230. }));
  231. const versionName = data.find(p => p.id === versionId).version;
  232. return { versionId, versionName };
  233. }
  234. export async function selectUpdate() {
  235. const { versionId } = await selectVersion();
  236. const data = (await axiosInstance.get('/version/update?search=' + JSON.stringify({ versionId }))).data;
  237. if (data.length === 0) {
  238. <<<<<<< HEAD
  239. logError('没有更新');
  240. =======
  241. console.error('没有更新');
  242. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  243. return;
  244. }
  245. const resultId = (await select({
  246. choices: data.map(p => ({
  247. value: p.id,
  248. name: p.version,
  249. })),
  250. message: '选择一个更新',
  251. }));
  252. return resultId;
  253. }
  254. //更新相关
  255. //========================================
  256. const stateConstant = [ 'Deleted', 'Normal', 'Deprecated' ];
  257. const typeConstant = [ 'Unknow', 'Web', 'app' ];
  258. const storageTypeConstant = [ 'Unknow', 'LocalStorage', 'AliOSS' ];
  259. export async function postUpdate(type, options) {
  260. switch (type) {
  261. case 'web': {
  262. await postWebUpdate(axiosInstance, options);
  263. break;
  264. }
  265. case 'app': {
  266. await postAppUpdate(axiosInstance, options);
  267. break;
  268. }
  269. default:
  270. <<<<<<< HEAD
  271. logError('Unknow type', type);
  272. =======
  273. console.error('Unknow type', type);
  274. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  275. break;
  276. }
  277. }
  278. export async function deprecateOrDeleteUpdate(updateId) {
  279. if (!updateId)
  280. updateId = await selectUpdate();
  281. const { currentUpdateInfo, currentVersionInfo } = await viewUpdate(updateId);
  282. const deprecate = (await select({
  283. choices: [
  284. {
  285. name: 'Deprecate',
  286. value: 0,
  287. },
  288. {
  289. name: 'Delete',
  290. value: 1,
  291. },
  292. ],
  293. message: '删除或弃用?',
  294. })) === 0;
  295. if (deprecate && currentUpdateInfo.type !== 1) {
  296. <<<<<<< HEAD
  297. consola.warn('只有Web更新允许弃用');
  298. return;
  299. }
  300. if (currentUpdateInfo.status === 0) {
  301. consola.warn(`当前状态 ${stateConstant[currentUpdateInfo.status]} 无法弃用`);
  302. =======
  303. console.log('只有Web更新允许弃用');
  304. return;
  305. }
  306. if (currentUpdateInfo.status === 0) {
  307. console.log(`当前状态 ${stateConstant[currentUpdateInfo.status]} 无法弃用`);
  308. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  309. return;
  310. }
  311. if (deprecate) {
  312. if (!await confirm({ message: `确定弃用当前版本 ${currentUpdateInfo.versionCode} ?弃用会删除存储文件以及备份。此操作无法恢复!`, default: false }))
  313. return;
  314. } else {
  315. if (!await confirm({ message: `确定删除当前版本 ${currentUpdateInfo.versionCode} ?此操作无法恢复!`, default: false }))
  316. return;
  317. }
  318. try {
  319. await axiosInstance.post(`/update/${deprecate ? 'deprecate' : 'delete'}`, { updateId });
  320. <<<<<<< HEAD
  321. consola.success(`${deprecate ? '弃用' : '删除'} 成功`);
  322. } catch (e) {
  323. logError(`操作失败`, e);
  324. =======
  325. console.log(`${deprecate ? '弃用' : '删除'} 成功`);
  326. } catch (e) {
  327. console.error(`操作失败`, e);
  328. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  329. }
  330. }
  331. export async function viewUpdate(updateId) {
  332. let currentUpdateInfo = null
  333. let currentVersionInfo = null
  334. try {
  335. currentUpdateInfo = (await axiosInstance.get('/update/' + updateId)).data;
  336. } catch (e) {
  337. <<<<<<< HEAD
  338. logError('加载更新信息失败', updateId);
  339. =======
  340. console.error('加载更新信息失败', updateId);
  341. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  342. }
  343. try {
  344. currentVersionInfo = (await axiosInstance.get('/version/' + currentUpdateInfo.versionId)).data;
  345. } catch (e) {
  346. <<<<<<< HEAD
  347. logError('加载版本信息失败', currentUpdateInfo.versionId);
  348. =======
  349. console.error('加载版本信息失败', currentUpdateInfo.versionId);
  350. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  351. }
  352. const table = new Table({
  353. head: ['key', 'data'],
  354. colWidths: [20, 40]
  355. });
  356. table.push(
  357. [ 'ID', currentUpdateInfo.id ],
  358. [ '所属应用', currentUpdateInfo.name ],
  359. [ '更新信息', currentUpdateInfo.updateInfo ],
  360. [ '版本号', currentUpdateInfo.versionCode ],
  361. [ '创建时间', new Date(currentUpdateInfo.createAt).toString() ],
  362. [ '类型', typeConstant[currentUpdateInfo.type] ],
  363. [ '状态', stateConstant[currentUpdateInfo.status] ],
  364. [ '强制更新', currentUpdateInfo.force ],
  365. [ '公共访问路径', currentUpdateInfo.publicUrl ],
  366. [ '存储类型', storageTypeConstant[currentUpdateInfo.storageType] ],
  367. [ '存储路径', currentUpdateInfo.storagePath ],
  368. );
  369. console.log(table.toString());
  370. if (currentUpdateInfo.activeWebVersionName) {
  371. table.push(
  372. [ '使用中的Web版本', currentUpdateInfo.activeWebVersionName ],
  373. );
  374. }
  375. if (currentUpdateInfo.activeAppVersionName) {
  376. table.push(
  377. [ '使用中的App版本', currentUpdateInfo.activeAppVersionName ],
  378. );
  379. }
  380. return {
  381. currentUpdateInfo,
  382. currentVersionInfo,
  383. }
  384. }
  385. export async function getUpdate(action, type, all, options) {
  386. const typeNotANumber = isNaN(new Number(type));
  387. if (action === 'view' && (!type || typeNotANumber)) {
  388. let hasSerch = false;
  389. const search = {};
  390. const sort = {
  391. field: "createAt",
  392. order: "descend"
  393. }
  394. if (typeNotANumber && type !== 'all') {
  395. hasSerch = true;
  396. search.version = type;
  397. }
  398. const data = await axiosInstance.get('/update/list?full=true' + (hasSerch ? ('&search=' + JSON.stringify(search)) : '') + '&sort=' + JSON.stringify(sort));
  399. const table = new Table({
  400. head: ['ID', '版本', '版本号', '类型', '状态'],
  401. colWidths: [10, 10, 20, 10, 15 ]
  402. });
  403. if (type !== 'all' && all !== 'all' && data.data.length > 10) {
  404. data.data = data.data.slice(0, 10);
  405. <<<<<<< HEAD
  406. consola.info('filter!', all);
  407. =======
  408. console.log('filter!', all);
  409. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  410. }
  411. data.data.forEach((d) => {
  412. table.push([
  413. d.id,
  414. (d.activeAppVersionName ? `${d.activeAppVersionName} (App)` : (
  415. d.activeWebVersionName? `${d.activeWebVersionName} (Web)` : '无'
  416. )),
  417. d.versionCode, typeConstant[d.type], stateConstant[d.status]
  418. ]);
  419. })
  420. console.log(table.toString());
  421. } else {
  422. switch (action) {
  423. case 'post': {
  424. await postUpdate(type, options);
  425. break;
  426. }
  427. case 'delete': {
  428. await deprecateOrDeleteUpdate(type);
  429. break;
  430. }
  431. case 'view': {
  432. viewUpdate(type);
  433. break;
  434. }
  435. default:
  436. <<<<<<< HEAD
  437. logError('Unknow action', action);
  438. =======
  439. console.error('Unknow action', action);
  440. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  441. break;
  442. }
  443. }
  444. }
  445. export async function testVersion(version) {
  446. try {
  447. const data = await axiosInstance.get('/update-get-info?version=' + version);
  448. <<<<<<< HEAD
  449. consola.info('版本信息', data.data);
  450. } catch(e) {
  451. logError('获取失败', e);
  452. =======
  453. console.log('版本信息', data.data);
  454. } catch(e) {
  455. console.log('获取失败', e);
  456. >>>>>>> 7c11b917b224c1bfd3d86d6cc15ea83fede0098f
  457. }
  458. }