UNPKG

28.6 kBJavaScriptView Raw
1class SvnCommit{
2 constructor(name,options){
3 const _ts = this;
4
5 //自定义模块
6 _ts.m = {
7 path:require('path'),
8 fs:require('fs-extra'),
9 os:require('os'),
10 url:require('url'),
11 svn:require('svn-spawn'),
12 clipboardy:require('clipboardy'),
13 zip:require('../lib/zip'),
14 pathInfo:require('../lib/getPathInfo'),
15 tip:require('../lib/tip')
16 };
17
18 _ts.name = name;
19 _ts.option = options;
20
21 //记录时间,用于提交代码的路径拼接
22 let time = new Date();
23 _ts.time = {
24 year:time.getFullYear()+'',
25 month:(()=>{
26 let m = time.getMonth() + 1;
27 return m < 10 ? '0'+m : m;
28 })()
29 };
30
31 let currentDirName = fws.cmdPath.replace(_ts.m.path.join(fws.cmdPath,'..','/'),''), //得到当前目录名称
32 projectType = currentDirName[0] === 'p' ? 'pc' : currentDirName[0] === 'm' ? 'mobile' : undefined; //得到项目类型
33
34 projectType = _ts.option.pc ? 'pc' : _ts.option.mobile ? 'mobile' : projectType;
35
36 //svn地址
37 _ts.config = {
38 svns:{
39 mobile:'https://sypt.4399svn.com/svn/Platform_Doc/page/4399gdc/',
40 pc:'https://gdc.4399svn.com/svn/yypt/page/'
41 },
42 preview:{
43 mobile:'http://static.4399gdc.com/sypt/4399gdc/',
44 pc:'http://static.4399gdc.com/yypt/'
45 },
46 currentDirName:currentDirName,
47 projectType:projectType
48 };
49
50 //svn配置
51 let option = {};
52 option.cwd = fws.cmdPath;
53 if(fws.config.svn[projectType]){
54 option.username = fws.config.svn[projectType].username;
55 option.password = fws.config.svn[projectType].password;
56 };
57
58 option.noAuthCache = true;
59
60 // SVN需要过滤的目录、文件
61 _ts.svnIgnoreConfig = require('../lib/svnIgnore.json');
62
63 _ts.client = new _ts.m.svn(option);
64 }
65
66 init(){
67 const _ts = this,
68 m = _ts.m,
69 config = _ts.config;
70
71 if(config.projectType === undefined){
72 m.tip.error('当前目录名不符合GDC FE Ground 项目命名规范,强行提交代码请手动指定项目类型');
73 return;
74 };
75
76 let data = {},
77 distDirItExist = m.fs.existsSync(fws.distPath),
78 urlInfo = _ts.getUrlInfo(distDirItExist),
79 svnPath = urlInfo.svnUrl,
80 preview = urlInfo.preview,
81 f = async()=>{
82 let svnInfo = await _ts.getSvnInfo(),
83 // excludePaths = _ts.getExcludePath(fws.cmdPath),
84 svnMkdir,
85 checkout,
86 update,
87 zipDist,
88 addLocal,
89 commit,
90 clipboardText,
91 srcPath,
92 outPath,
93 // addPaths = _ts.getDirFilesPath(fws.cmdPath),
94 ignore;
95
96 if(svnInfo.data === 'none'){
97 //无svn信息则在远程创建目录并检出到当前工作目录
98 svnMkdir = await _ts.svnMkdir(svnPath);
99 if(svnMkdir.status === 'error'){
100 m.tip.error('远程创建Svn目录失败');
101 throw new Error(svnMkdir);
102 };
103
104 checkout = await _ts.checkout(svnPath);
105 if(checkout.status === 'success'){
106 m.tip.success('检出远程目录到本地');
107 };
108 }else{
109 //获取svn首次提交的日期,并更新相关路径信息
110 let svnFirstSubmmitDate = svnInfo.data.commit.date.split('-');
111 if(svnFirstSubmmitDate.length){
112 _ts.time.year = svnFirstSubmmitDate[0];
113 _ts.time.month = svnFirstSubmmitDate[1];
114
115 // urlInfo = _ts.getUrlInfo(distDirItExist);
116 // svn地址去除用户名部分
117 svnPath = svnInfo.data.url.replace(new RegExp('\/\/.*@'),'//') + '/';
118 };
119
120 let localSvnVer = +svnInfo.data.$.revision, //得到本地SVN版本号
121 remotelySvnVer = +(await _ts.checkSvn(svnInfo.data.url)).data.Revision; //得到远程SVN版本号
122
123 //远程版本较新时则更新远程版本到本地
124 if(remotelySvnVer > localSvnVer){
125 update = await _ts.update();
126 if(update.status === 'success'){
127 m.tip.success('更新本地代码版本');
128 };
129 };
130 };
131
132 // 得到预览地址
133 preview = (()=>{
134 let s = config.svns[config.projectType],
135 p = config.preview[config.projectType],
136 url = svnPath.replace(s,p);
137 return distDirItExist ? `${url}dist/` : url;
138 })();
139
140 //如果有开启打包选项,并且dist目录存在,则打包dist目录
141 if(_ts.option.zip && distDirItExist){
142 srcPath = fws.distPath;
143 outPath = m.path.join(fws.cmdPath,`${config.currentDirName}--${m.path.basename(srcPath)}.tar`);
144
145 zipDist = await m.zip(srcPath,outPath);
146 if(zipDist.status === 'success'){
147 m.tip.success(`${srcPath} 目录压缩完成`);
148 };
149 }else if(_ts.option.zip){
150 let tempPath = m.path.join(m.os.homedir(),`${config.currentDirName}.tar`); //临时交换路径
151 srcPath = fws.cmdPath;
152 outPath = m.path.join(fws.cmdPath,`${config.currentDirName}.tar`);
153 zipDist = await m.zip(srcPath,tempPath);
154 m.fs.removeSync(outPath); //删除原本的压缩文件
155 m.fs.moveSync(tempPath,outPath); //将临时文件移动到目录地址
156 if(zipDist.status === 'success'){
157 m.tip.success(`${srcPath} 目录压缩完成`);
158 };
159 };
160
161 // 设置过滤规则
162 ignore = await _ts.ignore([..._ts.svnIgnoreConfig.dirs,..._ts.svnIgnoreConfig.files]);
163 if(ignore.success === 'success'){
164 m.tip.success(ignore.msg);
165 };
166
167 //添加所有文件
168 addLocal = await _ts.addLocal();
169 if(addLocal.status === 'success'){
170 m.tip.success('添加所有文件到仓库');
171 };
172
173
174
175 // for(let i=0,len=addPaths.length; i<len; i++){
176 // let item = addPaths[i],
177 // itemInfo = m.pathInfo(item),
178 // svnStatus = await _ts.getStatus(item);
179 // svnStatus = (()=>{
180 // if(svnStatus.data === ''){
181 // return ''
182 // };
183
184 // if(svnStatus.data.indexOf('Error') > -1){
185 // return '?';
186 // };
187
188 // return svnStatus.data[0];
189 // })();
190
191 // //if(svnStatus === '?' || svnStatus === '')
192 // console.log(svnStatus,item)
193 // };
194
195 // return;
196
197
198 // for(let i=0,len=addPaths.length; i<len; i++){
199 // let item = addPaths[i],
200 // itemInfo = m.pathInfo(item),
201 // itemStatus = await _ts.add(item);
202
203 // if(itemStatus.status === 'success'){
204 // m.tip.success(`强行添加 ${item} 完成`);
205 // };
206 // };
207
208 /* //从过滤中过滤要忽略的文件
209 for(let i=0,len=excludePaths.length; i<len; i++){
210 let item = excludePaths[i];
211
212 // item = item.replace(fws.cmdPath,'.');
213
214 // let statu = await _ts.getStatus(item);
215 let del = await _ts.delete(item);
216 if(del.status === 'success'){
217 m.tip.success(`过滤 ${item} 文件`);
218 }
219
220
221
222 // excludeTaskList[i] = await _ts.delete(item);
223 // if(excludeTaskList[i].status === 'success'){
224 // m.tip.success(`过滤 ${item} 文件`);
225 // };
226 }; */
227
228 //提交文件
229 commit = await _ts.commit(`由 ${fws.config.author} 通过 fws push 任务提交`);
230 if(commit.status === 'success'){
231 m.tip.success('提交文件成功');
232 };
233
234 //提交完成之后再刷新本地SVN版本信息
235 update = await _ts.update();
236 if(update.status === 'success'){
237 m.tip.success('更新本地代码版本');
238 };
239
240 //压缩包路径(有开启压缩项,且dist目录存在的,则压缩dist目录。否则压缩项目目录)
241 let zipFileInfo = (()=>{
242 let zipFileName,time,zipFilePath,
243 url = svnPath.replace(
244 config.svns[config.projectType],
245 config.preview[config.projectType]
246 );
247
248 if(_ts.option.zip){
249 zipFileName = m.path.basename(outPath);
250 time = _ts.time;
251 zipFilePath = `${url}${zipFileName}`;
252 return `
253
254**下载:**
255${zipFilePath}
256 `;
257 }else{
258 return '';
259 };
260 })();
261
262 clipboardText = `
263## 项目文件
264
265**预览:**
266${preview}
267${zipFileInfo}
268
269**SVN:**
270${svnPath}
271
272------
273By 4399 [GDC](http://www.4399gdc.com) @${fws.config.author}, From [FWS](https://sbfkcel.github.io/fws/)
274`;
275 m.clipboardy.writeSync(clipboardText);
276
277 m.tip.highlight(`
278以下信息已经复制到剪切板:
279==========`);
280 console.log(`${clipboardText}`);
281 return commit;
282 };
283
284 f().then(v => {
285 m.tip.success('代码提交任务执行完成');
286 }).catch(e => {
287 m.tip.error(v.msg);
288 });
289
290
291 }
292
293 //获取需要排除的目录
294 getExcludePath(path){
295 const _ts = this,
296 m = _ts.m;
297 let paths = [],
298 excludeDirs = _ts.svnIgnoreConfig.dirs,
299 excludeFiles = _ts.svnIgnoreConfig.files,
300 eathDir;
301
302 (eathDir = (path)=>{
303 let pathInfo = m.pathInfo(path), // 文件路径信息
304 isDir = pathInfo.type === 'dir', // 是否为目录
305 isFile = pathInfo.type === 'file', // 是否为文件
306 isExclude = (()=>{ // 是否需要过滤掉
307 let fileName = pathInfo.name + (pathInfo.extension !== undefined ? pathInfo.extension : ''),
308 exeName = pathInfo.extension;
309
310 // 是目录则从excludeDirs匹配文件名
311 return (isDir ? excludeDirs : excludeFiles).some(item => {
312 // 如果传入是文件类型,则对比文件扩展名是否相等,否则就直接匹配文件名
313 if(item[0] === '*'){
314 return exeName === item.substr(-(item.length - 1));
315 }else{
316 return item === fileName;
317 };
318 });
319 })();
320
321 // 如果是需要过滤的文件或目录,则添加保存起来
322 if(isExclude){
323 paths.push(path);
324 };
325
326 // 如果是目录并且不是需要过滤的则继续遍历当前目录
327 if(isDir && !isExclude){
328 m.fs.readdirSync(path).forEach(item => {
329 let filePath = m.path.join(path,item);
330 eathDir(filePath);
331 });
332 };
333 })(path);
334 return paths;
335 }
336
337 async revert(path){
338 const _ts = this;
339 return await new Promise((resolve,reject)=>{
340 try {
341 _ts.client.cmd(['revert',path],(err,data)=>{
342 resolve({
343 status:'success',
344 msg:`回滚${path}成功`,
345 data:data
346 });
347 })
348 } catch (error) {
349 reject({
350 status:'error',
351 msg:`回滚${path}错误`,
352 data:error
353 });
354 };
355 });
356 }
357
358
359 //添加全局过滤规则
360 async ignore(list){
361 const _ts = this;
362
363 // 过滤规则,每条一行
364 let ignoreConfig = (()=>{
365 let s = '';
366 list.forEach((item,index) => {
367 s += `\n${item}${index === list.length - 1 ? '\n':''}`;
368 });
369 return s;
370 })();
371 return await new Promise((resolve,reject)=>{
372 try {
373 _ts.client.cmd(['propset','svn:global-ignores',`'${ignoreConfig}'`,'.'],(err,data)=>{
374 resolve({
375 status:'success',
376 msg:`过滤${list}成功`,
377 data:data
378 });
379 });
380 } catch (error) {
381 reject({
382 status:'error',
383 msg:`过滤${list}失败`,
384 data:error
385 });
386 };
387 });
388 }
389
390 //获取文件状态
391 getStatus(path){
392 const _ts = this;
393 return new Promise((resolve,reject)=>{
394 try {
395 _ts.client.cmd(['status', path],(err,data)=>{
396 resolve({
397 status:'success',
398 msg:`获取 ${path} SVN状态成功`,
399 data:err ? err : data
400 });
401 });
402 } catch (error) {
403 reject({
404 status:'error',
405 msg:`获取 ${path} SVN状态失败`,
406 data:data
407 })
408 };
409 });
410 }
411
412 //获取目录内所有文件路径(包括目录)
413 getDirFilesPath(path){
414 const _ts = this,
415 m = _ts.m;
416 let paths = [],
417 excludeDirs = ['node_modules','.svn','.git'],
418 excludeFiles = ['.DS_Store','Thumbs.db','*.psd','*.psb','*.ppt','*.pptx','*.rp','*.xls','*.xlsx','*.doc','*.docx'],
419 eathDir;
420
421 (eathDir = (path)=>{
422 let pathInfo = m.pathInfo(path), // 文件路径信息
423 isDir = pathInfo.type === 'dir', // 是否为目录
424 isFile = pathInfo.type === 'file', // 是否为文件
425 isExclude = (()=>{ // 是否需要过滤掉
426 let fileName = pathInfo.name + (pathInfo.extension !== undefined ? pathInfo.extension : ''),
427 exeName = pathInfo.extension;
428
429 // 是目录则从excludeDirs匹配文件名
430 return (isDir ? excludeDirs : excludeFiles).some(item => {
431 // 如果传入是文件类型,则对比文件扩展名是否相等,否则就直接匹配文件名
432 if(item[0] === '*'){
433 return exeName === item.substr(-(item.length - 1));
434 }else{
435 return item === fileName;
436 };
437 });
438 })();
439
440 // 如果是需要过滤的文件或目录,则添加保存起来
441 if(!isExclude){
442 // paths.push(path);
443 if(isDir){
444 paths.unshift(path);
445 }else{
446 paths.push(path);
447 };
448 };
449
450 // 如果是目录并且不是需要过滤的则继续遍历当前目录
451 if(isDir && !isExclude){
452 m.fs.readdirSync(path).forEach(item => {
453 let filePath = m.path.join(path,item);
454 eathDir(filePath);
455 });
456 };
457 })(path);
458 return paths;
459 }
460
461
462 //获取Svn信息
463 async getSvnInfo(){
464 const _ts = this;
465 // 先强制执行更新
466 try {
467 await new Promise((resolve,reject)=>{
468 _ts.client.cmd('upgrade',()=>{
469 resolve();
470 })
471 });
472 } catch (error) {};
473
474 return new Promise((resolve,reject)=>{
475 try {
476 _ts.client.getInfo((err,data)=>{
477 if(err){
478 resolve({
479 status:'error',
480 msg:`${fws.cmdPath} 无svn信息`,
481 data:'none'
482 });
483 }else{
484 resolve({
485 status:'success',
486 msg:`${fws.cmdPath} 获取svn信息成功`,
487 data:data
488 });
489 };
490 });
491 } catch (error) {
492 reject({
493 status:'error',
494 msg:`获取svn信息错误`,
495 data:error
496 });
497 };
498 });
499 }
500
501
502 checkout(svnUrl){
503 const _ts = this;
504
505 return new Promise((resolve,reject)=>{
506 _ts.client.checkout(svnUrl,(err,data)=>{
507 if(err){
508 reject({
509 status:'error',
510 msg:`${svnUrl} 检出失败`,
511 data:err
512 });
513 }else{
514 resolve({
515 status:'success',
516 msg:`${svnUrl} 检出成功`,
517 data:data
518 });
519 };
520 });
521 });
522 }
523
524 //svn远程创建目录
525 svnMkdir(svnUrl){
526 const _ts = this,
527 m = _ts.m,
528 config = _ts.config,
529 svnKey = config.projectType;
530
531 //检查是否为部门Svn库
532 return (async()=>{
533 if(!svnKey){
534 throw new Error({
535 status:'error',
536 msg:`${svnUrl} 可能不是有效的GDC项目SVN地址`
537 });
538 };
539
540 //得到SVN远程从根目录到子目录的目录名称,例如:[2018,02,p123]
541 let svnPathDirs = (()=>{
542 let temp = [],
543 dirs = svnUrl.replace(config.svns[svnKey],'').split('/');
544 dirs.forEach(item => {
545 if(item !== ''){
546 temp.push(item);
547 };
548 });
549
550 return temp;
551 })();
552
553 let svnTempUrl = config.svns[svnKey];
554 //依次检查远程目录是否存在,如果不存在则创建
555 for(let i=0,len=svnPathDirs.length; i<len; i++){
556 let item = svnPathDirs[i];
557
558 //拼装svn地址
559 svnTempUrl += `${item}/`;
560
561 //检查远程是否存在该地址,如果地址不存在则创建
562 let check = await _ts.checkSvn(svnTempUrl);
563
564 //检查所有已经都已经存在则直接返回成功
565 if(check.status === 'success' && i===len-1){
566 return {
567 status:'success',
568 msg:`${svnUrl} 目录已经存在`
569 };
570 };
571
572 //目录不存在,则创建
573 if(check.status === 'error'){
574 let mkdir = await new Promise((resolve,reject)=>{
575 _ts.client.cmd(['mkdir','-m','工具创建目录',svnTempUrl],(err,data)=>{
576 if(err){
577 resolve({
578 status:'error',
579 msg:`${svnTempUrl} 目录创建失败`,
580 data:err
581 });
582 }else{
583 resolve({
584 status:'success',
585 msg:`${svnTempUrl} 目录创建成功`
586 });
587 };
588 });
589 });
590
591 if(mkdir.status === 'error'){
592 return {
593 status:'error',
594 msg:`${mkdir.msg}`
595 };
596 };
597
598 if(i===len-1 && mkdir.status === 'success'){
599 return {
600 status:'success',
601 msg:`${svnUrl} 目录创建完成`
602 };
603 };
604 };
605 };
606 })();
607 }
608
609 //svn添加文件
610 add(path){
611 const _ts = this;
612 return new Promise((resolve,reject)=>{
613 try {
614 _ts.client.add(path,(err,data)=>{
615 resolve({
616 status:'success',
617 msg:`svn添加${path}文件成功`,
618 data:err ? err : data
619 });
620 });
621 } catch (error) {
622 reject({
623 status:'error',
624 msg:`svn添加${path}文件错误`,
625 data:error
626 });
627 };
628 });
629 }
630
631 //svn添加所有文件
632 addLocal(){
633 const _ts = this;
634
635 return new Promise((resolve,reject)=>{
636 _ts.client.addLocal((err,data)=>{
637 if(err){
638 reject({
639 status:'error',
640 msg:'svn添加文件失败',
641 data:err
642 });
643 }else{
644 resolve({
645 status:'success',
646 msg:'svn添加文件成功'
647 });
648 };
649 });
650 });
651 }
652
653 //svn提交文件
654 commit(remark){
655 const _ts = this;
656 return new Promise((resolve,reject)=>{
657 _ts.client.commit(remark,(err,data)=>{
658 if(err){
659 reject({
660 status:'error',
661 msg:'svn提交失败',
662 data:err
663 });
664 }else{
665 resolve({
666 status:'success',
667 msg:'svn提交成功'
668 });
669 };
670 });
671 });
672 }
673
674 //删除
675 delete(path){
676 const _ts = this;
677 return new Promise((resolve,reject)=>{
678 _ts.client.del(path,(err,data)=>{
679 if(err){
680 reject({
681 status:'error',
682 msg:`${path} 从svn移除失败`,
683 data:err
684 });
685 }else{
686 resolve({
687 status:'success',
688 msg:`${path} 从svn移除成功`,
689 data:data
690 });
691 };
692 });
693 })
694 }
695
696 //svn更新
697 update(){
698 const _ts = this;
699
700 return new Promise((resolve,reject)=>{
701 _ts.client.update((err,data)=>{
702 if(err){
703 reject({
704 status:'error',
705 msg:'svn更新失败',
706 data:err
707 });
708 }else{
709 resolve({
710 status:'success',
711 msg:'svn更新成功'
712 });
713 };
714 });
715 });
716 }
717
718
719 //获取并创建fws临时交换目录
720 getUserDir(){
721 const _ts = this,
722 m = _ts.m;
723
724 let dirPath = m.os.homedir();
725 dirPath = m.path.join(dirPath,'fws','svnTemp');
726
727 m.fs.ensureDirSync(dirPath);
728 return dirPath;
729 }
730
731 //获取当前项目的的相关URL及信息
732 getUrlInfo(distDirItExist){
733 const _ts = this,
734 m = _ts.m,
735 config = _ts.config;
736
737 let currentDirName = config.currentDirName, //得到当前目录名称
738 projectType = config.projectType, //得到SVN类型
739 time = _ts.time,
740 year = time.year,
741 month = time.month,
742 svnUrl = `${config.svns[projectType]}${year}/${month}/${currentDirName}/`, //当前目录的SVN路径为项目类型SVN地址
743 previewUrl = `${config.preview[projectType]}${year}/${month}/${currentDirName}/${distDirItExist ? 'dist/':''}`;
744
745 return projectType ? {
746 svnUrl:svnUrl,
747 previewUrl:previewUrl
748 } : projectType;
749 }
750
751 //检查Svn地址是否有效
752 checkSvn(svnUrl){
753 const _ts = this;
754 return new Promise((resolve,reject)=>{
755 try {
756 _ts.client.cmd(['info',svnUrl],(err,data)=>{
757 if(err){
758 resolve({
759 status:'error',
760 msg:'svn地址无效',
761 path:svnUrl,
762 data:err
763 });
764 }else{
765 let temp = {};
766
767 //将获取到的远程SVN信息格式化为对象
768 data = data.split('\n');
769 data.forEach((item,index)=>{
770 item = item.split(': ');
771 let key = item[0].replace(/ /ig,''),
772 val = item[1];
773 if(key && val){
774 temp[key] = val;
775 };
776 });
777
778 resolve({
779 status:'success',
780 msg:'svn地址有效',
781 path:svnUrl,
782 data:temp
783 });
784 };
785 });
786 } catch (error) {
787 reject({
788 status:'error',
789 msg:`检查svn信息 ${svnUrl} 出现错误`,
790 path:svnUrl,
791 data:error
792 });
793 };
794 });
795 }
796}
797
798module.exports = {
799 regTask:{
800 command:'[name]',
801 description:'推送文件到SVN服务器',
802 option:[
803 ['-p, --pc [type]','指定项目为pc类型'],
804 ['-m, --mobile [type]','指定项目为mobile类型'],
805 ['-z, --zip [zip]','打包dist目录,并生成压缩包路径到剪切板']
806 ],
807 help:()=>{
808 console.log('');
809 console.log(' 补充说明:');
810 console.log(' ------------------------------------------------------------');
811 console.log(' 暂无');
812 },
813 action:SvnCommit
814 },
815 fun:()=>{}
816};
\No newline at end of file