@struggler/cli 1.0.7 → 1.0.8

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/command/deploy.js CHANGED
@@ -1,10 +1,12 @@
1
1
  const init = require('./init');
2
2
  const upload = require('./upload');
3
3
  const refresh = require('./refresh');
4
+ const chalk = require('chalk');
4
5
  const { collectDeployFiles, createSummary, finalizeOutput } = require('../lib/deploy');
5
6
  const { getDir } = require('../lib/config');
6
7
  const { createIgnoreMatcher } = require('../lib/ignore');
7
8
  const { printMessage } = require('../lib/output');
9
+ const { getLocale } = require('../lib/i18n');
8
10
 
9
11
  async function main(options) {
10
12
  const startedAt = Date.now();
@@ -47,7 +49,8 @@ async function main(options) {
47
49
  suppressOutput: true,
48
50
  });
49
51
  } else {
50
- printMessage(options, '[deploy] refresh step skipped');
52
+ const { messages } = getLocale(options && options.lang);
53
+ printMessage(options, ` ${chalk.dim(`↩ ${messages.deploySkipRefresh}`)}`);
51
54
  }
52
55
 
53
56
  const deploySummary = finalizeOutput(createSummary('deploy', Boolean(options.dryRun), [
@@ -68,7 +71,18 @@ async function main(options) {
68
71
  excludedPatterns: ignoreMatcher.patterns,
69
72
  }), options, manifestExtra);
70
73
 
71
- printMessage(options, `[deploy] upload=${uploadSummary.succeededCount}/${uploadSummary.total} refresh=${refreshSummary.succeededCount}/${refreshSummary.total}`);
74
+ const { messages } = getLocale(options && options.lang);
75
+ const uploadOk = uploadSummary.failedCount === 0;
76
+ const refreshOk = options.skipRefresh || refreshSummary.failedCount === 0;
77
+ const allOk = uploadOk && refreshOk;
78
+ printMessage(options, '');
79
+ printMessage(options, ` ${allOk ? chalk.green('✓') : chalk.red('✗')} ${chalk.bold(messages.deployDone)}`);
80
+ printMessage(options, '');
81
+ printMessage(options, ` ${chalk.dim(messages.deployUploadLabel)} ${chalk.green(uploadSummary.succeededCount)} / ${uploadSummary.total} ${messages.deployFileUnit}`);
82
+ if (!options.skipRefresh) {
83
+ printMessage(options, ` ${chalk.dim(messages.deployRefreshLabel)} ${chalk.green(refreshSummary.succeededCount)} / ${refreshSummary.total} ${messages.deployUnit}`);
84
+ }
85
+ printMessage(options, '');
72
86
  return deploySummary;
73
87
  }
74
88
 
