@struggler/cli 1.0.6 → 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 +16 -2
- package/command/init.js +10 -7
- package/command/refresh.js +15 -5
- package/command/upload.js +22 -6
- package/index.js +9 -2
- package/lib/deploy.js +75 -8
- package/lib/i18n.js +68 -0
- package/lib/progress.js +82 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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.
|
|
11
|
-
printMessage(options, chalk.
|
|
12
|
+
printMessage(options, chalk.yellow(` ⚠ ${messages.initTemplateCreated}`));
|
|
13
|
+
printMessage(options, ` ${chalk.dim(qiniuConfigPath)}`);
|
|
12
14
|
if (options.dryRun) {
|
|
13
|
-
printMessage(options,
|
|
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,
|
|
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,
|
|
38
|
+
printMessage(options, ` ${chalk.green('✓')} ${messages.initConfigUpdated}`);
|
|
39
|
+
printMessage(options, ` ${chalk.dim(configPath)}`);
|
|
37
40
|
setJsonData(configPath, config)
|
|
38
41
|
return config
|
|
39
42
|
}
|
package/command/refresh.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
@@ -17,6 +17,8 @@ const {
|
|
|
17
17
|
} = require('../lib/deploy');
|
|
18
18
|
const { printMessage } = require('../lib/output');
|
|
19
19
|
const { computeFileMd5, readCache, writeCache, isCacheHit, updateCacheEntry } = require('../lib/cache');
|
|
20
|
+
const { createProgressBar } = require('../lib/progress');
|
|
21
|
+
const { getLocale } = require('../lib/i18n');
|
|
20
22
|
|
|
21
23
|
async function main(options, runtime = {}) {
|
|
22
24
|
const qiniuConfig = getJsonData(getQiniuConfig(options))
|
|
@@ -74,7 +76,8 @@ async function main(options, runtime = {}) {
|
|
|
74
76
|
|
|
75
77
|
ensureRequiredConfig(
|
|
76
78
|
{ ...qiniuConfig, 'publicPath(config.json)': prefix },
|
|
77
|
-
['accessKey', 'secretKey', 'Bucket', 'zone', 'domain', 'publicPath(config.json)']
|
|
79
|
+
['accessKey', 'secretKey', 'Bucket', 'zone', 'domain', 'publicPath(config.json)'],
|
|
80
|
+
options
|
|
78
81
|
);
|
|
79
82
|
|
|
80
83
|
var accessKey = qiniuConfig.accessKey
|
|
@@ -99,7 +102,8 @@ async function main(options, runtime = {}) {
|
|
|
99
102
|
formUploader.putFile(uploadToken, plan.key, plan.localFile, putExtra, async function (respErr, respBody, respInfo) {
|
|
100
103
|
if (respErr || respInfo.statusCode !== 200) {
|
|
101
104
|
if (attempt < 3) {
|
|
102
|
-
|
|
105
|
+
const { messages } = getLocale(options && options.lang);
|
|
106
|
+
printMessage(options, messages.uploadRetrying(plan.localFile, attempt + 1));
|
|
103
107
|
try {
|
|
104
108
|
const retryResult = await upload(plan, attempt + 1);
|
|
105
109
|
resolve(retryResult);
|
|
@@ -123,12 +127,20 @@ async function main(options, runtime = {}) {
|
|
|
123
127
|
});
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
const totalFiles = plans.length + skippedPlans.length;
|
|
131
|
+
const bar = createProgressBar(totalFiles, {
|
|
132
|
+
json: options.json,
|
|
133
|
+
suppressOutput: runtime.suppressOutput,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
for (const plan of skippedPlans) {
|
|
137
|
+
bar.tick({ filename: plan.localFile, skipped: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
const results = await runWithConcurrency(plans, normalizeConcurrency(options.concurrency), async (plan) => {
|
|
127
141
|
try {
|
|
128
142
|
const response = await upload(plan);
|
|
129
|
-
|
|
130
|
-
printMessage(options, `[uploaded] ${plan.localFile} -> ${plan.target}`);
|
|
131
|
-
}
|
|
143
|
+
bar.tick({ filename: plan.localFile });
|
|
132
144
|
if (useCache && plan.localMd5) {
|
|
133
145
|
updateCacheEntry(cache, plan.key, plan.localMd5, response.hash);
|
|
134
146
|
}
|
|
@@ -140,6 +152,7 @@ async function main(options, runtime = {}) {
|
|
|
140
152
|
hash: response.hash,
|
|
141
153
|
};
|
|
142
154
|
} catch (error) {
|
|
155
|
+
bar.tick({ filename: plan.localFile, failed: true });
|
|
143
156
|
return {
|
|
144
157
|
ok: false,
|
|
145
158
|
localFile: plan.localFile,
|
|
@@ -150,6 +163,8 @@ async function main(options, runtime = {}) {
|
|
|
150
163
|
}
|
|
151
164
|
});
|
|
152
165
|
|
|
166
|
+
bar.finish();
|
|
167
|
+
|
|
153
168
|
if (useCache) {
|
|
154
169
|
writeCache(cachePath, cache);
|
|
155
170
|
}
|
|
@@ -172,7 +187,8 @@ async function main(options, runtime = {}) {
|
|
|
172
187
|
});
|
|
173
188
|
const finalSummary = runtime.suppressOutput ? summary : finalizeOutput(summary, options, runtime.manifestExtra);
|
|
174
189
|
if (finalSummary.failedCount > 0) {
|
|
175
|
-
|
|
190
|
+
const { messages } = getLocale(options && options.lang);
|
|
191
|
+
throw new Error(messages.uploadFailed(summary.failedCount));
|
|
176
192
|
}
|
|
177
193
|
|
|
178
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", {
|
|
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
|
-
|
|
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
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
+
const chalk = require('chalk');
|
|
2
3
|
const { listFiles } = require('./files');
|
|
3
4
|
const { createIgnoreMatcher } = require('./ignore');
|
|
4
5
|
const { printMessage, writeManifest, shouldUseJson, printJson } = require('./output');
|
|
6
|
+
const { getLocale } = require('./i18n');
|
|
5
7
|
|
|
6
8
|
const DEFAULT_CONCURRENCY = 5;
|
|
7
9
|
|
|
@@ -64,19 +66,76 @@ function createSummary(action, dryRun, results, startedAt, extra = {}) {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
function logPlan(action, items, options) {
|
|
67
|
-
|
|
69
|
+
const { messages } = getLocale(options && options.lang);
|
|
70
|
+
printMessage(options, messages.dryRunPlan(action, items.length));
|
|
68
71
|
items.forEach((item) => {
|
|
69
|
-
printMessage(options,
|
|
72
|
+
printMessage(options, ` ${item.localFile} -> ${item.target}`);
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
function formatDuration(ms) {
|
|
77
|
+
if (ms < 1000) return `${ms}ms`;
|
|
78
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
function logSummary(summary, options) {
|
|
74
|
-
const
|
|
75
|
-
|
|
82
|
+
const { messages } = getLocale(options && options.lang);
|
|
83
|
+
const allOk = summary.failedCount === 0;
|
|
84
|
+
const isDryRun = summary.dryRun;
|
|
85
|
+
const skipped = summary.skippedCount || 0;
|
|
86
|
+
const uploaded = summary.succeededCount - skipped;
|
|
87
|
+
|
|
88
|
+
const statusIcon = allOk ? chalk.green('✓') : chalk.red('✗');
|
|
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}]`) : '';
|
|
95
|
+
|
|
96
|
+
printMessage(options, '');
|
|
97
|
+
printMessage(options, ` ${statusIcon} ${chalk.bold(actionDoneLabel)}${modeTag} ${chalk.dim(formatDuration(summary.durationMs))}`);
|
|
98
|
+
printMessage(options, '');
|
|
99
|
+
|
|
100
|
+
const rows = [];
|
|
101
|
+
if (summary.action === 'upload') {
|
|
102
|
+
rows.push([chalk.dim(messages.uploadLabel), chalk.green(`${uploaded} ${messages.uploadFileUnit}`)]);
|
|
103
|
+
if (skipped > 0) {
|
|
104
|
+
rows.push([chalk.dim(messages.uploadSkippedLabel), chalk.cyan(`${skipped} ${messages.uploadFileUnit}`) + chalk.dim(` (${messages.uploadCacheHint})`)]);
|
|
105
|
+
}
|
|
106
|
+
if (summary.failedCount > 0) {
|
|
107
|
+
rows.push([chalk.dim(messages.uploadFailedLabel), chalk.red(`${summary.failedCount} ${messages.uploadFileUnit}`)]);
|
|
108
|
+
}
|
|
109
|
+
rows.push([chalk.dim(messages.uploadTotalLabel), chalk.white(`${summary.total} ${messages.uploadFileUnit}`)]);
|
|
110
|
+
} else {
|
|
111
|
+
rows.push([chalk.dim(messages.refreshSucceededLabel), chalk.green(`${summary.succeededCount} ${messages.refreshUnit}`)]);
|
|
112
|
+
if (summary.failedCount > 0) {
|
|
113
|
+
rows.push([chalk.dim(messages.refreshFailedLabel), chalk.red(`${summary.failedCount} ${messages.refreshUnit}`)]);
|
|
114
|
+
}
|
|
115
|
+
rows.push([chalk.dim(messages.refreshTotalLabel), chalk.white(`${summary.total} ${messages.refreshUnit}`)]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
rows.forEach(([label, value]) => {
|
|
119
|
+
printMessage(options, ` ${label} ${value}`);
|
|
120
|
+
});
|
|
121
|
+
printMessage(options, '');
|
|
122
|
+
|
|
76
123
|
if (summary.failedCount > 0) {
|
|
77
124
|
summary.failed.forEach((item) => {
|
|
78
|
-
printMessage(options, `
|
|
125
|
+
printMessage(options, ` ${chalk.red('✗')} ${chalk.dim(item.localFile)}`);
|
|
126
|
+
printMessage(options, ` ${chalk.red(item.error)}`);
|
|
127
|
+
});
|
|
128
|
+
printMessage(options, '');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const uploadedItems = (summary.succeeded || []).filter((item) => !item.skipped && item.target);
|
|
132
|
+
if (uploadedItems.length > 0) {
|
|
133
|
+
printMessage(options, ` ${chalk.dim(`─── ${messages.uploadLinksTitle} ───`)}`);
|
|
134
|
+
printMessage(options, '');
|
|
135
|
+
uploadedItems.forEach((item) => {
|
|
136
|
+
printMessage(options, ` ${chalk.cyan(item.target)}`);
|
|
79
137
|
});
|
|
138
|
+
printMessage(options, '');
|
|
80
139
|
}
|
|
81
140
|
}
|
|
82
141
|
|
|
@@ -103,17 +162,25 @@ function finalizeOutput(summary, options, extra = {}) {
|
|
|
103
162
|
} else {
|
|
104
163
|
logSummary(finalSummary, options);
|
|
105
164
|
if (finalSummary.manifestPath) {
|
|
106
|
-
|
|
165
|
+
const { messages } = getLocale(options && options.lang);
|
|
166
|
+
printMessage(options, ` ${chalk.dim(messages.manifestWritten)} ${finalSummary.manifestPath}`);
|
|
107
167
|
}
|
|
108
168
|
}
|
|
109
169
|
|
|
110
170
|
return finalSummary;
|
|
111
171
|
}
|
|
112
172
|
|
|
113
|
-
function ensureRequiredConfig(config, requiredFields) {
|
|
173
|
+
function ensureRequiredConfig(config, requiredFields, options) {
|
|
114
174
|
const missingFields = requiredFields.filter((field) => !config[field]);
|
|
115
175
|
if (missingFields.length > 0) {
|
|
116
|
-
|
|
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'));
|
|
117
184
|
}
|
|
118
185
|
}
|
|
119
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/lib/progress.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const BAR_WIDTH = 30;
|
|
4
|
+
|
|
5
|
+
function buildBar(completed, total) {
|
|
6
|
+
const ratio = total === 0 ? 1 : completed / total;
|
|
7
|
+
const filled = Math.round(BAR_WIDTH * ratio);
|
|
8
|
+
const empty = BAR_WIDTH - filled;
|
|
9
|
+
return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function truncateFilename(filename, maxLen) {
|
|
13
|
+
if (!filename || filename.length <= maxLen) {
|
|
14
|
+
return (filename || '').padEnd(maxLen);
|
|
15
|
+
}
|
|
16
|
+
return '...' + filename.slice(-(maxLen - 3));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createProgressBar(total, options = {}) {
|
|
20
|
+
if (options.json || options.suppressOutput || total === 0) {
|
|
21
|
+
return { tick: () => {}, finish: () => {} };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let completed = 0;
|
|
25
|
+
let failed = 0;
|
|
26
|
+
let skipped = 0;
|
|
27
|
+
let lastLine = '';
|
|
28
|
+
|
|
29
|
+
const isTTY = process.stdout.isTTY;
|
|
30
|
+
|
|
31
|
+
function render(currentFile, status) {
|
|
32
|
+
const bar = buildBar(completed, total);
|
|
33
|
+
const pct = total === 0 ? 100 : Math.round((completed / total) * 100);
|
|
34
|
+
const cols = (process.stdout.columns || 80) - BAR_WIDTH - 30;
|
|
35
|
+
const nameWidth = Math.max(10, Math.min(40, cols));
|
|
36
|
+
const name = truncateFilename(currentFile, nameWidth);
|
|
37
|
+
|
|
38
|
+
let statusIcon;
|
|
39
|
+
if (status === 'skipped') {
|
|
40
|
+
statusIcon = chalk.cyan('↩');
|
|
41
|
+
} else if (status === 'failed') {
|
|
42
|
+
statusIcon = chalk.red('✗');
|
|
43
|
+
} else {
|
|
44
|
+
statusIcon = chalk.green('✓');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const countStr = chalk.white(`${completed}/${total}`);
|
|
48
|
+
const pctStr = chalk.yellow(`${pct}%`);
|
|
49
|
+
const line = ` ${bar} ${countStr} ${pctStr} ${statusIcon} ${chalk.dim(name)}`;
|
|
50
|
+
|
|
51
|
+
if (isTTY) {
|
|
52
|
+
process.stdout.write('\r' + line.padEnd(lastLine.length));
|
|
53
|
+
} else {
|
|
54
|
+
console.log(line);
|
|
55
|
+
}
|
|
56
|
+
lastLine = line;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function tick({ filename = '', skipped: isSkipped = false, failed: isFailed = false } = {}) {
|
|
60
|
+
completed += 1;
|
|
61
|
+
if (isSkipped) skipped += 1;
|
|
62
|
+
if (isFailed) failed += 1;
|
|
63
|
+
render(filename, isSkipped ? 'skipped' : isFailed ? 'failed' : 'uploaded');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function finish() {
|
|
67
|
+
if (!isTTY) return;
|
|
68
|
+
const bar = buildBar(total, total);
|
|
69
|
+
const parts = [
|
|
70
|
+
` ${bar}`,
|
|
71
|
+
chalk.white(`${total}/${total}`),
|
|
72
|
+
chalk.green('100%'),
|
|
73
|
+
];
|
|
74
|
+
if (skipped > 0) parts.push(chalk.cyan(`${skipped} skipped`));
|
|
75
|
+
if (failed > 0) parts.push(chalk.red(`${failed} failed`));
|
|
76
|
+
process.stdout.write('\r' + parts.join(' ').padEnd(lastLine.length) + '\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { tick, finish };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { createProgressBar };
|