esa-cli 0.0.2-beta.2 → 0.0.2-beta.21

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.
Files changed (80) hide show
  1. package/dist/commands/commit/index.js +45 -117
  2. package/dist/commands/commit/prodBuild.js +2 -3
  3. package/dist/commands/common/constant.js +0 -19
  4. package/dist/commands/common/utils.js +419 -0
  5. package/dist/commands/config.js +1 -1
  6. package/dist/commands/deploy/helper.js +51 -72
  7. package/dist/commands/deploy/index.js +48 -187
  8. package/dist/commands/deployments/delete.js +32 -22
  9. package/dist/commands/deployments/index.js +3 -3
  10. package/dist/commands/deployments/list.js +22 -38
  11. package/dist/commands/dev/build.js +3 -3
  12. package/dist/commands/dev/doProcess.js +5 -5
  13. package/dist/commands/dev/ew2/cacheService.js +33 -0
  14. package/dist/commands/dev/ew2/devEntry.js +2 -1
  15. package/dist/commands/dev/ew2/devPack.js +30 -34
  16. package/dist/commands/dev/ew2/kvService.js +46 -0
  17. package/dist/commands/dev/ew2/mock/cache.js +99 -15
  18. package/dist/commands/dev/ew2/mock/kv.js +142 -21
  19. package/dist/commands/dev/ew2/server.js +165 -28
  20. package/dist/commands/dev/index.js +16 -16
  21. package/dist/commands/dev/mockWorker/devPack.js +32 -21
  22. package/dist/commands/dev/mockWorker/server.js +7 -6
  23. package/dist/commands/domain/add.js +3 -3
  24. package/dist/commands/domain/delete.js +7 -7
  25. package/dist/commands/domain/index.js +3 -3
  26. package/dist/commands/domain/list.js +10 -10
  27. package/dist/commands/init/helper.js +761 -0
  28. package/dist/commands/init/index.js +88 -220
  29. package/dist/commands/init/snippets/nextjs/next.config.mjs +6 -0
  30. package/dist/commands/init/snippets/nextjs/next.config.ts +7 -0
  31. package/dist/commands/init/snippets/react-router/react-router.config.ts +7 -0
  32. package/dist/commands/init/template.jsonc +84 -0
  33. package/dist/commands/init/types.js +1 -0
  34. package/dist/commands/lang.js +2 -2
  35. package/dist/commands/login/index.js +74 -34
  36. package/dist/commands/logout.js +6 -6
  37. package/dist/commands/route/add.js +138 -49
  38. package/dist/commands/route/delete.js +33 -27
  39. package/dist/commands/route/helper.js +124 -0
  40. package/dist/commands/route/index.js +3 -3
  41. package/dist/commands/route/list.js +56 -17
  42. package/dist/commands/routine/delete.js +2 -2
  43. package/dist/commands/routine/index.js +3 -3
  44. package/dist/commands/routine/list.js +9 -21
  45. package/dist/commands/site/index.js +2 -2
  46. package/dist/commands/site/list.js +6 -7
  47. package/dist/commands/utils.js +55 -19
  48. package/dist/components/descriptionInput.js +1 -1
  49. package/dist/components/mutiLevelSelect.js +43 -55
  50. package/dist/components/mutiSelectTable.js +1 -1
  51. package/dist/components/selectInput.js +2 -3
  52. package/dist/components/selectItem.js +1 -1
  53. package/dist/docs/Commands_en.md +142 -131
  54. package/dist/docs/Commands_zh_CN.md +139 -127
  55. package/dist/i18n/index.js +2 -2
  56. package/dist/i18n/locales.json +401 -21
  57. package/dist/index.js +27 -20
  58. package/dist/libs/api.js +32 -9
  59. package/dist/libs/apiService.js +262 -84
  60. package/dist/libs/git/index.js +86 -9
  61. package/dist/libs/interface.js +0 -1
  62. package/dist/libs/logger.js +162 -10
  63. package/dist/libs/service.js +2 -2
  64. package/dist/libs/templates/index.js +3 -2
  65. package/dist/utils/checkAssetsExist.js +80 -0
  66. package/dist/utils/checkDevPort.js +3 -17
  67. package/dist/utils/checkEntryFileExist.js +10 -0
  68. package/dist/utils/checkIsRoutineCreated.js +27 -26
  69. package/dist/utils/checkVersion.js +119 -1
  70. package/dist/utils/command.js +149 -0
  71. package/dist/utils/compress.js +136 -0
  72. package/dist/utils/download.js +182 -0
  73. package/dist/utils/fileMd5.js +1 -1
  74. package/dist/utils/fileUtils/base.js +1 -1
  75. package/dist/utils/fileUtils/index.js +69 -28
  76. package/dist/utils/installDeno.js +8 -8
  77. package/dist/utils/installEw2.js +7 -7
  78. package/dist/utils/openInBrowser.js +1 -1
  79. package/dist/utils/prompt.js +97 -0
  80. package/package.json +20 -12
