@taole/deploy-helper 1.1.7 → 1.1.8-beta.2
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/lib/archive.mjs +106 -18
- package/lib/lightDeploy.mjs +5 -2
- package/lib/project.mjs +56 -6
- package/package.json +2 -1
package/lib/archive.mjs
CHANGED
|
@@ -20,23 +20,112 @@ export function archiveToBuffer(setupArchive) {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
function toPosixRelativePath(distPath, filePath) {
|
|
24
|
+
return path.relative(distPath, filePath).split(path.sep).join("/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function matchDistFileFilter(relativePath, filterItem) {
|
|
28
|
+
if (typeof filterItem === "string") {
|
|
29
|
+
return relativePath === filterItem;
|
|
30
|
+
}
|
|
31
|
+
if (filterItem && typeof filterItem.regex === "string") {
|
|
32
|
+
return new RegExp(filterItem.regex).test(relativePath);
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`无效的 distFileFilters 规则: ${JSON.stringify(filterItem)}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function shouldIncludeDistFile(relativePath, distFileFilters) {
|
|
38
|
+
if (!distFileFilters) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const includeList = distFileFilters.include || [];
|
|
43
|
+
const excludeList = distFileFilters.exclude || [];
|
|
44
|
+
|
|
45
|
+
if (includeList.length > 0) {
|
|
46
|
+
const included = includeList.some((item) =>
|
|
47
|
+
matchDistFileFilter(relativePath, item)
|
|
48
|
+
);
|
|
49
|
+
if (!included) {
|
|
50
|
+
return false;
|
|
33
51
|
}
|
|
34
52
|
}
|
|
53
|
+
|
|
54
|
+
if (excludeList.length > 0) {
|
|
55
|
+
const excluded = excludeList.some((item) =>
|
|
56
|
+
matchDistFileFilter(relativePath, item)
|
|
57
|
+
);
|
|
58
|
+
if (excluded) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true;
|
|
35
64
|
}
|
|
36
65
|
|
|
37
|
-
|
|
66
|
+
function collectDistFiles(distPath, distFileFilters) {
|
|
67
|
+
const files = [];
|
|
68
|
+
|
|
69
|
+
function walk(dir) {
|
|
70
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
71
|
+
const fullPath = path.join(dir, entry.name);
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
walk(fullPath);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const relativePath = toPosixRelativePath(distPath, fullPath);
|
|
78
|
+
if (shouldIncludeDistFile(relativePath, distFileFilters)) {
|
|
79
|
+
files.push({ fullPath, relativePath });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
walk(distPath);
|
|
85
|
+
return files;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function appendFilesToArchive(archive, files, zipPrefix) {
|
|
89
|
+
for (const { fullPath, relativePath } of files) {
|
|
90
|
+
const zipPath = zipPrefix
|
|
91
|
+
? path.posix.join(zipPrefix, relativePath)
|
|
92
|
+
: relativePath;
|
|
93
|
+
archive.file(fullPath, { name: zipPath });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function validateDistFileFilters(distFileFilters) {
|
|
98
|
+
if (distFileFilters == null) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (typeof distFileFilters !== "object" || Array.isArray(distFileFilters)) {
|
|
102
|
+
throw new Error("distFileFilters 必须是包含 include/exclude 的对象");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const key of ["include", "exclude"]) {
|
|
106
|
+
const list = distFileFilters[key];
|
|
107
|
+
if (list == null) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!Array.isArray(list)) {
|
|
111
|
+
throw new Error(`distFileFilters.${key} 必须是数组`);
|
|
112
|
+
}
|
|
113
|
+
for (const item of list) {
|
|
114
|
+
if (typeof item === "string") {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (item && typeof item.regex === "string") {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`无效的 distFileFilters.${key} 规则: ${JSON.stringify(item)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function genProjectArchiveBuffer(distPath, distFileFilters) {
|
|
38
126
|
return archiveToBuffer((archive) => {
|
|
39
|
-
|
|
127
|
+
const files = collectDistFiles(distPath, distFileFilters);
|
|
128
|
+
appendFilesToArchive(archive, files);
|
|
40
129
|
});
|
|
41
130
|
}
|
|
42
131
|
|
|
@@ -44,16 +133,15 @@ export function genProjectArchiveBuffer(distPath) {
|
|
|
44
133
|
* lib 类型 CDN 打包:若 dist 内仅有名为 version 的子目录则原样打包;
|
|
45
134
|
* 否则为 zip 内所有文件加上 version 前缀。
|
|
46
135
|
*/
|
|
47
|
-
export function genLibArchiveBuffer(distPath, version) {
|
|
136
|
+
export function genLibArchiveBuffer(distPath, version, distFileFilters) {
|
|
48
137
|
const subdirs = fs
|
|
49
138
|
.readdirSync(distPath, { withFileTypes: true })
|
|
50
139
|
.filter((entry) => entry.isDirectory());
|
|
51
140
|
|
|
52
141
|
return archiveToBuffer((archive) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
142
|
+
const files = collectDistFiles(distPath, distFileFilters);
|
|
143
|
+
const zipPrefix =
|
|
144
|
+
subdirs.length === 1 && subdirs[0].name === version ? "" : version;
|
|
145
|
+
appendFilesToArchive(archive, files, zipPrefix);
|
|
58
146
|
});
|
|
59
147
|
}
|
package/lib/lightDeploy.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { getCache as getLoginCache } from "./login.mjs";
|
|
|
5
5
|
import {
|
|
6
6
|
genLibArchiveBuffer,
|
|
7
7
|
genProjectArchiveBuffer,
|
|
8
|
+
validateDistFileFilters,
|
|
8
9
|
} from "./archive.mjs";
|
|
9
10
|
|
|
10
11
|
const isDev = false;
|
|
@@ -102,6 +103,8 @@ export async function lightDeploy(config, mode) {
|
|
|
102
103
|
const workDir = process.cwd();
|
|
103
104
|
const distDir = config.distDir || "dist";
|
|
104
105
|
const distPath = path.join(workDir, distDir);
|
|
106
|
+
const distFileFilters = config.distFileFilters;
|
|
107
|
+
validateDistFileFilters(distFileFilters);
|
|
105
108
|
if (!fs.existsSync(distPath)) {
|
|
106
109
|
throw new Error(`${distPath} 不存在,请先执行构建`);
|
|
107
110
|
}
|
|
@@ -109,8 +112,8 @@ export async function lightDeploy(config, mode) {
|
|
|
109
112
|
log(`开始打包 ${distPath}`);
|
|
110
113
|
const distZipBuffer =
|
|
111
114
|
cdnType === "lib"
|
|
112
|
-
? await genLibArchiveBuffer(distPath, version)
|
|
113
|
-
: await genProjectArchiveBuffer(distPath);
|
|
115
|
+
? await genLibArchiveBuffer(distPath, version, distFileFilters)
|
|
116
|
+
: await genProjectArchiveBuffer(distPath, distFileFilters);
|
|
114
117
|
log(`打包完成,大小 ${distZipBuffer.length} 字节`);
|
|
115
118
|
|
|
116
119
|
const includeHtml = config.includeHtml === true;
|
package/lib/project.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { simpleGit } from "simple-git";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import inquirer from "inquirer";
|
|
5
6
|
import { log, getJsonConfig, runCmdAsync } from "./util.mjs";
|
|
6
7
|
import { getCache as getLoginCache } from "./login.mjs";
|
|
7
8
|
import { genProjectArchiveBuffer } from "./archive.mjs";
|
|
@@ -55,13 +56,36 @@ async function apiDeployProject(name, version, env, userCache) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
async function
|
|
59
|
+
async function apiGetTemplateProjects(userCache) {
|
|
59
60
|
try {
|
|
61
|
+
const res = await fetch(`${apiHost}/h5projects/template`, {
|
|
62
|
+
headers: {
|
|
63
|
+
Cookie: `Tuwan_Passport=${userCache.Tuwan_Passport}`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const resJson = await res.json();
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
const error = (resJson && resJson.message) || "获取模板列表失败";
|
|
69
|
+
throw new Error(error);
|
|
70
|
+
}
|
|
71
|
+
if (!Array.isArray(resJson)) {
|
|
72
|
+
throw new Error("获取模板列表失败: 响应格式不正确");
|
|
73
|
+
}
|
|
74
|
+
return resJson;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`获取模板列表失败: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function apiCreateProject(name, userCache, templateId) {
|
|
81
|
+
try {
|
|
82
|
+
const body = { name };
|
|
83
|
+
if (templateId) {
|
|
84
|
+
body.templateId = templateId;
|
|
85
|
+
}
|
|
60
86
|
const res = await fetch(`${apiHost}/h5projects`, {
|
|
61
87
|
method: "POST",
|
|
62
|
-
body: JSON.stringify(
|
|
63
|
-
name: name,
|
|
64
|
-
}),
|
|
88
|
+
body: JSON.stringify(body),
|
|
65
89
|
headers: {
|
|
66
90
|
"Content-Type": "application/json",
|
|
67
91
|
Cookie: `Tuwan_Passport=${userCache.Tuwan_Passport}`,
|
|
@@ -144,7 +168,6 @@ async function apiGetProjectVersion(name, userCache) {
|
|
|
144
168
|
*/
|
|
145
169
|
export const cmdProjectCreate = async () => {
|
|
146
170
|
const name = process.argv[3];
|
|
147
|
-
const projectDir = path.join(process.cwd(), name);
|
|
148
171
|
if (!name) {
|
|
149
172
|
log("请输入项目名称");
|
|
150
173
|
process.exit(1);
|
|
@@ -158,6 +181,7 @@ export const cmdProjectCreate = async () => {
|
|
|
158
181
|
log("项目名称不能是纯数字");
|
|
159
182
|
process.exit(1);
|
|
160
183
|
}
|
|
184
|
+
const projectDir = path.join(process.cwd(), name);
|
|
161
185
|
if (fs.existsSync(projectDir)) {
|
|
162
186
|
log(`项目目录${projectDir}已存在`);
|
|
163
187
|
process.exit(1);
|
|
@@ -167,7 +191,33 @@ export const cmdProjectCreate = async () => {
|
|
|
167
191
|
log("请先登录");
|
|
168
192
|
process.exit(1);
|
|
169
193
|
}
|
|
170
|
-
|
|
194
|
+
|
|
195
|
+
const templates = await apiGetTemplateProjects(userCache);
|
|
196
|
+
if (!templates.length) {
|
|
197
|
+
log("没有可用的项目模板");
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
const templateChoices = templates.map((item) => ({
|
|
201
|
+
name: item.description ? `${item.name} - ${item.description}` : item.name,
|
|
202
|
+
value: item.id,
|
|
203
|
+
}));
|
|
204
|
+
const { templateId } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: "list",
|
|
207
|
+
name: "templateId",
|
|
208
|
+
message: "请选择项目模板",
|
|
209
|
+
choices: templateChoices,
|
|
210
|
+
},
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
const selectedTemplate = templates.find((item) => item.id === templateId);
|
|
214
|
+
if (!selectedTemplate) {
|
|
215
|
+
log("选择的项目模板不存在");
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
log(`将以模板: ${selectedTemplate.name} 创建项目`);
|
|
219
|
+
// process.exit(0);
|
|
220
|
+
const createResult = await apiCreateProject(name, userCache, templateId);
|
|
171
221
|
const repoUrl = createResult.repoUrl;
|
|
172
222
|
log(`获得项目仓库地址: ${repoUrl}`);
|
|
173
223
|
const httpRepoUrl = repoUrl + ".git";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taole/deploy-helper",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8-beta.2",
|
|
4
4
|
"description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"archiver": "^7.0.1",
|
|
29
29
|
"form-data": "^4.0.5",
|
|
30
|
+
"inquirer": "^12.11.1",
|
|
30
31
|
"node-scp": "^0.0.25",
|
|
31
32
|
"simple-git": "^3.28.0"
|
|
32
33
|
}
|