Skip to content

Commit efae8c0

Browse files
committed
chore: development work
1 parent b4f7898 commit efae8c0

File tree

13 files changed

+256
-21
lines changed

13 files changed

+256
-21
lines changed

.github/workflows/todo.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626
uses: ./
2727
with:
2828
repo-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
29+
issue-title-template: src/templates/issueTitle.txt
30+
issue-body-template: src/templates/issueBody.md
2931
report: true
3032

3133
- name: Upload TODO report
@@ -40,4 +42,4 @@ jobs:
4042
git config --global user.email "github-actions[bot]@users.noreply.github.com"
4143
git add TODO_REPORT.md
4244
git commit -m "chore(report): update TODO report" || echo "No changes"
43-
git push
45+
git push
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
🗂 **File:** `{{file}}:{{line}}`
2+
3+
📝 **Content:**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{{tag}}] {{text}}

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,41 @@ If a label like `priority:high` or `due:2025-06-01` doesn't exist, it will be au
9898
- ✅ Support for issue comments and discussions
9999
- ✅ Customizable notification settings (e.g., email, Slack)
100100
- ✅ Support for issue closing and resolution tracking
101-
- ✅ Customizable issue lifecycle management (e.g., open, in progress, closed)
101+
- ✅ Customizable issue lifecycle management (e.g., open, in progress, closed)
102+
103+
104+
```plaintext
105+
smart-todo-action/
106+
├── .github/
107+
│ └── workflows/
108+
│ └── todo.yml
109+
110+
├── dist/
111+
│ └── index.js
112+
113+
├── src/
114+
│ ├── core/
115+
│ │ ├── issueManager.ts
116+
│ │ ├── labelManager.ts # 🆕 Label logic (static + metadata + creation)
117+
│ │ ├── report.ts
118+
│ │ ├── todoUtils.ts
119+
│ │ └── __tests__/ # (opcional) unit tests
120+
121+
│ ├── parser/
122+
│ │ ├── extractTodosFromDir.ts
123+
│ │ ├── extractTodosFromFile.ts
124+
│ │ └── types.ts
125+
126+
│ ├── templates/
127+
│ │ ├── issueTitle.txt
128+
│ │ ├── issueBody.md
129+
│ │ └── utils.ts
130+
131+
│ └── ActionMain.ts
132+
133+
├── .gitignore
134+
├── action.yml
135+
├── package.json
136+
├── tsconfig.json
137+
└── README.md
138+
```

action.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,25 @@ inputs:
66
repo-token:
77
required: true
88
description: GitHub token to create issues
9+
910
report:
1011
required: false
1112
description: Whether to generate a TODO markdown report
1213
default: 'false'
1314

15+
issue-title-template:
16+
required: false
17+
description: Optional path to custom issue title template
18+
19+
issue-body-template:
20+
required: false
21+
description: Optional path to custom issue body template
22+
1423
runs:
1524
using: 'node20'
1625
main: 'dist/index.js'
1726

1827
branding:
1928
icon: 'check-circle'
2029
color: 'blue'
30+

