1 | const path = require("path");
|
2 | const chalk = require("chalk");
|
3 | const fs = require("fs");
|
4 | const mm = require("micromatch");
|
5 | const requireFromString = require("require-from-string");
|
6 | const logger = require("./logger");
|
7 | const readMaidFile = require("./readMaidFile");
|
8 | const MaidError = require("./MaidError");
|
9 | const runCLICommand = require("./runCLICommand");
|
10 | const inquirer = require("inquirer");
|
11 | const moment = require("moment");
|
12 | const getColonTimeFromDate = () => new Date().toTimeString().slice(0, 8);
|
13 | const secretFilePath = path.join(process.cwd(), "./.secret.json");
|
14 |
|
15 | let secretFile = { tasks: [] };
|
16 | let taskListSorted = [];
|
17 |
|
18 | if (fs.existsSync(secretFilePath)) {
|
19 | secretFile = require(secretFilePath);
|
20 | taskListSorted = secretFile.tasks
|
21 | .sort((a, b) => (a.time > b.time ? 1 : b.time > a.time ? -1 : 0))
|
22 | .reverse()
|
23 | .slice(0, 3)
|
24 | .map(e => {
|
25 | if (e.task === undefined) {
|
26 | return e;
|
27 | }
|
28 |
|
29 | let taskItem = e.task.split(" ")[0];
|
30 | taskItem += "\t~\t" + moment(e.time).calendar() + "";
|
31 | return taskItem;
|
32 | });
|
33 | } else {
|
34 | fs.writeFileSync(secretFilePath, JSON.stringify(secretFile), "utf8");
|
35 | }
|
36 | class FcScripts {
|
37 | constructor(opts = {}) {
|
38 |
|
39 |
|
40 | return this.loadAsync(opts);
|
41 | }
|
42 |
|
43 | async loadAsync(opts) {
|
44 | this.maidfile = await readMaidFile(opts.section);
|
45 | if (!this.maidfile) {
|
46 | throw new MaidError("No maidfile was found. Stop.");
|
47 | }
|
48 | return this;
|
49 | }
|
50 |
|
51 | async runTasks(taskNames, inParallel) {
|
52 | if (!taskNames || taskNames.length === 0) return;
|
53 |
|
54 | if (inParallel) {
|
55 | await Promise.all(
|
56 | taskNames.map(taskName => {
|
57 | return this.runTask(taskName);
|
58 | })
|
59 | );
|
60 | } else {
|
61 | for (const taskName of taskNames) {
|
62 | await this.runTask(taskName);
|
63 | }
|
64 | }
|
65 | }
|
66 |
|
67 | async runFile(taskName) {
|
68 | await this.runTask("beforeAll", false);
|
69 | await this.runTask(taskName);
|
70 | await this.runTask("afterAll", false);
|
71 | }
|
72 |
|
73 | async runTask(taskName, throwWhenNoMatchedTask = true) {
|
74 | const task =
|
75 | taskName && this.maidfile && this.maidfile.tasks.find(task => task.name === taskName);
|
76 |
|
77 | if (!task) {
|
78 | if (throwWhenNoMatchedTask) {
|
79 | throw new MaidError(`No task called "${taskName}" was found. Stop.`);
|
80 | } else {
|
81 | return;
|
82 | }
|
83 | }
|
84 |
|
85 | await this.runTaskHooks(task, "before");
|
86 |
|
87 | const start = Date.now();
|
88 | logger.l("Starting", '"' + task.name + '..."');
|
89 |
|
90 | for (const script of task.scripts) {
|
91 | await this.runScript(script, task);
|
92 | }
|
93 | await this.runTaskHooks(task, "after");
|
94 |
|
95 | logger.l("Finished", '"' + task.name + ` after ${Date.now() - start} ms`);
|
96 | }
|
97 |
|
98 | runScript(script, task) {
|
99 | return new Promise((resolve, reject) => {
|
100 | const handleError = err => {
|
101 | throw new MaidError(`Task '${task.name}' failed.\n${err.stack}`);
|
102 | };
|
103 | if (checkTypes(script, ["sh", "bash"])) {
|
104 | return runCLICommand({ script, task, resolve, reject });
|
105 | }
|
106 | if (checkTypes(script, ["py", "python"])) {
|
107 | return runCLICommand({ type: "python", script, task, resolve, reject });
|
108 | }
|
109 | if (checkTypes(script, ["js", "javascript"])) {
|
110 | let res;
|
111 | try {
|
112 | res = requireFromString(script.src, this.maidfile.filepath);
|
113 | } catch (err) {
|
114 | return handleError(err);
|
115 | }
|
116 | res = res.default || res;
|
117 | return resolve(
|
118 | typeof res === "function" ? Promise.resolve(res()).catch(handleError) : res
|
119 | );
|
120 | }
|
121 |
|
122 | return resolve();
|
123 | });
|
124 | }
|
125 |
|
126 | async runTaskHooks(task, when) {
|
127 | const prefix = when === "before" ? "pre" : "post";
|
128 | const tasks = this.maidfile.tasks.filter(({ name }) => {
|
129 | return name === `${prefix}${task.name}`;
|
130 | });
|
131 | await this.runTasks(tasks.map(task => task.name));
|
132 | for (const item of task[when]) {
|
133 | const { taskNames, inParallel } = item;
|
134 | await this.runTasks(taskNames, inParallel);
|
135 | }
|
136 | }
|
137 |
|
138 | getHelp(patterns) {
|
139 | patterns = [].concat(patterns);
|
140 | const tasks =
|
141 | patterns.length > 0
|
142 | ? this.maidfile.tasks.filter(task => {
|
143 | return mm.some(task.name, patterns);
|
144 | })
|
145 | : this.maidfile.tasks;
|
146 |
|
147 | if (tasks.length === 0) {
|
148 | throw new MaidError(`No tasks for pattern "${patterns.join(" ")}" was found. Stop.`);
|
149 | }
|
150 |
|
151 | logger.log(
|
152 | `\n ${chalk.magenta.bold(
|
153 | `Task${tasks.length > 1 ? "s" : ""} in ${path.relative(
|
154 | process.cwd(),
|
155 | this.maidfile.filepath
|
156 | )}:`
|
157 | )}\n\n` +
|
158 | tasks
|
159 | .map(
|
160 | task =>
|
161 | ` ${chalk.bold(task.name)}\n${chalk.dim(
|
162 | task.description
|
163 | ? task.description
|
164 | .split("\n")
|
165 | .map(v => ` ${v.trim()}`)
|
166 | .join("\n")
|
167 | : " No description"
|
168 | )}`
|
169 | )
|
170 | .join("\n\n") +
|
171 | "\n"
|
172 | );
|
173 | }
|
174 |
|
175 | runList(patterns) {
|
176 | let choiceCategories = [
|
177 | ...taskListSorted,
|
178 | ...["-------------"],
|
179 | ...Object.keys(this.maidfile.catScripts)
|
180 | ];
|
181 | inquirer
|
182 | .prompt([
|
183 | {
|
184 | type: "list",
|
185 | name: "category",
|
186 | message: "What category do you want to run?",
|
187 | choices: choiceCategories
|
188 | }
|
189 | ])
|
190 | .then(({ category }) => {
|
191 | let sepInd = choiceCategories.indexOf("-------------");
|
192 | let chosenInd = choiceCategories.indexOf(category);
|
193 | if (sepInd === chosenInd) {
|
194 | console.log("NOPE");
|
195 | } else if (chosenInd < sepInd) {
|
196 | console.log(" RUUNNNING", category);
|
197 | let taskToRun = category.split("\t~\t")[0].trim();
|
198 | updateTimeTask(taskToRun, { category: "" });
|
199 | this.runTask(taskToRun);
|
200 | } else {
|
201 | let taskNames = this.maidfile.catScripts[category].map(
|
202 | task =>
|
203 | `${task.name} ${
|
204 | task.desc ? "\t~\t" + task.desc.replace(/\n/g, " ").trim() : ""
|
205 | }`
|
206 | );
|
207 | inquirer
|
208 | .prompt([
|
209 | {
|
210 | type: "list",
|
211 | name: "taskToRun",
|
212 | message: "Which task do you want to run",
|
213 | choices: taskNames
|
214 | }
|
215 | ])
|
216 | .then(({ taskToRun }) => {
|
217 |
|
218 | taskToRun = taskToRun.split("\t~\t")[0].trim();
|
219 | updateTimeTask(taskToRun, category);
|
220 | this.runTask(taskToRun);
|
221 | });
|
222 | }
|
223 | });
|
224 | }
|
225 |
|
226 | getList() {
|
227 | const tasks = this.maidfile.tasks;
|
228 | }
|
229 | }
|
230 |
|
231 | function updateTimeTask(taskToRun, category) {
|
232 | let taskIndex = secretFile.tasks.findIndex(e => e.task.split(" ")[0] === taskToRun);
|
233 | if (taskIndex === -1) {
|
234 | secretFile.tasks.push({
|
235 | category,
|
236 | task: taskToRun,
|
237 | time: Date.now()
|
238 | });
|
239 | } else {
|
240 | secretFile.tasks[taskIndex].time = Date.now();
|
241 | }
|
242 | fs.writeFileSync(secretFilePath, JSON.stringify(secretFile), "utf8");
|
243 | }
|
244 |
|
245 | function checkTypes(task, types) {
|
246 | return types.some(type => type === task.type);
|
247 | }
|
248 |
|
249 | module.exports = opts => new FcScripts(opts);
|