esa-cli 1.0.1 → 1.0.2-beta.1

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>
@@ -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
+ * Deploy specified multiple versions and their percentages
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
+ * Parse --versions parameter and execute distributed deployment by percentage; print display and percentage allocation after successful deployment
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());
@@ -10,9 +10,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { isCancel, select as clackSelect, text as clackText } from '@clack/prompts';
11
11
  import chalk from 'chalk';
12
12
  import t from '../../i18n/index.js';
13
- import { ApiService } from '../../libs/apiService.js';
14
13
  import logger from '../../libs/logger.js';
15
- import { getApiConfig, getCliConfig, updateCliConfigFile, generateDefaultConfig } from '../../utils/fileUtils/index.js';
14
+ import { getCliConfig, updateCliConfigFile, generateDefaultConfig } from '../../utils/fileUtils/index.js';
15
+ import { validateCredentials } from '../../utils/validateCredentials.js';
16
16
  const login = {
17
17
  command: 'login',
18
18
  describe: `🔑 ${t('login_describe').d('Login to the server')}`,
@@ -38,17 +38,34 @@ export default login;
38
38
  export function handleLogin(argv) {
39
39
  return __awaiter(this, void 0, void 0, function* () {
40
40
  generateDefaultConfig();
41
+ if (process.env.ESA_ACCESS_KEY_ID && process.env.ESA_ACCESS_KEY_SECRET) {
42
+ const result = yield validateCredentials(process.env.ESA_ACCESS_KEY_ID, process.env.ESA_ACCESS_KEY_SECRET);
43
+ if (result.valid) {
44
+ logger.log(t('login_get_credentials_from_environment_variables').d('Get credentials from environment variables'));
45
+ logger.success(t('login_success').d('Login success!'));
46
+ }
47
+ else {
48
+ logger.error(result.message || 'Login failed');
49
+ }
50
+ return;
51
+ }
41
52
  const accessKeyId = argv === null || argv === void 0 ? void 0 : argv['access-key-id'];
42
53
  const accessKeySecret = argv === null || argv === void 0 ? void 0 : argv['access-key-secret'];
43
54
  if (accessKeyId && accessKeySecret) {
44
- yield handleLoginWithAKSK(accessKeyId, accessKeySecret);
45
- return;
46
- }
47
- if (process.env.ESA_ACCESS_KEY_ID && process.env.ESA_ACCESS_KEY_SECRET) {
48
- 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
+ const result = yield validateCredentials(accessKeyId, accessKeySecret);
56
+ if (result.valid) {
57
+ logger.success(t('login_success').d('Login success!'));
58
+ updateCliConfigFile(Object.assign({ auth: {
59
+ accessKeyId,
60
+ accessKeySecret
61
+ } }, (result.endpoint ? { endpoint: result.endpoint } : {})));
62
+ }
63
+ else {
64
+ logger.error(result.message || 'Login failed');
65
+ }
50
66
  return;
51
67
  }
68
+ // interactive login
52
69
  const cliConfig = getCliConfig();
53
70
  if (!cliConfig)
54
71
  return;
@@ -56,9 +73,8 @@ export function handleLogin(argv) {
56
73
  cliConfig.auth &&
57
74
  cliConfig.auth.accessKeyId &&
58
75
  cliConfig.auth.accessKeySecret) {
59
- const service = yield ApiService.getInstance();
60
- const loginStatus = yield service.checkLogin();
61
- if (loginStatus.success) {
76
+ const loginStatus = yield validateCredentials(cliConfig.auth.accessKeyId, cliConfig.auth.accessKeySecret);
77
+ if (loginStatus.valid) {
62
78
  logger.warn(t('login_already').d('You are already logged in.'));
63
79
  const selected = (yield clackSelect({
64
80
  message: t('login_existing_credentials_message').d('Existing credentials found. What do you want to do?'),
@@ -73,47 +89,15 @@ export function handleLogin(argv) {
73
89
  if (isCancel(selected) || selected === 'exit') {
74
90
  return;
75
91
  }
76
- yield getUserInputAuthInfo();
77
92
  }
78
93
  else {
79
94
  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
- logger.log(`${t('login_logging').d('Logging in')}...`);
81
- yield getUserInputAuthInfo();
82
- }
83
- }
84
- else {
85
- logger.log(`${t('login_logging').d('Logging in')}...`);
86
- yield getUserInputAuthInfo();
87
- }
88
- });
89
- }
90
- function handleLoginWithAKSK(accessKeyId, accessKeySecret) {
91
- return __awaiter(this, void 0, void 0, function* () {
92
- let apiConfig = getApiConfig();
93
- apiConfig.auth = {
94
- accessKeyId,
95
- accessKeySecret
96
- };
97
- try {
98
- yield updateCliConfigFile({
99
- auth: apiConfig.auth
100
- });
101
- const service = yield ApiService.getInstance();
102
- service.updateConfig(apiConfig);
103
- const loginStatus = yield service.checkLogin();
104
- if (loginStatus.success) {
105
- logger.success(t('login_success').d('Login success!'));
106
95
  }
107
- else {
108
- logger.error(loginStatus.message || 'Login failed');
109
- }
110
- }
111
- catch (error) {
112
- logger.error(t('login_failed').d('An error occurred while trying to log in.'));
113
96
  }
97
+ yield interactiveLogin();
114
98
  });
115
99
  }
116
- export function getUserInputAuthInfo() {
100
+ export function interactiveLogin() {
117
101
  return __awaiter(this, void 0, void 0, function* () {
118
102
  const styledUrl = chalk.underline.blue('https://ram.console.aliyun.com/manage/ak');
119
103
  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`))}`);
@@ -122,27 +106,16 @@ export function getUserInputAuthInfo() {
122
106
  const accessKeySecret = (yield clackText({
123
107
  message: 'AccessKey Secret:'
124
108
  }));
125
- let apiConfig = getApiConfig();
126
- apiConfig.auth = {
127
- accessKeyId,
128
- accessKeySecret
129
- };
130
- try {
131
- yield updateCliConfigFile({
132
- auth: apiConfig.auth
133
- });
134
- const service = yield ApiService.getInstance();
135
- service.updateConfig(apiConfig);
136
- const loginStatus = yield service.checkLogin();
137
- if (loginStatus.success) {
138
- logger.success(t('login_success').d('Login success!'));
139
- }
140
- else {
141
- logger.error(loginStatus.message || 'Login failed');
142
- }
109
+ const loginStatus = yield validateCredentials(accessKeyId, accessKeySecret);
110
+ if (loginStatus.valid) {
111
+ yield updateCliConfigFile(Object.assign({ auth: {
112
+ accessKeyId,
113
+ accessKeySecret
114
+ } }, (loginStatus.endpoint ? { endpoint: loginStatus.endpoint } : {})));
115
+ logger.success(t('login_success').d('Login success!'));
143
116
  }
144
- catch (error) {
145
- logger.error(t('login_failed').d('An error occurred while trying to log in.'));
117
+ else {
118
+ logger.error(loginStatus.message || 'Login failed');
146
119
  }
147
120
  });
148
121
  }
@@ -18,6 +18,7 @@ import logger from '../libs/logger.js';
18
18
  import { getRoot } from '../utils/fileUtils/base.js';
19
19
  import { getCliConfig, projectConfigPath } from '../utils/fileUtils/index.js';
20
20
  import { getRoutineDetails } from './common/utils.js';
21
+ import { validateCredentials } from '../utils/validateCredentials.js';
21
22
  export const checkDirectory = (isCheckGit = false) => {
22
23
  const root = getRoot();
23
24
  if (fs.existsSync(projectConfigPath)) {
@@ -78,55 +79,26 @@ export function validDomain(domain) {
78
79
  export function checkIsLoginSuccess() {
79
80
  return __awaiter(this, void 0, void 0, function* () {
80
81
  var _a, _b;
81
- let accessKeyId = process.env.ESA_ACCESS_KEY_ID;
82
- let accessKeySecret = process.env.ESA_ACCESS_KEY_SECRET;
83
- let endpoint = process.env.ESA_ENDPOINT;
84
82
  const cliConfig = getCliConfig();
85
- if (!accessKeyId || !accessKeySecret) {
86
- accessKeyId = (_a = cliConfig === null || cliConfig === void 0 ? void 0 : cliConfig.auth) === null || _a === void 0 ? void 0 : _a.accessKeyId;
87
- accessKeySecret = (_b = cliConfig === null || cliConfig === void 0 ? void 0 : cliConfig.auth) === null || _b === void 0 ? void 0 : _b.accessKeySecret;
88
- }
89
- if (!endpoint) {
90
- endpoint = cliConfig === null || cliConfig === void 0 ? void 0 : cliConfig.endpoint;
91
- }
92
- const namedCommand = chalk.green('esa-cli login');
93
- if (!accessKeyId || !accessKeySecret) {
94
- logger.log(`❌ ${t('utils_login_error').d('Maybe you are not logged in yet.')}`);
95
- logger.log(`🔔 ${t('utils_login_error_config', { namedCommand }).d(`Please run command to login: ${namedCommand}`)}`);
96
- return false;
97
- }
98
- return yield validateLoginCredentials(accessKeyId, accessKeySecret, endpoint, namedCommand);
99
- });
100
- }
101
- /**
102
- * 验证登录凭据的公共函数
103
- * @param accessKeyId AccessKey ID
104
- * @param accessKeySecret AccessKey Secret
105
- * @param namedCommand 命令名称(用于错误提示)
106
- * @param showError 是否显示错误信息
107
- * @returns 登录是否成功
108
- */
109
- export function validateLoginCredentials(accessKeyId_1, accessKeySecret_1, endpoint_1, namedCommand_1) {
110
- return __awaiter(this, arguments, void 0, function* (accessKeyId, accessKeySecret, endpoint, namedCommand, showError = true) {
111
- const server = yield ApiService.getInstance();
112
- server.updateConfig({
113
- auth: {
114
- accessKeyId,
115
- accessKeySecret
116
- },
117
- endpoint: endpoint
118
- });
119
- const res = yield server.checkLogin();
120
- if (res.success) {
121
- return true;
122
- }
123
- if (showError) {
124
- logger.log(res.message || '');
125
- logger.log(`❌ ${t('utils_login_error').d('Maybe you are not logged in yet.')}`);
126
- if (namedCommand) {
127
- logger.log(`🔔 ${t('utils_login_error_config', { namedCommand }).d(`Please run command to login: ${namedCommand}`)}`);
83
+ let accessKeyId = process.env.ESA_ACCESS_KEY_ID || ((_a = cliConfig === null || cliConfig === void 0 ? void 0 : cliConfig.auth) === null || _a === void 0 ? void 0 : _a.accessKeyId);
84
+ let accessKeySecret = process.env.ESA_ACCESS_KEY_SECRET || ((_b = cliConfig === null || cliConfig === void 0 ? void 0 : cliConfig.auth) === null || _b === void 0 ? void 0 : _b.accessKeySecret);
85
+ if (accessKeyId && accessKeySecret) {
86
+ const result = yield validateCredentials(accessKeyId, accessKeySecret);
87
+ const server = yield ApiService.getInstance();
88
+ if (result.valid) {
89
+ server.updateConfig({
90
+ auth: {
91
+ accessKeyId,
92
+ accessKeySecret
93
+ },
94
+ endpoint: result.endpoint
95
+ });
96
+ return true;
128
97
  }
129
98
  }
99
+ const namedCommand = chalk.green('esa-cli login');
100
+ logger.log(`❌ ${t('utils_login_error').d('Maybe you are not logged in yet.')}`);
101
+ logger.log(`🔔 ${t('utils_login_error_config', { namedCommand }).d(`Please run command to login: ${namedCommand}`)}`);
130
102
  return false;
131
103
  });
132
104
  }