esa-cli 1.0.0 → 1.0.2-beta.0

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.
package/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  ESA CLI is a command-line tool for building with Alibaba Cloud ESA Functions and Pages.
4
4
 
5
5
  <p>
6
- <a href="https://discord.gg/xygV6MYx">
6
+ <a href="https://discord.gg/BxcRVEeh">
7
7
  <img alt="Discord CN" src="https://img.shields.io/badge/Discord-中文-5865F2?logo=discord&logoColor=white" />
8
8
  </a>
9
- <a href="https://discord.gg/YeFg4yUA" style="margin-left:8px;">
9
+ <a href="https://discord.gg/SHYe5926" style="margin-left:8px;">
10
10
  <img alt="Discord EN" src="https://img.shields.io/badge/Discord-English-5865F2?logo=discord&logoColor=white" />
11
11
  </a>
12
12
  </p>
@@ -27,6 +27,12 @@ To ensure consistent collaboration across your team, we recommend installing `es
27
27
  npm i -D esa-cli@latest
28
28
  ```
29
29
 
30
+ Alternatively, install globally to use the `esa-cli` command system-wide:
31
+
32
+ ```bash
33
+ npm i -g esa-cli@latest
34
+ ```
35
+
30
36
  > [!TIP]
31
37
  > When `esa-cli` is not previously installed, `npx` will fetch and run the latest version from the registry.
32
38
 
@@ -39,6 +39,11 @@ const commit = {
39
39
  alias: 'n',
40
40
  describe: t('commit_option_name').d('Functions& Pages name'),
41
41
  type: 'string'
42
+ })
43
+ .option('bundle', {
44
+ describe: 'Bundle with esbuild (use --no-bundle to skip)',
45
+ type: 'boolean',
46
+ default: true
42
47
  });
43
48
  },
44
49
  handler: (argv) => __awaiter(void 0, void 0, void 0, function* () {
@@ -68,7 +73,7 @@ export function handleCommit(argv) {
68
73
  }));
69
74
  }
70
75
  logger.startSubStep('Generating code version');
71
- const res = yield generateCodeVersion(projectName, description, argv === null || argv === void 0 ? void 0 : argv.entry, argv === null || argv === void 0 ? void 0 : argv.assets, argv === null || argv === void 0 ? void 0 : argv.minify);
76
+ const res = yield generateCodeVersion(projectName, description, argv === null || argv === void 0 ? void 0 : argv.entry, argv === null || argv === void 0 ? void 0 : argv.assets, argv === null || argv === void 0 ? void 0 : argv.minify, undefined, (argv.bundle === false));
72
77
  const { isSuccess } = res || {};
