@taole/deploy-helper 1.0.0-beta.5 → 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.
@@ -0,0 +1,314 @@
1
+ import fs from 'fs';
2
+ import archiver from 'archiver';
3
+ import { join } from "node:path"
4
+ import { log, getOssToken, getUserDeployHelperConfig } from './util.mjs';
5
+ import { uploadFile } from './upload.js';
6
+ import md5File from 'md5-file';
7
+
8
+
9
+
10
+ function genArchive(outputPath, dir) {
11
+ return new Promise((resolve, reject) => {
12
+ const output = fs.createWriteStream(outputPath);
13
+ const archive = archiver('zip', {
14
+ zlib: { level: 1 } // Sets the compression level.
15
+ });
16
+ archive.on('error', reject);
17
+ archive.on("finish", resolve);
18
+ archive.pipe(output);
19
+ archive.directory(dir, false);
20
+ archive.finalize();
21
+ })
22
+ }
23
+
24
+ /**
25
+ * json配置
26
+ * @param {string} workDir 项目工作目录
27
+ * @returns {Object|null} 配置
28
+ */
29
+ export function getJsonConfig(workDir, name) {
30
+ const filePath = join(workDir, name);
31
+ if (fs.existsSync(filePath)) {
32
+ try {
33
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
34
+ } catch (_error) {
35
+ // pass
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * 同步离线包列表到api
43
+ * @param {{offlineApi: {get: string, set: string}}} userDeployHelperConfig 用户配置
44
+ * @param {"prod"|"test"} mode 部署模式 prod: 线上, test: 测试
45
+ * @param {{name: string, remove: boolean}} offlineConfig 离线包配置
46
+ * @param {any} packageDescJson 离线包描述json
47
+ */
48
+ export async function syncApi(userDeployHelperConfig, mode, offlineConfig, packageDescJson) {
49
+ // 根据部署模式,决定更新调用哪个接口
50
+ const doAction = offlineConfig.remove ? "remove" : "add";
51
+ const apiDomain = mode === "prod" ? "https://yapi.tuwan.com" : "https://yapi-test.tuwan.com";
52
+ const getPath = userDeployHelperConfig.offlineApi.get;
53
+ const setPath = userDeployHelperConfig.offlineApi.set;
54
+ const getUrl = `${apiDomain}${getPath}`;
55
+ const setUrl = `${apiDomain}${setPath}`;
56
+ const envPrefix = `${mode === "prod" ? "线上环境" : "测试环境"}[platform=${offlineConfig.platform}]`;
57
+ const nowRes = await fetch(getUrl, {
58
+ headers: {
59
+ platform: String(offlineConfig.platform),
60
+ }
61
+ });
62
+ const nowData = await nowRes.json();
63
+ let needSetData = true;
64
+ let finalJsonData = null;
65
+ if (!nowData || nowData.error !== 0) {
66
+ throw new Error(`获取离线包列表失败, 获取离线包当前配置失败: ${nowData.error_msg || "未知错误"}`);
67
+ }
68
+ const jsonData = JSON.parse(nowData.data.json || "{}");
69
+ finalJsonData = jsonData;
70
+ jsonData.packages = jsonData.packages || [];
71
+ if (doAction === "add") {
72
+ const idx = jsonData.packages.findIndex(item => item.name === offlineConfig.name);
73
+ if (idx !== -1) {
74
+ log(`离线包${offlineConfig.name}已存在, 更新离线包`);
75
+ jsonData.packages[idx] = packageDescJson;
76
+ } else {
77
+ log(`离线包${offlineConfig.name}不存在, 添加离线包`);
78
+ jsonData.packages.push(packageDescJson);
79
+ }
80
+ } else {
81
+ const idx = jsonData.packages.findIndex(item => item.name === offlineConfig.name);
82
+ if (idx === -1) {
83
+ log(`离线包${offlineConfig.name}不存在, 无需删除`);
84
+ needSetData = false;
85
+ }
86
+ jsonData.packages = jsonData.packages.filter(item => item.name !== offlineConfig.name);
87
+ }
88
+ jsonData.packages = jsonData.packages.filter(item => item.name);
89
+ if (needSetData) {
90
+ // console.log('jsonData', jsonData);
91
+ var formdata = new FormData();
92
+ formdata.append("json", JSON.stringify(jsonData));
93
+ const setRes = await fetch(setUrl, {
94
+ method: "POST",
95
+ body: formdata,
96
+ headers: {
97
+ platform: String(offlineConfig.platform),
98
+ }
99
+ });
100
+ const setData = await setRes.json();
101
+ if (!setData || setData.error !== 0) {
102
+ throw new Error(`更新离线包列表失败, 更新离线包当前配置失败: ${setData.error_msg || "未知错误"}`);
103
+ }
104
+ log(`${envPrefix}离线包列表更新完成: ${JSON.stringify(setData)}`);
105
+ // 再次获取列表,确保更新成功
106
+ const nowRes2 = await fetch(getUrl, {
107
+ headers: {
108
+ platform: String(offlineConfig.platform),
109
+ }
110
+ });
111
+ const nowData2 = await nowRes2.json();
112
+ if (!nowData2 || nowData2.error !== 0) {
113
+ throw new Error(`再次获取离线包列表失败, 获取离线包当前配置失败: ${nowData2.error_msg || "未知错误"}`);
114
+ }
115
+ finalJsonData = JSON.parse(nowData2.data.json || "{}");
116
+ }
117
+
118
+ const packages = finalJsonData.packages || [];
119
+ console.log(`${envPrefix}当前离线包列表:`, packages.map(item => ({ ...item, items: '省略' + item.items.length + '个条目' })));
120
+ }
121
+
122
+ /**
123
+ * @deprecated 不再需要这个函数, 如果插件使用的是@taole/vite-plugin-dynamic-base, 则不需要这个函数
124
+ * hack
125
+ * 由于vite-plugin-dynamic-base插件和legacy插件同时使用, 生存的入口文件中代码有点问题,这里直接修改有问题的代码
126
+ */
127
+ function fixEntryHtml(config, offlineConfig) {
128
+ if (!offlineConfig || !config) return;
129
+ const entryHtmlPath = join(config.workDir, offlineConfig.distDir, "index.html");
130
+ if (!fs.existsSync(entryHtmlPath)) {
131
+ throw new Error(`构建产物${entryHtmlPath}不存在`);
132
+ }
133
+ const markLine = "} else if (item.tagName == 'script') {";
134
+ const replaceLine = `} else if (item.tagName == 'script')/*injected by deploy-helper*/{
135
+ if(item.attrs.id=='vite-legacy-polyfill')childNode.onload=function(){System.import(window.__dynamic_base__ + document.getElementById('vite-legacy-entry').getAttribute('data-src'))};
136
+ `;
137
+ let entryHtml = fs.readFileSync(entryHtmlPath, "utf-8");
138
+ entryHtml = entryHtml.replace(markLine, replaceLine);
139
+ fs.writeFileSync(entryHtmlPath, entryHtml);
140
+ }
141
+
142
+ /**
143
+ * @typedef {Object} CheckOfflinePkgResult
144
+ * @property {string} workDir 工作目录
145
+ * @property {boolean} canBuild 是否可以构建离线包
146
+ * @property {string} errorMsg 错误信息
147
+ * @property {function} hookPostBuild 构建后钩子
148
+ * @property {function} hookPostDeployTest 部署到测试环境后钩子
149
+ * @property {function} hookPostDeployProd 部署到生产环境后钩子
150
+ */
151
+
152
+ /**
153
+ * 在项目构建好之后,将项目打包成离线包,并上传到指定服务器
154
+ * @param {Object} config 配置
155
+ * @param {string} config.workDir 工作目录
156
+ * @param {"prod"|"test"} config.mode 部署模式
157
+ * @returns {CheckOfflinePkgResult} 结果
158
+ */
159
+ export function checkOfflinePkg(config) {
160
+ let canBuildOfflinePkg = true;
161
+ let errorMsg = "";
162
+ const offlineConfig = getJsonConfig(config.workDir, "offline.config.json");
163
+ if (offlineConfig) {
164
+ offlineConfig.distDir = offlineConfig.distDir || "dist";
165
+ offlineConfig.platform = offlineConfig.platform || 3; // 3,4是点点
166
+ }
167
+ const packageJson = getJsonConfig(config.workDir, "package.json");
168
+ const userDeployHelperConfig = getUserDeployHelperConfig();
169
+ const distPath = join(config.workDir, offlineConfig && offlineConfig.distDir ? offlineConfig.distDir : "dist");
170
+ const allDeps = {
171
+ ...packageJson.devDependencies,
172
+ ...packageJson.dependencies,
173
+ };
174
+ const useOldPDB = !!allDeps['vite-plugin-dynamic-base'];
175
+
176
+ // 如果offlineConfig.remove为true,则不构建离线包
177
+ const willRemovePkg = offlineConfig && offlineConfig.name && offlineConfig.remove === true && offlineConfig.skip !== true;
178
+ const willSkip = !offlineConfig || !offlineConfig.name || offlineConfig.skip === true || (offlineConfig.onlyTest === true && config.mode === "prod");
179
+ if (willRemovePkg) {
180
+ return {
181
+ canBuild: true,
182
+ errorMsg: "",
183
+ hookPostBuild: async () => {
184
+ if (useOldPDB) {
185
+ fixEntryHtml(config, offlineConfig);
186
+ }
187
+ log(`开始删除离线包${offlineConfig.name}`);
188
+ await syncApi(userDeployHelperConfig, config.mode, offlineConfig, null);
189
+ }
190
+ }
191
+ }
192
+ if (!fs.existsSync(distPath)) {
193
+ errorMsg = `构建产物${distPath}不存在, 请先构建项目`;
194
+ canBuildOfflinePkg = false;
195
+ } else if (offlineConfig && !offlineConfig.name) {
196
+ errorMsg = "离线包配置文件中未配置name, 请先配置";
197
+ canBuildOfflinePkg = false;
198
+ } else if (!userDeployHelperConfig || !userDeployHelperConfig.offlineApi || !userDeployHelperConfig.offlineApi.get || !userDeployHelperConfig.offlineApi.set) {
199
+ errorMsg = "未配置离线包接口, 请移步看配置文档: https://alidocs.dingtalk.com/i/nodes/R1zknDm0WRkbz13oHn0LDz3ZVBQEx5rG?doc_type=wiki_doc";
200
+ canBuildOfflinePkg = false;
201
+ } else if (!packageJson) {
202
+ errorMsg = "啊没有package.json?";
203
+ canBuildOfflinePkg = false;
204
+ } else {
205
+ if (!allDeps['vite-plugin-dynamic-base'] && !allDeps['@taole/vite-plugin-dynamic-base']) {
206
+ errorMsg = "检查到项目中未安装依赖@taole/vite-plugin-dynamic-base, 请先安装依赖并调整构建代码以支持离线化,相关文档:https://alidocs.dingtalk.com/i/nodes/ydxXB52LJqexwD71F9K5XNMrJqjMp697?doc_type=wiki_doc";
207
+ canBuildOfflinePkg = false;
208
+ }
209
+ }
210
+ if (willSkip) {
211
+ if (!offlineConfig) {
212
+ return {
213
+ canBuild: false,
214
+ errorMsg: ""
215
+ }
216
+ }
217
+ if (offlineConfig.onlyTest === true && config.mode === "prod") {
218
+ console.log(`离线包${offlineConfig.name}只用于测试环境, 跳过构建线上环境的离线包构建`);
219
+ }
220
+ return {
221
+ canBuild: true,
222
+ errorMsg: "",
223
+ hookPostBuild: async () => {
224
+ if (useOldPDB) {
225
+ fixEntryHtml(config, offlineConfig);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ let ossToken = "";
231
+ if (canBuildOfflinePkg) {
232
+ ossToken = getOssToken();
233
+ if (!ossToken) {
234
+ errorMsg = "获取ossToken失败";
235
+ canBuildOfflinePkg = false;
236
+ }
237
+ }
238
+ if (!canBuildOfflinePkg) {
239
+ return {
240
+ canBuild: false,
241
+ errorMsg: errorMsg
242
+ }
243
+ }
244
+ const hookPostBuild = async () => {
245
+ if (!offlineConfig || !config) return;
246
+ // 在构建完成后,执行一些操作
247
+ if (useOldPDB) {
248
+ fixEntryHtml(config, offlineConfig);
249
+ }
250
+ // 开始进行打包流程
251
+ const offlinePkgDir = join(config.workDir, "./offlinepkgDist");
252
+ if (fs.existsSync(offlinePkgDir)) {
253
+ fs.rmSync(offlinePkgDir, { recursive: true });
254
+ } else {
255
+ fs.mkdirSync(offlinePkgDir, { recursive: true });
256
+ }
257
+
258
+ const packageDir = join(offlinePkgDir, "./package");
259
+ fs.mkdirSync(packageDir, { recursive: true });
260
+ // cp dist到offlinepkgDist目录下
261
+ fs.cpSync(join(config.workDir, offlineConfig.distDir), join(packageDir, offlineConfig.distDir), { recursive: true });
262
+
263
+ const targetArchive = join(offlinePkgDir, `./${packageJson.version}.zip`);
264
+ await genArchive(targetArchive, packageDir);
265
+ log(`离线包zip包打包完成: ${targetArchive}`);
266
+
267
+ const packageDescJson = { // 包描述json,之后会通过接口更新
268
+ name: offlineConfig.name,
269
+ version: packageJson.version,
270
+ md5: "",
271
+ packageUrl: "",
272
+ items: [],
273
+ updateTime: String(Math.floor(Date.now() / 1000)),
274
+ cacheTime: offlineConfig.cacheTime || "7200",
275
+ isLazy: offlineConfig.isLazy || 0,
276
+ openType: offlineConfig.openType || "file",
277
+ };
278
+ Object.keys(offlineConfig.entry || {}).forEach(key => {
279
+ const val = offlineConfig.entry[key];
280
+ let vals = [];
281
+ if (typeof val === 'string') {
282
+ vals.push(val);
283
+ } else if (Array.isArray(val)) {
284
+ vals = val;
285
+ }
286
+ vals.forEach(v => {
287
+ packageDescJson.items.push({
288
+ "path": key,
289
+ "mimeType": "text/html",
290
+ "remoteUrl": v,
291
+ "isEntrance": 1
292
+ });
293
+ });
294
+ });
295
+ packageDescJson.md5 = await md5File(targetArchive);
296
+ const uploadResult = await uploadFile(ossToken, targetArchive, packageDescJson.md5);
297
+ packageDescJson.packageUrl = uploadResult.url;
298
+ fs.writeFileSync(join(offlinePkgDir, "./offline.package.json"), JSON.stringify(packageDescJson, null, 2));
299
+ log(`离线包描述json写入完成: ${join(offlinePkgDir, "./offline.package.json")}`);
300
+
301
+ if (config.mode !== "prod") {
302
+ packageDescJson.items.forEach(item => {
303
+ item.remoteUrl = item.remoteUrl.replace("https://y.tuwan.com", "https://y-test.tuwan.com");
304
+ });
305
+ }
306
+ await syncApi(userDeployHelperConfig, config.mode, offlineConfig, packageDescJson);
307
+ }
308
+ return {
309
+ canBuild: canBuildOfflinePkg,
310
+ errorMsg: errorMsg,
311
+ hookPostBuild: hookPostBuild,
312
+ }
313
+ }
314
+
@@ -354,8 +354,7 @@ export async function runPipeline(config, mode) {
354
354
  }
355
355
  const token = getYunxiaoToken(pipelineConfig);
356
356
  if (!token) {
357
- log(`未设置云效token, 此次流水线未自动执行: ${JSON.stringify(pipelineConfig)}, 请移步网页版${`https://flow.aliyun.com/pipelines/${pipelineConfig.id}`}手动执行`);
358
- return;
357
+ log(`未设置云效token, 此次流水线未自动执行: ${JSON.stringify(pipelineConfig)}`);
359
358
  }
360
359
  process.env.YUNXIAO_ACCESS_TOKEN = token;
361
360
 
@@ -0,0 +1,346 @@
1
+ // 处理项目相关的操作
2
+ import { simpleGit } from "simple-git";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import archiver from "archiver";
6
+ import { log, getJsonConfig, runCmdAsync } from "./util.mjs";
7
+ import { getCache as getLoginCache } from "./login.mjs";
8
+
9
+ const isDev = false;
10
+ const apiHost = isDev ? "http://localhost:9000" : "https://fapi.tuwan.com";
11
+
12
+ function genArchive(outputPath, dir) {
13
+ return new Promise((resolve, reject) => {
14
+ const output = fs.createWriteStream(outputPath);
15
+ const archive = archiver("zip", {
16
+ zlib: { level: 1 }, // Sets the compression level.
17
+ });
18
+ archive.on("error", reject);
19
+ archive.on("finish", () => {
20
+ // console.log("genArchive finish");
21
+ setTimeout(() => {
22
+ // console.log("genArchive finish true");
23
+ resolve();
24
+ }, 300);
25
+ });
26
+ archive.pipe(output);
27
+ archive.directory(dir, false);
28
+ archive.finalize();
29
+ });
30
+ }
31
+
32
+ async function apiDeployProject(name, version, env, userCache) {
33
+ try {
34
+ const urlPath = `/h5projects/${name}/deploy`;
35
+ const res = await fetch(`${apiHost}${urlPath}`, {
36
+ method: "POST",
37
+ body: JSON.stringify({
38
+ name: name,
39
+ version: version,
40
+ env: env,
41
+ }),
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ Cookie: `Tuwan_Passport=${userCache.Tuwan_Passport}`,
45
+ },
46
+ });
47
+ const resJson = await res.json();
48
+ if (!res.ok) {
49
+ const error = (resJson && resJson.message) || "部署项目失败";
50
+ throw new Error(error);
51
+ }
52
+ return resJson;
53
+ } catch (error) {
54
+ const errorMessage = error.message || "部署项目失败";
55
+ throw new Error(errorMessage);
56
+ }
57
+ }
58
+
59
+ async function apiCreateProject(name, userCache) {
60
+ try {
61
+ const res = await fetch(`${apiHost}/h5projects`, {
62
+ method: "POST",
63
+ body: JSON.stringify({
64
+ name: name,
65
+ }),
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ Cookie: `Tuwan_Passport=${userCache.Tuwan_Passport}`,
69
+ },
70
+ });
71
+ const resJson = await res.json();
72
+ if (!res.ok) {
73
+ const error = (resJson && resJson.message) || "创建项目失败";
74
+ throw new Error(error);
75
+ }
76
+ if (!resJson || resJson.name !== name) {
77
+ throw new Error(
78
+ `创建项目失败: 项目名称不匹配: ${resJson.name} !== ${name}`
79
+ );
80
+ }
81
+ return resJson;
82
+ } catch (error) {
83
+ throw new Error(`创建项目失败: ${error.message}`);
84
+ }
85
+ }
86
+
87
+ async function apiPublishProject(name, version, distZipPath, userCache) {
88
+ const urlPath = `/h5projects/${name}/publish`;
89
+ const formData = new FormData();
90
+ formData.append("updaterUid", userCache.userInfo.uid);
91
+ formData.append("version", version);
92
+ formData.append(
93
+ "file",
94
+ new Blob([fs.readFileSync(distZipPath)], { type: "application/zip" }),
95
+ "dist.zip"
96
+ );
97
+ const res = await fetch(`${apiHost}${urlPath}`, {
98
+ method: "POST",
99
+ body: formData,
100
+ headers: {
101
+ Cookie: `Tuwan_Passport=${userCache.Tuwan_Passport}`,
102
+ },
103
+ });
104
+ const resJson = await res.json();
105
+ if (!res.ok) {
106
+ const error = (resJson && resJson.message) || "更新项目失败";
107
+ throw new Error(error);
108
+ }
109
+ return resJson;
110
+ }
111
+
112
+ function patchVersion(version) {
113
+ const versions = version.split(".");
114
+ versions[versions.length - 1] = parseInt(versions[versions.length - 1]) + 1;
115
+ return versions.join(".");
116
+ }
117
+
118
+ /**
119
+ * 获取最新可以使用的版本号
120
+ */
121
+ async function apiGetProjectVersion(name, userCache) {
122
+ const urlPath = `/h5projects/${name}/build/lasted`;
123
+ const res = await fetch(`${apiHost}${urlPath}`, {
124
+ headers: {
125
+ Cookie: `Tuwan_Passport=${userCache.Tuwan_Passport}`,
126
+ },
127
+ });
128
+ const resJson = await res.json();
129
+ if (!res.ok) {
130
+ if (res.status === 404) {
131
+ return "0.0.1";
132
+ }
133
+ const error = (resJson && resJson.message) || "获取项目版本失败";
134
+ throw new Error(error);
135
+ }
136
+ const version = resJson.version;
137
+ if (/^\d+\.\d+\.\d+$/.test(version)) {
138
+ return patchVersion(version);
139
+ }
140
+ throw new Error(`获取项目版本失败: ${version}`);
141
+ }
142
+
143
+ /**
144
+ * 创建项目
145
+ */
146
+ export const cmdProjectCreate = async () => {
147
+ const name = process.argv[3];
148
+ const projectDir = path.join(process.cwd(), name);
149
+ if (!name) {
150
+ log("请输入项目名称");
151
+ process.exit(1);
152
+ }
153
+ if (!/^[a-zA-Z0-9-_]+$/.test(name)) {
154
+ log("项目名称只能包含字母、数字和-_");
155
+ process.exit(1);
156
+ }
157
+ // 不能是纯数字
158
+ if (/^\d+$/.test(name)) {
159
+ log("项目名称不能是纯数字");
160
+ process.exit(1);
161
+ }
162
+ if (fs.existsSync(projectDir)) {
163
+ log(`项目目录${projectDir}已存在`);
164
+ process.exit(1);
165
+ }
166
+ const userCache = await getLoginCache();
167
+ if (!userCache || !userCache.userInfo || !userCache.userInfo.uid) {
168
+ log("请先登录");
169
+ process.exit(1);
170
+ }
171
+ const createResult = await apiCreateProject(name, userCache);
172
+ const repoUrl = createResult.repoUrl;
173
+ log(`获得项目仓库地址: ${repoUrl}`);
174
+ const httpRepoUrl = repoUrl + ".git";
175
+ const sshRepoUrl = httpRepoUrl.replace(
176
+ "https://codeup.aliyun.com/",
177
+ "git@codeup.aliyun.com:"
178
+ );
179
+ // https://codeup.aliyun.com/69b3821af7b43e00d420cb32/Web-Act/demo2-cli.git
180
+ // https://codeup.aliyun.com/69b3821af7b43e00d420cb32/Web-Act/demo2-cli
181
+ // git@codeup.aliyun.com:69b3821af7b43e00d420cb32/Web-Act/demo2-cli.git
182
+ let git = simpleGit(process.cwd());
183
+ let sshFail = false;
184
+ try {
185
+ await git.clone(sshRepoUrl, name);
186
+ } catch (error) {
187
+ log(`使用SSH协议克隆失败, 尝试使用HTTP协议克隆`);
188
+ sshFail = true;
189
+ }
190
+ if (sshFail) {
191
+ try {
192
+ await git.clone(httpRepoUrl, name);
193
+ } catch (error) {
194
+ throw new Error(`克隆仓库失败`);
195
+ }
196
+ }
197
+ log(`项目创建成功, 项目目录: ${name}`);
198
+ git = simpleGit(projectDir);
199
+ const targetPackageJson = getJsonConfig(projectDir, "package.json");
200
+ targetPackageJson.name = name;
201
+ fs.writeFileSync(
202
+ path.join(projectDir, "package.json"),
203
+ JSON.stringify(targetPackageJson, null, 2)
204
+ );
205
+ log(`项目package.json覆盖成功`);
206
+ await git.add(".");
207
+ await git.commit(`-#DH-07 feat: 项目初始化`);
208
+ log(`请cd至项目目录: ${projectDir} 手动执行: npm install 安装依赖`);
209
+ process.exit(0);
210
+ };
211
+
212
+ /**
213
+ * 更新项目到H5平台
214
+ */
215
+
216
+ export const cmdProjectPublish = async () => {
217
+ const env = process.argv[3];
218
+ if (!env) {
219
+ log("请输入环境: test, prod");
220
+ process.exit(1);
221
+ }
222
+ if (env !== "test" && env !== "prod") {
223
+ log("环境只能输入test或prod");
224
+ process.exit(1);
225
+ }
226
+
227
+ const git = simpleGit(process.cwd());
228
+ const gitStatus = await git.status();
229
+ if (!gitStatus.isClean()) {
230
+ log("工作区还有未提交的代码,请先提交代码");
231
+ process.exit(1);
232
+ }
233
+ const userCache = await getLoginCache();
234
+ if (!userCache || !userCache.userInfo || !userCache.userInfo.uid) {
235
+ log("请先登录");
236
+ process.exit(1);
237
+ }
238
+ const packageJson = getJsonConfig(process.cwd(), "package.json");
239
+ if (!packageJson || !packageJson.version || !packageJson.name) {
240
+ log("package.json或者其version字段或name字段不存在");
241
+ process.exit(1);
242
+ }
243
+
244
+ const newVersion = await apiGetProjectVersion(packageJson.name, userCache);
245
+ packageJson.version = newVersion;
246
+ fs.writeFileSync(
247
+ path.join(process.cwd(), "package.json"),
248
+ JSON.stringify(packageJson, null, 2)
249
+ );
250
+ log(`更新package.json版本号为: ${newVersion}`);
251
+ await git.add(".");
252
+ await git.commit(`-#DH-07 feat: 更新版本号为: ${newVersion}`);
253
+ await git.push();
254
+ log(`提交代码成功,开始构建项目`);
255
+
256
+ const buildCmd = "npm run build";
257
+ await runCmdAsync(buildCmd);
258
+ log(`构建项目成功,开始打包构建产物`);
259
+ const distPath = path.join(process.cwd(), "dist");
260
+ const distZipPath = path.join(process.cwd(), "dist.zip");
261
+ if (!fs.existsSync(distPath)) {
262
+ log("dist目录不存在");
263
+ process.exit(1);
264
+ }
265
+ // 把dist目录打包为zip包
266
+ if (fs.existsSync(distZipPath)) {
267
+ fs.unlinkSync(distZipPath);
268
+ }
269
+ await genArchive(distZipPath, distPath);
270
+ log(`dist目录打包为zip包: ${distZipPath}, 开始推送构建产物`);
271
+ const _publishResult = await apiPublishProject(
272
+ packageJson.name,
273
+ packageJson.version,
274
+ distZipPath,
275
+ userCache
276
+ );
277
+ log(`构建产物推送成功, 开始部署项目至${env}环境`);
278
+ const _deployResult = await apiDeployProject(
279
+ packageJson.name,
280
+ packageJson.version,
281
+ env,
282
+ userCache
283
+ );
284
+ log(`部署项目至${env}环境成功`);
285
+ process.exit(0);
286
+ };
287
+
288
+
289
+ export const cmdProjectPull = async () => {
290
+ const name = process.argv[3];
291
+ const projectDir = path.join(process.cwd(), name);
292
+ if (!name) {
293
+ log("请输入项目名称");
294
+ process.exit(1);
295
+ }
296
+ if (!/^[a-zA-Z0-9-_]+$/.test(name)) {
297
+ log("项目名称只能包含字母、数字和-_");
298
+ process.exit(1);
299
+ }
300
+ // 不能是纯数字
301
+ if (/^\d+$/.test(name)) {
302
+ log("项目名称不能是纯数字");
303
+ process.exit(1);
304
+ }
305
+ if (fs.existsSync(projectDir)) {
306
+ log(`项目目录${projectDir}已存在`);
307
+ process.exit(1);
308
+ }
309
+ const repoUrl = `https://codeup.aliyun.com/69b3821af7b43e00d420cb32/Web-Act/${name}`;
310
+ log(`项目仓库地址: ${repoUrl}`);
311
+ const httpRepoUrl = repoUrl + ".git";
312
+ const sshRepoUrl = httpRepoUrl.replace(
313
+ "https://codeup.aliyun.com/",
314
+ "git@codeup.aliyun.com:"
315
+ );
316
+ let git = simpleGit(process.cwd());
317
+ let sshFail = false;
318
+ try {
319
+ await git.clone(sshRepoUrl, name);
320
+ } catch (error) {
321
+ log(`使用SSH协议克隆失败, 尝试使用HTTP协议克隆`);
322
+ sshFail = true;
323
+ }
324
+ if (sshFail) {
325
+ try {
326
+ await git.clone(httpRepoUrl, name);
327
+ } catch (error) {
328
+ throw new Error(`克隆仓库失败`);
329
+ }
330
+ }
331
+ log(`项目拉取成功, 项目目录: ${name}`);
332
+ git = simpleGit(projectDir);
333
+ const targetPackageJson = getJsonConfig(projectDir, "package.json");
334
+ if(targetPackageJson.name !== name){
335
+ targetPackageJson.name = name;
336
+ fs.writeFileSync(
337
+ path.join(projectDir, "package.json"),
338
+ JSON.stringify(targetPackageJson, null, 2)
339
+ );
340
+ log(`项目package.json覆盖成功`);
341
+ await git.add(".");
342
+ await git.commit(`-#DH-07 feat: 项目初始化`);
343
+ }
344
+ log(`请cd至项目目录: ${projectDir} 进行后续开发`);
345
+ process.exit(0);
346
+ }