echoapi-cron-scheduler
Version:
A Node.js cron scheduler for managing and executing scheduled tasks.
2 lines (1 loc) • 8.82 kB
JavaScript
var e,r,t=require("node-schedule"),n=require("lodash"),s=require("axios"),o=require("path"),i=require("os"),a=require("json5"),c=require("better-sqlite3"),u=require("runner-runtime"),d=require("uuid"),l=require("fs"),p=require("dayjs"),b=require("dayjs/plugin/utc"),h=require("dayjs/plugin/timezone"),g=require("net"),E="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function _(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var f=_(function(){if(r)return e;r=1;const _=t,f=n,y=s,T=o,j=i,m=a,S=c,{run:$}=u,{v4:w}=d,O=process?.env.OPENAPI_DOMAIN?f.trim(process?.env.OPENAPI_DOMAIN):"https://open.apipost.net",I=l;return e=class{constructor(){this.jobExecutionHistory=new Map,this.jobList=new Map,this.dbFile=T.resolve(process?.env?.TEMP_DIR||j.tmpdir(),"schedule-tasks-list.sqlite"),this.db=new S(this.dbFile),this._initDB(),process.on("SIGINT",async()=>{await _.gracefulShutdown(),this.db.close(),process.exit(0)})}getJobFile(){return this.dbFile}_initDB(){try{this.db.prepare("ALTER TABLE jobs ADD COLUMN executor_userid TEXT").run(),this.db.prepare("ALTER TABLE jobs ADD COLUMN executor_user TEXT").run(),this.db.prepare("ALTER TABLE jobs ADD COLUMN option TEXT").run(),console.log("Upgrade operation success.")}catch(e){const r=String(e?.message||e);f.includes(r,"duplicate column name")||console.log(r)}this.db.prepare("\n CREATE TABLE IF NOT EXISTS jobs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT,\n executor_userid TEXT,\n executor_user TEXT,\n project_id TEXT,\n name TEXT,\n env TEXT,\n frequency TEXT,\n testing_id TEXT,\n scenario TEXT,\n is_cancel INTEGER DEFAULT 0,\n api_token TEXT,\n create_dtime INTEGER,\n option TEXT,\n test_events TEXT\n )\n ").run()}convertToCron(e){if("preset"!==e.type){if("cron"===e.type){const r=f.trim(e.cron.expression);if(6===r.split(" ").length)return r;throw new Error("Invalid cron expression format")}throw new Error("Invalid schedule type")}{const{cycle:r,config:t}=e.preset;switch(r){case"minute":return`*/${t.interval} * * * *`;case"hour":return`0 */${t.interval} * * *`;case"day":{const[e,r]=t.time.split(":"),n=new _.RecurrenceRule;return n.second=0,n.minute=parseInt(r)||0,n.hour=parseInt(e)||0,n.tz="Asia/Shanghai",{rule:n,cron:`${r} ${e} * * *`}}case"week":{const{weekdays:e,time:r}=t,[n,s]=r.split(":"),o=new _.RecurrenceRule;return o.second=0,o.minute=parseInt(s)||0,o.hour=parseInt(n)||0,o.dayOfWeek=e||[],o.tz="Asia/Shanghai",{rule:o,cron:`${s} ${n} * * ${e.join(",")}`}}default:throw new Error("Unsupported cycle type")}}}formatTimeToISO(e){const r=p,t=b,n=h;return r.extend(t),r.extend(n),r(e).tz("Asia/Shanghai").format()}async loadJobs(){await _.gracefulShutdown();const e=this.db.prepare("SELECT * FROM jobs").all(),r=this;f.forEach(e,e=>{if(f.toInteger(e.is_cancel)<1)try{let t=this.convertToCron(m.parse(e.frequency));f.isObject(t)&&(t=t.rule),this.startJob(t,e.job_id,()=>{console.log(`Job ID ${e.job_id} created...`),this.pipStart();const{test_events:t,option:n}=e;$(m.parse(t),m.parse(n),async t=>{if("complete"===t?.action)try{const n=await y.post(`${O}/open/automated_testing/report/add`,f.assign(t?.data,{source:"scheduled",start_at:r.formatTimeToISO(t?.data?.start_at),end_at:r.formatTimeToISO(t?.data?.end_at)}),{headers:{"Content-Type":"application/json","api-token":e.api_token}});0!=n?.data?.code?console.log(`Job ID ${e.job_id} reporting error: ${n?.data?.msg}`):console.log(`Job ID ${e.job_id} reported success...`)}catch(r){console.log(`Job ID ${e.job_id} reporting error: ${r.message}`)}})})}catch(e){console.error("Error while scheduling job:",e)}})}startJob(e,r,t){return!f.includes(f.keys(_.scheduledJobs),r)&&_.scheduleJob(r,e,async()=>{try{await t()}finally{this.jobExecutionHistory.set(r,Date.now())}})}createJob(e,r){const t=["job_id","executor_userid","executor_user","project_id","name","env","frequency","testing_id","scenario","api_token"],n=f.assign({create_dtime:Date.now()},f.pick(e,t),{is_cancel:-1,option:JSON.stringify(e),test_events:JSON.stringify(r)});f.forEach(n,(e,r)=>{f.isObject(e)&&(n[r]=JSON.stringify(e)),f.isUndefined(e)&&(n[r]="")}),f.forEach(t,e=>{f.isUndefined(n[e])&&(n[e]="")}),this.db.prepare("\n INSERT INTO jobs (job_id,executor_userid,executor_user, project_id, name, env, frequency, testing_id, scenario, is_cancel, api_token, create_dtime, option,test_events)\n VALUES (@job_id, @executor_userid,@executor_user,@project_id, @name, @env, @frequency, @testing_id, @scenario, @is_cancel, @api_token, @create_dtime, @option,@test_events)\n ").run(n),this.loadJobs()}restartJob(e){try{return this.db.prepare("UPDATE jobs SET is_cancel=-1 WHERE job_id=?").run(e),this.loadJobs(),!0}catch(e){return console.error("Error restarting job:",e),!1}}cancelJob(e){if(_.scheduledJobs[e]?.cancel){_.scheduledJobs[e].cancel();try{this.db.prepare("UPDATE jobs SET is_cancel=1 WHERE job_id=?").run(e),this.loadJobs()}catch(e){console.error("Error canceling job:",e)}return!0}return!1}deleteJob(e){try{return this.db.prepare("DELETE FROM jobs WHERE job_id=?").run(e),this.loadJobs(),!0}catch(e){return console.error("Error deleting job:",e),!1}}getAllRunningJobs(){return f.map(_.scheduledJobs,(e,r)=>({jobId:r,nextRun:new Date(e.nextInvocation()?.toISOString()).getTime(),lastRun:this.jobExecutionHistory.get(r)}))}getAllJobs(e){const r=[];try{const t=this.db.prepare("SELECT * FROM jobs WHERE project_id=?").all(e),n=this.getAllRunningJobs();f.forEach(t,e=>{const t=f.find(n,r=>r.jobId===e.job_id);f.isObject(t)?f.assign(e,f.mapKeys(f.pick(t,["nextRun","lastRun"]),(e,r)=>f.snakeCase(r)),{running:1}):f.assign(e,{running:-1,next_run:0,last_run:0}),["frequency","env","test_events","option","executor_user"].forEach(r=>{try{e[r]=JSON.parse(e[r])}catch(t){e[r]={}}}),r.push(f.pick(e,["name","env","frequency","job_id","testing_id","scenario","next_run","last_run","running","create_dtime","is_cancel"]))})}catch(e){console.error("Error retrieving jobs:",e)}return r}getNextExecutionTime(e){f.isObject(e)&&(e=e?.cron);try{const{CronExpressionParser:r}=require("cron-parser"),t=r.parse(e,{tz:"Asia/Shanghai",utc:!1});return new Date(t.next().toISOString()).getTime()}catch(e){return`Error: ${e.message}`}}pipStart(){const e=g,r=((e,r="socket_",t=".sock")=>{const n=`${r}${w()}${t}`;return T.join(e,n)})(j.tmpdir()),t=Buffer.from(r,"utf-8").toString("base64");f.set(E,"ELECTRON_PIPE",t),f.set(E,"env.ELECTRON_PIPE",t),f.set(process,"env.ELECTRON_PIPE",t);try{"win32"!==j.platform()&&I.existsSync(r)&&I.unlinkSync(r)}catch(e){console.warn(`Failed to check or delete existing socket file: ${e.message}`)}I.existsSync(r)&&I.unlinkSync(r);e.createServer(e=>{e.on("data",async r=>{const{action:t,data:n}=JSON.parse(String(Buffer.from(r)));try{switch(t){case"queryDatabase":{const{dbconfig:r,query:t}=n;try{const{DatabaseQuery:n}=require("database-query"),s=await n(r,t);e.write(JSON.stringify(s)+"\n\n")}catch(r){e.write(JSON.stringify(r)+"\n\n")}break}case"execute":{const{execSync:r}=require("child_process"),{file:t,args:s=[],option:o={}}=n;try{const n=T.extname(t).slice(1).toLowerCase();let i;switch(n){case"jar":{const e=T.resolve(__dirname,"jar-main-1.0-SNAPSHOT.jar"),{className:r,method:n}=o||{};if(f.isString(r)&&f.isString(n)){i=`java -jar ${e} ${t} ${r} '${Buffer.from(JSON.stringify({methodName:n,args:s})).toString("base64")}'`}else i=`java -jar ${t} ${f.map(s,JSON.stringify).join(" ")}`;break}case"php":i=`php -f ${t} ${s.join(" ")}`;break;case"js":i=`node ${t} ${s.join(" ")}`;break;case"py":i=`python ${t} ${s.join(" ")}`;break;case"py3":i=`python3 ${t} ${s.join(" ")}`;break;case"bsh":i=`bsh ${t} ${s.join(" ")}`;break;case"go":i=`go run ${t} ${s.join(" ")}`;break;case"sh":i=`sh ${t} ${s.join(" ")}`;break;case"rb":case"ruby":i=`ruby ${t} ${s.join(" ")}`;break;case"lua":i=`lua ${t} ${s.join(" ")}`;break;case"rs":{const e=t.replace(/\.rs$/,"");i=`rustc ${t} && ./${e} ${s.join(" ")}`;break}case"bat":i=`${t} ${s.join(" ")}`;break;case"ps1":i=`powershell -File ${t} ${s.join(" ")}`;break;default:throw new Error(`Unsupported file extension: ${n}`)}const a="win32"===process.platform,c=f.assign(a?{encoding:"cp936"}:{encoding:"utf8"},o);let u=String(r(i,c));try{u=JSON.stringify(m.parse(u))}catch(e){}e.write(JSON.stringify({status:"success",result:u})+"\n\n")}catch(r){e.write(JSON.stringify({status:"error",message:r.message})+"\n\n")}break}}}catch(r){e.write(JSON.stringify(r)+"\n\n")}})}).listen(r,()=>{console.log("Init success.")})}}}());module.exports=f;
;