73
78
  if (!isSuccess) {
74
79
  logger.endSubStep('Generate version failed');
@@ -120,9 +120,9 @@ export function getRoutineDetails(projectName) {
120
120
  * 支持assets和普通代码两种模式
121
121
  */
122
122
  export function generateCodeVersion(projectName_1, description_1, entry_1, assets_1) {
123
- return __awaiter(this, arguments, void 0, function* (projectName, description, entry, assets, minify = false, projectPath) {
123
+ return __awaiter(this, arguments, void 0, function* (projectName, description, entry, assets, minify = false, projectPath, noBundle = false) {
124
124
  var _a;
125
- const { zip, sourceList, dynamicSources } = yield compress(entry, assets, minify, projectPath);
125
+ const { zip, sourceList, dynamicSources } = yield compress(entry, assets, minify, projectPath, noBundle);
126
126
  // Pretty print upload directory tree
127
127
  const buildTree = (paths, decorateTopLevel) => {
128
128
  const root = { children: new Map(), isFile: false };
@@ -260,7 +260,7 @@ export function deployToEnvironments(name, codeVersion, env) {
260
260
  * 结合了压缩、提交和部署的完整流程
261
261
  */
262
262
  export function commitAndDeployVersion(projectName_1, scriptEntry_1, assets_1) {
263
- return __awaiter(this, arguments, void 0, function* (projectName, scriptEntry, assets, description = '', projectPath, env = 'production', minify = false, version) {
263
+ return __awaiter(this, arguments, void 0, function* (projectName, scriptEntry, assets, description = '', projectPath, env = 'production', minify = false, version, noBundle = false) {
264
264
  var _a, _b, _c;
265
265
  const projectInfo = yield validateAndInitializeProject(projectName, projectPath);
266
266
  if (!projectInfo) {
@@ -274,7 +274,7 @@ export function commitAndDeployVersion(projectName_1, scriptEntry_1, assets_1) {
274
274
  logger.endSubStep(deployed ? 'Deploy finished' : 'Deploy failed');
275
275
  return deployed;
276
276
  }
277
- const res = yield generateCodeVersion(projectInfo.projectName, description, scriptEntry || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.entry), assets || ((_a = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.assets) === null || _a === void 0 ? void 0 : _a.directory), minify || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.minify), projectPath);
277
+ const res = yield generateCodeVersion(projectInfo.projectName, description, scriptEntry || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.entry), assets || ((_a = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.assets) === null || _a === void 0 ? void 0 : _a.directory), minify || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.minify), projectPath, noBundle);
278
278
  const isCommitSuccess = res === null || res === void 0 ? void 0 : res.isSuccess;
279
279
  if (!isCommitSuccess) {
280
280
  logger.endSubStep('Generate version failed');
@@ -317,6 +317,32 @@ export function deployCodeVersion(name, codeVersion, environment) {
317
317
  }
318
318
  });
319
319
  }
320
+ /**
321
+ * 部署指定的多个版本及其百分比
322
+ */
323
+ export function deployCodeVersions(name, versions, env) {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ const server = yield ApiService.getInstance();
326
+ const doDeploy = (targetEnv) => __awaiter(this, void 0, void 0, function* () {
327
+ const res = yield server.createRoutineCodeDeployment({
328
+ Name: name,
329
+ CodeVersions: versions.map((v) => ({
330
+ Percentage: v.percentage,
331
+ CodeVersion: v.codeVersion
332
+ })),
333
+ Strategy: 'percentage',
334
+ Env: targetEnv
335
+ });
336
+ return !!res;
337
+ });
338
+ if (env === 'all') {
339
+ const s = yield doDeploy('staging');
340
+ const p = yield doDeploy('production');
341
+ return s && p;
342
+ }
343
+ return yield doDeploy(env);
344
+ });
345
+ }
320
346
  /**
321
347
  * Poll routine code version status until it becomes ready
322
348
  */
@@ -417,3 +443,57 @@ export function displayDeploySuccess(projectName_1) {
417
443
  logger.block();
418
444
  });
419
445
  }
446
+ /**
447
+ * 解析 --versions 参数并执行按百分比分发部署;成功后打印展示和百分比分配
448
+ */
449
+ export function deployWithVersionPercentages(nameArg, versionsArg, env, projectPath) {
450
+ return __awaiter(this, void 0, void 0, function* () {
451
+ const raw = (versionsArg || [])
452
+ .flatMap((v) => String(v).split(','))
453
+ .map((s) => s.trim())
454
+ .filter(Boolean);
455
+ const pairs = raw.map((s) => {
456
+ const [codeVersion, percentStr] = s.split(':');
457
+ return {
458
+ codeVersion: codeVersion === null || codeVersion === void 0 ? void 0 : codeVersion.trim(),
459
+ percentage: Number((percentStr || '').trim())
460
+ };
461
+ });
462
+ if (pairs.length > 2) {
463
+ logger.error('Deploy failed: at most two versions are supported');
464
+ return false;
465
+ }
466
+ if (pairs.some((p) => !p.codeVersion || Number.isNaN(p.percentage) || p.percentage < 0)) {
467
+ logger.error('Deploy failed: invalid --versions format. Use v1:80,v2:20');
468
+ return false;
469
+ }
470
+ if (pairs.length === 1) {
471
+ if (pairs[0].percentage !== 100) {
472
+ logger.error('Deploy failed: single version must be 100%');
473
+ return false;
474
+ }
475
+ }
476
+ else if (pairs.length === 2) {
477
+ const sum = pairs[0].percentage + pairs[1].percentage;
478
+ if (sum !== 100) {
479
+ logger.error('Deploy failed: percentages must sum to 100');
480
+ return false;
481
+ }
482
+ }
483
+ const projectInfo = yield validateAndInitializeProject(nameArg, projectPath);
484
+ if (!projectInfo) {
485
+ return false;
486
+ }
487
+ const ok = yield deployCodeVersions(projectInfo.projectName, pairs, env);
488
+ if (!ok)
489
+ return false;
490
+ yield displayDeploySuccess(projectInfo.projectName, true, true);
491
+ logger.block();
492
+ logger.log('📦 Versions rollout:');
493
+ pairs.forEach((p) => {
494
+ logger.log(`- ${p.codeVersion}: ${p.percentage}%`);
495
+ });
496
+ logger.block();
497
+ return true;
498
+ });
499
+ }
@@ -12,7 +12,7 @@ import { intro, outro } from '@clack/prompts';
12
12
  import t from '../../i18n/index.js';
13
13
  import { getRoot } from '../../utils/fileUtils/base.js';
14
14
  import { getProjectConfig } from '../../utils/fileUtils/index.js';
15
- import { commitAndDeployVersion, displayDeploySuccess } from '../common/utils.js';
15
+ import { commitAndDeployVersion, displayDeploySuccess, deployWithVersionPercentages } from '../common/utils.js';
16
16
  const deploy = {
17
17
  command: 'deploy [entry]',
18
18
  builder: (yargs) => {
@@ -52,6 +52,15 @@ const deploy = {
52
52
  alias: 'm',
53
53
  describe: t('deploy_option_minify').d('Minify the code'),
54
54
  type: 'boolean'
55
+ })
56
+ .option('bundle', {
57
+ describe: 'Bundle with esbuild (use --no-bundle to skip)',
58
+ type: 'boolean',
59
+ default: true
60
+ })
61
+ .option('versions', {
62
+ describe: 'Deploy two versions with percentages, format: v1:80,v2:20 or repeat --versions v1:80 --versions v2:20',
63
+ type: 'array'
55
64
  });
56
65
  },
57
66
  describe: `🚀 ${t('deploy_describe').d('Deploy your project')}`,
@@ -65,8 +74,15 @@ export function handleDeploy(argv) {
65
74
  var _a;
66
75
  const entry = argv.entry;
67
76
  const assets = (_a = argv.assets) !== null && _a !== void 0 ? _a : undefined;
77
+ const versionsArg = argv.versions || [];
68
78
  intro(`Deploy an application with ESA`);
69
- const success = yield commitAndDeployVersion(argv.name || undefined, entry, assets, argv.description || '', getRoot(), argv.environment || 'all', argv.minify, argv.version);
79
+ if (versionsArg.length > 0) {
80
+ const env = argv.environment || 'all';
81
+ const ok = yield deployWithVersionPercentages(argv.name || undefined, versionsArg, env, getRoot());
82
+ outro(ok ? 'Deploy finished' : 'Deploy failed');
83
+ exit(ok ? 0 : 1);
84
+ }
85
+ const success = yield commitAndDeployVersion(argv.name || undefined, entry, assets, argv.description || '', getRoot(), argv.environment || 'all', argv.minify, argv.version, (argv.bundle === false));
70
86
  outro(success ? 'Deploy finished' : 'Deploy failed');
71
87
  if (success) {
72
88
  const projectConfig = getProjectConfig(getRoot());
@@ -17,7 +17,7 @@ const login = {
17
17
  command: 'login',
18
18
  describe: `🔑 ${t('login_describe').d('Login to the server')}`,
19
19
  builder: (yargs) => {
20
- var _a, _b;
20
+ var _a, _b, _c;
21
21
  return yargs
22
22
  .option('access-key-id', {
23
23
  alias: 'ak',
@@ -28,6 +28,11 @@ const login = {
28
28
  alias: 'sk',
29
29
  describe: (_b = t('login_option_access_key_secret')) === null || _b === void 0 ? void 0 : _b.d('AccessKey Secret'),
30
30
  type: 'string'
31
+ })
32
+ .option('endpoint', {
33
+ alias: 'e',
34
+ describe: (_c = t('login_option_endpoint')) === null || _c === void 0 ? void 0 : _c.d('Endpoint'),
35
+ type: 'string'
31
36
  });
32
37
  },
33
38
  handler: (argv) => __awaiter(void 0, void 0, void 0, function* () {
@@ -40,13 +45,14 @@ export function handleLogin(argv) {
40
45
  generateDefaultConfig();
41
46
  const accessKeyId = argv === null || argv === void 0 ? void 0 : argv['access-key-id'];
42
47
  const accessKeySecret = argv === null || argv === void 0 ? void 0 : argv['access-key-secret'];
48
+ const endpoint = (argv === null || argv === void 0 ? void 0 : argv['endpoint']) || process.env.ESA_ENDPOINT;
43
49
  if (accessKeyId && accessKeySecret) {
44
- yield handleLoginWithAKSK(accessKeyId, accessKeySecret);
50
+ yield handleLoginWithAKSK(accessKeyId, accessKeySecret, endpoint);
45
51
  return;
46
52
  }
47
53
  if (process.env.ESA_ACCESS_KEY_ID && process.env.ESA_ACCESS_KEY_SECRET) {
48
54
  logger.log(`🔑 ${t('login_get_from_env').d(`Get AccessKey ID and AccessKey Secret from environment variables.`)}`);
49
- yield handleLoginWithAKSK(process.env.ESA_ACCESS_KEY_ID, process.env.ESA_ACCESS_KEY_SECRET);
55
+ yield handleLoginWithAKSK(process.env.ESA_ACCESS_KEY_ID, process.env.ESA_ACCESS_KEY_SECRET, endpoint);
50
56
  return;
51
57
  }
52
58
  const cliConfig = getCliConfig();
@@ -73,31 +79,32 @@ export function handleLogin(argv) {
73
79
  if (isCancel(selected) || selected === 'exit') {
74
80
  return;
75
81
  }
76
- yield getUserInputAuthInfo();
82
+ yield getUserInputAuthInfo(endpoint);
77
83
  }
78
84
  else {
79
85
  logger.error(t('pre_login_failed').d('The previously entered Access Key ID (AK) and Secret Access Key (SK) are incorrect. Please enter them again.'));
80
86
  logger.log(`${t('login_logging').d('Logging in')}...`);
81
- yield getUserInputAuthInfo();
87
+ yield getUserInputAuthInfo(endpoint);
82
88
  }
83
89
  }
84
90
  else {
85
91
  logger.log(`${t('login_logging').d('Logging in')}...`);
86
- yield getUserInputAuthInfo();
92
+ yield getUserInputAuthInfo(endpoint);
87
93
  }
88
94
  });
89
95
  }
90
- function handleLoginWithAKSK(accessKeyId, accessKeySecret) {
96
+ function handleLoginWithAKSK(accessKeyId, accessKeySecret, endpoint) {
91
97
  return __awaiter(this, void 0, void 0, function* () {
92
98
  let apiConfig = getApiConfig();
93
99
  apiConfig.auth = {
94
100
  accessKeyId,
95
101
  accessKeySecret
96
102
  };
103
+ if (endpoint) {
104
+ apiConfig.endpoint = endpoint;
105
+ }
97
106
  try {
98
- yield updateCliConfigFile({
99
- auth: apiConfig.auth
100
- });
107
+ yield updateCliConfigFile(Object.assign({ auth: apiConfig.auth }, (endpoint ? { endpoint } : {})));
101
108
  const service = yield ApiService.getInstance();
102
109
  service.updateConfig(apiConfig);
103
110
  const loginStatus = yield service.checkLogin();
@@ -113,7 +120,7 @@ function handleLoginWithAKSK(accessKeyId, accessKeySecret) {
113
120
  }
114
121
  });
115
122
  }
116
- export function getUserInputAuthInfo() {
123
+ export function getUserInputAuthInfo(endpoint) {
117
124
  return __awaiter(this, void 0, void 0, function* () {
118
125
  const styledUrl = chalk.underline.blue('https://ram.console.aliyun.com/manage/ak');
119
126
  logger.log(`🔑 ${chalk.underline(t('login_get_ak_sk').d(`Please go to the following link to get your account's AccessKey ID and AccessKey Secret`))}`);
@@ -127,10 +134,11 @@ export function getUserInputAuthInfo() {
127
134
  accessKeyId,
128
135
  accessKeySecret
129
136
  };
137
+ if (endpoint) {
138
+ apiConfig.endpoint = endpoint;
139
+ }
130
140
  try {
131
- yield updateCliConfigFile({
132
- auth: apiConfig.auth
133
- });
141
+ yield updateCliConfigFile(Object.assign({ auth: apiConfig.auth }, (endpoint ? { endpoint } : {})));
134
142
  const service = yield ApiService.getInstance();
135
143
  service.updateConfig(apiConfig);
136
144
  const loginStatus = yield service.checkLogin();