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 +21 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +114 -7
- package/package.json +1 -1
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 {
|
|
6
|
-
import {
|
|
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 {
|
|
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) {
|