esa-cli 0.0.2-beta.15 → 0.0.2-beta.18

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.
@@ -18,37 +18,55 @@ const list = {
18
18
  command: 'list',
19
19
  describe: `📋 ${t('list_describe').d('List all your routines')}`,
20
20
  builder: (yargs) => {
21
- return yargs.usage(`${t('common_usage').d('Usage')}: \$0 list []`);
21
+ return yargs
22
+ .option('keyword', {
23
+ alias: 'k',
24
+ describe: t('deploy_option_keyword').d('Keyword to search for routines'),
25
+ type: 'string'
26
+ })
27
+ .usage(`${t('common_usage').d('Usage')}: \$0 list [--keyword <keyword>]`);
22
28
  },
23
29
  handler: (argv) => __awaiter(void 0, void 0, void 0, function* () {
24
30
  handleList(argv);
25
31
  })
26
32
  };
27
33
  export default list;
34
+ export function getAllRoutines(options) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ var _a;
37
+ const server = yield ApiService.getInstance();
38
+ const allRoutines = [];
39
+ let pageNumber = 1;
40
+ const pageSize = (options === null || options === void 0 ? void 0 : options.PageSize) || 50;
41
+ while (true) {
42
+ const res = yield server.listUserRoutines({
43
+ RegionId: options === null || options === void 0 ? void 0 : options.RegionId,
44
+ PageNumber: pageNumber,
45
+ PageSize: pageSize,
46
+ SearchKeyWord: options === null || options === void 0 ? void 0 : options.SearchKeyWord
47
+ });
48
+ if (!((_a = res === null || res === void 0 ? void 0 : res.body) === null || _a === void 0 ? void 0 : _a.Routines)) {
49
+ break;
50
+ }
51
+ allRoutines.push(...res.body.Routines);
52
+ const totalCount = res.body.TotalCount;
53
+ const currentCount = allRoutines.length;
54
+ if (currentCount >= totalCount) {
55
+ break;
56
+ }
57
+ pageNumber++;
58
+ }
59
+ return allRoutines;
60
+ });
61
+ }
28
62
  export function handleList(argv) {
29
63
  return __awaiter(this, void 0, void 0, function* () {
30
- var _a, _b;
31
- const { site } = argv;
32
64
  const isSuccess = yield checkIsLoginSuccess();
33
65
  if (!isSuccess)
34
66
  return;
35
- const server = yield ApiService.getInstance();
36
- if (site) {
37
- const req = {
38
- SiteSearchType: 'fuzzy',
39
- Status: 'active',
40
- PageNumber: 1,
41
- PageSize: 50
42
- };
43
- const res = yield server.listSites(req);
44
- const siteList = (_a = res === null || res === void 0 ? void 0 : res.data.Sites) !== null && _a !== void 0 ? _a : [];
45
- const siteNameList = siteList === null || siteList === void 0 ? void 0 : siteList.map((item) => item.SiteName);
46
- logger.log(chalk.bold.bgGray(`📃 ${t('list_site_name_title').d('List all of site names')}:`));
47
- logger.tree(siteNameList);
48
- return;
49
- }
50
- const res = yield server.listUserRoutines();
51
- const routineList = (_b = res === null || res === void 0 ? void 0 : res.body) === null || _b === void 0 ? void 0 : _b.Routines;
67
+ const routineList = yield getAllRoutines({
68
+ SearchKeyWord: argv.keyword
69
+ });
52
70
  if (routineList) {
53
71
  logger.log(chalk.bold.bgGray(`📃 ${t('list_routine_name_title').d('List all of routine')}:`));
54
72
  displayRoutineList(routineList);
@@ -26,7 +26,8 @@ export default function multiLevelSelect(items_1) {
26
26
  options: [
27
27
  ...currentItems.map((item) => ({
28
28
  label: item.label,
29
- value: item.value
29
+ value: item.value,
30
+ hint: item.hint
30
31
  })),
31
32
  ...(stack.length > 0 ? [{ label: 'Back', value: '__back__' }] : [])
32
33
  ]
@@ -0,0 +1,68 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Box, render, Text } from 'ink';
11
+ import TextInput from 'ink-text-input';
12
+ import React, { useState } from 'react';
13
+ import t from '../i18n/index.js';
14
+ export const RouteBuilder = ({ siteName, onSubmit, onCancel }) => {
15
+ const [prefix, setPrefix] = useState('');
16
+ const [suffix, setSuffix] = useState('');
17
+ const [currentInput, setCurrentInput] = useState('prefix');
18
+ const [error, setError] = useState('');
19
+ const handleSubmit = () => {
20
+ if (currentInput === 'prefix') {
21
+ setCurrentInput('suffix');
22
+ return;
23
+ }
24
+ // Build complete route, add dot before prefix and slash before suffix if not empty
25
+ const prefixWithDot = prefix ? `${prefix}.` : '';
26
+ const suffixWithDot = suffix ? `/${suffix}` : '';
27
+ const route = `${prefixWithDot}${siteName}${suffixWithDot}`;
28
+ onSubmit(route);
29
+ };
30
+ const handleCancel = () => {
31
+ onCancel();
32
+ };
33
+ const currentPrompt = currentInput === 'prefix'
34
+ ? t('route_builder_prefix_prompt')
35
+ .d(`Enter route prefix for ${siteName} (e.g., abc, def):`)
36
+ .replace('${siteName}', siteName)
37
+ : t('route_builder_suffix_prompt')
38
+ .d(`Enter route suffix for ${siteName} (e.g., *, users/*):`)
39
+ .replace('${siteName}', siteName);
40
+ const prefixWithDot = prefix ? `${prefix}.` : '';
41
+ const suffixWithDot = suffix ? `/${suffix}` : '';
42
+ const preview = `Preview: ${prefixWithDot}${siteName}${suffixWithDot}`;
43
+ return (React.createElement(Box, { flexDirection: "column" },
44
+ React.createElement(Box, null,
45
+ React.createElement(Text, null, "Building route for site: "),
46
+ React.createElement(Text, { color: "cyan" }, siteName)),
47
+ React.createElement(Box, { marginTop: 1 },
48
+ React.createElement(Text, null, currentPrompt)),
49
+ React.createElement(Box, { marginTop: 1 },
50
+ React.createElement(TextInput, { value: currentInput === 'prefix' ? prefix : suffix, onChange: currentInput === 'prefix' ? setPrefix : setSuffix, onSubmit: handleSubmit })),
51
+ preview && (React.createElement(Box, { marginTop: 1 },
52
+ React.createElement(Text, { color: "green" }, preview))),
53
+ React.createElement(Box, { marginTop: 1 },
54
+ React.createElement(Text, { color: "gray" }, t('route_builder_instructions').d('Press Enter to continue, Ctrl+C to cancel'))),
55
+ error && (React.createElement(Box, { marginTop: 1 },
56
+ React.createElement(Text, { color: "red" }, error)))));
57
+ };
58
+ export const routeBuilder = (siteName) => __awaiter(void 0, void 0, void 0, function* () {
59
+ return new Promise((resolve) => {
60
+ const { unmount } = render(React.createElement(RouteBuilder, { siteName: siteName, onSubmit: (route) => {
61
+ unmount();
62
+ resolve(route);
63
+ }, onCancel: () => {
64
+ unmount();
65
+ resolve(null);
66
+ } }));
67
+ });
68
+ });
@@ -72,7 +72,7 @@ esa deploy [entry]
72
72
  - -v, --version string: Version to deploy (skip interactive selection)
73
73
  - -e, --environment string: Environment to deploy to. Choices: staging | production
74
74
  - -n, --name string: Name of the routine
75
- - -a, --assets boolean: Deploy assets
75
+ - -a, --assets string: Assets directory (e.g., ./dist)
76
76
  - -d, --description string: Description of the version
77
77
  - -m, --minify boolean: Minify the code
78
78
 
@@ -153,6 +153,39 @@ esa route delete <routeName>
153
153
  - list: List all related routes
154
154
  - delete <routeName>: Delete a related route
155
155
 
156
+ #### esa route add
157
+
158
+ Bind a route to the current routine.
159
+
160
+ ```bash
161
+ esa route add [route] [site] [--alias <routeName>] [--route <route>] [--site <site>]
162
+ ```
163
+
164
+ - Positionals (optional):
165
+ - route: The route value, e.g. example.com/_ or _.example.com/\*
166
+ - site: The site name, e.g. example.com
167
+
168
+ - Options:
169
+ - -r, --route string: Route value, e.g. example.com/\*
170
+ - Host supports leading `*` for suffix match (e.g., `*.example.com`)
171
+ - Path supports trailing `*` for prefix match (e.g., `/api/*`)
172
+ - -s, --site string: Site name (must be an active site)
173
+ - -a, --alias string: Route name (alias)
174
+
175
+ - Interactive behavior:
176
+ - If `--alias` is missing, you will be prompted to input a route name
177
+ - If `--site` is missing, you will be prompted to choose from active sites
178
+ - If `--route` is missing, you will be prompted to input the route value
179
+
180
+ - Route matching notes:
181
+ - Host supports `*` prefix: `*.example.com` matches any host ending with `.example.com`
182
+ - Path supports `*` suffix: `/api/*` matches any path starting with `/api/`
183
+ - Examples: `example.com/*`, `*.example.com/`, `*.example.com/api/*`
184
+
185
+ - Examples:
186
+ - `esa route add -a home -s example.com -r example.com/*`
187
+ - `esa route add example.com/* example.com -a home`
188
+
156
189
  ---
157
190
 
158
191
  ### esa login
@@ -15,7 +15,7 @@ esa init [name]
15
15
  - -f, --framework string:选择前端框架(react/vue/nextjs...)
16
16
  - -l, --language string:选择语言(typescript/javascript)。可选:typescript | javascript
17
17
  - -t, --template string:指定模板名称
18
- - -y, --yes boolean:对所有交互询问选择“是”(默认 false
18
+ - -y, --yes boolean:对所有交互询问选择“是”(默认 false),模版采用helloworld
19
19
  - --git boolean:在项目中初始化 git
20
20
  - --deploy boolean:初始化完成后自动部署
21
21
 
@@ -65,14 +65,12 @@ esa commit [entry]
65
65
  esa deploy [entry]
66
66
  ```
67
67
 
68
- - 位置参数:
69
- - entry:(可选的)边缘函数入口文件
70
-
71
68
  - 选项:
69
+ - entry 可选参数,默认以`esa.jsonc`中entry配置为准
72
70
  - -v, --version string:指定要部署的版本(跳过交互选择)
73
71
  - -e, --environment string:部署环境。可选:staging | production
74
72
  - -n, --name string:边缘函数名称
75
- - -a, --assets boolean:是否部署静态资源
73
+ - -a, --assets string:静态资源目录(例如:./dist)
76
74
  - -d, --description string:版本描述
77
75
  - -m, --minify boolean:是否压缩代码
78
76
 
@@ -117,7 +115,7 @@ esa site list
117
115
  ```
118
116
 
119
117
  - 子命令:
120
- - list:列出账户下所有已激活站点
118
+ - list:列出所有已激活站点
121
119
 
122
120
  ---
123
121
 
@@ -153,6 +151,39 @@ esa route delete <routeName>
153
151
  - list:查看所有已绑定路由
154
152
  - delete `<routeName>`:删除已绑定路由
155
153
 
154
+ #### esa route add
155
+
156
+ 为当前边缘函数绑定一个路由。
157
+
158
+ ```bash
159
+ esa route add [route] [site] [--alias <routeName>] [--route <route>] [--site <site>]
160
+ ```
161
+
162
+ - 位置参数(可选):
163
+ - route:路由值,例如:example.com/_ 或 _.example.com/\*
164
+ - site:站点名称,例如:example.com
165
+
166
+ - 选项:
167
+ - -r, --route string:路由值,例如:example.com/\*
168
+ - 主机名支持以 `*` 开头表示后缀匹配(如:`*.example.com`)
169
+ - 路径支持以 `*` 结尾表示前缀匹配(如:`/api/*`)
170
+ - -s, --site string:站点名称(需为已激活站点)
171
+ - -a, --alias string:路由名称(别名)
172
+
173
+ - 交互提示:
174
+ - 未提供 `--alias` 时,会提示输入路由名称
175
+ - 未提供 `--site` 时,会列出账号下已激活站点供选择
176
+ - 未提供 `--route` 时,会提示输入路由值
177
+
178
+ - 路由匹配说明:
179
+ - host 支持前缀 `*`:`*.example.com` 表示匹配所有以 `.example.com` 结尾的域名
180
+ - path 支持后缀 `*`:`/api/*` 表示匹配以 `/api/` 开头的路径
181
+ - 示例:`example.com/*`、`*.example.com/`、`*.example.com/api/*`
182
+
183
+ - 示例:
184
+ - `esa route add -a home -s example.com -r example.com/*`
185
+ - `esa route add example.com/* example.com -a home`
186
+
156
187
  ---
157
188
 
158
189
  ### esa login
@@ -166,6 +197,9 @@ esa login
166
197
  - 选项:
167
198
  - --access-key-id, --ak string:AccessKey ID (AK)
168
199
  - --access-key-secret, --sk string:AccessKey Secret (SK)
200
+ - 从环境变量中读取process.env
201
+ - ESA_ACCESS_KEY_ID
202
+ - ESA_ACCESS_KEY_SECRET
169
203
 
170
204
  ---
171
205
 
@@ -1234,5 +1234,77 @@
1234
1234
  "step": {
1235
1235
  "en": "Step:",
1236
1236
  "zh_CN": ""
1237
+ },
1238
+ "version_title_update_available": {
1239
+ "en": "ESA CLI Update Available",
1240
+ "zh_CN": "ESA CLI 有新版本可用"
1241
+ },
1242
+ "version_current": {
1243
+ "en": "Current",
1244
+ "zh_CN": "当前版本"
1245
+ },
1246
+ "version_latest": {
1247
+ "en": "Latest",
1248
+ "zh_CN": "最新版本"
1249
+ },
1250
+ "version_note": {
1251
+ "en": "Note",
1252
+ "zh_CN": "提示"
1253
+ },
1254
+ "version_note_incompatible": {
1255
+ "en": "This version may have incompatibilities, please upgrade soon.",
1256
+ "zh_CN": "当前版本可能存在不兼容,建议尽快升级至最新版本。"
1257
+ },
1258
+ "version_update": {
1259
+ "en": "Update",
1260
+ "zh_CN": "升级"
1261
+ },
1262
+ "version_continue": {
1263
+ "en": "You can continue using the current version; commands will proceed.",
1264
+ "zh_CN": "你可以继续使用当前版本;命令将继续执行。"
1265
+ },
1266
+ "version_prompt_update_now": {
1267
+ "en": "Update now to the latest version?",
1268
+ "zh_CN": "是否立即更新到最新版本?"
1269
+ },
1270
+ "version_update_failed": {
1271
+ "en": "Global update failed. You may need elevated permissions (sudo) or use yarn/pnpm:",
1272
+ "zh_CN": "全局更新失败。你可能需要更高权限(sudo)或使用 yarn/pnpm:"
1273
+ },
1274
+ "deploy_option_keyword": {
1275
+ "en": "Keyword to search for routines",
1276
+ "zh_CN": ""
1277
+ },
1278
+ "route_input_method": {
1279
+ "en": "How would you like to input the route?",
1280
+ "zh_CN": "您希望如何输入路由?"
1281
+ },
1282
+ "route_input_builder": {
1283
+ "en": "Use route builder (prefix + suffix)",
1284
+ "zh_CN": "使用路由构建器(前缀 + 后缀)"
1285
+ },
1286
+ "route_input_manual": {
1287
+ "en": "Enter route manually",
1288
+ "zh_CN": "手动输入路由"
1289
+ },
1290
+ "route_build_cancelled": {
1291
+ "en": "Route building cancelled",
1292
+ "zh_CN": "路由构建已取消"
1293
+ },
1294
+ "route_builder_prefix_prompt": {
1295
+ "en": "Enter route prefix for ${siteName} (e.g., api, v1):",
1296
+ "zh_CN": "为 ${siteName} 输入路由前缀(例如:api, v1):"
1297
+ },
1298
+ "route_builder_suffix_prompt": {
1299
+ "en": "Enter route suffix for ${siteName} (e.g., *, users/*):",
1300
+ "zh_CN": "为 ${siteName} 输入路由后缀(例如:*, users/*):"
1301
+ },
1302
+ "route_builder_preview": {
1303
+ "en": "Preview: ${siteName}${prefix}${suffix}",
1304
+ "zh_CN": "预览:{route}"
1305
+ },
1306
+ "route_builder_instructions": {
1307
+ "en": "Press Enter to continue, Ctrl+C to cancel",
1308
+ "zh_CN": "按回车键继续,Ctrl+C 取消"
1237
1309
  }
1238
1310
  }
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import routeCommand from './commands/route/index.js';
24
24
  import routine from './commands/routine/index.js';
25
25
  import site from './commands/site/index.js';
26
26
  import t from './i18n/index.js';
27
- import { handleCheckVersion } from './utils/checkVersion.js';
27
+ import { handleCheckVersion, checkCLIVersion } from './utils/checkVersion.js';
28
28
  import { getCliConfig } from './utils/fileUtils/index.js';
29
29
  const main = () => __awaiter(void 0, void 0, void 0, function* () {
30
30
  const argv = hideBin(process.argv);
@@ -39,6 +39,16 @@ const main = () => __awaiter(void 0, void 0, void 0, function* () {
39
39
  .version(false)
40
40
  .wrap(null)
41
41
  .help()
42
+ .middleware((argv) => __awaiter(void 0, void 0, void 0, function* () {
43
+ try {
44
+ // Pass current command (first positional) so version check can decide prompting behavior
45
+ yield checkCLIVersion((argv._ && argv._[0] ? String(argv._[0]) : ''));
46
+ }
47
+ catch (e) {
48
+ console.log(e);
49
+ console.log('error');
50
+ }
51
+ }))
42
52
  .epilogue(`${t('main_epilogue').d('For more information, visit ESA')}: ${chalk.underline.blue('https://www.aliyun.com/product/esa')}`)
43
53
  .options('version', {
44
54
  describe: t('main_version_describe').d('Show version'),
@@ -219,7 +219,7 @@ export class ApiService {
219
219
  return null;
220
220
  });
221
221
  }
222
- listUserRoutines() {
222
+ listUserRoutines(requestParams) {
223
223
  return __awaiter(this, void 0, void 0, function* () {
224
224
  try {
225
225
  let params = {
@@ -236,7 +236,9 @@ export class ApiService {
236
236
  return this;
237
237
  }
238
238
  };
239
- let request = new $OpenApi.OpenApiRequest();
239
+ let request = new $OpenApi.OpenApiRequest({
240
+ query: requestParams || {}
241
+ });
240
242
  let runtime = {
241
243
  toMap: function () {
242
244
  return this;
@@ -175,8 +175,47 @@ class Logger {
175
175
  console.log(message);
176
176
  }
177
177
  notInProject() {
178
- const initCommand = chalk.green('esa init');
179
- 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();
180
219
  }
181
220
  pathEacces(localPath) {
182
221
  this.block();
@@ -8,11 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { exit } from 'process';
11
+ import { log } from '@clack/prompts';
11
12
  import chalk from 'chalk';
12
13
  import t from '../i18n/index.js';
13
14
  import { ApiService } from '../libs/apiService.js';
14
15
  import logger from '../libs/logger.js';
15
- import { log } from '@clack/prompts';
16
16
  export function isRoutineExist(name) {
17
17
  return __awaiter(this, void 0, void 0, function* () {
18
18
  const server = yield ApiService.getInstance();
@@ -48,17 +48,10 @@ export function ensureRoutineExists(name) {
48
48
  });
49
49
  const isSuccess = (createRes === null || createRes === void 0 ? void 0 : createRes.data.Status) === 'OK';
50
50
  if (isSuccess) {
51
- // logger.endSubStep(
52
- // t('routine_create_success').d('Routine created successfully.')
53
- // );
54
51
  logger.endSubStep('Routine created successfully');
55
- // tlog.success(
56
- // t('routine_create_success').d('Routine created successfully.')
57
- // );
58
52
  }
59
53
  else {
60
54
  logger.endSubStep('Routine created failed');
61
- // tlog.error(t('routine_create_fail').d('Routine created failed.'));
62
55
  exit();
63
56
  }
64
57
  }
@@ -9,6 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { promises as fs } from 'fs';
11
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';
12
18
  import { getDirName } from '../utils/fileUtils/base.js';
13
19
  export function handleCheckVersion() {
14
20
  return __awaiter(this, void 0, void 0, function* () {
@@ -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
+ }