@struggler/cli 1.0.9 → 1.0.11

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/upload.js CHANGED
@@ -19,6 +19,7 @@ const { printMessage } = require('../lib/output');
19
19
  const { computeFileMd5, readCache, writeCache, isCacheHit, updateCacheEntry } = require('../lib/cache');
20
20
  const { createProgressBar } = require('../lib/progress');
21
21
  const { getLocale } = require('../lib/i18n');
22
+ const { resolveMimeType } = require('../lib/mime');
22
23
 
23
24
  async function main(options, runtime = {}) {
24
25
  const qiniuConfig = getJsonData(getQiniuConfig(options))
@@ -89,9 +90,10 @@ async function main(options, runtime = {}) {
89
90
  config.useCdnDomain = true;
90
91
 
91
92
  var formUploader = new qiniu.form_up.FormUploader(config);
92
- var putExtra = new qiniu.form_up.PutExtra();
93
-
94
93
  function upload(plan, attempt = 1) {
94
+ var putExtra = new qiniu.form_up.PutExtra();
95
+ putExtra.mimeType = resolveMimeType(plan.localFile);
96
+
95
97
  var uploadOptions = {
96
98
  scope: `${qiniuConfig.Bucket}:${plan.key}`
97
99
  };
package/index.js CHANGED
@@ -58,7 +58,7 @@ program.configureHelp({
58
58
  },
59
59
  })
60
60
 
61
- program.name("struggler-cli").description(locale.appDescription).version(packageJson.version, "-v, --version", locale.options.version).helpOption("-h, --help", locale.options.help).option("-c, --config <path>", locale.options.config, "./command/qiniu.json").option("-d, --dir <path>", locale.options.dir, "./dist").option("--dry-run", locale.options.dryRun).option("--concurrency <number>", locale.options.concurrency, "5").option("--exclude <pattern>", locale.options.exclude).option("--ignore-file <path>", locale.options.ignoreFile, ".strugglerignore").option("--manifest <path>", locale.options.manifest).option("--json", locale.options.json).option("--skip-init", locale.options.skipInit).option("--skip-refresh", locale.options.skipRefresh).option("--no-cache", locale.options.noCache).option("--lang <lang>", locale.options.lang, lang)
61
+ program.name("struggler-cli").description(locale.appDescription).version(packageJson.version, "-v, --version", locale.options.version).helpOption("-h, --help", locale.options.help).option("-c, --config <path>", locale.options.config, "./command/qiniu.json").option("--config-dir <path>", locale.options.configDir).option("-d, --dir <path>", locale.options.dir, "./dist").option("--dry-run", locale.options.dryRun).option("--concurrency <number>", locale.options.concurrency, "5").option("--exclude <pattern>", locale.options.exclude).option("--ignore-file <path>", locale.options.ignoreFile, ".strugglerignore").option("--manifest <path>", locale.options.manifest).option("--json", locale.options.json).option("--skip-init", locale.options.skipInit).option("--skip-refresh", locale.options.skipRefresh).option("--no-cache", locale.options.noCache).option("--lang <lang>", locale.options.lang, lang)
62
62
 
63
63
  program
64
64
  .command("init")
package/lib/config.js CHANGED
@@ -1,6 +1,8 @@
1
1
  let path = require('path')
2
+ const { getJsonData } = require('./files');
2
3
 
3
4
  const DEFAULT_QINIU_CONFIG = './command/qiniu.json';
5
+ const DEFAULT_META_DIR = './command';
4
6
  const DEFAULT_CONFIG = './command/config.json';
5
7
  const DEFAULT_CACHE = './command/upload-cache.json';
6
8
 
@@ -12,22 +14,31 @@ function getQiniuConfigPath(options = {}) {
12
14
  return resolveFromCwd(options.config || DEFAULT_QINIU_CONFIG);
13
15
  }
14
16
 
15
- function getConfigPath(options = {}) {
16
- if (!options.config) {
17
- return resolveFromCwd(DEFAULT_CONFIG);
17
+ /** config.json / upload-cache.json 所在目录;优先级:CLI --config-dir > qiniu.json configDir > 与 qiniu 同目录 > ./command */
18
+ function getMetaDir(options = {}) {
19
+ if (options.configDir) {
20
+ return resolveFromCwd(options.configDir);
18
21
  }
19
22
 
20
23
  const qiniuConfigPath = getQiniuConfigPath(options);
21
- return path.join(path.dirname(qiniuConfigPath), 'config.json');
22
- }
24
+ const qiniuConfig = getJsonData(qiniuConfigPath);
25
+ if (qiniuConfig.configDir) {
26
+ return resolveFromCwd(qiniuConfig.configDir);
27
+ }
23
28
 
24
- function getCachePath(options = {}) {
25
29
  if (!options.config) {
26
- return resolveFromCwd(DEFAULT_CACHE);
30
+ return resolveFromCwd(DEFAULT_META_DIR);
27
31
  }
28
32
 
29
- const qiniuConfigPath = getQiniuConfigPath(options);
30
- return path.join(path.dirname(qiniuConfigPath), 'upload-cache.json');
33
+ return path.dirname(qiniuConfigPath);
34
+ }
35
+
36
+ function getConfigPath(options = {}) {
37
+ return path.join(getMetaDir(options), 'config.json');
38
+ }
39
+
40
+ function getCachePath(options = {}) {
41
+ return path.join(getMetaDir(options), 'upload-cache.json');
31
42
  }
32
43
 
33
44
  module.exports = {
@@ -40,6 +51,9 @@ module.exports = {
40
51
  getCachePath: (options) => {
41
52
  return getCachePath(options)
42
53
  },
54
+ getMetaDir: (options) => {
55
+ return getMetaDir(options)
56
+ },
43
57
  getDir: (options) => {
44
58
  return resolveFromCwd(options.dir || './dist')
45
59
  },
package/lib/i18n.js CHANGED
@@ -38,6 +38,7 @@ const LANGUAGES = {
38
38
  options: {
39
39
  version: '显示版本号。',
40
40
  config: '指定上传配置文件路径。',
41
+ configDir: '指定 config.json、upload-cache.json 所在目录(默认与 -c 同目录,未传 -c 时为 ./command)。',
41
42
  dir: '指定要上传的目录。',
42
43
  dryRun: '仅预览执行计划,不写文件也不调用七牛接口。',
43
44
  concurrency: '设置上传并发数。',
@@ -111,6 +112,7 @@ const LANGUAGES = {
111
112
  options: {
112
113
  version: 'Display the version number.',
113
114
  config: 'Specify the path to the upload configuration file.',
115
+ configDir: 'Directory for config.json and upload-cache.json (default: same dir as -c, or ./command).',
114
116
  dir: 'Specify the directory to upload.',
115
117
  dryRun: 'Preview actions without writing files or calling Qiniu APIs.',
116
118
  concurrency: 'Set upload concurrency.',
package/lib/mime.js ADDED
@@ -0,0 +1,55 @@
1
+ const path = require('path');
2
+
3
+ const MIME_TYPES = {
4
+ '.js': 'application/javascript',
5
+ '.mjs': 'application/javascript',
6
+ '.cjs': 'application/javascript',
7
+ '.css': 'text/css',
8
+ '.html': 'text/html',
9
+ '.htm': 'text/html',
10
+ '.json': 'application/json',
11
+ '.map': 'application/json',
12
+ '.txt': 'text/plain',
13
+ '.xml': 'application/xml',
14
+ '.svg': 'image/svg+xml',
15
+ '.png': 'image/png',
16
+ '.jpg': 'image/jpeg',
17
+ '.jpeg': 'image/jpeg',
18
+ '.gif': 'image/gif',
19
+ '.webp': 'image/webp',
20
+ '.ico': 'image/x-icon',
21
+ '.woff': 'font/woff',
22
+ '.woff2': 'font/woff2',
23
+ '.ttf': 'font/ttf',
24
+ '.otf': 'font/otf',
25
+ '.eot': 'application/vnd.ms-fontobject',
26
+ '.wasm': 'application/wasm',
27
+ };
28
+
29
+ function loadMimeResolver() {
30
+ try {
31
+ const qiniuDir = path.dirname(require.resolve('qiniu'));
32
+ const mimePath = require.resolve('mime', { paths: [qiniuDir] });
33
+ return require(mimePath);
34
+ } catch (error) {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ const mimeResolver = loadMimeResolver();
40
+
41
+ function resolveMimeType(filePath) {
42
+ if (mimeResolver && typeof mimeResolver.getType === 'function') {
43
+ const detected = mimeResolver.getType(filePath);
44
+ if (detected) {
45
+ return detected;
46
+ }
47
+ }
48
+
49
+ const extension = path.extname(filePath).toLowerCase();
50
+ return MIME_TYPES[extension] || null;
51
+ }
52
+
53
+ module.exports = {
54
+ resolveMimeType,
55
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@struggler/cli",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "CLI to Upload vite packaged files to Qiniu Cloud OSS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -16,14 +16,16 @@
16
16
  ],
17
17
  "author": "moqi(str@li.cm)",
18
18
  "license": "ISC",
19
+ "engines": {
20
+ "node": ">=16"
21
+ },
19
22
  "dependencies": {
20
23
  "chalk": "^v4.1.2",
21
24
  "clear": "^0.1.0",
22
25
  "commander": "^11.0.0",
23
- "figlet": "^1.6.0",
24
26
  "qiniu": "^7.8.0"
25
27
  },
26
28
  "publicConfig": {
27
29
  "registry": "http://registry.npmjs.org/"
28
30
  }
29
- }
31
+ }
@@ -24,6 +24,18 @@ test('config helpers resolve qiniu and derived config paths together', () => {
24
24
  );
25
25
  });
26
26
 
27
+ test('config-dir overrides default meta directory', () => {
28
+ const options = {
29
+ config: './test/command/qiniu.json',
30
+ configDir: './test/meta',
31
+ };
32
+
33
+ assert.equal(
34
+ getConfig(options),
35
+ `${process.cwd()}/test/meta/config.json`
36
+ );
37
+ });
38
+
27
39
  test('deploy helpers normalize concurrency and build remote keys', () => {
28
40
  assert.equal(normalizeConcurrency('0'), 5);
29
41
  assert.equal(normalizeConcurrency('3'), 3);
@@ -3,8 +3,10 @@ const assert = require('node:assert/strict');
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
+ const qiniu = require('qiniu');
6
7
  const upload = require('../command/upload');
7
8
  const deploy = require('../command/deploy');
9
+ const { resolveMimeType } = require('../lib/mime');
8
10
 
9
11
  function createWorkspace() {
10
12
  const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'struggler-cli-upload-'));
@@ -111,3 +113,71 @@ test('deploy dry-run supports skip-refresh and json-friendly summary data', asyn
111
113
  fs.rmSync(workspace, { recursive: true, force: true });
112
114
  }
113
115
  });
116
+
117
+ test('upload sets explicit mimeType for web assets', async () => {
118
+ const previousCwd = process.cwd();
119
+ const workspace = createWorkspace();
120
+ const captured = [];
121
+
122
+ const originalMac = qiniu.auth.digest.Mac;
123
+ const originalPutPolicy = qiniu.rs.PutPolicy;
124
+ const originalFormUploader = qiniu.form_up.FormUploader;
125
+
126
+ try {
127
+ process.chdir(workspace);
128
+
129
+ fs.writeFileSync(path.join(workspace, 'command/qiniu.json'), JSON.stringify({
130
+ accessKey: 'ak',
131
+ secretKey: 'sk',
132
+ Bucket: 'demo-bucket',
133
+ zone: 'Zone_z0',
134
+ path: 'demo-app',
135
+ domain: 'https://cdn.example.com/',
136
+ }, null, 2));
137
+
138
+ qiniu.auth.digest.Mac = function MockMac() {};
139
+ qiniu.rs.PutPolicy = function MockPutPolicy() {
140
+ this.uploadToken = () => 'mock-token';
141
+ };
142
+ qiniu.form_up.FormUploader = function MockFormUploader() {
143
+ this.putFile = (uploadToken, key, localFile, putExtra, callback) => {
144
+ captured.push({
145
+ uploadToken,
146
+ key,
147
+ localFile,
148
+ mimeType: putExtra.mimeType,
149
+ });
150
+ callback(null, { hash: `${path.basename(localFile)}-hash` }, { statusCode: 200 });
151
+ };
152
+ };
153
+
154
+ const summary = await upload({
155
+ config: './command/qiniu.json',
156
+ dir: './dist',
157
+ concurrency: '1',
158
+ }, {
159
+ suppressOutput: true,
160
+ });
161
+
162
+ assert.equal(summary.failedCount, 0);
163
+ assert.deepEqual(
164
+ captured.map((item) => [path.basename(item.localFile), item.mimeType]).sort(),
165
+ [
166
+ ['app.js', 'application/javascript'],
167
+ ['index.html', 'text/html'],
168
+ ]
169
+ );
170
+ } finally {
171
+ qiniu.auth.digest.Mac = originalMac;
172
+ qiniu.rs.PutPolicy = originalPutPolicy;
173
+ qiniu.form_up.FormUploader = originalFormUploader;
174
+ process.chdir(previousCwd);
175
+ fs.rmSync(workspace, { recursive: true, force: true });
176
+ }
177
+ });
178
+
179
+ test('resolveMimeType covers common assets and keeps safe fallback', () => {
180
+ assert.equal(resolveMimeType('/tmp/app.js'), 'application/javascript');
181
+ assert.equal(resolveMimeType('/tmp/logo.svg'), 'image/svg+xml');
182
+ assert.equal(resolveMimeType('/tmp/data.unknown-ext'), null);
183
+ });