@seayoo-web/finder 1.1.0 → 2.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 +2 -3
- package/__test__/compress.test.ts +81 -0
- package/dist/index.cjs +355 -0
- package/dist/index.js +355 -0
- package/index.ts +2 -0
- package/package.json +25 -8
- package/src/compress.ts +67 -0
- package/src/core.ts +104 -0
- package/src/plugin.ts +39 -0
- package/src/service.ts +180 -0
- package/src/utils.ts +97 -0
- package/tsconfig.json +9 -0
- package/types/index.d.ts +2 -0
- package/types/src/compress.d.ts +4 -0
- package/types/src/core.d.ts +31 -0
- package/types/src/plugin.d.ts +14 -0
- package/types/src/service.d.ts +21 -0
- package/types/src/utils.d.ts +18 -0
- package/vite.config.ts +27 -0
- package/dist/core.js +0 -1
- package/dist/plugin.vite.js +0 -1
- package/dist/plugin.webpack.js +0 -1
- package/index.d.ts +0 -81
- package/index.js +0 -11
package/dist/index.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import fs, { writeFileSync, readdirSync, lstatSync, existsSync, readFileSync } from "fs";
|
|
2
|
+
import path, { relative, join, basename, normalize, sep, dirname, resolve, extname } from "path";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import "colors";
|
|
5
|
+
import { zip } from "compressing";
|
|
6
|
+
import os from "os";
|
|
7
|
+
const presetIgnores = ["node_modules/", ".git/", ".vscode/", ".DS_Store"];
|
|
8
|
+
function compressToBuffer(sourceDir, ignoreFiles, debug) {
|
|
9
|
+
const ignoreFileList = [...presetIgnores, ...ignoreFiles || []];
|
|
10
|
+
const filesToCompress = getAllFiles(sourceDir, ignoreFileList);
|
|
11
|
+
const zipStream = new zip.Stream();
|
|
12
|
+
filesToCompress.forEach((file) => {
|
|
13
|
+
zipStream.addEntry(file, { relativePath: relative(sourceDir, file) });
|
|
14
|
+
});
|
|
15
|
+
if (debug) {
|
|
16
|
+
console.log({
|
|
17
|
+
method: "compressToBuffer",
|
|
18
|
+
sourceDir,
|
|
19
|
+
ignores: ignoreFileList,
|
|
20
|
+
filesCount: filesToCompress.length
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const chunks = [];
|
|
24
|
+
return new Promise(function(resolve2, reject) {
|
|
25
|
+
zipStream.on("data", (chunk) => chunks.push(chunk)).on("end", () => {
|
|
26
|
+
const buffer = Buffer.concat(chunks);
|
|
27
|
+
if (debug) {
|
|
28
|
+
writeFileSync(join(process.cwd(), "_finder_deploy_buffer.zip"), buffer);
|
|
29
|
+
}
|
|
30
|
+
resolve2(buffer);
|
|
31
|
+
}).on("error", reject);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function getAllFiles(dir, ignores = []) {
|
|
35
|
+
const list = [];
|
|
36
|
+
readdirSync(dir).forEach((file) => {
|
|
37
|
+
const filePath = join(dir, file);
|
|
38
|
+
const stats = lstatSync(filePath);
|
|
39
|
+
if (stats.isDirectory()) {
|
|
40
|
+
list.push(...getAllFiles(filePath, ignores));
|
|
41
|
+
} else if (!isIgnoreFile(filePath, ignores)) {
|
|
42
|
+
list.push(filePath);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return list;
|
|
46
|
+
}
|
|
47
|
+
function isIgnoreFile(filePath, ignores) {
|
|
48
|
+
const filename = basename(filePath);
|
|
49
|
+
const dirs = normalize(filePath).split(sep);
|
|
50
|
+
return ignores.some((name) => {
|
|
51
|
+
if (name.endsWith("/") && name !== "/") {
|
|
52
|
+
return dirs.includes(name.slice(0, -1));
|
|
53
|
+
}
|
|
54
|
+
return filename === name;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function pure(url) {
|
|
58
|
+
return url.replace(/(?:^https?:\/\/|\/*$)/gi, "");
|
|
59
|
+
}
|
|
60
|
+
function getSystemTempDir() {
|
|
61
|
+
const dir = path.resolve(os.tmpdir(), "webfinder");
|
|
62
|
+
if (!fs.existsSync(dir)) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
return dir;
|
|
66
|
+
}
|
|
67
|
+
async function request({
|
|
68
|
+
url,
|
|
69
|
+
method,
|
|
70
|
+
headers,
|
|
71
|
+
data
|
|
72
|
+
}) {
|
|
73
|
+
const hasFileUpload = method === "POST" && data && Object.values(data).some((value) => typeof value === "object" && "buffer" in value);
|
|
74
|
+
const requestInit = {
|
|
75
|
+
method,
|
|
76
|
+
headers: { ...headers }
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
if (data) {
|
|
80
|
+
if (hasFileUpload) {
|
|
81
|
+
const formData = new FormData();
|
|
82
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
83
|
+
if (typeof value === "object" && "buffer" in value) {
|
|
84
|
+
const { buffer, filename, contentType: contentType2 } = value;
|
|
85
|
+
const blob = new Blob([buffer], { type: contentType2 });
|
|
86
|
+
formData.append(key, blob, filename);
|
|
87
|
+
} else {
|
|
88
|
+
formData.append(key, String(value));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
requestInit.body = formData;
|
|
92
|
+
} else {
|
|
93
|
+
requestInit.headers = {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
...headers
|
|
96
|
+
};
|
|
97
|
+
requestInit.body = JSON.stringify(data);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const response = await fetch(url, requestInit);
|
|
101
|
+
let responseData;
|
|
102
|
+
const contentType = response.headers.get("content-type");
|
|
103
|
+
if (contentType == null ? void 0 : contentType.includes("application/json")) {
|
|
104
|
+
responseData = await response.json();
|
|
105
|
+
} else {
|
|
106
|
+
responseData = await response.text();
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
status: response.status,
|
|
110
|
+
message: response.statusText,
|
|
111
|
+
data: responseData
|
|
112
|
+
};
|
|
113
|
+
} catch (err) {
|
|
114
|
+
return {
|
|
115
|
+
status: 500,
|
|
116
|
+
message: err instanceof Error ? err.message : String(err),
|
|
117
|
+
data: null
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const FinderServers = {
|
|
122
|
+
"finder.seayoo.io": [],
|
|
123
|
+
"finder.seayoo.com": [],
|
|
124
|
+
"finder.seayoo.internal": [],
|
|
125
|
+
"finder.dev.seayoo.com": []
|
|
126
|
+
};
|
|
127
|
+
const FinderApiPaths = {
|
|
128
|
+
deploy: "/service/deploy",
|
|
129
|
+
inspect: "/inspect/supported/projects",
|
|
130
|
+
upload: "/service/upload"
|
|
131
|
+
};
|
|
132
|
+
async function deploy(option) {
|
|
133
|
+
const { debug, target, buffer, user, key, payload } = option;
|
|
134
|
+
const targetServer = await findTargetServer(target, debug);
|
|
135
|
+
if (!targetServer) {
|
|
136
|
+
throw `finder不支持该域名部署,请检查 ${target}`.bgRed;
|
|
137
|
+
}
|
|
138
|
+
if (!user || !key) {
|
|
139
|
+
throw `部署缺少认证信息(user & key)`.bgRed;
|
|
140
|
+
}
|
|
141
|
+
const zipMockName = `${Date.now()}${Math.random().toString(16).slice(-3)}.zip`;
|
|
142
|
+
const { status, message, data } = await request({
|
|
143
|
+
url: `${getFinderServerFullPath(targetServer)}${FinderApiPaths.deploy}?target=${encodeURIComponent(pure(target))}`,
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { user, key },
|
|
146
|
+
data: {
|
|
147
|
+
path: zipMockName,
|
|
148
|
+
file: { buffer, filename: zipMockName, contentType: "application/octet-stream" },
|
|
149
|
+
payload: JSON.stringify(payload || "")
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (status !== 200) {
|
|
153
|
+
throw `${`部署接口错误(${status})`.bgRed} ${message || ""}.red`;
|
|
154
|
+
}
|
|
155
|
+
if (!data || typeof data !== "object") {
|
|
156
|
+
throw `部署接口响应错误,请自行检查是否部署成功。`.bgRed + JSON.stringify(data).red;
|
|
157
|
+
}
|
|
158
|
+
if ("err" in data || !("data" in data) || typeof data.data !== "string") {
|
|
159
|
+
throw `部署接口错误。`.bgRed + JSON.stringify(data).red;
|
|
160
|
+
}
|
|
161
|
+
const url = data.data;
|
|
162
|
+
if (debug) {
|
|
163
|
+
console.log("部署完毕,接口返回内容", data);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
previewUrl: url.endsWith("/") ? url.replace(/\/*$/, "/") + "index.html?" + Math.random().toString(16).slice(2) : url.startsWith("http") ? url : ""
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function upload(option) {
|
|
170
|
+
const { debug, target, buffer, user, key } = option;
|
|
171
|
+
const targetServer = await findTargetServer(target, debug);
|
|
172
|
+
if (!targetServer) {
|
|
173
|
+
throw `finder不支持该域名部署,请检查 ${target}`.bgRed;
|
|
174
|
+
}
|
|
175
|
+
if (!user || !key) {
|
|
176
|
+
throw `部署缺少认证信息(user & key)`.bgRed;
|
|
177
|
+
}
|
|
178
|
+
const filename = basename(target);
|
|
179
|
+
const deployTarget = dirname(pure(target));
|
|
180
|
+
const { status, message, data } = await request({
|
|
181
|
+
url: `${getFinderServerFullPath(targetServer)}${FinderApiPaths.upload}?target=${encodeURIComponent(deployTarget)}`,
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers: { user, key },
|
|
184
|
+
data: {
|
|
185
|
+
path: filename,
|
|
186
|
+
file: { buffer, filename, contentType: "application/octet-stream" }
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (status !== 200) {
|
|
190
|
+
throw `${`上传接口错误(${status})`.bgRed} ${message || ""}.red`;
|
|
191
|
+
}
|
|
192
|
+
if (!data || typeof data !== "object") {
|
|
193
|
+
throw `上传接口响应错误,请自行检查是否上传成功。`.bgRed + JSON.stringify(data).red;
|
|
194
|
+
}
|
|
195
|
+
if ("err" in data || !("data" in data) || typeof data.data !== "string") {
|
|
196
|
+
throw `上传接口错误。`.bgRed + JSON.stringify(data).red;
|
|
197
|
+
}
|
|
198
|
+
return { previewUrl: `https://${pure(target)}` };
|
|
199
|
+
}
|
|
200
|
+
const getFinderServerFullPath = function(domain) {
|
|
201
|
+
return (domain.endsWith("internal") ? "http://" : "https://") + domain;
|
|
202
|
+
};
|
|
203
|
+
async function findTargetServer(target, debug) {
|
|
204
|
+
const t = pure(target);
|
|
205
|
+
await updateSupportedProjects(false, debug);
|
|
206
|
+
for (const domain in FinderServers) {
|
|
207
|
+
if (FinderServers[domain].find((url) => t.startsWith(url))) {
|
|
208
|
+
return domain;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
await updateSupportedProjects(true, debug);
|
|
212
|
+
for (const domain in FinderServers) {
|
|
213
|
+
if (FinderServers[domain].find((url) => t.startsWith(url))) {
|
|
214
|
+
return domain;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
async function updateSupportedProjects(force = false, debug) {
|
|
220
|
+
const domains = Object.keys(FinderServers);
|
|
221
|
+
for (const domain of domains) {
|
|
222
|
+
FinderServers[domain] = await getServerSupportedProjects(domain, force, debug) || [];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function getServerSupportedProjects(serverDomain, ignoreCache = false, debug) {
|
|
226
|
+
const cacheFile = resolve(getSystemTempDir(), `${serverDomain}.json`);
|
|
227
|
+
if (existsSync(cacheFile) && !ignoreCache) {
|
|
228
|
+
try {
|
|
229
|
+
const cache = JSON.parse(readFileSync(cacheFile).toString());
|
|
230
|
+
if (Array.isArray(cache) && cache.every((d) => typeof d === "string")) {
|
|
231
|
+
if (debug) {
|
|
232
|
+
console.log({ method: "getServerSupportedProjects", serverDomain, cache });
|
|
233
|
+
}
|
|
234
|
+
return cache;
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error("ReadFinderCacheError", e);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const inspectURL = `${getFinderServerFullPath(serverDomain)}${FinderApiPaths.inspect}`;
|
|
241
|
+
const { status, message, data } = await request({
|
|
242
|
+
url: inspectURL,
|
|
243
|
+
method: "GET",
|
|
244
|
+
headers: { UserAgent: `web finder agent v2` }
|
|
245
|
+
});
|
|
246
|
+
if (status !== 200) {
|
|
247
|
+
console.error(`服务器 ${inspectURL} 检查接口错误`.bgRed, (message || "").red);
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
if (!Array.isArray(data) || !data.every((d) => typeof d === "string")) {
|
|
251
|
+
console.error(`服务器 ${inspectURL} 接口返回内容错误`.bgRed, JSON.stringify(data).red);
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
if (debug) {
|
|
255
|
+
console.log({ method: "getServerSupportedProjects", serverDomain, list: data });
|
|
256
|
+
}
|
|
257
|
+
const pureList = data.map(pure);
|
|
258
|
+
writeFileSync(cacheFile, JSON.stringify(pureList));
|
|
259
|
+
return pureList;
|
|
260
|
+
}
|
|
261
|
+
async function finderDeploy(option) {
|
|
262
|
+
const { dist, ignoreFiles, deployTo, user, key, debug, preview, commitLogs } = option;
|
|
263
|
+
if (!dist) {
|
|
264
|
+
throw "部署参数 dist 缺失".bgRed;
|
|
265
|
+
}
|
|
266
|
+
if (!existsSync(resolve(dist)) || !lstatSync(resolve(dist)).isDirectory()) {
|
|
267
|
+
throw "部署参数错误,dist 需要是一个存在的文件目录".bgRed + " " + dist.red;
|
|
268
|
+
}
|
|
269
|
+
const buffer = await compressToBuffer(dist, ignoreFiles || [], debug).catch((e) => {
|
|
270
|
+
throw "部署预处理之压缩代码失败".bgRed + " " + (e instanceof Error ? e.message : String(e));
|
|
271
|
+
});
|
|
272
|
+
const payload = commitLogs ? { 更新内容: commitLogs } : void 0;
|
|
273
|
+
if (debug) {
|
|
274
|
+
console.log({
|
|
275
|
+
method: "finderDeploy",
|
|
276
|
+
dist,
|
|
277
|
+
deployTo,
|
|
278
|
+
ignoreFiles,
|
|
279
|
+
payload,
|
|
280
|
+
user,
|
|
281
|
+
preview
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(deployTo)) {
|
|
285
|
+
const results = await Promise.all(
|
|
286
|
+
deployTo.map((target) => {
|
|
287
|
+
return deploy({ debug, target, buffer, user, key, payload });
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
const lastDeployResult = results[results.length - 1];
|
|
291
|
+
if (preview && lastDeployResult && lastDeployResult.previewUrl) {
|
|
292
|
+
open(lastDeployResult.previewUrl);
|
|
293
|
+
}
|
|
294
|
+
return lastDeployResult.previewUrl || "";
|
|
295
|
+
}
|
|
296
|
+
const deployResult = await deploy({ debug, target: deployTo, buffer, user, key, payload });
|
|
297
|
+
if (preview && deployResult && deployResult.previewUrl) {
|
|
298
|
+
open(deployResult.previewUrl);
|
|
299
|
+
}
|
|
300
|
+
return deployResult.previewUrl || "";
|
|
301
|
+
}
|
|
302
|
+
async function finderUpload(option) {
|
|
303
|
+
const { filePath, deployTo, user, key, preview, debug } = option;
|
|
304
|
+
if (!filePath) {
|
|
305
|
+
throw `部署缺少参数 filePath(文件全路径)`.bgRed;
|
|
306
|
+
}
|
|
307
|
+
if (filePath && !existsSync(filePath)) {
|
|
308
|
+
throw `部署文件不存在(请确保传入完整文件路径)`.bgRed + " " + filePath;
|
|
309
|
+
}
|
|
310
|
+
if (!deployTo) {
|
|
311
|
+
throw `部署缺少参数 deployTo(部署目标)`.bgRed;
|
|
312
|
+
}
|
|
313
|
+
const fileExtension = extname(filePath);
|
|
314
|
+
const filename = basename(filePath);
|
|
315
|
+
const target = !deployTo.endsWith(fileExtension) ? `${pure(deployTo)}/${filename}` : pure(deployTo);
|
|
316
|
+
const resp = await upload({ debug, target, buffer: Buffer.from(readFileSync(filePath)), user, key });
|
|
317
|
+
if (preview && resp.previewUrl) {
|
|
318
|
+
open(resp.previewUrl);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function viteDeployPlugin(option) {
|
|
322
|
+
let distDir = null;
|
|
323
|
+
return {
|
|
324
|
+
name: "finerDeployAgent",
|
|
325
|
+
generateBundle({ dir }) {
|
|
326
|
+
distDir = process.cwd();
|
|
327
|
+
if (dir) {
|
|
328
|
+
distDir = resolve(distDir, dir);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
async closeBundle() {
|
|
332
|
+
var _a, _b;
|
|
333
|
+
if (!distDir) {
|
|
334
|
+
console.error("没有找到部署资源,请尝试检查 build 是否生成了正确的资源".bgRed);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const result = await finderDeploy({
|
|
338
|
+
preview: true,
|
|
339
|
+
...option,
|
|
340
|
+
dist: distDir
|
|
341
|
+
}).catch(() => null);
|
|
342
|
+
if (result === null) {
|
|
343
|
+
(_a = option.onError) == null ? void 0 : _a.call(option);
|
|
344
|
+
} else {
|
|
345
|
+
(_b = option.onFinished) == null ? void 0 : _b.call(option);
|
|
346
|
+
console.log("部署成功".bgGreen, (result || "").green);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
export {
|
|
352
|
+
finderDeploy,
|
|
353
|
+
finderUpload,
|
|
354
|
+
viteDeployPlugin
|
|
355
|
+
};
|
package/index.ts
ADDED
package/package.json
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seayoo-web/finder",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "agent for web finder",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"source": "index.ts",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./types/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
6
18
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
19
|
+
"build": "vite build && tsc --emitDeclarationOnly",
|
|
20
|
+
"test": "vitest",
|
|
21
|
+
"pub": "pnpm build && npm publish"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">= 18"
|
|
9
25
|
},
|
|
10
26
|
"author": "web@seayoo.com",
|
|
11
27
|
"license": "MIT",
|
|
@@ -13,12 +29,13 @@
|
|
|
13
29
|
"access": "public"
|
|
14
30
|
},
|
|
15
31
|
"dependencies": {
|
|
16
|
-
"compressing": "^1.5.1",
|
|
17
|
-
"needle": "^3.1.0",
|
|
18
32
|
"colors": "^1.4.0",
|
|
19
|
-
"
|
|
33
|
+
"compressing": "^1.10.1",
|
|
34
|
+
"open": "^10.1.0"
|
|
20
35
|
},
|
|
21
36
|
"devDependencies": {
|
|
22
|
-
"
|
|
37
|
+
"@seayoo-web/tsconfig": "workspace:^",
|
|
38
|
+
"@types/node": "^22.13.1",
|
|
39
|
+
"vitest": "^3.0.5"
|
|
23
40
|
}
|
|
24
41
|
}
|
package/src/compress.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readdirSync, lstatSync, writeFileSync } from "fs";
|
|
2
|
+
import { join, relative, normalize, basename, sep } from "path";
|
|
3
|
+
|
|
4
|
+
import { zip } from "compressing";
|
|
5
|
+
|
|
6
|
+
// 部署代码默认的忽略列表
|
|
7
|
+
const presetIgnores = ["node_modules/", ".git/", ".vscode/", ".DS_Store"];
|
|
8
|
+
|
|
9
|
+
/** 代码压缩 */
|
|
10
|
+
export function compressToBuffer(sourceDir: string, ignoreFiles?: string[], debug?: boolean): Promise<Buffer> {
|
|
11
|
+
const ignoreFileList = [...presetIgnores, ...(ignoreFiles || [])];
|
|
12
|
+
const filesToCompress = getAllFiles(sourceDir, ignoreFileList);
|
|
13
|
+
const zipStream = new zip.Stream();
|
|
14
|
+
// 计算文件在压缩包中的相对路径
|
|
15
|
+
filesToCompress.forEach((file) => {
|
|
16
|
+
zipStream.addEntry(file, { relativePath: relative(sourceDir, file) });
|
|
17
|
+
});
|
|
18
|
+
if (debug) {
|
|
19
|
+
console.log({
|
|
20
|
+
method: "compressToBuffer",
|
|
21
|
+
sourceDir,
|
|
22
|
+
ignores: ignoreFileList,
|
|
23
|
+
filesCount: filesToCompress.length,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// 将压缩流写入buffer
|
|
27
|
+
const chunks: Buffer[] = [];
|
|
28
|
+
return new Promise(function (resolve, reject) {
|
|
29
|
+
zipStream
|
|
30
|
+
.on("data", (chunk) => chunks.push(chunk as Buffer))
|
|
31
|
+
.on("end", () => {
|
|
32
|
+
const buffer = Buffer.concat(chunks);
|
|
33
|
+
if (debug) {
|
|
34
|
+
writeFileSync(join(process.cwd(), "_finder_deploy_buffer.zip"), buffer);
|
|
35
|
+
}
|
|
36
|
+
resolve(buffer);
|
|
37
|
+
})
|
|
38
|
+
.on("error", reject); // 处理错误
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getAllFiles(dir: string, ignores: string[] = []) {
|
|
43
|
+
const list: string[] = [];
|
|
44
|
+
readdirSync(dir).forEach((file) => {
|
|
45
|
+
const filePath = join(dir, file);
|
|
46
|
+
const stats = lstatSync(filePath);
|
|
47
|
+
if (stats.isDirectory()) {
|
|
48
|
+
// 如果是文件夹,递归处理
|
|
49
|
+
list.push(...getAllFiles(filePath, ignores));
|
|
50
|
+
// 如果是文件,检查是否符合过滤规则
|
|
51
|
+
} else if (!isIgnoreFile(filePath, ignores)) {
|
|
52
|
+
list.push(filePath);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return list;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isIgnoreFile(filePath: string, ignores: string[]) {
|
|
59
|
+
const filename = basename(filePath);
|
|
60
|
+
const dirs = normalize(filePath).split(sep);
|
|
61
|
+
return ignores.some((name) => {
|
|
62
|
+
if (name.endsWith("/") && name !== "/") {
|
|
63
|
+
return dirs.includes(name.slice(0, -1));
|
|
64
|
+
}
|
|
65
|
+
return filename === name;
|
|
66
|
+
});
|
|
67
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync } from "fs";
|
|
2
|
+
import { basename, extname, resolve } from "path";
|
|
3
|
+
|
|
4
|
+
import open from "open";
|
|
5
|
+
|
|
6
|
+
import "colors";
|
|
7
|
+
import { compressToBuffer } from "./compress";
|
|
8
|
+
import { deploy, upload } from "./service";
|
|
9
|
+
import { pure } from "./utils";
|
|
10
|
+
|
|
11
|
+
/** 部署一个目录 */
|
|
12
|
+
export async function finderDeploy(option: {
|
|
13
|
+
/** 需要推送的代码目录 */
|
|
14
|
+
dist: string;
|
|
15
|
+
/** 忽略部署的文件,如果是目录则需要以 / 结尾。暂不支持模糊匹配 */
|
|
16
|
+
ignoreFiles?: string[];
|
|
17
|
+
/** 部署的目标地址 */
|
|
18
|
+
deployTo: string | string[];
|
|
19
|
+
/** 部署 user */
|
|
20
|
+
user: string;
|
|
21
|
+
/** 部署 key */
|
|
22
|
+
key: string;
|
|
23
|
+
/** 是否输出更多调试信息 */
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
/** 是否部署完毕后打开目标文件(index.html) */
|
|
26
|
+
preview?: boolean;
|
|
27
|
+
/** 代码的 commit log 信息,换行用 \n */
|
|
28
|
+
commitLogs?: string;
|
|
29
|
+
}): Promise<string> {
|
|
30
|
+
const { dist, ignoreFiles, deployTo, user, key, debug, preview, commitLogs } = option;
|
|
31
|
+
if (!dist) {
|
|
32
|
+
throw "部署参数 dist 缺失".bgRed;
|
|
33
|
+
}
|
|
34
|
+
if (!existsSync(resolve(dist)) || !lstatSync(resolve(dist)).isDirectory()) {
|
|
35
|
+
throw "部署参数错误,dist 需要是一个存在的文件目录".bgRed + " " + dist.red;
|
|
36
|
+
}
|
|
37
|
+
// 压缩代码准备上传
|
|
38
|
+
const buffer = await compressToBuffer(dist, ignoreFiles || [], debug).catch((e) => {
|
|
39
|
+
throw "部署预处理之压缩代码失败".bgRed + " " + (e instanceof Error ? e.message : String(e));
|
|
40
|
+
});
|
|
41
|
+
const payload = commitLogs ? { 更新内容: commitLogs } : undefined;
|
|
42
|
+
if (debug) {
|
|
43
|
+
console.log({
|
|
44
|
+
method: "finderDeploy",
|
|
45
|
+
dist,
|
|
46
|
+
deployTo,
|
|
47
|
+
ignoreFiles,
|
|
48
|
+
payload,
|
|
49
|
+
user,
|
|
50
|
+
preview,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(deployTo)) {
|
|
54
|
+
// 多次部署
|
|
55
|
+
const results = await Promise.all(
|
|
56
|
+
deployTo.map((target) => {
|
|
57
|
+
return deploy({ debug, target, buffer, user, key, payload });
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
const lastDeployResult = results[results.length - 1];
|
|
61
|
+
if (preview && lastDeployResult && lastDeployResult.previewUrl) {
|
|
62
|
+
open(lastDeployResult.previewUrl);
|
|
63
|
+
}
|
|
64
|
+
return lastDeployResult.previewUrl || "";
|
|
65
|
+
}
|
|
66
|
+
// 单次部署
|
|
67
|
+
const deployResult = await deploy({ debug, target: deployTo, buffer, user, key, payload });
|
|
68
|
+
if (preview && deployResult && deployResult.previewUrl) {
|
|
69
|
+
open(deployResult.previewUrl);
|
|
70
|
+
}
|
|
71
|
+
return deployResult.previewUrl || "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 上传一个文件到 finder */
|
|
75
|
+
export async function finderUpload(option: {
|
|
76
|
+
/** 需要上传的文件全局路 */
|
|
77
|
+
filePath: string;
|
|
78
|
+
/** 部署目标全路径,需要包含文件名 */
|
|
79
|
+
deployTo: string;
|
|
80
|
+
user: string;
|
|
81
|
+
key: string;
|
|
82
|
+
debug?: boolean;
|
|
83
|
+
preview?: boolean;
|
|
84
|
+
}) {
|
|
85
|
+
const { filePath, deployTo, user, key, preview, debug } = option;
|
|
86
|
+
|
|
87
|
+
if (!filePath) {
|
|
88
|
+
throw `部署缺少参数 filePath(文件全路径)`.bgRed;
|
|
89
|
+
}
|
|
90
|
+
if (filePath && !existsSync(filePath)) {
|
|
91
|
+
throw `部署文件不存在(请确保传入完整文件路径)`.bgRed + " " + filePath;
|
|
92
|
+
}
|
|
93
|
+
if (!deployTo) {
|
|
94
|
+
throw `部署缺少参数 deployTo(部署目标)`.bgRed;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fileExtension = extname(filePath);
|
|
98
|
+
const filename = basename(filePath);
|
|
99
|
+
const target = !deployTo.endsWith(fileExtension) ? `${pure(deployTo)}/${filename}` : pure(deployTo);
|
|
100
|
+
const resp = await upload({ debug, target, buffer: Buffer.from(readFileSync(filePath)), user, key });
|
|
101
|
+
if (preview && resp.previewUrl) {
|
|
102
|
+
open(resp.previewUrl);
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
|
|
3
|
+
import { finderDeploy } from "./core";
|
|
4
|
+
import "colors";
|
|
5
|
+
|
|
6
|
+
type FinderDeployVitePluginOption = Omit<Parameters<typeof finderDeploy>[0], "dist"> & {
|
|
7
|
+
onFinished?: () => unknown;
|
|
8
|
+
onError?: () => unknown;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function viteDeployPlugin(option: FinderDeployVitePluginOption) {
|
|
12
|
+
let distDir: string | null = null;
|
|
13
|
+
return {
|
|
14
|
+
name: "finerDeployAgent",
|
|
15
|
+
generateBundle({ dir }: { dir: string }) {
|
|
16
|
+
distDir = process.cwd();
|
|
17
|
+
if (dir) {
|
|
18
|
+
distDir = resolve(distDir, dir);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
async closeBundle() {
|
|
22
|
+
if (!distDir) {
|
|
23
|
+
console.error("没有找到部署资源,请尝试检查 build 是否生成了正确的资源".bgRed);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const result = await finderDeploy({
|
|
27
|
+
preview: true,
|
|
28
|
+
...option,
|
|
29
|
+
dist: distDir,
|
|
30
|
+
}).catch(() => null);
|
|
31
|
+
if (result === null) {
|
|
32
|
+
option.onError?.();
|
|
33
|
+
} else {
|
|
34
|
+
option.onFinished?.();
|
|
35
|
+
console.log("部署成功".bgGreen, (result || "").green);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|