@@ -1,22 +1,23 @@
1
+ import os from 'os';
2
+ import path from 'path';
1
3
  import chalk from 'chalk';
2
- import { format, createLogger } from 'winston';
3
- import DailyRotateFile from 'winston-daily-rotate-file';
4
4
  import Table from 'cli-table3';
5
- import path from 'path';
6
- import os from 'os';
7
5
  import ora from 'ora';
6
+ import { format, createLogger } from 'winston';
7
+ import DailyRotateFile from 'winston-daily-rotate-file';
8
8
  import t from '../i18n/index.js';
9
9
  import { getProjectConfig } from '../utils/fileUtils/index.js';
10
10
  const transport = new DailyRotateFile({
11
11
  filename: path.join(os.homedir(), '.esa-logs/esa-debug-%DATE%.log'),
12
12
  level: 'info',
13
- datePattern: 'YYYY-MM-DD-HH:mm:ss',
13
+ datePattern: 'YYYY-MM-DD-HH',
14
14
  zippedArchive: true,
15
15
  maxSize: '10m',
16
16
  maxFiles: '7d'
17
17
  });
18
18
  class Logger {
19
19
  constructor() {
20
+ this.spinnerText = '';
20
21
  const { combine, timestamp, label, printf } = format;
21
22
  const customFormat = printf(({ level, message, label: printLabel, timestamp: printTimestamp }) => {
22
23
  var _a;
@@ -65,6 +66,60 @@ class Logger {
65
66
  setLogLevel(level) {
66
67
  this.logger.level = level;
67
68
  }
69
+ /**
70
+ * Start a sub-step: show a spinner with the provided message.
71
+ * If a spinner is already running, just update its text.
72
+ */
73
+ startSubStep(message) {
74
+ this.spinnerText = message;
75
+ this.spinner.text = message;
76
+ if (!this.spinner.isSpinning) {
77
+ this.spinner.start();
78
+ }
79
+ }
80
+ /**
81
+ * End a sub-step: stop loading and replace spinner with `├` and final message.
82
+ * This overwrites the previous spinner line with the provided message.
83
+ */
84
+ endSubStep(message) {
85
+ // console.log(chalk.gray('├') + ' ' + this.spinnerText);
86
+ try {
87
+ if (this.spinner && this.spinner.isSpinning) {
88
+ this.spinner.stop();
89
+ }
90
+ }
91
+ catch (_a) { }
92
+ console.log(chalk.gray(`│ `));
93
+ console.log(chalk.gray('├ ') + this.spinnerText);
94
+ console.log(chalk.gray(`│ ${message}`));
95
+ }
96
+ stopSpinner() {
97
+ try {
98
+ if (this.spinner && this.spinner.isSpinning) {
99
+ this.spinner.stop();
100
+ }
101
+ }
102
+ catch (_a) { }
103
+ }
104
+ /**
105
+ * Prepare terminal output just before showing an interactive prompt.
106
+ * - Stops any active spinner
107
+ * - Replaces the previous line with a clean `╰ <text>` indicator
108
+ */
109
+ prepareForPrompt(text) {
110
+ this.stopSpinner();
111
+ const content = `╰ ${text || ''}`;
112
+ this.replacePrevLine(content);
113
+ }
114
+ /**
115
+ * Consolidate interactive prompt output after completion by replacing
116
+ * the previous N lines with a concise summary line.
117
+ * Defaults to 2 lines (prompt + answer line in most cases).
118
+ */
119
+ consolidateAfterPrompt(summary, linesToReplace = 2) {
120
+ const content = `├ ${summary}`;
121
+ this.replacePrevLines(linesToReplace, content);
122
+ }
68
123
  log(message) {
69
124
  console.log(message);
70
125
  }
@@ -72,7 +127,7 @@ class Logger {
72
127
  console.log(`\t${message}`);
73
128
  }
74
129
  success(message) {
75
- console.log(`\n🎉 ${chalk.bgGreen(' SUCCESS ')} ${chalk.green(message)}`);
130
+ console.log(`🎉 ${chalk.bgGreen(' SUCCESS ')} ${chalk.green(message)}`);
76
131
  }
77
132
  debug(message) {
78
133
  this.logger.debug(message);
@@ -120,8 +175,47 @@ class Logger {
120
175
  console.log(message);
121
176
  }
122
177
  notInProject() {
123
- const initCommand = chalk.green('esa init');
124
- this.error(t('common_not_edge_project', { initCommand }).d(`You are not in an esa project, Please run ${initCommand} to initialize a project, or enter an esa project.`));
178
+ this.block();
179
+ this.error('Missing ESA project configuration (esa.jsonc or esa.toml)');
180
+ this.block();
181
+ this.log('If there is code to deploy, you can either:');
182
+ this.subLog(`- Specify an entry-point to your Routine via the command line (ex: ${chalk.green('esa deploy src/index.ts')})`);
183
+ this.subLog('- Or add the following to your "esa.jsonc" file:');
184
+ console.log('```jsonc\n' +
185
+ '{\n' +
186
+ ' "name": "my-routine",\n' +
187
+ ' "entry": "src/index.ts",\n' +
188
+ ' "dev": { "port": 18080 }\n' +
189
+ '}\n' +
190
+ '```');
191
+ this.subLog('- Or, if you prefer TOML, add to your "esa.toml":');
192
+ console.log('```toml\n' +
193
+ 'name = "my-routine"\n' +
194
+ 'entry = "src/index.ts"\n' +
195
+ '\n' +
196
+ '[dev]\n' +
197
+ 'port = 18080\n' +
198
+ '```\n');
199
+ this.log('If you are deploying a directory of static assets, you can either:');
200
+ this.subLog(`- Add the assets directory to your "esa.jsonc" and run ${chalk.green('esa deploy -a ./dist')}`);
201
+ console.log('```jsonc\n' +
202
+ '{\n' +
203
+ ' "name": "my-routine",\n' +
204
+ ' "assets": {\n' +
205
+ ' "directory": "./dist"\n' +
206
+ ' }\n' +
207
+ '}\n' +
208
+ '```');
209
+ this.subLog(`- Or add to your "esa.toml" and run ${chalk.green('esa deploy -a ./dist')}`);
210
+ console.log('```toml\n' +
211
+ 'name = "my-routine"\n' +
212
+ '\n' +
213
+ '[assets]\n' +
214
+ 'directory = "./dist"\n' +
215
+ '```\n');
216
+ this.log('Alternatively, initialize a new ESA project:');
217
+ this.log(chalk.green('$ esa init my-routine'));
218
+ this.block();
125
219
  }
126
220
  pathEacces(localPath) {
127
221
  this.block();
@@ -146,15 +240,73 @@ class Logger {
146
240
  if (messages.length === 0)
147
241
  return;
148
242
  const lines = [];
149
- lines.push(`╭─ ${messages[0]}`);
243
+ lines.push(`╭ ${messages[0]}`);
150
244
  for (let i = 1; i < messages.length - 1; i++) {
151
245
  lines.push(`│ ${messages[i]}`);
152
246
  }
153
247
  if (messages.length > 1) {
154
- lines.push(`╰─ ${messages[messages.length - 1]}`);
248
+ lines.push(`╰ ${messages[messages.length - 1]}`);
155
249
  }
156
250
  console.log(lines.join('\n'));
157
251
  }
252
+ StepHeader(title, step, total) {
253
+ console.log(`\n╭ ${title} ${chalk.green(`Step ${step} of ${total}`)}`);
254
+ console.log('│');
255
+ }
256
+ StepItem(prompt) {
257
+ console.log(`├ ${prompt}`);
258
+ }
259
+ StepStart(prompt) {
260
+ console.log(`╭ ${prompt}`);
261
+ }
262
+ StepKV(key, value) {
263
+ const orange = chalk.hex('#FFA500');
264
+ console.log(`│ ${orange(key)} ${value}`);
265
+ }
266
+ StepSpacer() {
267
+ console.log('│');
268
+ }
269
+ StepEnd(str) {
270
+ console.log(`╰ ${str || ''}`);
271
+ }
272
+ StepEndInline() {
273
+ try {
274
+ process.stdout.write('╰ ');
275
+ }
276
+ catch (_a) {
277
+ console.log('╰');
278
+ }
279
+ }
280
+ divider() {
281
+ console.log(chalk.yellow('--------------------------------------------------------'));
282
+ }
283
+ // Replace the previous single terminal line with new content
284
+ replacePrevLine(content) {
285
+ try {
286
+ // Move cursor up 1 line, clear it, carriage return, print new content
287
+ process.stdout.write('\x1b[1A');
288
+ process.stdout.write('\x1b[2K');
289
+ process.stdout.write('\r');
290
+ console.log(content);
291
+ }
292
+ catch (_a) {
293
+ console.log(content);
294
+ }
295
+ }
296
+ // Replace multiple previous lines with one consolidated line
297
+ replacePrevLines(linesToReplace, content) {
298
+ try {
299
+ for (let i = 0; i < linesToReplace; i++) {
300
+ process.stdout.write('\x1b[1A'); // move up
301
+ process.stdout.write('\x1b[2K'); // clear line
302
+ }
303
+ process.stdout.write('\r');
304
+ console.log(content);
305
+ }
306
+ catch (_a) {
307
+ console.log(content);
308
+ }
309
+ }
158
310
  }
159
311
  const logger = Logger.getInstance();
160
312
  export default logger;
@@ -7,10 +7,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import ora from 'ora';
11
- import chain from 'lodash';
12
10
  import FormData from 'form-data';
11
+ import chain from 'lodash';
13
12
  import fetch from 'node-fetch';
13
+ import ora from 'ora';
14
14
  import t from '../i18n/index.js';
15
15
  import api from './api.js';
16
16
  import { Environment } from './interface.js';
@@ -1,5 +1,6 @@
1
- import { getSummary } from '../../commands/common/constant.js';
2
1
  import chalk from 'chalk';
2
+ import { getSummary } from '../../commands/common/constant.js';
3
+ import logger from '../logger.js';
3
4
  export default class Template {
4
5
  constructor(path, title) {
5
6
  this.path = path;
@@ -10,7 +11,7 @@ export default class Template {
10
11
  list.forEach((summary) => {
11
12
  const title = chalk.bold(summary.title);
12
13
  const command = chalk.green(summary.command);
13
- console.log(`${title}: ${command}`);
14
+ logger.log(`${title}: ${command}`);
14
15
  });
15
16
  }
16
17
  }
@@ -0,0 +1,80 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { getProjectConfig } from './fileUtils/index.js';
4
+ /**
5
+ * Check if the assets directory exists in the project config
6
+ * @returns {boolean} true if the assets directory exists, false otherwise
7
+ */
8
+ const checkConfigAssetsExist = () => {
9
+ var _a;
10
+ const projectConfig = getProjectConfig();
11
+ if (!projectConfig) {
12
+ return false;
13
+ }
14
+ const directory = (_a = projectConfig.assets) === null || _a === void 0 ? void 0 : _a.directory;
15
+ if (!directory) {
16
+ return false;
17
+ }
18
+ return true;
19
+ };
20
+ export var EDGE_ROUTINE_TYPE;
21
+ (function (EDGE_ROUTINE_TYPE) {
22
+ EDGE_ROUTINE_TYPE["ASSETS_ONLY"] = "assets_only";
23
+ EDGE_ROUTINE_TYPE["JS_ONLY"] = "js_only";
24
+ EDGE_ROUTINE_TYPE["JS_AND_ASSETS"] = "js_and_assets";
25
+ EDGE_ROUTINE_TYPE["NOT_EXIST"] = "not_exist";
26
+ })(EDGE_ROUTINE_TYPE || (EDGE_ROUTINE_TYPE = {}));
27
+ /**
28
+ * Check if a path exists and is valid
29
+ * @param filePath - The path to check
30
+ * @param isDirectory - Whether the path should be a directory
31
+ * @returns boolean
32
+ */
33
+ const isValidPath = (filePath, isDirectory = false) => {
34
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
35
+ return false;
36
+ }
37
+ try {
38
+ const resolvedPath = path.isAbsolute(filePath)
39
+ ? filePath
40
+ : path.resolve(filePath);
41
+ const exists = fs.existsSync(resolvedPath);
42
+ if (!exists) {
43
+ return false;
44
+ }
45
+ if (isDirectory) {
46
+ return fs.statSync(resolvedPath).isDirectory();
47
+ }
48
+ else {
49
+ return fs.statSync(resolvedPath).isFile();
50
+ }
51
+ }
52
+ catch (error) {
53
+ return false;
54
+ }
55
+ };
56
+ export const checkEdgeRoutineType = (scriptEntry, assetsDirectory, projectPath) => {
57
+ var _a;
58
+ const projectConfig = getProjectConfig(projectPath);
59
+ const entry = scriptEntry || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.entry);
60
+ const assets = assetsDirectory || ((_a = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.assets) === null || _a === void 0 ? void 0 : _a.directory);
61
+ const entryPath = path.resolve(projectPath !== null && projectPath !== void 0 ? projectPath : '', entry !== null && entry !== void 0 ? entry : '');
62
+ const assetsPath = path.resolve(projectPath !== null && projectPath !== void 0 ? projectPath : '', assets !== null && assets !== void 0 ? assets : '');
63
+ const hasAssets = isValidPath(assetsPath, true) && assets;
64
+ const hasEntry = isValidPath(entryPath, false) && entry;
65
+ // Both assets and entry exist
66
+ if (hasAssets && hasEntry) {
67
+ return EDGE_ROUTINE_TYPE.JS_AND_ASSETS;
68
+ }
69
+ // Only assets exist
70
+ if (hasAssets && !hasEntry) {
71
+ return EDGE_ROUTINE_TYPE.ASSETS_ONLY;
72
+ }
73
+ // Only entry exists
74
+ if (!hasAssets && hasEntry) {
75
+ return EDGE_ROUTINE_TYPE.JS_ONLY;
76
+ }
77
+ // Neither exists
78
+ return EDGE_ROUTINE_TYPE.NOT_EXIST;
79
+ };
80
+ export default checkConfigAssetsExist;
@@ -7,12 +7,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import portscanner from 'portscanner';
11
- import SelectItems from '../components/selectInput.js';
10
+ import chalk from 'chalk';
12
11
  import inquirer from 'inquirer';
13
- import logger from '../libs/logger.js';
12
+ import portscanner from 'portscanner';
14
13
  import t from '../i18n/index.js';
15
- import chalk from 'chalk';
14
+ import logger from '../libs/logger.js';
16
15
  export const checkPort = (port) => {
17
16
  return new Promise((resolve) => {
18
17
  portscanner.checkPortStatus(port, '127.0.0.1', (error, status) => {
@@ -28,19 +27,6 @@ export const checkPort = (port) => {
28
27
  });
29
28
  });
30
29
  };
31
- const ask = () => {
32
- return new Promise((resolve) => {
33
- SelectItems({
34
- items: [
35
- { label: 'Yes', value: 'yes' },
36
- { label: 'No', value: 'no' }
37
- ],
38
- handleSelect: (item) => __awaiter(void 0, void 0, void 0, function* () {
39
- resolve(item.value === 'yes');
40
- })
41
- });
42
- });
43
- };
44
30
  const findAvailablePort = (startPort) => __awaiter(void 0, void 0, void 0, function* () {
45
31
  return yield portscanner.findAPortNotInUse(startPort, 65535);
46
32
  });
@@ -0,0 +1,10 @@
1
+ import fs from 'fs';
2
+ import { getProjectConfig } from './fileUtils/index.js';
3
+ export const checkEntryFileExist = () => {
4
+ const projectConfig = getProjectConfig();
5
+ const entry = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.entry;
6
+ if (!entry) {
7
+ return true;
8
+ }
9
+ return fs.existsSync(entry);
10
+ };
@@ -7,16 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import path from 'path';
11
- import { createEdgeRoutine } from '../commands/commit/index.js';
12
- import { displaySelectSpec } from '../commands/deploy/index.js';
13
- import { ApiService } from '../libs/apiService.js';
14
- import { readEdgeRoutineFile } from './fileUtils/index.js';
15
- import logger from '../libs/logger.js';
16
- import t from '../i18n/index.js';
17
- import prodBuild from '../commands/commit/prodBuild.js';
18
10
  import { exit } from 'process';
11
+ import { log } from '@clack/prompts';
19
12
  import chalk from 'chalk';
13
+ import t from '../i18n/index.js';
14
+ import { ApiService } from '../libs/apiService.js';
15
+ import logger from '../libs/logger.js';
20
16
  export function isRoutineExist(name) {
21
17
  return __awaiter(this, void 0, void 0, function* () {
22
18
  const server = yield ApiService.getInstance();
@@ -34,28 +30,33 @@ export function validRoutine(name) {
34
30
  }
35
31
  });
36
32
  }
37
- export function checkRoutineExist(name, entry) {
33
+ /**
34
+ * Ensure routine exists, if not, create a new routine
35
+ * @param name - Routine name
36
+ */
37
+ export function ensureRoutineExists(name) {
38
38
  return __awaiter(this, void 0, void 0, function* () {
39
- var _a, _b;
40
- const isCreatedRoutine = yield isRoutineExist(name);
41
- if (!isCreatedRoutine) {
42
- logger.log(t('first_deploy').d('This is the first time to deploy, we will create a new routine for you.'));
43
- const entryFile = path.resolve(entry !== null && entry !== void 0 ? entry : '', 'src/index.js');
44
- yield prodBuild(false, entryFile, entry);
45
- const code = readEdgeRoutineFile(entry) || '';
39
+ const isExist = yield isRoutineExist(name);
40
+ // If routine does not exist, create a new routine
41
+ if (!isExist) {
42
+ logger.startSubStep(`Creating routine ${chalk.gray(name)}`);
46
43
  const server = yield ApiService.getInstance();
47
- const specList = ((_b = (_a = (yield server.ListRoutineOptionalSpecs())) === null || _a === void 0 ? void 0 : _a.data.Specs) !== null && _b !== void 0 ? _b : []).reduce((acc, item) => {
48
- if (item.IsAvailable) {
49
- acc.push(item.SpecName);
50
- }
51
- return acc;
52
- }, []);
53
- const spec = yield displaySelectSpec(specList);
54
- yield createEdgeRoutine({
44
+ const createRes = yield server.createRoutine({
55
45
  name: name,
56
- specName: spec,
57
- code: code
46
+ description: '',
47
+ hasAssets: true
58
48
  });
49
+ const isSuccess = (createRes === null || createRes === void 0 ? void 0 : createRes.data.Status) === 'OK';
50
+ if (isSuccess) {
51
+ logger.endSubStep('Routine created successfully');
52
+ }
53
+ else {
54
+ logger.endSubStep('Routine created failed');
55
+ exit();
56
+ }
57
+ }
58
+ else {
59
+ log.step('Routine has already exists');
59
60
  }
60
61
  });
61
62
  }
@@ -8,8 +8,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { promises as fs } from 'fs';
11
- import { getDirName } from '../utils/fileUtils/base.js';
12
11
  import path from 'path';
12
+ import chalk from 'chalk';
13
+ import inquirer from 'inquirer';
14
+ import fetch from 'node-fetch';
15
+ import t from '../i18n/index.js';
16
+ import logger from '../libs/logger.js';
17
+ import execCommand from '../utils/command.js';
18
+ import { getDirName } from '../utils/fileUtils/base.js';
13
19
  export function handleCheckVersion() {
14
20
  return __awaiter(this, void 0, void 0, function* () {
15
21
  const __dirname = getDirName(import.meta.url);
@@ -24,3 +30,115 @@ export function handleCheckVersion() {
24
30
  }
25
31
  });
26
32
  }
33
+ /**
34
+ * 检查CLI是否为最新版本,如果不是则提示用户更新
35
+ * @returns 是否为最新版本
36
+ */
37
+ export function checkCLIVersion(currentCommand) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ try {
40
+ const __dirname = getDirName(import.meta.url);
41
+ const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
42
+ const jsonString = yield fs.readFile(packageJsonPath, 'utf-8');
43
+ const packageJson = JSON.parse(jsonString);
44
+ const currentVersion = packageJson.version;
45
+ const response = yield fetch('https://registry.npmjs.org/esa-cli/latest');
46
+ if (!response.ok) {
47
+ return true;
48
+ }
49
+ const data = (yield response.json());
50
+ const latestVersion = data.version;
51
+ if (currentVersion !== latestVersion) {
52
+ const accent = chalk.hex('#7C3AED').bold;
53
+ const labelColor = chalk.hex('#22c55e');
54
+ const currentLabelRaw = t('version_current').d('Current');
55
+ const latestLabelRaw = t('version_latest').d('Latest');
56
+ const noteLabelRaw = t('version_note').d('Note');
57
+ const updateLabelRaw = t('version_update').d('Update');
58
+ const labelsRaw = [
59
+ currentLabelRaw,
60
+ latestLabelRaw,
61
+ noteLabelRaw,
62
+ updateLabelRaw
63
+ ];
64
+ const labelWidth = Math.max(...labelsRaw.map((l) => l.length));
65
+ const gap = ' ';
66
+ const padLabel = (raw, colored) => `${colored}${' '.repeat(labelWidth - raw.length)}`;
67
+ const lines = [
68
+ `${accent('🚀 ' + t('version_title_update_available').d('ESA CLI Update Available'))}`,
69
+ '',
70
+ `${padLabel(currentLabelRaw, labelColor(currentLabelRaw))}${gap}${chalk.yellowBright('v' + currentVersion)}`,
71
+ `${padLabel(latestLabelRaw, labelColor(latestLabelRaw))}${gap}${chalk.greenBright('v' + latestVersion)}`,
72
+ '',
73
+ `${padLabel(noteLabelRaw, chalk.yellowBright.bold(noteLabelRaw))}${gap}${chalk.yellowBright(t('version_note_incompatible').d('This version may have incompatibilities, please upgrade soon.'))}`,
74
+ '',
75
+ `${padLabel(updateLabelRaw, labelColor(updateLabelRaw))}${gap}${chalk.cyanBright('npm i -g esa-cli@latest')}`,
76
+ `${' '.repeat(labelWidth)}${gap}${chalk.cyanBright('yarn global add esa-cli@latest')}`,
77
+ `${' '.repeat(labelWidth)}${gap}${chalk.cyanBright('pnpm add -g esa-cli@latest')}`,
78
+ '',
79
+ `${chalk.gray(t('version_continue').d('You can continue using the current version; commands will proceed.'))}`
80
+ ];
81
+ // Render with deploy-success-style box (cyan double border)
82
+ const stripAnsi = (s) => s.replace(/\x1B\[[0-?]*[ -\/]*[@-~]/g, '');
83
+ const contentWidth = Math.max(...lines.map((l) => stripAnsi(l).length));
84
+ const borderColor = chalk.hex('#00D4FF').bold;
85
+ const top = `${borderColor('╔')}${borderColor('═'.repeat(contentWidth + 2))}${borderColor('╗')}`;
86
+ const bottom = `${borderColor('╚')}${borderColor('═'.repeat(contentWidth + 2))}${borderColor('╝')}`;
87
+ const box = [
88
+ top,
89
+ ...lines.map((l) => {
90
+ const pad = ' '.repeat(contentWidth - stripAnsi(l).length);
91
+ const left = borderColor('║');
92
+ const right = borderColor('║');
93
+ return `${left} ${l}${pad} ${right}`;
94
+ }),
95
+ bottom
96
+ ];
97
+ logger.block();
98
+ box.forEach((l) => logger.log(l));
99
+ logger.block();
100
+ // Only prompt interactively on init command; others just display notice
101
+ if (currentCommand === 'init') {
102
+ const { updateNow } = yield inquirer.prompt([
103
+ {
104
+ type: 'confirm',
105
+ name: 'updateNow',
106
+ message: chalk.bold(t('version_prompt_update_now').d('Update now to the latest version?')),
107
+ default: true
108
+ }
109
+ ]);
110
+ if (updateNow) {
111
+ const startText = 'Updating ESA CLI to latest (npm i -g esa-cli@latest)';
112
+ const doneText = 'ESA CLI update finished';
113
+ try {
114
+ const res = yield execCommand(['npm', 'i', '-g', 'esa-cli@latest'], {
115
+ startText,
116
+ doneText,
117
+ useSpinner: true,
118
+ interactive: false
119
+ });
120
+ if (!res.success) {
121
+ logger.warn(t('version_update_failed').d('Global update failed. You may need elevated permissions (sudo) or use yarn/pnpm:'));
122
+ logger.subLog('sudo npm i -g esa-cli@latest');
123
+ logger.subLog('yarn global add esa-cli@latest');
124
+ logger.subLog('pnpm add -g esa-cli@latest');
125
+ }
126
+ }
127
+ catch (e) {
128
+ logger.warn(t('version_update_failed').d('Global update failed. You may need elevated permissions (sudo) or use yarn/pnpm:'));
129
+ logger.subLog('sudo npm i -g esa-cli@latest');
130
+ logger.subLog('yarn global add esa-cli@latest');
131
+ logger.subLog('pnpm add -g esa-cli@latest');
132
+ }
133
+ logger.divider();
134
+ }
135
+ }
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+ catch (error) {
141
+ return true;
142
+ }
143
+ });
144
+ }