vite-plugin-deploy-oss 3.1.0 → 3.2.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
@@ -39,6 +39,9 @@ export default {
39
39
  // 默认 true:有上传失败时抛错并让构建失败
40
40
  failOnError: true,
41
41
 
42
+ // 生成并上传 OSS 汇总文件
43
+ manifest: true,
44
+
42
45
  // 修改打包后的资源路径
43
46
  configBase: `https://oss.eventnet.cn/H5/zz/test/`,
44
47
  }),
@@ -52,3 +55,21 @@ export default {
52
55
  - `open` 默认 `true`,建议通过环境变量控制开关(例如 `DEPLOY_OSS=1` 时再上传)。
53
56
  - `fancy` 默认 `true`,TTY 终端下会显示实时动效进度(速度、预计剩余、并发、当前文件)。
54
57
  - `failOnError` 默认 `true`,上传有失败会抛错,适合 CI 场景保证发布质量。
58
+ - `manifest` 默认关闭。开启后会在构建目录生成并上传 `oss-manifest.json`。
59
+ - `manifest: true` 时默认文件名为 `oss-manifest.json`,也支持 `manifest: { fileName: 'meta/oss-manifest.json' }` 自定义路径。
60
+ - `oss-manifest.json` 仅包含本次成功上传的文件,不包含汇总文件自身。
61
+ - `oss-manifest.json` 内容示例:
62
+
63
+ ```json
64
+ {
65
+ "version": 1742467200000,
66
+ "files": [
67
+ {
68
+ "file": "assets/index-abc123.js",
69
+ "key": "H5/zz/test/assets/index-abc123.js",
70
+ "url": "https://oss.eventnet.cn/H5/zz/test/assets/index-abc123.js",
71
+ "md5": "d41d8cd98f00b204e9800998ecf8427e"
72
+ }
73
+ ]
74
+ }
75
+ ```
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import oss from 'ali-oss';
2
2
  import { Plugin } from 'vite';
3
3
 
4
+ interface ManifestOption {
5
+ fileName?: string;
6
+ }
4
7
  interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'accessKeySecret' | 'bucket' | 'region'> {
5
8
  configBase?: string;
6
9
  accessKeyId: string;
@@ -20,6 +23,7 @@ interface vitePluginDeployOssOption extends Omit<oss.Options, 'accessKeyId' | 'a
20
23
  concurrency?: number;
21
24
  retryTimes?: number;
22
25
  multipartThreshold?: number;
26
+ manifest?: boolean | ManifestOption;
23
27
  }
24
28
  declare function vitePluginDeployOss(option: vitePluginDeployOssOption): Plugin;
25
29
 
package/dist/index.js CHANGED
@@ -2,11 +2,23 @@
2
2
  import oss from "ali-oss";
3
3
  import chalk from "chalk";
4
4
  import { globSync } from "glob";
5
- import { readdir, rm, stat, unlink } from "fs/promises";
6
- import { resolve } from "path";
5
+ import { createHash } from "crypto";
6
+ import { createReadStream } from "fs";
7
+ import { mkdir, readdir, rm, stat, unlink, writeFile } from "fs/promises";
8
+ import { dirname, resolve } from "path";
7
9
  import ora from "ora";
8
10
  import { normalizePath } from "vite";
11
+ var getFileMd5 = (filePath) => {
12
+ return new Promise((resolve2, reject) => {
13
+ const hash = createHash("md5");
14
+ const stream = createReadStream(filePath);
15
+ stream.on("error", (err) => reject(err));
16
+ stream.on("data", (chunk) => hash.update(chunk));
17
+ stream.on("end", () => resolve2(hash.digest("hex")));
18
+ });
19
+ };
9
20
  var GARBAGE_FILE_REGEX = /(?:Thumbs\.db|\.DS_Store)$/i;
21
+ var DEFAULT_MANIFEST_FILE_NAME = "oss-manifest.json";
10
22
  var removeEmptyDirectories = async (rootDir) => {
11
23
  const deletedDirectories = [];
12
24
  const visit = async (dirPath) => {
@@ -32,6 +44,55 @@ var removeEmptyDirectories = async (rootDir) => {
32
44
  return deletedDirectories;
33
45
  };
34
46
  var normalizeObjectKey = (targetDir, relativeFilePath) => normalizePath(`${targetDir}/${relativeFilePath}`).replace(/\/{2,}/g, "/").replace(/^\/+/, "");
47
+ var normalizeManifestFileName = (fileName) => {
48
+ const normalized = normalizePath(fileName || DEFAULT_MANIFEST_FILE_NAME).replace(/^\/+/, "").replace(/\/{2,}/g, "/");
49
+ return normalized || DEFAULT_MANIFEST_FILE_NAME;
50
+ };
51
+ var resolveManifestFileName = (manifest) => {
52
+ if (!manifest) return null;
53
+ if (manifest === true) return DEFAULT_MANIFEST_FILE_NAME;
54
+ return normalizeManifestFileName(manifest.fileName);
55
+ };
56
+ var normalizeUrlBase = (base) => {
57
+ const normalized = base.replace(/\\/g, "/");
58
+ const protocolSeparatorIndex = normalized.indexOf("://");
59
+ if (protocolSeparatorIndex >= 0) {
60
+ const pathIndex = normalized.indexOf("/", protocolSeparatorIndex + 3);
61
+ if (pathIndex < 0) return normalized;
62
+ return `${normalized.slice(0, pathIndex)}${normalized.slice(pathIndex).replace(/\/{2,}/g, "/")}`;
63
+ }
64
+ if (normalized.startsWith("//")) {
65
+ const pathIndex = normalized.indexOf("/", 2);
66
+ if (pathIndex < 0) return normalized;
67
+ return `${normalized.slice(0, pathIndex)}${normalized.slice(pathIndex).replace(/\/{2,}/g, "/")}`;
68
+ }
69
+ return normalized.replace(/\/{2,}/g, "/");
70
+ };
71
+ var encodeUrlPath = (path) => encodeURI(path.replace(/^\/+/, ""));
72
+ var joinUrlPath = (base, path) => `${normalizeUrlBase(base).replace(/\/+$/, "")}/${encodeUrlPath(path)}`;
73
+ var resolveUploadedFileUrl = (relativeFilePath, objectKey, configBase, alias) => {
74
+ if (configBase) return joinUrlPath(configBase, relativeFilePath);
75
+ if (alias) return joinUrlPath(alias, objectKey);
76
+ return objectKey;
77
+ };
78
+ var createManifestPayload = async (results, configBase, alias) => {
79
+ const successfulResults = results.filter((result) => result.success);
80
+ const files = await Promise.all(
81
+ successfulResults.map(async (result) => {
82
+ const md5 = await getFileMd5(result.file);
83
+ return {
84
+ file: result.relativeFilePath,
85
+ key: result.name,
86
+ url: resolveUploadedFileUrl(result.relativeFilePath, result.name, configBase, alias),
87
+ md5
88
+ };
89
+ })
90
+ );
91
+ return {
92
+ version: Date.now(),
93
+ files
94
+ };
95
+ };
35
96
  var formatBytes = (bytes) => {
36
97
  if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
37
98
  const units = ["B", "KB", "MB", "GB", "TB"];
@@ -91,6 +152,7 @@ function vitePluginDeployOss(option) {
91
152
  concurrency = 5,
92
153
  retryTimes = 3,
93
154
  multipartThreshold = 10 * 1024 * 1024,
155
+ manifest = false,
94
156
  ...props
95
157
  } = option || {};
96
158
  let buildFailed = false;
@@ -115,12 +177,13 @@ function vitePluginDeployOss(option) {
115
177
  errors.push("multipartThreshold must be > 0");
116
178
  return errors;
117
179
  };
180
+ const uploadSingleTask = async (client, task) => uploadFileWithRetry(client, task, false);
118
181
  const uploadFileWithRetry = async (client, task, silentLogs, maxRetries = retryTimes) => {
119
182
  const shouldUseMultipart = task.size >= multipartThreshold;
120
183
  const headers = {
121
184
  "x-oss-storage-class": "Standard",
122
185
  "x-oss-object-acl": "default",
123
- "Cache-Control": noCache ? "no-cache" : "public, max-age=86400, immutable",
186
+ "Cache-Control": task.cacheControl || (noCache || task.name.endsWith(".html") ? "no-cache" : "public, max-age=86400, immutable"),
124
187
  "x-oss-forbid-overwrite": overwrite ? "false" : "true"
125
188
  };
126
189
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -142,7 +205,14 @@ function vitePluginDeployOss(option) {
142
205
  console.warn(`${chalk.yellow("\u26A0")} \u5220\u9664\u672C\u5730\u6587\u4EF6\u5931\u8D25: ${task.filePath}`);
143
206
  }
144
207
  }
145
- return { success: true, file: task.filePath, name: task.name, size: task.size, retries: attempt - 1 };
208
+ return {
209
+ success: true,
210
+ file: task.filePath,
211
+ relativeFilePath: task.relativeFilePath,
212
+ name: task.name,
213
+ size: task.size,
214
+ retries: attempt - 1
215
+ };
146
216
  } else {
147
217
  throw new Error(`Upload failed with status: ${result.res.status}`);
148
218
  }
@@ -156,6 +226,7 @@ function vitePluginDeployOss(option) {
156
226
  return {
157
227
  success: false,
158
228
  file: task.filePath,
229
+ relativeFilePath: task.relativeFilePath,
159
230
  name: task.name,
160
231
  size: task.size,
161
232
  retries: attempt - 1,
@@ -172,6 +243,7 @@ function vitePluginDeployOss(option) {
172
243
  return {
173
244
  success: false,
174
245
  file: task.filePath,
246
+ relativeFilePath: task.relativeFilePath,
175
247
  name: task.name,
176
248
  size: task.size,
177
249
  retries: maxRetries,
@@ -192,9 +264,9 @@ function vitePluginDeployOss(option) {
192
264
  const name = normalizeObjectKey(uploadDir, relativeFilePath);
193
265
  try {
194
266
  const fileStats = await stat(filePath);
195
- return { task: { filePath, name, size: fileStats.size } };
267
+ return { task: { filePath, relativeFilePath, name, size: fileStats.size } };
196
268
  } catch (error) {
197
- return { task: null, error, filePath, name };
269
+ return { task: null, error, filePath, relativeFilePath, name };
198
270
  }
199
271
  })
200
272
  );
@@ -207,6 +279,7 @@ function vitePluginDeployOss(option) {
207
279
  results.push({
208
280
  success: false,
209
281
  file: candidate.filePath,
282
+ relativeFilePath: candidate.relativeFilePath,
210
283
  name: candidate.name,
211
284
  size: 0,
212
285
  retries: 0,
@@ -321,11 +394,12 @@ ${validationErrors.map((err) => ` - ${err}`).join("\n")}`);
321
394
  if (!open || !upload || buildFailed || !resolvedConfig) return;
322
395
  const startTime = Date.now();
323
396
  const client = new oss({ region, accessKeyId, accessKeySecret, secure, bucket, ...props });
397
+ const manifestFileName = resolveManifestFileName(manifest);
324
398
  const files = globSync("**/*", {
325
399
  cwd: outDir,
326
400
  nodir: true,
327
401
  ignore: Array.isArray(skip) ? skip : [skip]
328
- }).map((file) => normalizePath(file));
402
+ }).map((file) => normalizePath(file)).filter((file) => file !== manifestFileName);
329
403
  if (files.length === 0) {
330
404
  console.log(`${chalk.yellow("\u26A0 \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6")}`);
331
405
  return;
@@ -382,6 +456,39 @@ ${chalk.gray("\u7EDF\u8BA1:")}`);
382
456
  }
383
457
  console.log("");
384
458
  }
459
+ if (manifestFileName) {
460
+ const manifestRelativeFilePath = manifestFileName;
461
+ const manifestFilePath = normalizePath(resolve(outDir, manifestRelativeFilePath));
462
+ const manifestObjectKey = normalizeObjectKey(uploadDir, manifestRelativeFilePath);
463
+ await mkdir(dirname(manifestFilePath), { recursive: true });
464
+ await writeFile(
465
+ manifestFilePath,
466
+ JSON.stringify(await createManifestPayload(results, configBase, alias), null, 2),
467
+ "utf8"
468
+ );
469
+ const manifestStats = await stat(manifestFilePath);
470
+ const manifestResult = await uploadSingleTask(client, {
471
+ filePath: manifestFilePath,
472
+ relativeFilePath: manifestRelativeFilePath,
473
+ name: manifestObjectKey,
474
+ size: manifestStats.size,
475
+ cacheControl: "no-cache, no-store, must-revalidate"
476
+ });
477
+ if (!manifestResult.success) {
478
+ throw manifestResult.error || new Error(`Failed to upload manifest: ${manifestRelativeFilePath}`);
479
+ }
480
+ const manifestUrl = resolveUploadedFileUrl(
481
+ manifestRelativeFilePath,
482
+ manifestObjectKey,
483
+ configBase,
484
+ alias
485
+ );
486
+ console.log(chalk.cyan("Manifest:"));
487
+ console.log(` ${chalk.gray("File:")} ${chalk.yellow(manifestFilePath)}`);
488
+ console.log(` ${chalk.gray("Target:")} ${chalk.yellow(manifestObjectKey)}`);
489
+ console.log(` ${chalk.gray("URL:")} ${chalk.green(manifestUrl)}`);
490
+ console.log("");
491
+ }
385
492
  try {
386
493
  await removeEmptyDirectories(outDir);
387
494
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-deploy-oss",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",