package/command/init.js CHANGED
@@ -4,21 +4,23 @@ let { getJsonData, setJsonData, setSyncJsonData, directoryExists } = require('..
4
4
  let path = require('path')
5
5
  const { formatDate } = require('../lib/date');
6
6
  const { printMessage } = require('../lib/output');
7
+ const { getLocale } = require('../lib/i18n');
7
8
 
8
9
  function init(qiniuConfigPath, options){
10
+ const { messages } = getLocale(options && options.lang);
9
11
  if (!directoryExists(qiniuConfigPath)){
10
- printMessage(options, chalk.red("七牛配置不存在 正在生成模版 请稍后在下面的文件里填写必要的信息!"))
11
- printMessage(options, chalk.blue(qiniuConfigPath))
12
+ printMessage(options, chalk.yellow(` ⚠ ${messages.initTemplateCreated}`));
13
+ printMessage(options, ` ${chalk.dim(qiniuConfigPath)}`);
12
14
  if (options.dryRun) {
13
- printMessage(options, `[dry-run] init would create template ${qiniuConfigPath}`)
15
+ printMessage(options, chalk.dim(messages.initDryRunCreate(qiniuConfigPath)));
14
16
  return
15
17
  }
16
18
  setSyncJsonData(qiniuConfigPath, getJsonData(path.resolve(__dirname, '../def/qiniu.json')))
17
19
  }
18
-
19
20
  }
20
21
 
21
22
  function main(options){
23
+ const { messages } = getLocale(options && options.lang);
22
24
  let configPath = getConfig(options)
23
25
  let qiniuConfigPath = getQiniuConfig(options)
24
26
  init(qiniuConfigPath, options)
@@ -29,11 +31,12 @@ function main(options){
29
31
  config.publicPath = `${qiniuConfig.path || ''}/${versionPrefix}/`
30
32
  config.base = `${domain || ''}${qiniuConfig.path || ''}/${versionPrefix}/`
31
33
  if (options.dryRun) {
32
- printMessage(options, `[dry-run] init would write ${configPath}`)
33
- printMessage(options, JSON.stringify(config, null, 2))
34
+ printMessage(options, chalk.dim(messages.initDryRunWrite(configPath)));
35
+ printMessage(options, chalk.dim(JSON.stringify(config, null, 2)));
34
36
  return config
35
37
  }
36
- printMessage(options, JSON.stringify(config, null, 2))
38
+ printMessage(options, ` ${chalk.green('✓')} ${messages.initConfigUpdated}`);
39
+ printMessage(options, ` ${chalk.dim(configPath)}`);
37
40
  setJsonData(configPath, config)
38
41
  return config
39
42
  }
@@ -14,6 +14,8 @@ const {
14
14
  toRemoteKey,
15
15
  } = require('../lib/deploy');
16
16
  const { printMessage } = require('../lib/output');
17
+ const { createProgressBar } = require('../lib/progress');
18
+ const { getLocale } = require('../lib/i18n');
17
19
 
18
20
  async function main(options, runtime = {}) {
19
21
  const qiniuConfig = getJsonData(getQiniuConfig(options))
@@ -50,7 +52,8 @@ async function main(options, runtime = {}) {
50
52
 
51
53
  ensureRequiredConfig(
52
54
  { ...qiniuConfig, 'publicPath(config.json)': prefix },
53
- ['accessKey', 'secretKey', 'domain', 'publicPath(config.json)']
55
+ ['accessKey', 'secretKey', 'domain', 'publicPath(config.json)'],
56
+ options
54
57
  );
55
58
 
56
59
  var accessKey = qiniuConfig.accessKey
@@ -72,13 +75,16 @@ async function main(options, runtime = {}) {
72
75
  });
73
76
  }
74
77
 
78
+ const bar = createProgressBar(plans.length, {
79
+ json: options.json,
80
+ suppressOutput: runtime.suppressOutput,
81
+ });
82
+
75
83
  const results = [];
76
84
  for (const plan of plans) {
77
85
  try {
78
86
  await refresh(plan);
79
- if (!runtime.suppressOutput) {
80
- printMessage(options, `[refreshed] ${plan.target}`);
81
- }
87
+ bar.tick({ filename: plan.target });
82
88
  results.push({
83
89
  ok: true,
84
90
  localFile: plan.localFile,
@@ -86,6 +92,7 @@ async function main(options, runtime = {}) {
86
92
  target: plan.target,
87
93
  });
88
94
  } catch (error) {
95
+ bar.tick({ filename: plan.target, failed: true });
89
96
  results.push({
90
97
  ok: false,
91
98
  localFile: plan.localFile,
@@ -96,13 +103,16 @@ async function main(options, runtime = {}) {
96
103
  }
97
104
  }
98
105
 
106
+ bar.finish();
107
+
99
108
  const summary = createSummary('refresh', false, results, startedAt, {
100
109
  prefix,
101
110
  excludedPatterns,
102
111
  });
103
112
  const finalSummary = runtime.suppressOutput ? summary : finalizeOutput(summary, options, runtime.manifestExtra);
104
113
  if (finalSummary.failedCount > 0) {
105
- throw new Error(`Refresh finished with ${summary.failedCount} failures`);
114
+ const { messages } = getLocale(options && options.lang);
115
+ throw new Error(messages.refreshFailed(summary.failedCount));
106
116
  }
107
117
 
108
118
  return finalSummary;
package/command/upload.js CHANGED
@@ -18,6 +18,7 @@ const {
18
18
  const { printMessage } = require('../lib/output');
19
19
  const { computeFileMd5, readCache, writeCache, isCacheHit, updateCacheEntry } = require('../lib/cache');
20
20
  const { createProgressBar } = require('../lib/progress');
21
+ const { getLocale } = require('../lib/i18n');
21
22
 
22
23
  async function main(options, runtime = {}) {
23
24
  const qiniuConfig = getJsonData(getQiniuConfig(options))
@@ -75,7 +76,8 @@ async function main(options, runtime = {}) {
75
76
 
76
77
  ensureRequiredConfig(
77
78
  { ...qiniuConfig, 'publicPath(config.json)': prefix },
78
- ['accessKey', 'secretKey', 'Bucket', 'zone', 'domain', 'publicPath(config.json)']
79
+ ['accessKey', 'secretKey', 'Bucket', 'zone', 'domain', 'publicPath(config.json)'],
80
+ options
79
81
  );
80
82
 
81
83
  var accessKey = qiniuConfig.accessKey
@@ -100,7 +102,8 @@ async function main(options, runtime = {}) {
100
102
  formUploader.putFile(uploadToken, plan.key, plan.localFile, putExtra, async function (respErr, respBody, respInfo) {
101
103
  if (respErr || respInfo.statusCode !== 200) {
102
104
  if (attempt < 3) {
103
- console.log(`${plan.localFile} 上传失败,正在进行第 ${attempt + 1} 次重试`);
105
+ const { messages } = getLocale(options && options.lang);
106
+ printMessage(options, messages.uploadRetrying(plan.localFile, attempt + 1));
104
107
  try {
105
108
  const retryResult = await upload(plan, attempt + 1);
106
109
  resolve(retryResult);
@@ -184,7 +187,8 @@ async function main(options, runtime = {}) {
184
187
  });
185
188
  const finalSummary = runtime.suppressOutput ? summary : finalizeOutput(summary, options, runtime.manifestExtra);
186
189
  if (finalSummary.failedCount > 0) {
187
- throw new Error(`Upload finished with ${summary.failedCount} failures`);
190
+ const { messages } = getLocale(options && options.lang);
191
+ throw new Error(messages.uploadFailed(summary.failedCount));
188
192
  }
189
193
 
190
194
  return finalSummary;
package/index.js CHANGED
@@ -19,7 +19,7 @@ if (!shouldUseJson({ json: isJsonMode })) {
19
19
 
20
20
  // 输出Logo
21
21
  if (!isJsonMode) {
22
- console.log(magentaBright(figlet.textSync("struggler-cli", { horizontalLayout: "full" })), "\n\n")
22
+ console.log(magentaBright(figlet.textSync("struggler-cli", { font: "Small" })), "\n")
23
23
  }
24
24
 
25
25
  function formatItems(items, getLeft, getRight) {
@@ -97,6 +97,13 @@ program.parseAsync().catch(error => {
97
97
  printJson({ ok: false, error: error.message || String(error) })
98
98
  return
99
99
  }
100
- printError({}, error.message || error)
100
+ const { redBright, dim } = require('chalk')
101
+ const { messages } = getLocale(lang)
102
+ const msg = error.message || String(error)
103
+ console.error('')
104
+ console.error(redBright(` ✗ ${messages.errorTitle}`))
105
+ console.error('')
106
+ msg.split('\n').forEach(line => console.error(` ${dim(line)}`))
107
+ console.error('')
101
108
  process.exitCode = 1
102
109
  })
package/lib/deploy.js CHANGED
@@ -3,6 +3,7 @@ const chalk = require('chalk');
3
3
  const { listFiles } = require('./files');
4
4
  const { createIgnoreMatcher } = require('./ignore');
5
5
  const { printMessage, writeManifest, shouldUseJson, printJson } = require('./output');
6
+ const { getLocale } = require('./i18n');
6
7
 
7
8
  const DEFAULT_CONCURRENCY = 5;
8
9
 
@@ -65,9 +66,10 @@ function createSummary(action, dryRun, results, startedAt, extra = {}) {
65
66
  }
66
67
 
67
68
  function logPlan(action, items, options) {
68
- printMessage(options, `[dry-run] ${action} plan (${items.length} files)`);
69
+ const { messages } = getLocale(options && options.lang);
70
+ printMessage(options, messages.dryRunPlan(action, items.length));
69
71
  items.forEach((item) => {
70
- printMessage(options, `- ${item.localFile} -> ${item.target}`);
72
+ printMessage(options, ` ${item.localFile} -> ${item.target}`);
71
73
  });
72
74
  }
73
75
 
@@ -77,40 +79,44 @@ function formatDuration(ms) {
77
79
  }
78
80
 
79
81
  function logSummary(summary, options) {
82
+ const { messages } = getLocale(options && options.lang);
80
83
  const allOk = summary.failedCount === 0;
81
84
  const isDryRun = summary.dryRun;
82
85
  const skipped = summary.skippedCount || 0;
83
86
  const uploaded = summary.succeededCount - skipped;
84
87
 
85
88
  const statusIcon = allOk ? chalk.green('✓') : chalk.red('✗');
86
- const actionLabel = summary.action === 'upload' ? '上传' : summary.action === 'refresh' ? '刷新' : summary.action;
87
- const modeTag = isDryRun ? chalk.yellow(' [dry-run]') : '';
89
+ const actionDoneLabel = summary.action === 'upload'
90
+ ? messages.uploadDone
91
+ : summary.action === 'refresh'
92
+ ? messages.refreshDone
93
+ : messages.deployDone;
94
+ const modeTag = isDryRun ? chalk.yellow(` [${messages.dryRunLabel}]`) : '';
88
95
 
89
96
  printMessage(options, '');
90
- printMessage(options, ` ${statusIcon} ${chalk.bold(actionLabel + '完成')}${modeTag} ${chalk.dim(formatDuration(summary.durationMs))}`);
97
+ printMessage(options, ` ${statusIcon} ${chalk.bold(actionDoneLabel)}${modeTag} ${chalk.dim(formatDuration(summary.durationMs))}`);
91
98
  printMessage(options, '');
92
99
 
93
100
  const rows = [];
94
101
  if (summary.action === 'upload') {
95
- rows.push([chalk.dim('上传'), chalk.green(`${uploaded} 个文件`)]);
102
+ rows.push([chalk.dim(messages.uploadLabel), chalk.green(`${uploaded} ${messages.uploadFileUnit}`)]);
96
103
  if (skipped > 0) {
97
- rows.push([chalk.dim('跳过'), chalk.cyan(`${skipped} 个文件`) + chalk.dim(' (缓存命中,无变更)')]);
104
+ rows.push([chalk.dim(messages.uploadSkippedLabel), chalk.cyan(`${skipped} ${messages.uploadFileUnit}`) + chalk.dim(` (${messages.uploadCacheHint})`)]);
98
105
  }
99
106
  if (summary.failedCount > 0) {
100
- rows.push([chalk.dim('失败'), chalk.red(`${summary.failedCount} 个文件`)]);
107
+ rows.push([chalk.dim(messages.uploadFailedLabel), chalk.red(`${summary.failedCount} ${messages.uploadFileUnit}`)]);
101
108
  }
102
- rows.push([chalk.dim('合计'), chalk.white(`${summary.total} 个文件`)]);
109
+ rows.push([chalk.dim(messages.uploadTotalLabel), chalk.white(`${summary.total} ${messages.uploadFileUnit}`)]);
103
110
  } else {
104
- rows.push([chalk.dim('成功'), chalk.green(`${summary.succeededCount} 个`)]);
111
+ rows.push([chalk.dim(messages.refreshSucceededLabel), chalk.green(`${summary.succeededCount} ${messages.refreshUnit}`)]);
105
112
  if (summary.failedCount > 0) {
106
- rows.push([chalk.dim('失败'), chalk.red(`${summary.failedCount} 个`)]);
113
+ rows.push([chalk.dim(messages.refreshFailedLabel), chalk.red(`${summary.failedCount} ${messages.refreshUnit}`)]);
107
114
  }
108
- rows.push([chalk.dim('合计'), chalk.white(`${summary.total} 个`)]);
115
+ rows.push([chalk.dim(messages.refreshTotalLabel), chalk.white(`${summary.total} ${messages.refreshUnit}`)]);
109
116
  }
110
117
 
111
- const labelWidth = 4;
112
118
  rows.forEach(([label, value]) => {
113
- printMessage(options, ` ${label.padEnd ? label : label} ${value}`);
119
+ printMessage(options, ` ${label} ${value}`);
114
120
  });
115
121
  printMessage(options, '');
116
122
 
@@ -124,7 +130,7 @@ function logSummary(summary, options) {
124
130
 
125
131
  const uploadedItems = (summary.succeeded || []).filter((item) => !item.skipped && item.target);
126
132
  if (uploadedItems.length > 0) {
127
- printMessage(options, ` ${chalk.dim('─── 已上传文件链接 ───')}`);
133
+ printMessage(options, ` ${chalk.dim(`─── ${messages.uploadLinksTitle} ───`)}`);
128
134
  printMessage(options, '');
129
135
  uploadedItems.forEach((item) => {
130
136
  printMessage(options, ` ${chalk.cyan(item.target)}`);
@@ -156,17 +162,25 @@ function finalizeOutput(summary, options, extra = {}) {
156
162
  } else {
157
163
  logSummary(finalSummary, options);
158
164
  if (finalSummary.manifestPath) {
159
- printMessage(options, `[manifest] ${finalSummary.manifestPath}`);
165
+ const { messages } = getLocale(options && options.lang);
166
+ printMessage(options, ` ${chalk.dim(messages.manifestWritten)} ${finalSummary.manifestPath}`);
160
167
  }
161
168
  }
162
169
 
163
170
  return finalSummary;
164
171
  }
165
172
 
166
- function ensureRequiredConfig(config, requiredFields) {
173
+ function ensureRequiredConfig(config, requiredFields, options) {
167
174
  const missingFields = requiredFields.filter((field) => !config[field]);
168
175
  if (missingFields.length > 0) {
169
- throw new Error(`Missing required config: ${missingFields.join(', ')}`);
176
+ const { messages } = getLocale(options && options.lang);
177
+ const lines = [
178
+ messages.errorMissingConfig,
179
+ ...missingFields.map((f) => ` · ${f}`),
180
+ '',
181
+ messages.errorMissingConfigHint,
182
+ ];
183
+ throw new Error(lines.join('\n'));
170
184
  }
171
185
  }
172
186
 
package/lib/i18n.js CHANGED
@@ -1,6 +1,40 @@
1
1
  const LANGUAGES = {
2
2
  zh: {
3
3
  appDescription: '用于将前端打包产物上传到七牛云 OSS 的命令行工具。',
4
+ messages: {
5
+ initTemplateCreated: '七牛配置不存在,已生成模版,请填写必要信息:',
6
+ initConfigUpdated: '配置已更新:',
7
+ initDryRunCreate: (p) => `[预览] 将创建配置模版:${p}`,
8
+ initDryRunWrite: (p) => `[预览] 将写入配置:${p}`,
9
+ uploadRetrying: (file, attempt) => `${file} 上传失败,正在进行第 ${attempt} 次重试`,
10
+ uploadDone: '上传完成',
11
+ uploadLabel: '上传',
12
+ uploadSkippedLabel: '跳过',
13
+ uploadFailedLabel: '失败',
14
+ uploadTotalLabel: '合计',
15
+ uploadCacheHint: '缓存命中,无变更',
16
+ uploadLinksTitle: '已上传文件链接',
17
+ uploadFileUnit: '个文件',
18
+ uploadFailed: (n) => `上传完成,${n} 个失败`,
19
+ refreshDone: '刷新完成',
20
+ refreshSucceededLabel: '成功',
21
+ refreshFailedLabel: '失败',
22
+ refreshTotalLabel: '合计',
23
+ refreshUnit: '个',
24
+ refreshFailed: (n) => `刷新完成,${n} 个失败`,
25
+ deployDone: '部署完成',
26
+ deploySkipRefresh: '刷新步骤已跳过 (--skip-refresh)',
27
+ deployUploadLabel: '上传',
28
+ deployRefreshLabel: '刷新',
29
+ deployFileUnit: '个文件',
30
+ deployUnit: '个',
31
+ errorTitle: '出错了',
32
+ errorMissingConfig: '配置不完整,以下字段缺失或为空:',
33
+ errorMissingConfigHint: '请检查 command/qiniu.json 和 command/config.json,或运行 struggler-cli init 初始化配置。',
34
+ dryRunLabel: '预览',
35
+ dryRunPlan: (action, n) => `[预览] ${action} 计划 (${n} 个文件)`,
36
+ manifestWritten: '清单已写入:',
37
+ },
4
38
  options: {
5
39
  version: '显示版本号。',
6
40
  config: '指定上传配置文件路径。',
@@ -40,6 +74,40 @@ const LANGUAGES = {
40
74
  },
41
75
  en: {
42
76
  appDescription: 'CLI to upload front-end build files to Qiniu Cloud OSS.',
77
+ messages: {
78
+ initTemplateCreated: 'Qiniu config not found, template created. Please fill in the required fields:',
79
+ initConfigUpdated: 'Config updated:',
80
+ initDryRunCreate: (p) => `[dry-run] would create template: ${p}`,
81
+ initDryRunWrite: (p) => `[dry-run] would write config: ${p}`,
82
+ uploadRetrying: (file, attempt) => `${file} upload failed, retrying (attempt ${attempt})`,
83
+ uploadDone: 'Upload complete',
84
+ uploadLabel: 'Uploaded',
85
+ uploadSkippedLabel: 'Skipped',
86
+ uploadFailedLabel: 'Failed',
87
+ uploadTotalLabel: 'Total',
88
+ uploadCacheHint: 'cached, no changes',
89
+ uploadLinksTitle: 'Uploaded file links',
90
+ uploadFileUnit: 'files',
91
+ uploadFailed: (n) => `Upload finished with ${n} failures`,
92
+ refreshDone: 'Refresh complete',
93
+ refreshSucceededLabel: 'Succeeded',
94
+ refreshFailedLabel: 'Failed',
95
+ refreshTotalLabel: 'Total',
96
+ refreshUnit: '',
97
+ refreshFailed: (n) => `Refresh finished with ${n} failures`,
98
+ deployDone: 'Deploy complete',
99
+ deploySkipRefresh: 'Refresh step skipped (--skip-refresh)',
100
+ deployUploadLabel: 'Upload',
101
+ deployRefreshLabel: 'Refresh',
102
+ deployFileUnit: 'files',
103
+ deployUnit: '',
104
+ errorTitle: 'Error',
105
+ errorMissingConfig: 'Incomplete config, the following fields are missing or empty:',
106
+ errorMissingConfigHint: 'Check command/qiniu.json and command/config.json, or run struggler-cli init.',
107
+ dryRunLabel: 'dry-run',
108
+ dryRunPlan: (action, n) => `[dry-run] ${action} plan (${n} files)`,
109
+ manifestWritten: 'Manifest written:',
110
+ },
43
111
  options: {
44
112
  version: 'Display the version number.',
45
113
  config: 'Specify the path to the upload configuration file.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@struggler/cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "CLI to Upload vite packaged files to Qiniu Cloud OSS.",
5
5
  "main": "index.js",
6
6
  "scripts": {