vite-plugin-multiversion 1.0.0
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 +135 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +126 -0
- package/dist/index.mjs +88 -0
- package/package.json +37 -0
- package/src/cleanOldVersions.ts +77 -0
- package/src/index.ts +2 -0
- package/src/versionLink.ts +38 -0
- package/tsconfig.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# vite-plugin-multiversion
|
|
2
|
+
|
|
3
|
+
通过打包进行多版本控制的 Vite 插件
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install vite-plugin-multiversion
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
### 1. vite.config.ts 配置
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineConfig, loadEnv } from 'vite'
|
|
17
|
+
import { resolve } from 'path'
|
|
18
|
+
import { versionLink, cleanOldVersions } from 'vite-plugin-multiversion'
|
|
19
|
+
|
|
20
|
+
export default defineConfig(({ mode }) => {
|
|
21
|
+
const env = loadEnv(mode, resolve(process.cwd()))
|
|
22
|
+
const NowVersion = Date.now()
|
|
23
|
+
const outDir = mode === 'prod' ? 'dist-prod' : 'dist'
|
|
24
|
+
|
|
25
|
+
const appUrlPath = () => {
|
|
26
|
+
const base = env.VITE_APP_URL_PATH
|
|
27
|
+
if (mode === 'dev') return base
|
|
28
|
+
return base + NowVersion + '/'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
base: appUrlPath(),
|
|
33
|
+
build: {
|
|
34
|
+
outDir: outDir + `/${NowVersion}`,
|
|
35
|
+
},
|
|
36
|
+
plugins: [
|
|
37
|
+
// 其他插件
|
|
38
|
+
].concat(env.VITE_APP_ENV === 'dev' ? [] : [
|
|
39
|
+
cleanOldVersions({
|
|
40
|
+
outDir,
|
|
41
|
+
now: NowVersion,
|
|
42
|
+
days: 1,
|
|
43
|
+
}),
|
|
44
|
+
versionLink({
|
|
45
|
+
versionDir: String(NowVersion),
|
|
46
|
+
outDir,
|
|
47
|
+
}),
|
|
48
|
+
]),
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. 环境变量
|
|
54
|
+
|
|
55
|
+
`.env` 文件:
|
|
56
|
+
```
|
|
57
|
+
VITE_APP_URL_PATH=/
|
|
58
|
+
VITE_APP_ENV=prod
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`.env.admin` 文件:
|
|
62
|
+
```
|
|
63
|
+
VITE_APP_URL_PATH=/admin-web/
|
|
64
|
+
VITE_APP_ENV=prod
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 插件说明
|
|
68
|
+
|
|
69
|
+
### versionLink
|
|
70
|
+
|
|
71
|
+
将版本目录下的 `index.html` 复制到 dist 根目录。
|
|
72
|
+
|
|
73
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
74
|
+
|------|------|--------|------|
|
|
75
|
+
| versionDir | string | - | 版本目录名(如时间戳) |
|
|
76
|
+
| outDir | string | 'dist-prod' | 输出目录 |
|
|
77
|
+
|
|
78
|
+
### cleanOldVersions
|
|
79
|
+
|
|
80
|
+
删除超过指定天数的旧版本目录。
|
|
81
|
+
|
|
82
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
83
|
+
|------|------|--------|------|
|
|
84
|
+
| outDir | string | 'dist-prod' | 输出目录 |
|
|
85
|
+
| days | number | 2 | 保留天数 |
|
|
86
|
+
| now | number | Date.now() | 当前时间戳(用于测试) |
|
|
87
|
+
| minVersions | number | 1 | 最少保留版本数 |
|
|
88
|
+
|
|
89
|
+
## 目录结构
|
|
90
|
+
|
|
91
|
+
打包后的目录结构:
|
|
92
|
+
```
|
|
93
|
+
dist-prod/
|
|
94
|
+
├── index.html # 当前版本入口
|
|
95
|
+
├── 1709200000000/ # 版本1
|
|
96
|
+
│ ├── index.html
|
|
97
|
+
│ ├── assets/
|
|
98
|
+
│ └── ...
|
|
99
|
+
├── 1709300000000/ # 版本2
|
|
100
|
+
│ ├── index.html
|
|
101
|
+
│ ├── assets/
|
|
102
|
+
│ └── ...
|
|
103
|
+
└── 1709400000000/ # 当前版本
|
|
104
|
+
├── index.html
|
|
105
|
+
├── assets/
|
|
106
|
+
└── ...
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Nginx 配置
|
|
110
|
+
|
|
111
|
+
```conf
|
|
112
|
+
# root写法
|
|
113
|
+
# VITE_APP_URL_PATH = '/'
|
|
114
|
+
server {
|
|
115
|
+
listen 8084;
|
|
116
|
+
server_name localhost;
|
|
117
|
+
location / {
|
|
118
|
+
root 项目路径/dist-prod;
|
|
119
|
+
try_files $uri $uri/ /index.html;
|
|
120
|
+
index index.html;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# alias写法
|
|
125
|
+
# VITE_APP_URL_PATH = '/admin-web/'
|
|
126
|
+
server {
|
|
127
|
+
listen 8077;
|
|
128
|
+
server_name localhost;
|
|
129
|
+
location /admin-web {
|
|
130
|
+
alias 项目路径/dist-prod/;
|
|
131
|
+
try_files $uri $uri/ /admin-web/index.html;
|
|
132
|
+
index index.html;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface VersionLinkOptions {
|
|
4
|
+
versionDir: string;
|
|
5
|
+
outDir?: string;
|
|
6
|
+
}
|
|
7
|
+
declare function versionLink(options: VersionLinkOptions): Plugin;
|
|
8
|
+
|
|
9
|
+
interface CleanOldVersionsOptions {
|
|
10
|
+
outDir?: string;
|
|
11
|
+
days?: number;
|
|
12
|
+
now?: number;
|
|
13
|
+
minVersions?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function cleanOldVersions(options: CleanOldVersionsOptions): Plugin;
|
|
16
|
+
|
|
17
|
+
export { type CleanOldVersionsOptions, type VersionLinkOptions, cleanOldVersions, versionLink };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface VersionLinkOptions {
|
|
4
|
+
versionDir: string;
|
|
5
|
+
outDir?: string;
|
|
6
|
+
}
|
|
7
|
+
declare function versionLink(options: VersionLinkOptions): Plugin;
|
|
8
|
+
|
|
9
|
+
interface CleanOldVersionsOptions {
|
|
10
|
+
outDir?: string;
|
|
11
|
+
days?: number;
|
|
12
|
+
now?: number;
|
|
13
|
+
minVersions?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function cleanOldVersions(options: CleanOldVersionsOptions): Plugin;
|
|
16
|
+
|
|
17
|
+
export { type CleanOldVersionsOptions, type VersionLinkOptions, cleanOldVersions, versionLink };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
cleanOldVersions: () => cleanOldVersions,
|
|
34
|
+
versionLink: () => versionLink
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/versionLink.ts
|
|
39
|
+
var import_node_fs = __toESM(require("fs"));
|
|
40
|
+
var import_node_path = __toESM(require("path"));
|
|
41
|
+
function versionLink(options) {
|
|
42
|
+
const { versionDir, outDir = "dist-prod" } = options;
|
|
43
|
+
if (!versionDir) {
|
|
44
|
+
throw new Error("[version-link] versionDir is required");
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
name: "version-link",
|
|
48
|
+
apply: "build",
|
|
49
|
+
closeBundle() {
|
|
50
|
+
try {
|
|
51
|
+
const outDirPath = import_node_path.default.resolve(process.cwd(), outDir);
|
|
52
|
+
const versionPath = import_node_path.default.join(outDirPath, versionDir, "index.html");
|
|
53
|
+
const copyPath = import_node_path.default.join(outDirPath, "index.html");
|
|
54
|
+
if (!import_node_fs.default.existsSync(versionPath)) {
|
|
55
|
+
console.error(`[version-link] \u6587\u4EF6\u4E0D\u5B58\u5728: ${versionPath}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
import_node_fs.default.copyFileSync(versionPath, copyPath);
|
|
59
|
+
console.log(`[version-link] \u2705 \u590D\u5236\u6210\u529F: ${versionDir}/index.html -> index.html`);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error("[version-link] \u64CD\u4F5C\u5931\u8D25:", err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/cleanOldVersions.ts
|
|
68
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
69
|
+
var import_node_path2 = __toESM(require("path"));
|
|
70
|
+
function cleanOldVersions(options) {
|
|
71
|
+
const { outDir = "dist-prod", days = 2, now, minVersions = 1 } = options;
|
|
72
|
+
return {
|
|
73
|
+
name: "clean-old-versions",
|
|
74
|
+
apply: "build",
|
|
75
|
+
closeBundle() {
|
|
76
|
+
if (process.env.NODE_ENV !== "production") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const outDirPath = import_node_path2.default.resolve(process.cwd(), outDir);
|
|
81
|
+
if (!import_node_fs2.default.existsSync(outDirPath)) {
|
|
82
|
+
console.error(`[clean-old-versions] \u76EE\u5F55\u4E0D\u5B58\u5728: ${outDirPath}`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const currentTime = now || Date.now();
|
|
86
|
+
const msPerDay = 24 * 60 * 60 * 1e3;
|
|
87
|
+
const threshold = currentTime - days * msPerDay;
|
|
88
|
+
const entries = import_node_fs2.default.readdirSync(outDirPath, { withFileTypes: true });
|
|
89
|
+
const oldVersions = [];
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (!entry.isDirectory()) continue;
|
|
92
|
+
if (entry.name === "index.html") continue;
|
|
93
|
+
const timestamp = Number(entry.name);
|
|
94
|
+
if (isNaN(timestamp)) continue;
|
|
95
|
+
if (timestamp < threshold) {
|
|
96
|
+
oldVersions.push({
|
|
97
|
+
dirPath: import_node_path2.default.join(outDirPath, entry.name),
|
|
98
|
+
timestamp,
|
|
99
|
+
entryName: entry.name
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let deletedCount = 0;
|
|
104
|
+
const maxDeletable = oldVersions.length - minVersions;
|
|
105
|
+
oldVersions.forEach((item) => {
|
|
106
|
+
const { dirPath, timestamp, entryName } = item;
|
|
107
|
+
if (deletedCount >= maxDeletable) {
|
|
108
|
+
console.log(`[clean-old-versions] \u89E6\u53D1\u6700\u5C11\u4FDD\u7559 ${minVersions} \u4E2A\u65E7\u5386\u53F2: ${entryName}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
import_node_fs2.default.rmSync(dirPath, { recursive: true, force: true });
|
|
112
|
+
console.log(`[clean-old-versions] \u5DF2\u5220\u9664: ${entryName}`);
|
|
113
|
+
deletedCount++;
|
|
114
|
+
});
|
|
115
|
+
console.log(`[clean-old-versions] \u2705 \u6E05\u7406\u5B8C\u6210\uFF0C\u5220\u9664 ${deletedCount} \u4E2A\u76EE\u5F55`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error("[clean-old-versions] \u64CD\u4F5C\u5931\u8D25:", err.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
cleanOldVersions,
|
|
125
|
+
versionLink
|
|
126
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/versionLink.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
function versionLink(options) {
|
|
5
|
+
const { versionDir, outDir = "dist-prod" } = options;
|
|
6
|
+
if (!versionDir) {
|
|
7
|
+
throw new Error("[version-link] versionDir is required");
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
name: "version-link",
|
|
11
|
+
apply: "build",
|
|
12
|
+
closeBundle() {
|
|
13
|
+
try {
|
|
14
|
+
const outDirPath = path.resolve(process.cwd(), outDir);
|
|
15
|
+
const versionPath = path.join(outDirPath, versionDir, "index.html");
|
|
16
|
+
const copyPath = path.join(outDirPath, "index.html");
|
|
17
|
+
if (!fs.existsSync(versionPath)) {
|
|
18
|
+
console.error(`[version-link] \u6587\u4EF6\u4E0D\u5B58\u5728: ${versionPath}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
fs.copyFileSync(versionPath, copyPath);
|
|
22
|
+
console.log(`[version-link] \u2705 \u590D\u5236\u6210\u529F: ${versionDir}/index.html -> index.html`);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error("[version-link] \u64CD\u4F5C\u5931\u8D25:", err.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/cleanOldVersions.ts
|
|
31
|
+
import fs2 from "fs";
|
|
32
|
+
import path2 from "path";
|
|
33
|
+
function cleanOldVersions(options) {
|
|
34
|
+
const { outDir = "dist-prod", days = 2, now, minVersions = 1 } = options;
|
|
35
|
+
return {
|
|
36
|
+
name: "clean-old-versions",
|
|
37
|
+
apply: "build",
|
|
38
|
+
closeBundle() {
|
|
39
|
+
if (process.env.NODE_ENV !== "production") {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const outDirPath = path2.resolve(process.cwd(), outDir);
|
|
44
|
+
if (!fs2.existsSync(outDirPath)) {
|
|
45
|
+
console.error(`[clean-old-versions] \u76EE\u5F55\u4E0D\u5B58\u5728: ${outDirPath}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const currentTime = now || Date.now();
|
|
49
|
+
const msPerDay = 24 * 60 * 60 * 1e3;
|
|
50
|
+
const threshold = currentTime - days * msPerDay;
|
|
51
|
+
const entries = fs2.readdirSync(outDirPath, { withFileTypes: true });
|
|
52
|
+
const oldVersions = [];
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (!entry.isDirectory()) continue;
|
|
55
|
+
if (entry.name === "index.html") continue;
|
|
56
|
+
const timestamp = Number(entry.name);
|
|
57
|
+
if (isNaN(timestamp)) continue;
|
|
58
|
+
if (timestamp < threshold) {
|
|
59
|
+
oldVersions.push({
|
|
60
|
+
dirPath: path2.join(outDirPath, entry.name),
|
|
61
|
+
timestamp,
|
|
62
|
+
entryName: entry.name
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let deletedCount = 0;
|
|
67
|
+
const maxDeletable = oldVersions.length - minVersions;
|
|
68
|
+
oldVersions.forEach((item) => {
|
|
69
|
+
const { dirPath, timestamp, entryName } = item;
|
|
70
|
+
if (deletedCount >= maxDeletable) {
|
|
71
|
+
console.log(`[clean-old-versions] \u89E6\u53D1\u6700\u5C11\u4FDD\u7559 ${minVersions} \u4E2A\u65E7\u5386\u53F2: ${entryName}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
fs2.rmSync(dirPath, { recursive: true, force: true });
|
|
75
|
+
console.log(`[clean-old-versions] \u5DF2\u5220\u9664: ${entryName}`);
|
|
76
|
+
deletedCount++;
|
|
77
|
+
});
|
|
78
|
+
console.log(`[clean-old-versions] \u2705 \u6E05\u7406\u5B8C\u6210\uFF0C\u5220\u9664 ${deletedCount} \u4E2A\u76EE\u5F55`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error("[clean-old-versions] \u64CD\u4F5C\u5931\u8D25:", err.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
cleanOldVersions,
|
|
87
|
+
versionLink
|
|
88
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-multiversion",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Vite plugin for multi-version control through building",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"vite",
|
|
22
|
+
"plugin",
|
|
23
|
+
"multi-version",
|
|
24
|
+
"version-control"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.5.0",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.0.0",
|
|
32
|
+
"vite": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"vite": ">=5.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export interface CleanOldVersionsOptions {
|
|
6
|
+
outDir?: string
|
|
7
|
+
days?: number
|
|
8
|
+
now?: number
|
|
9
|
+
minVersions?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function cleanOldVersions(options: CleanOldVersionsOptions): Plugin {
|
|
13
|
+
const { outDir = 'dist-prod', days = 2, now, minVersions = 1 } = options
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
name: 'clean-old-versions',
|
|
17
|
+
apply: 'build',
|
|
18
|
+
closeBundle() {
|
|
19
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const outDirPath = path.resolve(process.cwd(), outDir)
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(outDirPath)) {
|
|
27
|
+
console.error(`[clean-old-versions] 目录不存在: ${outDirPath}`)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const currentTime = now || Date.now()
|
|
32
|
+
const msPerDay = 24 * 60 * 60 * 1000
|
|
33
|
+
const threshold = currentTime - days * msPerDay
|
|
34
|
+
|
|
35
|
+
const entries = fs.readdirSync(outDirPath, { withFileTypes: true })
|
|
36
|
+
|
|
37
|
+
const oldVersions: { dirPath: string; timestamp: number; entryName: string }[] = []
|
|
38
|
+
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (!entry.isDirectory()) continue
|
|
41
|
+
if (entry.name === 'index.html') continue
|
|
42
|
+
|
|
43
|
+
const timestamp = Number(entry.name)
|
|
44
|
+
if (isNaN(timestamp)) continue
|
|
45
|
+
|
|
46
|
+
if (timestamp < threshold) {
|
|
47
|
+
oldVersions.push({
|
|
48
|
+
dirPath: path.join(outDirPath, entry.name),
|
|
49
|
+
timestamp,
|
|
50
|
+
entryName: entry.name,
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let deletedCount = 0
|
|
56
|
+
const maxDeletable = oldVersions.length - minVersions
|
|
57
|
+
|
|
58
|
+
oldVersions.forEach(item => {
|
|
59
|
+
const { dirPath, timestamp, entryName } = item
|
|
60
|
+
|
|
61
|
+
if (deletedCount >= maxDeletable) {
|
|
62
|
+
console.log(`[clean-old-versions] 触发最少保留 ${minVersions} 个旧历史: ${entryName}`)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.rmSync(dirPath, { recursive: true, force: true })
|
|
67
|
+
console.log(`[clean-old-versions] 已删除: ${entryName}`)
|
|
68
|
+
deletedCount++
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
console.log(`[clean-old-versions] ✅ 清理完成,删除 ${deletedCount} 个目录`)
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
console.error('[clean-old-versions] 操作失败:', err.message)
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export interface VersionLinkOptions {
|
|
6
|
+
versionDir: string
|
|
7
|
+
outDir?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function versionLink(options: VersionLinkOptions): Plugin {
|
|
11
|
+
const { versionDir, outDir = 'dist-prod' } = options
|
|
12
|
+
|
|
13
|
+
if (!versionDir) {
|
|
14
|
+
throw new Error('[version-link] versionDir is required')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
name: 'version-link',
|
|
19
|
+
apply: 'build',
|
|
20
|
+
closeBundle() {
|
|
21
|
+
try {
|
|
22
|
+
const outDirPath = path.resolve(process.cwd(), outDir)
|
|
23
|
+
const versionPath = path.join(outDirPath, versionDir, 'index.html')
|
|
24
|
+
const copyPath = path.join(outDirPath, 'index.html')
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(versionPath)) {
|
|
27
|
+
console.error(`[version-link] 文件不存在: ${versionPath}`)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fs.copyFileSync(versionPath, copyPath)
|
|
32
|
+
console.log(`[version-link] ✅ 复制成功: ${versionDir}/index.html -> index.html`)
|
|
33
|
+
} catch (err: any) {
|
|
34
|
+
console.error('[version-link] 操作失败:', err.message)
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|