/** * 가이드 생성 : md to html * * @Modification Information * 수정일 : 수정자 : 수정내용 * -------------------------- * 2024.01.02 : Mabongpapa : 최초생성 */ import type {IServer} from "../@types/gulpfile.d"; import path from "path"; import fs from "fs"; import glob from "fast-glob"; import {src,watch,dest} from "gulp"; import marked from "marked"; import * as Config from "../config"; // 해당 포트 및 폴더 변경 가능 import timeStamp from "../util/getTimeStamp"; import getSrcDist from "../util/getSrcDist"; import pretty from "pretty"; import cEjs from "../util/customEjsToEjs"; import findPidFromPort from "../util/findPidFromPort"; import asyncRequest from "../util/asyncRequestPrettyHtml"; // URL to HTML const getSrcRelative = (srcPath:string) => path.relative(path.dirname(srcPath),path.resolve(__dirname,"../../","src")).replace(/\\/gi,"/"); const mdToEjsHtml = (options:{src:string,dist:string},callerName?:string):Promise => { return new Promise(async (resolve,reject)=>{ const headerRaw = fs.readFileSync(Config.guideHeader,"utf8"); // 가이드 헤더파일 const footerRaw = fs.readFileSync(Config.guideFooter,"utf8"); // 가이드 풋터파일 const {src,dist} = options; const srcRelative = getSrcRelative(src); // 소스파일 경로 부터, 소스루트경로(./src) 까지의 "상대경로" 위치 // md 원본 추출 const mdRaw = fs.readFileSync(src,"utf8"); // markdown to customEjs 처리. let cEjsRaw = await marked.parse(mdRaw); // md to html cEjsRaw = cEjsRaw.replace(/\.md/gi,".html"); // md주소는 html 주소로 변경한다. // custom Ejs 를 순수 Ejs로 변경. let htmlRaw = cEjs((headerRaw+"\n"+cEjsRaw +"\n"+footerRaw)); // 완전한 가이드 ejs 파일 생성 htmlRaw = htmlRaw.replace(/\@src\//g,srcRelative+"/") // @src 주소는 상대경로로 변경한다. // 가이드 변환 파일 저장 fs.mkdirSync(path.dirname(dist),{recursive:true}); // 저장할 폴더 생성 fs.writeFileSync(dist,htmlRaw,"utf8"); console.log(`${timeStamp().task}[${callerName}] ${options.src}`); resolve(); }); } const getMatchText = (str:string,reg:RegExp) => str.match(reg) ? str.match(reg)[0] : "null"; const crawler = async (options:{base:string,src:string|string[]}):Promise<{name:string,devUrl:string,buildUrl:string}[]> => { const guideMap:{name:string,devUrl:string,buildUrl:string}[] = []; const addGuideMapp = (name:string,pageOption:{ devUrl:string, buildUrl:string }) => { guideMap.push({ name, devUrl:pageOption.devUrl.replace(options.base,""), buildUrl:pageOption.buildUrl.replace(options.base,"") }); }; const srcs = await glob(options.src); for await (const src of srcs){ // md 원본 추출 const mdRaw = fs.readFileSync(src,"utf8"); const name = getMatchText(mdRaw,/(\#\s)(.+)/g).replace("# ",""); addGuideMapp(name,{ devUrl:src.replace(".md",".html"), buildUrl:src.replace(".md",".html"), }) }; return guideMap; }; export const save = async (options:{base:string,src:string|string[]},jsonSavePath:string):Promise => { const pageMap = await crawler(options); fs.mkdirSync(path.dirname(jsonSavePath),{recursive:true}); // 저장할 폴더 생성 fs.writeFileSync(jsonSavePath,JSON.stringify(pageMap),"utf8"); // 파일 저장 console.log(`${timeStamp().task}[Guide:save] ${pageMap.length} page crawlings`); } export const libCopy = async () => { return src(Config.guideLibOptions.src).pipe(dest(Config.guideLibOptions.dist)); } export const libDistCopy = async () => { return src(Config.guideLibDistOptions.src).pipe(dest(Config.guideLibDistOptions.dist)); } export const compiler = async (options:{base:string,src:string|string[],dist:string}):Promise => { const srcs = await glob(options.src); for await (const srcPath_ of srcs){ const {src , dist} = getSrcDist({base:options.base,dist:options.dist,src:srcPath_}); const save = dist.replace(/((.md)|(.ejs))$/g,".html"); // 저징될 경로(파일명 포함) , .md, .ejs 인경우, .html로 변환 됨. await mdToEjsHtml({src,dist:save},"Guide:Compiler"); }; await crawler(options); }; export const watcher = (options:{base:string,src:string|string[],dist:string}):Promise => { return new Promise(async (resolve,reject)=>{ watch(options.src,{usePolling:true}).on("change",async (path)=>{ const {src,dist} = getSrcDist(Object.assign(options,{src:path})); const save = dist.replace(/((.md)|(.ejs))$/g,".html"); // 저징될 경로(파일명 포함) , .md, .ejs 인경우, .html로 변환 됨. await mdToEjsHtml({src,dist:save},"Guide:Watcher"); }).on("ready",()=>{ console.log(`${timeStamp().task}[Guide:Watch] Ready`); resolve(); }); }); }; // 시작시 전체 컴파일용 export const dist = (options:{src:string|string[],base:string,dist:string}):Promise => { return new Promise(async (resolve,reject) => { const guideMap = await crawler(options); // 개발서버 실행(기존에 실행되어 있다면 미동작) let pid = await findPidFromPort(Config.devPort); // 서버실행 여부 확인 let isDevServerReady = !!pid; // 서버가 있다면 true , 없으면 false. if(!isDevServerReady){ // 서버가 없었으면 실행 시킨다. const server:IServer = await import("../servers/server"); await server.dev({root:options.base,port:Config.devPort}); }; for await (const page of guideMap){ const url = `http://localhost:${Config.devPort}${page.devUrl}`; const htmlRaw = await asyncRequest(url); const buildPath = Config.buildServerRoot+page.buildUrl; fs.mkdirSync(path.dirname(buildPath),{recursive:true}); fs.writeFileSync(buildPath,htmlRaw,"utf8"); console.log(`${timeStamp().task}[guide:dist] ${buildPath}`); }; if(!isDevServerReady){ pid = await findPidFromPort(Config.devPort); // 서버실행 여부 확인 setTimeout(()=>{ console.log(`${timeStamp().task}[guide:dist] process.kill , port ${Config.devPort}`); process.kill(pid); },2000); }; resolve(); }); };