maro-plugin-tasks 1.0.0 → 1.0.2
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.
- package/README.md +14 -0
- package/dist/src/commands/open.d.ts +3 -0
- package/dist/src/commands/open.js +13 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +20 -0
- package/dist/src/lib/action.d.ts +8 -0
- package/dist/src/lib/action.js +34 -0
- package/{src/lib/index.ts → dist/src/lib/index.d.ts} +1 -0
- package/dist/src/lib/index.js +11 -0
- package/dist/src/lib/repo_task_tracker.d.ts +13 -0
- package/dist/src/lib/repo_task_tracker.js +56 -0
- package/dist/src/lib/task.d.ts +24 -0
- package/dist/src/lib/task.js +49 -0
- package/dist/src/lib/tracker.d.ts +7 -0
- package/dist/src/lib/tracker.js +12 -0
- package/dist/src/lib/utils.d.ts +4 -0
- package/dist/src/lib/utils.js +30 -0
- package/package.json +6 -2
- package/.eslintrc.json +0 -118
- package/src/commands/open.ts +0 -16
- package/src/index.ts +0 -18
- package/src/lib/action.ts +0 -32
- package/src/lib/repo_task_tracker.ts +0 -175
- package/src/lib/task.ts +0 -77
- package/src/lib/tracker.ts +0 -15
- package/src/lib/utils.ts +0 -29
- package/tsconfig.json +0 -38
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ✅ maro-plugin-tasks
|
|
2
|
+
|
|
3
|
+
Transfigura la lista interminable en un sendero claro y conquistable.
|
|
4
|
+
|
|
5
|
+
Con este plugin, Maro convierte cada pendiente en un paso, y cada ciclo en un avance visible. No más tareas difusas ni recordatorios invisibles: el flujo se anota, se revisa y se completa, guiando a tu equipo desde el caos hacia la claridad funcional.
|
|
6
|
+
|
|
7
|
+
Lleva el pulso real de tus compromisos, desde lo urgente hasta lo postergado, todo sin soltar la brújula del terminal.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**Capacidades principales**
|
|
12
|
+
- Integrar tareas con otros “círculos” de Maro para flujos automáticos.
|
|
13
|
+
|
|
14
|
+
Haz de cada compromiso un rito visible y convierte el avance en legado palpable para el equipo.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../lib/utils");
|
|
4
|
+
const OpenCommand = {
|
|
5
|
+
name: "open",
|
|
6
|
+
description: "Open task from cwd in editor",
|
|
7
|
+
run: async ({ ctx }) => {
|
|
8
|
+
const tasks = (0, utils_1.getTasksFromDir)(ctx.cwd);
|
|
9
|
+
const task = await ctx.ui.promptChoice(tasks, { message: "Choose task to open" });
|
|
10
|
+
task.openInEditor();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
exports.default = OpenCommand;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const open_1 = __importDefault(require("./commands/open"));
|
|
7
|
+
const plugin = {
|
|
8
|
+
name: "maro-plugin-tasks",
|
|
9
|
+
commands: [
|
|
10
|
+
{
|
|
11
|
+
name: "task",
|
|
12
|
+
description: "Track tasks asociated with TODOS",
|
|
13
|
+
aliases: [],
|
|
14
|
+
subcommands: [
|
|
15
|
+
open_1.default
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
};
|
|
20
|
+
exports.default = plugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Action, MrCreateEvent } from "@maro/maro";
|
|
2
|
+
import { TaskTracker } from "./tracker";
|
|
3
|
+
export declare class CreateMissingTasksAction implements Action {
|
|
4
|
+
tracker: TaskTracker;
|
|
5
|
+
constructor(tracker: TaskTracker);
|
|
6
|
+
register(): void;
|
|
7
|
+
execute(event: MrCreateEvent): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CreateMissingTasksAction = void 0;
|
|
10
|
+
const maro_1 = require("@maro/maro");
|
|
11
|
+
const utils_1 = require("./utils");
|
|
12
|
+
class CreateMissingTasksAction {
|
|
13
|
+
constructor(tracker) {
|
|
14
|
+
this.tracker = tracker;
|
|
15
|
+
}
|
|
16
|
+
register() {
|
|
17
|
+
maro_1.ActionRegistry.on(maro_1.MrCreateEvent, (event) => this.execute(event));
|
|
18
|
+
}
|
|
19
|
+
async execute(event) {
|
|
20
|
+
const repo = event.ctx;
|
|
21
|
+
const tasks = (0, utils_1.getTasksFromDir)(repo.dir);
|
|
22
|
+
await Promise.all(tasks.map(async (t) => {
|
|
23
|
+
if (this.tracker.isTracked(t))
|
|
24
|
+
return;
|
|
25
|
+
await this.tracker.track(t);
|
|
26
|
+
await repo.add(t.file_location.file);
|
|
27
|
+
}));
|
|
28
|
+
await repo.commit("fix: add issues");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.CreateMissingTasksAction = CreateMissingTasksAction;
|
|
32
|
+
__decorate([
|
|
33
|
+
(0, maro_1.loading)("Generating missing issues")
|
|
34
|
+
], CreateMissingTasksAction.prototype, "execute", null);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TaskTracker = exports.Task = exports.CreateMissingTasksAction = exports.RepoTaskTracker = void 0;
|
|
4
|
+
var repo_task_tracker_1 = require("./repo_task_tracker");
|
|
5
|
+
Object.defineProperty(exports, "RepoTaskTracker", { enumerable: true, get: function () { return repo_task_tracker_1.RepoTaskTracker; } });
|
|
6
|
+
var action_1 = require("./action");
|
|
7
|
+
Object.defineProperty(exports, "CreateMissingTasksAction", { enumerable: true, get: function () { return action_1.CreateMissingTasksAction; } });
|
|
8
|
+
var task_1 = require("./task");
|
|
9
|
+
Object.defineProperty(exports, "Task", { enumerable: true, get: function () { return task_1.Task; } });
|
|
10
|
+
var tracker_1 = require("./tracker");
|
|
11
|
+
Object.defineProperty(exports, "TaskTracker", { enumerable: true, get: function () { return tracker_1.TaskTracker; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Repo } from "@maro/maro";
|
|
2
|
+
import { Task } from "./task";
|
|
3
|
+
import { TaskTracker } from "./tracker";
|
|
4
|
+
export declare function sleep(ms: number): Promise<unknown>;
|
|
5
|
+
export declare const TASK_FILE = "TASK.md";
|
|
6
|
+
export declare class RepoTaskTracker extends TaskTracker {
|
|
7
|
+
repo: Repo;
|
|
8
|
+
constructor(repo: Repo);
|
|
9
|
+
addIdToTodo(id: string): string;
|
|
10
|
+
save(task: Task): Promise<string>;
|
|
11
|
+
isTracked(task: Task): boolean;
|
|
12
|
+
private toMdString;
|
|
13
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RepoTaskTracker = exports.TASK_FILE = void 0;
|
|
4
|
+
exports.sleep = sleep;
|
|
5
|
+
const tracker_1 = require("./tracker");
|
|
6
|
+
function sleep(ms) {
|
|
7
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
}
|
|
9
|
+
exports.TASK_FILE = "TASK.md";
|
|
10
|
+
// TODO: for now Tasks change status manually outside the app,
|
|
11
|
+
// add support for trackers to change task status, could be useful for RepoActions
|
|
12
|
+
const TASK_STATUS = {
|
|
13
|
+
OPEN: "OPEN",
|
|
14
|
+
CLOSED: "CLOSED"
|
|
15
|
+
};
|
|
16
|
+
class RepoTaskTracker extends tracker_1.TaskTracker {
|
|
17
|
+
constructor(repo) {
|
|
18
|
+
super();
|
|
19
|
+
this.repo = repo;
|
|
20
|
+
}
|
|
21
|
+
addIdToTodo(id) {
|
|
22
|
+
return `(${id})`;
|
|
23
|
+
}
|
|
24
|
+
async save(task) {
|
|
25
|
+
const d = new Date();
|
|
26
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
27
|
+
const YYYY = d.getFullYear();
|
|
28
|
+
const MM = pad(d.getMonth() + 1);
|
|
29
|
+
const DD = pad(d.getDate());
|
|
30
|
+
const HH = pad(d.getHours());
|
|
31
|
+
const mm = pad(d.getMinutes());
|
|
32
|
+
const SS = pad(d.getSeconds());
|
|
33
|
+
await sleep(1 * 1000);
|
|
34
|
+
const id = `${YYYY}${MM}${DD}-${HH}${mm}${SS}`;
|
|
35
|
+
const tasks_dir = this.repo.dir.sub("tasks");
|
|
36
|
+
tasks_dir.create();
|
|
37
|
+
const task_dir = tasks_dir.sub(id);
|
|
38
|
+
task_dir.createFile(exports.TASK_FILE).write(this.toMdString(task));
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
isTracked(task) {
|
|
42
|
+
const todoLine = task.getTodo();
|
|
43
|
+
const regex = /TODO[^:]*\([^)]*\)[^:]*:/;
|
|
44
|
+
return regex.test(todoLine);
|
|
45
|
+
}
|
|
46
|
+
toMdString(task) {
|
|
47
|
+
const relative = task.getPathInProject();
|
|
48
|
+
return `# ${task.title}
|
|
49
|
+
|
|
50
|
+
- FILE_LOCATION: ${relative}:${task.file_location.row}:${task.file_location.col}
|
|
51
|
+
- STATUS: ${TASK_STATUS.OPEN}
|
|
52
|
+
|
|
53
|
+
${task.description} `;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.RepoTaskTracker = RepoTaskTracker;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Choice, Dir, TextFile } from "@maro/maro";
|
|
2
|
+
type FileLocation = {
|
|
3
|
+
file: TextFile;
|
|
4
|
+
row: number;
|
|
5
|
+
col: number;
|
|
6
|
+
};
|
|
7
|
+
export declare class Task {
|
|
8
|
+
project: Dir;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
file_location: FileLocation;
|
|
12
|
+
constructor({ title, file_location, project, description }: {
|
|
13
|
+
description?: string;
|
|
14
|
+
project: Dir;
|
|
15
|
+
title: string;
|
|
16
|
+
file_location: FileLocation;
|
|
17
|
+
});
|
|
18
|
+
getTodo(): string;
|
|
19
|
+
editTodo(add: string): void;
|
|
20
|
+
openInEditor(): void;
|
|
21
|
+
getPathInProject(): string;
|
|
22
|
+
toChoice(): Choice;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Task = void 0;
|
|
4
|
+
const maro_1 = require("@maro/maro");
|
|
5
|
+
class Task {
|
|
6
|
+
constructor({ title, file_location, project, description }) {
|
|
7
|
+
const file = file_location.file;
|
|
8
|
+
if (!project.contains(file))
|
|
9
|
+
throw new Error(`${file} is not in ${project}`);
|
|
10
|
+
this.title = title;
|
|
11
|
+
this.file_location = file_location;
|
|
12
|
+
this.project = project;
|
|
13
|
+
this.description = description;
|
|
14
|
+
}
|
|
15
|
+
getTodo() {
|
|
16
|
+
const { file, row } = this.file_location;
|
|
17
|
+
const lines = file.read().split(/\r?\n/);
|
|
18
|
+
const normalized_row = row - 1;
|
|
19
|
+
return lines[normalized_row];
|
|
20
|
+
}
|
|
21
|
+
editTodo(add) {
|
|
22
|
+
const todoLine = this.getTodo();
|
|
23
|
+
const { file, col, row } = this.file_location;
|
|
24
|
+
const normalized_row = row - 1;
|
|
25
|
+
const normalized_col = col - 1;
|
|
26
|
+
const prefix = todoLine.slice(0, normalized_col);
|
|
27
|
+
const afterTodo = todoLine.slice(normalized_col);
|
|
28
|
+
const colonIndex = afterTodo.indexOf(":");
|
|
29
|
+
const originalDecorations = colonIndex === -1
|
|
30
|
+
? afterTodo.slice("TODO".length)
|
|
31
|
+
: afterTodo.slice("TODO".length, colonIndex);
|
|
32
|
+
const originalBody = colonIndex === -1 ? "" : afterTodo.slice(colonIndex + 1).trim();
|
|
33
|
+
const newTodo = prefix + "TODO" + originalDecorations + add + ": " + originalBody;
|
|
34
|
+
const lines = file.read().split(/\r?\n/);
|
|
35
|
+
lines[normalized_row] = newTodo;
|
|
36
|
+
file.write(lines.join("\n"));
|
|
37
|
+
}
|
|
38
|
+
openInEditor() {
|
|
39
|
+
const { col, row } = this.file_location;
|
|
40
|
+
(0, maro_1.openInEditor)(this.file_location.file, { line: row, column: col });
|
|
41
|
+
}
|
|
42
|
+
getPathInProject() {
|
|
43
|
+
return this.project.relativePathTo(this.file_location.file);
|
|
44
|
+
}
|
|
45
|
+
toChoice() {
|
|
46
|
+
return { name: this.title, hint: `${this.file_location.file}:${this.file_location.row}:${this.file_location.col}` };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.Task = Task;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TaskTracker = void 0;
|
|
4
|
+
class TaskTracker {
|
|
5
|
+
async track(task) {
|
|
6
|
+
if (this.isTracked(task))
|
|
7
|
+
return;
|
|
8
|
+
const id = await this.save(task);
|
|
9
|
+
task.editTodo(this.addIdToTodo(id, task) ?? "");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.TaskTracker = TaskTracker;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTasksFromFile = exports.getTasksFromDir = void 0;
|
|
4
|
+
const task_1 = require("./task");
|
|
5
|
+
const getTasksFromDir = (dir) => {
|
|
6
|
+
const files = dir.traverse();
|
|
7
|
+
const tasks = [];
|
|
8
|
+
for (const f of files)
|
|
9
|
+
tasks.push(...(0, exports.getTasksFromFile)(dir, f));
|
|
10
|
+
return tasks;
|
|
11
|
+
};
|
|
12
|
+
exports.getTasksFromDir = getTasksFromDir;
|
|
13
|
+
const getTasksFromFile = (project, fileInDir) => {
|
|
14
|
+
const lines = fileInDir.read().split(/\r?\n/);
|
|
15
|
+
const results = [];
|
|
16
|
+
const regex = /TODO.*:\s*(.*)/g;
|
|
17
|
+
for (let row = 0; row < lines.length; row++) {
|
|
18
|
+
const line = lines[row];
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = regex.exec(line)) !== null) {
|
|
21
|
+
const col = match.index;
|
|
22
|
+
const title = match[1]?.trim() ?? "";
|
|
23
|
+
const file_location = { file: fileInDir, row: row + 1, col: col + 1 };
|
|
24
|
+
// TODO: Task.description from file
|
|
25
|
+
results.push(new task_1.Task({ title, project, file_location }));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return results;
|
|
29
|
+
};
|
|
30
|
+
exports.getTasksFromFile = getTasksFromFile;
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maro-plugin-tasks",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"main": "./dist/lib/index.js",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"main": "./dist/src/lib/index.js",
|
|
5
|
+
"types": "./dist/src/lib/index.d.ts",
|
|
5
6
|
"type": "commonjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**"
|
|
9
|
+
],
|
|
6
10
|
"scripts": {
|
|
7
11
|
"build": "tsc --project ./tsconfig.json",
|
|
8
12
|
"start": "node ./dist/src/index.js",
|
package/.eslintrc.json
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"node": true,
|
|
4
|
-
"es2021": true
|
|
5
|
-
},
|
|
6
|
-
"ignorePatterns": [
|
|
7
|
-
"dist"
|
|
8
|
-
],
|
|
9
|
-
"extends": [
|
|
10
|
-
"eslint:recommended",
|
|
11
|
-
"plugin:de-morgan/recommended-legacy",
|
|
12
|
-
"plugin:@typescript-eslint/recommended",
|
|
13
|
-
"plugin:math/recommended-legacy",
|
|
14
|
-
"plugin:promise/recommended"
|
|
15
|
-
],
|
|
16
|
-
"plugins": [
|
|
17
|
-
"promise",
|
|
18
|
-
"@stylistic",
|
|
19
|
-
"de-morgan",
|
|
20
|
-
"import",
|
|
21
|
-
"unused-imports"
|
|
22
|
-
],
|
|
23
|
-
"parser": "@typescript-eslint/parser",
|
|
24
|
-
"parserOptions": {
|
|
25
|
-
"ecmaVersion": 12,
|
|
26
|
-
"sourceType": "module"
|
|
27
|
-
},
|
|
28
|
-
"rules": {
|
|
29
|
-
"no-inner-declarations": "off",
|
|
30
|
-
"import/no-deprecated": "error",
|
|
31
|
-
"import/no-cycle": "error",
|
|
32
|
-
"import/no-duplicates": "error",
|
|
33
|
-
"import/enforce-node-protocol-usage": [
|
|
34
|
-
"error",
|
|
35
|
-
"always"
|
|
36
|
-
],
|
|
37
|
-
"unused-imports/no-unused-imports": "error",
|
|
38
|
-
"@stylistic/arrow-parens": 2,
|
|
39
|
-
"@stylistic/brace-style": 2,
|
|
40
|
-
"@stylistic/comma-dangle": 2,
|
|
41
|
-
"@stylistic/comma-spacing": 2,
|
|
42
|
-
"@stylistic/dot-location": [
|
|
43
|
-
2,
|
|
44
|
-
"property"
|
|
45
|
-
],
|
|
46
|
-
"@stylistic/func-call-spacing": 2,
|
|
47
|
-
"@stylistic/indent": [
|
|
48
|
-
"error",
|
|
49
|
-
2
|
|
50
|
-
],
|
|
51
|
-
"@stylistic/indent-binary-ops": [
|
|
52
|
-
"error",
|
|
53
|
-
2
|
|
54
|
-
],
|
|
55
|
-
"@stylistic/key-spacing": 2,
|
|
56
|
-
"@stylistic/keyword-spacing": 2,
|
|
57
|
-
"@stylistic/member-delimiter-style": 2,
|
|
58
|
-
"@stylistic/no-extra-parens": 2,
|
|
59
|
-
"@stylistic/no-extra-semi": 2,
|
|
60
|
-
"@stylistic/no-floating-decimal": 2,
|
|
61
|
-
"@stylistic/no-mixed-spaces-and-tabs": 2,
|
|
62
|
-
"@stylistic/no-multiple-empty-lines": [
|
|
63
|
-
2,
|
|
64
|
-
{
|
|
65
|
-
"max": 1
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"@stylistic/no-tabs": 2,
|
|
69
|
-
"@stylistic/no-trailing-spaces": 2,
|
|
70
|
-
"@stylistic/no-whitespace-before-property": 2,
|
|
71
|
-
"@stylistic/nonblock-statement-body-position": 2,
|
|
72
|
-
"@stylistic/operator-linebreak": [
|
|
73
|
-
2,
|
|
74
|
-
"before"
|
|
75
|
-
],
|
|
76
|
-
"@stylistic/padding-line-between-statements": 2,
|
|
77
|
-
"@stylistic/quotes": [
|
|
78
|
-
"error",
|
|
79
|
-
"double"
|
|
80
|
-
],
|
|
81
|
-
"@stylistic/semi": [
|
|
82
|
-
"error"
|
|
83
|
-
],
|
|
84
|
-
"@stylistic/semi-spacing": 2,
|
|
85
|
-
"@stylistic/space-before-blocks": 2,
|
|
86
|
-
"@stylistic/space-in-parens": 2,
|
|
87
|
-
"@stylistic/space-infix-ops": 2,
|
|
88
|
-
"@stylistic/space-unary-ops": 2,
|
|
89
|
-
"@stylistic/spaced-comment": 2,
|
|
90
|
-
"@stylistic/template-curly-spacing": 2,
|
|
91
|
-
"@stylistic/type-annotation-spacing": 2,
|
|
92
|
-
"@stylistic/type-generic-spacing": 2,
|
|
93
|
-
"@stylistic/type-named-tuple-spacing": 2,
|
|
94
|
-
"@typescript-eslint/no-unused-vars": [
|
|
95
|
-
"error",
|
|
96
|
-
{
|
|
97
|
-
"varsIgnorePattern": "_+"
|
|
98
|
-
}
|
|
99
|
-
],
|
|
100
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
101
|
-
"@typescript-eslint/ban-types": "off",
|
|
102
|
-
"arrow-spacing": "error",
|
|
103
|
-
"dot-notation": "error",
|
|
104
|
-
"eqeqeq": "error",
|
|
105
|
-
"no-alert": "error",
|
|
106
|
-
"no-const-assign": "error",
|
|
107
|
-
"no-multi-spaces": "error",
|
|
108
|
-
"no-multiple-empty-lines": "error",
|
|
109
|
-
"prefer-const": "error",
|
|
110
|
-
"quotes": [
|
|
111
|
-
"error",
|
|
112
|
-
"double",
|
|
113
|
-
{
|
|
114
|
-
"allowTemplateLiterals": true
|
|
115
|
-
}
|
|
116
|
-
]
|
|
117
|
-
}
|
|
118
|
-
}
|
package/src/commands/open.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { getTasksFromDir } from "src/lib/utils";
|
|
2
|
-
|
|
3
|
-
import { Command } from "@maro/maro";
|
|
4
|
-
|
|
5
|
-
const OpenCommand: Command = {
|
|
6
|
-
name: "open",
|
|
7
|
-
description: "Open task from cwd in editor",
|
|
8
|
-
run: async ({ ctx }) => {
|
|
9
|
-
const tasks = getTasksFromDir(ctx.cwd);
|
|
10
|
-
const task = await ctx.ui.promptChoice(tasks, { message: "Choose task to open" });
|
|
11
|
-
task.openInEditor();
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export default OpenCommand;
|
|
16
|
-
|
package/src/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import OpenCommand from "./commands/open";
|
|
2
|
-
import { PluginExport } from "../../../dist/lib";
|
|
3
|
-
|
|
4
|
-
const plugin: PluginExport = {
|
|
5
|
-
name: "maro-plugin-tasks",
|
|
6
|
-
commands: [
|
|
7
|
-
{
|
|
8
|
-
name: "task",
|
|
9
|
-
description: "Track tasks asociated with TODOS",
|
|
10
|
-
aliases: [],
|
|
11
|
-
subcommands: [
|
|
12
|
-
OpenCommand
|
|
13
|
-
]
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default plugin;
|
package/src/lib/action.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Action, ActionRegistry, loading, MrCreateEvent } from "@maro/maro";
|
|
2
|
-
|
|
3
|
-
import { TaskTracker } from "./tracker";
|
|
4
|
-
import { getTasksFromDir } from "./utils";
|
|
5
|
-
|
|
6
|
-
export class CreateMissingTasksAction implements Action {
|
|
7
|
-
tracker: TaskTracker;
|
|
8
|
-
|
|
9
|
-
constructor(tracker: TaskTracker) {
|
|
10
|
-
this.tracker = tracker;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
register(): void {
|
|
14
|
-
ActionRegistry.on(MrCreateEvent, (event) => this.execute(event));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@loading("Generating missing issues")
|
|
18
|
-
async execute(event: MrCreateEvent) {
|
|
19
|
-
const repo = event.ctx;
|
|
20
|
-
const tasks = getTasksFromDir(repo.dir);
|
|
21
|
-
|
|
22
|
-
await Promise.all(
|
|
23
|
-
tasks.map(async (t) => {
|
|
24
|
-
if (this.tracker.isTracked(t)) return;
|
|
25
|
-
await this.tracker.track(t);
|
|
26
|
-
await repo.add(t.file_location.file);
|
|
27
|
-
}));
|
|
28
|
-
await repo.commit("fix: add issues");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { Repo } from "@maro/maro";
|
|
2
|
-
|
|
3
|
-
import { Task } from "./task";
|
|
4
|
-
import { TaskTracker } from "./tracker";
|
|
5
|
-
|
|
6
|
-
export function sleep(ms: number) {
|
|
7
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const TASK_FILE = "TASK.md";
|
|
11
|
-
|
|
12
|
-
// TODO: for now Tasks change status manually outside the app,
|
|
13
|
-
// add support for trackers to change task status, could be useful for RepoActions
|
|
14
|
-
const TASK_STATUS = {
|
|
15
|
-
OPEN: "OPEN",
|
|
16
|
-
CLOSED: "CLOSED"
|
|
17
|
-
} as const;
|
|
18
|
-
|
|
19
|
-
export class RepoTaskTracker extends TaskTracker {
|
|
20
|
-
repo: Repo;
|
|
21
|
-
|
|
22
|
-
constructor(repo: Repo) {
|
|
23
|
-
super();
|
|
24
|
-
this.repo = repo;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
override addIdToTodo(id: string): string {
|
|
28
|
-
return `(${id})`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
override async save(task: Task): Promise<string> {
|
|
32
|
-
const d = new Date();
|
|
33
|
-
const pad = (n: number) => String(n).padStart(2, "0");
|
|
34
|
-
const YYYY = d.getFullYear();
|
|
35
|
-
const MM = pad(d.getMonth() + 1);
|
|
36
|
-
const DD = pad(d.getDate());
|
|
37
|
-
const HH = pad(d.getHours());
|
|
38
|
-
const mm = pad(d.getMinutes());
|
|
39
|
-
const SS = pad(d.getSeconds());
|
|
40
|
-
await sleep(1 * 1000);
|
|
41
|
-
const id = `${YYYY}${MM}${DD}-${HH}${mm}${SS}`;
|
|
42
|
-
|
|
43
|
-
const tasks_dir = this.repo.dir.sub("tasks");
|
|
44
|
-
tasks_dir.create();
|
|
45
|
-
const task_dir = tasks_dir.sub(id);
|
|
46
|
-
task_dir.createFile(TASK_FILE).write(this.toMdString(task));
|
|
47
|
-
return id;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
override isTracked(task: Task): boolean {
|
|
51
|
-
const todoLine = task.getTodo();
|
|
52
|
-
const regex = /TODO[^:]*\([^)]*\)[^:]*:/;
|
|
53
|
-
return regex.test(todoLine);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private toMdString(task: Task) {
|
|
57
|
-
const relative = task.getPathInProject();
|
|
58
|
-
return `# ${task.title}
|
|
59
|
-
|
|
60
|
-
- PROJECT: ${task.project}
|
|
61
|
-
- FILE-LOCATION: ${relative}:${task.file_location.row}:${task.file_location.col}
|
|
62
|
-
- STATUS: ${TASK_STATUS.OPEN}
|
|
63
|
-
|
|
64
|
-
${task.description} `;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// async getTasks(project?: string) {
|
|
68
|
-
// await this.update();
|
|
69
|
-
// const dirs = project ? [project] : readdirs(this.full_path).map((d) => d.name);
|
|
70
|
-
// const full_dirs = dirs.map((d) => path.join(this.full_path, d));
|
|
71
|
-
// return full_dirs.flatMap((d) => readdirs(d).map((task_dir) => Task.fromTaskFile(path.join(d, task_dir.name, TASK_FILE))));
|
|
72
|
-
// }
|
|
73
|
-
//
|
|
74
|
-
// addTask(task: Task) {
|
|
75
|
-
// const dir = path.join(this.full_path, task.project, task.id);
|
|
76
|
-
// const task_path = path.join(dir, TASK_FILE);
|
|
77
|
-
// createDirIfNotExists(dir);
|
|
78
|
-
// task.save(task_path);
|
|
79
|
-
// }
|
|
80
|
-
|
|
81
|
-
// TODO: move to TaskRepo
|
|
82
|
-
// private static parseFileLocation(raw: string): FileLocation {
|
|
83
|
-
// const [file_path, row, col] = raw.split(":");
|
|
84
|
-
// if (!file_path) throw new Error(`Could not parse file location ${raw} file_path missing`);
|
|
85
|
-
// return { file_path, row: Number(row), col: Number(col) };
|
|
86
|
-
// }
|
|
87
|
-
|
|
88
|
-
// TODO: move to TaskRepo
|
|
89
|
-
// static fromTaskFile(file: string) {
|
|
90
|
-
// const raw = fs.readFileSync(file, "utf8");
|
|
91
|
-
//
|
|
92
|
-
// const tree = fromMarkdown(raw);
|
|
93
|
-
//
|
|
94
|
-
// let title: string | undefined;
|
|
95
|
-
// let id: string | undefined;
|
|
96
|
-
// let project: string | undefined;
|
|
97
|
-
// let status: TaskStatus | undefined;
|
|
98
|
-
// let priority: number | undefined;
|
|
99
|
-
// let fileLocation: FileLocation | undefined;
|
|
100
|
-
// let jiraId: string | undefined;
|
|
101
|
-
// let description = "";
|
|
102
|
-
//
|
|
103
|
-
// for (const node of tree.children) {
|
|
104
|
-
// if (node.type === "heading" && node.depth === 1) {
|
|
105
|
-
// if (!node.children[0] || !("value" in node.children[0])) continue;
|
|
106
|
-
// title = node.children[0]?.value.trim();
|
|
107
|
-
// continue;
|
|
108
|
-
// }
|
|
109
|
-
//
|
|
110
|
-
// if (node.type === "list") {
|
|
111
|
-
// for (const item of node.children) {
|
|
112
|
-
// if (!item.children[0]
|
|
113
|
-
// || !("children" in item.children[0])
|
|
114
|
-
// || !item.children[0].children[0]
|
|
115
|
-
// || !("value" in item.children[0].children[0])) continue;
|
|
116
|
-
// const text = item.children[0].children[0]?.value;
|
|
117
|
-
// const [key, rawVal, ...rest] = text.split(":");
|
|
118
|
-
// const value = [rawVal, ...rest].join(":")?.trim();
|
|
119
|
-
// switch (key) {
|
|
120
|
-
// case "ID":
|
|
121
|
-
// id = value;
|
|
122
|
-
// break;
|
|
123
|
-
// case "PROJECT":
|
|
124
|
-
// project = value;
|
|
125
|
-
// break;
|
|
126
|
-
// case "STATUS": {
|
|
127
|
-
// const statuses = Object.values(TASK_STATUS);
|
|
128
|
-
// if (!statuses.includes(value as TaskStatus)) throw new Error(`Invalid STATUS "${value}". Must be one of: ${statuses.join(", ")}`);
|
|
129
|
-
// status = value as TaskStatus;
|
|
130
|
-
// break;
|
|
131
|
-
// }
|
|
132
|
-
// case "PRIORITY":
|
|
133
|
-
// priority = Number(value);
|
|
134
|
-
// break;
|
|
135
|
-
// case "JIRA-ID":
|
|
136
|
-
// jiraId = value;
|
|
137
|
-
// break;
|
|
138
|
-
// case "FILE-LOCATION":
|
|
139
|
-
// fileLocation = this.parseFileLocation(value ?? "");
|
|
140
|
-
// break;
|
|
141
|
-
// }
|
|
142
|
-
// }
|
|
143
|
-
// continue;
|
|
144
|
-
// }
|
|
145
|
-
//
|
|
146
|
-
// if (node.type === "paragraph") {
|
|
147
|
-
// const text = node.children.map((c) => {
|
|
148
|
-
// if (!("value" in c)) return "";
|
|
149
|
-
// return c.value;
|
|
150
|
-
// }).join("").trim();
|
|
151
|
-
// if (text) description += (description ? "\n" : "") + text;
|
|
152
|
-
// }
|
|
153
|
-
// }
|
|
154
|
-
//
|
|
155
|
-
// if (!id) throw new Error(`Could not find id for task ${file}`);
|
|
156
|
-
// if (!project) throw new Error(`Could not find project for task ${file}`);
|
|
157
|
-
// if (!title) throw new Error(`Could not find title for task ${id} from project ${project}`);
|
|
158
|
-
// if (!priority) throw new Error(`Could not find priority for task ${id} from project ${project}`);
|
|
159
|
-
// if (!status) throw new Error(`Could not find status for task ${id} from project ${project}`);
|
|
160
|
-
//
|
|
161
|
-
// return new Task({
|
|
162
|
-
// md_path: file,
|
|
163
|
-
// id,
|
|
164
|
-
// title,
|
|
165
|
-
// project,
|
|
166
|
-
// description,
|
|
167
|
-
// tags: {
|
|
168
|
-
// status,
|
|
169
|
-
// priority,
|
|
170
|
-
// jira_id: jiraId,
|
|
171
|
-
// file_location: fileLocation
|
|
172
|
-
// }
|
|
173
|
-
// });
|
|
174
|
-
// }
|
|
175
|
-
}
|
package/src/lib/task.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Choice,
|
|
3
|
-
Dir,
|
|
4
|
-
openInEditor,
|
|
5
|
-
TextFile
|
|
6
|
-
} from "@maro/maro";
|
|
7
|
-
|
|
8
|
-
type FileLocation = {
|
|
9
|
-
file: TextFile;
|
|
10
|
-
row: number;
|
|
11
|
-
col: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export class Task {
|
|
15
|
-
project: Dir;
|
|
16
|
-
|
|
17
|
-
title: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
file_location: FileLocation;
|
|
20
|
-
|
|
21
|
-
constructor({ title, file_location, project, description }: {
|
|
22
|
-
description?: string;
|
|
23
|
-
project: Dir;
|
|
24
|
-
title: string;
|
|
25
|
-
file_location: FileLocation;
|
|
26
|
-
}) {
|
|
27
|
-
const file = file_location.file;
|
|
28
|
-
if (!project.contains(file)) throw new Error(`${file} is not in ${project}`);
|
|
29
|
-
this.title = title;
|
|
30
|
-
this.file_location = file_location;
|
|
31
|
-
this.project = project;
|
|
32
|
-
this.description = description;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
getTodo() {
|
|
36
|
-
const { file, row } = this.file_location;
|
|
37
|
-
const lines = file.read().split(/\r?\n/);
|
|
38
|
-
const normalized_row = row - 1;
|
|
39
|
-
return lines[normalized_row]!;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
editTodo(add: string) {
|
|
43
|
-
const todoLine = this.getTodo();
|
|
44
|
-
const { file, col, row } = this.file_location;
|
|
45
|
-
const normalized_row = row - 1;
|
|
46
|
-
const normalized_col = col - 1;
|
|
47
|
-
|
|
48
|
-
const prefix = todoLine.slice(0, normalized_col);
|
|
49
|
-
const afterTodo = todoLine.slice(normalized_col);
|
|
50
|
-
const colonIndex = afterTodo.indexOf(":");
|
|
51
|
-
|
|
52
|
-
const originalDecorations = colonIndex === -1
|
|
53
|
-
? afterTodo.slice("TODO".length)
|
|
54
|
-
: afterTodo.slice("TODO".length, colonIndex);
|
|
55
|
-
|
|
56
|
-
const originalBody = colonIndex === -1 ? "" : afterTodo.slice(colonIndex + 1).trim();
|
|
57
|
-
|
|
58
|
-
const newTodo = prefix + "TODO" + originalDecorations + add + ": " + originalBody;
|
|
59
|
-
const lines = file.read().split(/\r?\n/);
|
|
60
|
-
lines[normalized_row] = newTodo;
|
|
61
|
-
file.write(lines.join("\n"));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
openInEditor() {
|
|
65
|
-
const { col, row } = this.file_location;
|
|
66
|
-
openInEditor(this.file_location.file, { line: row, column: col });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
getPathInProject() {
|
|
70
|
-
return this.project.relativePathTo(this.file_location.file);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
toChoice(): Choice {
|
|
74
|
-
return { name: this.title, hint: `${this.file_location.file}:${this.file_location.row}:${this.file_location.col}` };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
}
|
package/src/lib/tracker.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Task } from "./task";
|
|
2
|
-
|
|
3
|
-
export abstract class TaskTracker {
|
|
4
|
-
|
|
5
|
-
abstract save(task: Task): Promise<string>;
|
|
6
|
-
abstract addIdToTodo(id: string, task: Task): string;
|
|
7
|
-
abstract isTracked(task: Task): boolean;
|
|
8
|
-
|
|
9
|
-
async track(task: Task) {
|
|
10
|
-
if (this.isTracked(task)) return;
|
|
11
|
-
const id = await this.save(task);
|
|
12
|
-
task.editTodo(this.addIdToTodo(id, task) ?? "");
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
package/src/lib/utils.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Dir, TextFile } from "@maro/maro";
|
|
2
|
-
|
|
3
|
-
import { Task } from "./task";
|
|
4
|
-
|
|
5
|
-
export const getTasksFromDir = (dir: Dir) => {
|
|
6
|
-
const files = dir.traverse();
|
|
7
|
-
const tasks: Task[] = [];
|
|
8
|
-
for (const f of files) tasks.push(...getTasksFromFile(dir, f));
|
|
9
|
-
return tasks;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const getTasksFromFile = (project: Dir, fileInDir: TextFile) => {
|
|
13
|
-
const lines = fileInDir.read().split(/\r?\n/);
|
|
14
|
-
const results: Task[] = [];
|
|
15
|
-
const regex = /TODO.*:\s*(.*)/g;
|
|
16
|
-
for (let row = 0; row < lines.length; row++) {
|
|
17
|
-
const line = lines[row]!;
|
|
18
|
-
let match;
|
|
19
|
-
while ((match = regex.exec(line)) !== null) {
|
|
20
|
-
const col = match.index;
|
|
21
|
-
const title = match[1]?.trim() ?? "";
|
|
22
|
-
const file_location = { file: fileInDir, row: row + 1, col: col + 1 };
|
|
23
|
-
// TODO: Task.description from file
|
|
24
|
-
results.push(new Task({ title, project, file_location }));
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return results;
|
|
28
|
-
};
|
|
29
|
-
|
package/tsconfig.json
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"lib": [
|
|
5
|
-
"es2021",
|
|
6
|
-
"dom"
|
|
7
|
-
],
|
|
8
|
-
"module": "commonjs",
|
|
9
|
-
"outDir": "./dist",
|
|
10
|
-
"rootDir": "./",
|
|
11
|
-
"baseUrl": "./",
|
|
12
|
-
"strict": true,
|
|
13
|
-
"moduleResolution": "node",
|
|
14
|
-
"esModuleInterop": true,
|
|
15
|
-
"skipLibCheck": true,
|
|
16
|
-
"forceConsistentCasingInFileNames": true,
|
|
17
|
-
"noImplicitAny": true,
|
|
18
|
-
"strictNullChecks": true,
|
|
19
|
-
"strictFunctionTypes": true,
|
|
20
|
-
"strictBindCallApply": true,
|
|
21
|
-
"strictPropertyInitialization": true,
|
|
22
|
-
"noImplicitThis": true,
|
|
23
|
-
"useUnknownInCatchVariables": true,
|
|
24
|
-
"experimentalDecorators": true,
|
|
25
|
-
"useDefineForClassFields": false,
|
|
26
|
-
"alwaysStrict": true,
|
|
27
|
-
"noUnusedLocals": true,
|
|
28
|
-
"noUnusedParameters": true,
|
|
29
|
-
"noImplicitReturns": true,
|
|
30
|
-
"noFallthroughCasesInSwitch": true,
|
|
31
|
-
"noImplicitOverride": true,
|
|
32
|
-
"allowUnusedLabels": false,
|
|
33
|
-
"allowUnreachableCode": false,
|
|
34
|
-
"noUncheckedIndexedAccess": true,
|
|
35
|
-
"noEmit": false,
|
|
36
|
-
"noEmitOnError": false
|
|
37
|
-
}
|
|
38
|
-
}
|