app.mjs 11 KB

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