src/ActionMain.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ async function run(): Promise<void> {
1111
try {
1212
const token = core.getInput('repo-token', { required: true });
1313
const generateReport = core.getInput('report') === 'true';
14+
const titleTemplatePath = core.getInput('issue-title-template');
15+
const bodyTemplatePath = core.getInput('issue-body-template');
1416
const workspace = process.env.GITHUB_WORKSPACE || '.';
1517

1618
const todos: TodoItem[] = extractTodosFromDir(workspace);
@@ -32,7 +34,15 @@ async function run(): Promise<void> {
3234
const todosToCreate = limitTodos(uniqueTodos, 5);
3335

3436
for (const todo of todosToCreate) {
35-
await createIssueIfNeeded(octokit, owner, repo, todo, existingTitles);
37+
await createIssueIfNeeded(
38+
octokit,
39+
owner,
40+
repo,
41+
todo,
42+
existingTitles,
43+
titleTemplatePath,
44+
bodyTemplatePath
45+
);
3646
}
3747

3848
if (generateReport) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { applyTemplate } from '../../templates/utils';
2+
import { describe, it, expect } from 'vitest'
3+
4+
describe('applyTemplate', () => {
5+
it('replaces simple variables', () => {
6+
const template = '[{{tag}}] {{text}}';
7+
const data = {
8+
tag: 'TODO',
9+
text: 'Implement login flow'
10+
};
11+
12+
const result = applyTemplate(template, data);
13+
expect(result).toBe('[TODO] Implement login flow');
14+
});
15+
16+
it('ignores missing variables', () => {
17+
const template = 'Priority: {{priority}}';
18+
const data = {
19+
tag: 'TODO'
20+
};
21+
22+
const result = applyTemplate(template, data);
23+
expect(result).toBe('Priority: ');
24+
});
25+
26+
it('handles numeric values', () => {
27+
const template = 'Line {{line}}: {{text}}';
28+
const data = {
29+
line: 42,
30+
text: 'Optimize loop'
31+
};
32+
33+
const result = applyTemplate(template, data);
34+
expect(result).toBe('Line 42: Optimize loop');
35+
});
36+
});
37+
38+
39+
40+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { extractTodosFromString } from '../../parser/extractTodosFromContent';
3+
4+
describe('extractTodosFromString - comment support by extension', () => {
5+
it('extracts from JS-style (//) for .js/.ts/.go/.java', () => {
6+
const code = `// TODO: js comment\n// BUG: broken`;
7+
const extensions = ['.js', '.ts', '.go', '.java'];
8+
9+
for (const ext of extensions) {
10+
const todos = extractTodosFromString(code, ext);
11+
expect(todos.length).toBe(2);
12+
expect(todos[0].tag).toBe('TODO');
13+
expect(todos[1].tag).toBe('BUG');
14+
}
15+
});
16+
17+
it('extracts from Python-style (#) for .py/.sh/.rb', () => {
18+
const code = `# TODO: python comment\n# FIXME: fix me`;
19+
const extensions = ['.py', '.sh', '.rb'];
20+
21+
for (const ext of extensions) {
22+
const todos = extractTodosFromString(code, ext);
23+
expect(todos.length).toBe(2);
24+
expect(todos[0].tag).toBe('TODO');
25+
expect(todos[1].tag).toBe('FIXME');
26+
}
27+
});
28+
29+
it('extracts from HTML-style (<!-- -->) for .html/.xml', () => {
30+
const code = `<!-- TODO: html fix -->\n<!-- HACK: temp hack -->`;
31+
const extensions = ['.html', '.xml'];
32+
33+
for (const ext of extensions) {
34+
const todos = extractTodosFromString(code, ext);
35+
expect(todos.length).toBe(2);
36+
expect(todos[0].tag).toBe('TODO');
37+
expect(todos[1].tag).toBe('HACK');
38+
}
39+
});
40+
41+
it('returns [] for unsupported extensions', () => {
42+
const code = `// TODO: will not be parsed`;
43+
const todos = extractTodosFromString(code, '.txt');
44+
expect(todos).toEqual([]);
45+
});
46+
});
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
import { describe, it, expect } from 'vitest';
2-
import { extractTodosFromFile } from '../../parser/extractTodos'; // Ensure this file exists at the specified path
2+
import { extractTodosFromString } from '../../parser/extractTodosFromContent';
33
import { TodoItem } from '../../parser/types';
44

55

66

77
describe('extractTodos', () => {
88
it('extracts simple TODOs with //', () => {
99
const content = `// TODO: clean this up\nconst a = 1;`;
10-
const todos = extractTodosFromFile(content);
10+
const todos = extractTodosFromString(content, '.js');
1111
expect(todos.length).toBe(1);
1212
expect(todos[0].text).toBe('clean this up');
1313
expect(todos[0].tag).toBe('TODO');
1414
expect(todos[0].line).toBe(1);
1515
});
1616

1717
it('extracts multiple tags', () => {
18-
const content = `// BUG: crashes\n# FIXME: something wrong`;
19-
const todos = extractTodosFromFile(content);
18+
const content = `# BUG: crashes\n# FIXME: something wrong`;
19+
const todos = extractTodosFromString(content, '.py');
2020
expect(todos.length).toBe(2);
2121
expect(todos.map(t => t.tag)).toEqual(['BUG', 'FIXME']);
2222
});
2323

2424
it('extracts metadata key=value pairs', () => {
2525
const content = `// TODO(priority=high, due=2025-06-01): fix it`;
26-
const todos = extractTodosFromFile(content);
26+
const todos = extractTodosFromString(content, '.js');
2727
expect(todos.length).toBe(1);
2828
expect(todos[0].metadata).toEqual({
2929
priority: 'high',
@@ -33,14 +33,14 @@ describe('extractTodos', () => {
3333

3434
it('supports HTML comments', () => {
3535
const content = `<!-- TODO: fix layout -->`;
36-
const todos = extractTodosFromFile(content);
36+
const todos = extractTodosFromString(content, '.html');
3737
expect(todos.length).toBe(1);
3838
expect(todos[0].tag).toBe('TODO');
3939
});
4040

4141
it('returns empty list if no TODOs are found', () => {
4242
const content = `const x = 5; // just a comment`;
43-
const todos = extractTodosFromFile(content);
43+
const todos = extractTodosFromString(content, '.js');
4444
expect(todos.length).toBe(0);
4545
});
4646
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { extractTodosFromString } from '../../parser/extractTodosFromContent';
2+
import { describe, it, expect } from 'vitest'
3+
4+
describe('extractTodosFromString', () => {
5+
it('extracts multiple TODO-style tags', () => {
6+
const content = `// BUG: crash here\n# FIXME: wrong\n<!-- TODO: layout -->`;
7+
const jsTodos = extractTodosFromString(content, '.js');
8+
const pyTodos = extractTodosFromString(content, '.py');
9+
const htmlTodos = extractTodosFromString(content, '.html');
10+
11+
expect(jsTodos.length).toBe(1);
12+
expect(jsTodos[0].tag).toBe('BUG');
13+
14+
expect(pyTodos.length).toBe(1);
15+
expect(pyTodos[0].tag).toBe('FIXME');
16+
17+
expect(htmlTodos.length).toBe(1);
18+
expect(htmlTodos[0].tag).toBe('TODO');
19+
});
20+
21+
it('extracts metadata key=value pairs', () => {
22+
const content = `// TODO(priority=high, due=2025-06-01): fix it`;
23+
const todos = extractTodosFromString(content, '.js');
24+
25+
expect(todos.length).toBe(1);
26+
expect(todos[0].metadata).toEqual({
27+
priority: 'high',
28+
due: '2025-06-01'
29+
});
30+
});
31+
});

0 commit comments

Comments
 (0)