trello-cli-unofficial 0.6.9 → 0.7.3
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 +26 -13
- package/bunfig.toml +2 -0
- package/dist/main.js +10709 -10223
- package/package.json +20 -2
- package/scripts/check-coverage.js +67 -0
- package/src/application/use-cases/AuthenticateUserUseCase.ts +2 -2
- package/src/application/use-cases/CreateCardUseCase.ts +2 -2
- package/src/application/use-cases/DeleteCardUseCase.ts +1 -1
- package/src/application/use-cases/GetBoardsUseCase.ts +2 -2
- package/src/application/use-cases/GetCardsUseCase.ts +2 -2
- package/src/application/use-cases/GetListsUseCase.ts +2 -2
- package/src/application/use-cases/MoveCardUseCase.ts +2 -2
- package/src/application/use-cases/UpdateCardUseCase.ts +2 -2
- package/src/domain/repositories/ConfigRepository.ts +1 -1
- package/src/domain/repositories/TrelloRepository.ts +7 -1
- package/src/domain/services/AuthenticationService.ts +2 -2
- package/src/infrastructure/repositories/FileConfigRepository.ts +4 -6
- package/src/infrastructure/repositories/TrelloApiRepository.ts +3 -3
- package/src/presentation/cli/AuthController.ts +38 -0
- package/src/presentation/cli/BoardController.ts +85 -0
- package/src/presentation/cli/CardController.ts +319 -0
- package/src/presentation/cli/CommandController.ts +113 -10
- package/src/presentation/cli/ConfigController.ts +57 -0
- package/src/presentation/cli/MainMenuController.ts +62 -0
- package/src/presentation/cli/TrelloCliController.ts +35 -390
- package/src/presentation/cli/index.ts +5 -0
- package/test_trigger.txt +3 -0
- package/.eslintignore +0 -26
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trello-cli-unofficial",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.3",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Unofficial Trello CLI using Power-Up authentication, built with Bun for maximum performance",
|
|
7
7
|
"author": "Matheus Caiser <matheus.kaiser@gmail.com> (https://www.mrdeveloper.com.br/)",
|
|
@@ -27,6 +27,23 @@
|
|
|
27
27
|
"trello-cli-unofficial": "./main.ts",
|
|
28
28
|
"tcu": "./main.ts"
|
|
29
29
|
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"provenance": true
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"CHANGELOG.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"README.md",
|
|
38
|
+
"bun.lock",
|
|
39
|
+
"bunfig.toml",
|
|
40
|
+
"dist",
|
|
41
|
+
"install.sh",
|
|
42
|
+
"main.ts",
|
|
43
|
+
"scripts",
|
|
44
|
+
"src",
|
|
45
|
+
"test_trigger.txt"
|
|
46
|
+
],
|
|
30
47
|
"engines": {
|
|
31
48
|
"bun": ">=1.0.0"
|
|
32
49
|
},
|
|
@@ -40,10 +57,11 @@
|
|
|
40
57
|
"test:unit": "bun test tests/unit",
|
|
41
58
|
"test:integration": "bun test tests/integration",
|
|
42
59
|
"test:coverage": "bun test --coverage",
|
|
60
|
+
"test:coverage:threshold": "bun test --coverage --coverage-reporter=lcov && bun run scripts/check-coverage.js",
|
|
43
61
|
"lint": "eslint .",
|
|
44
62
|
"lint:fix": "eslint . --fix",
|
|
45
63
|
"typecheck": "tsc --noEmit",
|
|
46
|
-
"validate": "bun run lint && bun run typecheck && bun run test",
|
|
64
|
+
"validate": "bun run lint && bun run typecheck && bun run test:coverage:threshold",
|
|
47
65
|
"version:patch": "bun version patch && git push --follow-tags",
|
|
48
66
|
"version:minor": "bun version minor && git push --follow-tags",
|
|
49
67
|
"version:major": "bun version major && git push --follow-tags",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { exit } from 'node:process';
|
|
5
|
+
|
|
6
|
+
// Read LCOV coverage data
|
|
7
|
+
const lcovContent = readFileSync('./coverage/lcov.info', 'utf-8');
|
|
8
|
+
|
|
9
|
+
// Parse LCOV format to extract totals
|
|
10
|
+
const lines = lcovContent.split('\n');
|
|
11
|
+
const totals = { lines: 0, functions: 0, branches: 0, statements: 0 };
|
|
12
|
+
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
if (line.startsWith('LF:')) {
|
|
15
|
+
// Lines found
|
|
16
|
+
const totalLines = Number.parseInt(line.split(':')[1]);
|
|
17
|
+
const nextLine = lines[lines.indexOf(line) + 1];
|
|
18
|
+
if (nextLine && nextLine.startsWith('LH:')) {
|
|
19
|
+
const hitLines = Number.parseInt(nextLine.split(':')[1]);
|
|
20
|
+
totals.lines = Math.round((hitLines / totalLines) * 100);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (line.startsWith('FNF:')) {
|
|
24
|
+
// Functions found
|
|
25
|
+
const totalFunctions = Number.parseInt(line.split(':')[1]);
|
|
26
|
+
const nextLine = lines[lines.indexOf(line) + 1];
|
|
27
|
+
if (nextLine && nextLine.startsWith('FNH:')) {
|
|
28
|
+
const hitFunctions = Number.parseInt(nextLine.split(':')[1]);
|
|
29
|
+
totals.functions = Math.round((hitFunctions / totalFunctions) * 100);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// For statements, we'll use lines as approximation since LCOV doesn't have separate statement count
|
|
35
|
+
totals.statements = totals.lines;
|
|
36
|
+
|
|
37
|
+
// Check if all metrics meet the 95% threshold
|
|
38
|
+
const thresholds = {
|
|
39
|
+
lines: 95,
|
|
40
|
+
functions: 95,
|
|
41
|
+
statements: 95,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let allPassed = true;
|
|
45
|
+
const results = [];
|
|
46
|
+
|
|
47
|
+
for (const [metric, threshold] of Object.entries(thresholds)) {
|
|
48
|
+
const actual = totals[metric] || 0;
|
|
49
|
+
const passed = actual >= threshold;
|
|
50
|
+
results.push(`${metric}: ${actual}% (threshold: ${threshold}%) - ${passed ? '✅' : '❌'}`);
|
|
51
|
+
|
|
52
|
+
if (!passed) {
|
|
53
|
+
allPassed = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('Coverage Threshold Check:');
|
|
58
|
+
results.forEach(result => console.log(` ${result}`));
|
|
59
|
+
|
|
60
|
+
if (!allPassed) {
|
|
61
|
+
console.log('\n❌ Coverage threshold not met!');
|
|
62
|
+
exit(1);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log('\n✅ All coverage thresholds met!');
|
|
66
|
+
exit(0);
|
|
67
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ConfigRepository } from '
|
|
2
|
-
import { AuthenticationService } from '
|
|
1
|
+
import type { ConfigRepository } from '@domain/repositories';
|
|
2
|
+
import { AuthenticationService } from '@domain/services';
|
|
3
3
|
|
|
4
4
|
export class AuthenticateUserUseCase {
|
|
5
5
|
private authService: AuthenticationService;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CardEntity, CreateCardData } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
1
|
+
import type { CardEntity, CreateCardData } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class CreateCardUseCase {
|
|
5
5
|
constructor(private trelloRepository: TrelloRepository) {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BoardEntity } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
1
|
+
import type { BoardEntity } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class GetBoardsUseCase {
|
|
5
5
|
constructor(private trelloRepository: TrelloRepository) {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CardEntity } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
1
|
+
import type { CardEntity } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class GetCardsUseCase {
|
|
5
5
|
constructor(private trelloRepository: TrelloRepository) {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ListEntity } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
1
|
+
import type { ListEntity } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class GetListsUseCase {
|
|
5
5
|
constructor(private trelloRepository: TrelloRepository) {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CardEntity } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
1
|
+
import type { CardEntity } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class MoveCardUseCase {
|
|
5
5
|
constructor(private trelloRepository: TrelloRepository) {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CardEntity, UpdateCardData } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
1
|
+
import type { CardEntity, UpdateCardData } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class UpdateCardUseCase {
|
|
5
5
|
constructor(private trelloRepository: TrelloRepository) {}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BoardEntity,
|
|
3
|
+
CardEntity,
|
|
4
|
+
CreateCardData,
|
|
5
|
+
ListEntity,
|
|
6
|
+
UpdateCardData,
|
|
7
|
+
} from '@domain/entities';
|
|
2
8
|
|
|
3
9
|
export interface TrelloRepository {
|
|
4
10
|
getBoards: () => Promise<BoardEntity[]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ConfigEntity } from '
|
|
2
|
-
import type { ConfigRepository } from '
|
|
1
|
+
import type { ConfigEntity } from '@domain/entities';
|
|
2
|
+
import type { ConfigRepository } from '@domain/repositories';
|
|
3
3
|
|
|
4
4
|
export class AuthenticationService {
|
|
5
5
|
constructor(private configRepository: ConfigRepository) {}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ConfigRepository } from '
|
|
1
|
+
import type { ConfigRepository } from '@domain/repositories';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { ConfigEntity } from '@domain/entities';
|
|
3
4
|
import fs from 'fs-extra';
|
|
4
|
-
import { ConfigEntity } from '../../domain/entities';
|
|
5
5
|
|
|
6
6
|
export class FileConfigRepository implements ConfigRepository {
|
|
7
7
|
private readonly configDir: string;
|
|
@@ -24,8 +24,7 @@ export class FileConfigRepository implements ConfigRepository {
|
|
|
24
24
|
data.token || process.env.TRELLO_TOKEN,
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
27
|
+
} catch (error) {
|
|
29
28
|
console.error('Error loading config:', (error as Error).message);
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -43,8 +42,7 @@ export class FileConfigRepository implements ConfigRepository {
|
|
|
43
42
|
},
|
|
44
43
|
{ spaces: 2 },
|
|
45
44
|
);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
45
|
+
} catch (error) {
|
|
48
46
|
console.error('Error saving config:', (error as Error).message);
|
|
49
47
|
}
|
|
50
48
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { CreateCardData, UpdateCardData } from '
|
|
2
|
-
import type { TrelloRepository } from '
|
|
3
|
-
import { BoardEntity, CardEntity, ListEntity } from '
|
|
1
|
+
import type { CreateCardData, UpdateCardData } from '@domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
3
|
+
import { BoardEntity, CardEntity, ListEntity } from '@domain/entities';
|
|
4
4
|
|
|
5
5
|
export class TrelloApiRepository implements TrelloRepository {
|
|
6
6
|
private readonly baseUrl = 'https://api.trello.com/1';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ConfigRepository } from '@domain/repositories';
|
|
2
|
+
import { AuthenticateUserUseCase } from '@application/use-cases';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
|
|
5
|
+
export class AuthController {
|
|
6
|
+
private authenticateUseCase: AuthenticateUserUseCase;
|
|
7
|
+
|
|
8
|
+
constructor(private configRepository: ConfigRepository) {
|
|
9
|
+
this.authenticateUseCase = new AuthenticateUserUseCase(configRepository);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async ensureAuthenticated(): Promise<void> {
|
|
13
|
+
const result = await this.authenticateUseCase.execute();
|
|
14
|
+
if (!result.success) {
|
|
15
|
+
console.log(result.message);
|
|
16
|
+
await this.setupToken();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async setupToken(): Promise<void> {
|
|
21
|
+
const { token } = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'input',
|
|
24
|
+
name: 'token',
|
|
25
|
+
message: 'Digite seu token do Trello (ATTA...):',
|
|
26
|
+
validate: input =>
|
|
27
|
+
input.startsWith('ATTA') || 'Token deve começar com ATTA',
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const result = await this.authenticateUseCase.execute(token);
|
|
32
|
+
console.log(result.message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getConfig() {
|
|
36
|
+
return await this.authenticateUseCase.getConfig();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { TrelloRepository } from '@domain/repositories';
|
|
2
|
+
import { GetBoardsUseCase, GetCardsUseCase, GetListsUseCase } from '@application/use-cases';
|
|
3
|
+
|
|
4
|
+
export class BoardController {
|
|
5
|
+
private getBoardsUseCase: GetBoardsUseCase;
|
|
6
|
+
private getListsUseCase: GetListsUseCase;
|
|
7
|
+
private getCardsUseCase: GetCardsUseCase;
|
|
8
|
+
|
|
9
|
+
constructor(trelloRepository: TrelloRepository) {
|
|
10
|
+
this.getBoardsUseCase = new GetBoardsUseCase(trelloRepository);
|
|
11
|
+
this.getListsUseCase = new GetListsUseCase(trelloRepository);
|
|
12
|
+
this.getCardsUseCase = new GetCardsUseCase(trelloRepository);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async showBoards(): Promise<void> {
|
|
16
|
+
const boards = await this.getBoardsUseCase.execute();
|
|
17
|
+
|
|
18
|
+
console.log('📋 Seus Quadros do Trello:');
|
|
19
|
+
boards.forEach((board, index) => {
|
|
20
|
+
console.log(`${index + 1}. ${board.name}`);
|
|
21
|
+
console.log(` 🔗 ${board.url}`);
|
|
22
|
+
console.log(` 🆔 ${board.id}\n`);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async showLists(boardName: string): Promise<void> {
|
|
27
|
+
const boards = await this.getBoardsUseCase.execute();
|
|
28
|
+
const board = boards.find(b => b.name === boardName);
|
|
29
|
+
|
|
30
|
+
if (!board) {
|
|
31
|
+
throw new Error(`Quadro "${boardName}" não encontrado`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lists = await this.getListsUseCase.execute(board.id);
|
|
35
|
+
|
|
36
|
+
console.log(`📋 Listas do quadro "${boardName}":`);
|
|
37
|
+
lists.forEach((list, index) => {
|
|
38
|
+
console.log(`${index + 1}. ${list.name}`);
|
|
39
|
+
console.log(` 🆔 ${list.id}\n`);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async showCards(boardName: string, listName: string): Promise<void> {
|
|
44
|
+
const boards = await this.getBoardsUseCase.execute();
|
|
45
|
+
const board = boards.find(b => b.name === boardName);
|
|
46
|
+
|
|
47
|
+
if (!board) {
|
|
48
|
+
throw new Error(`Quadro "${boardName}" não encontrado`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const lists = await this.getListsUseCase.execute(board.id);
|
|
52
|
+
const list = lists.find(l => l.name === listName);
|
|
53
|
+
|
|
54
|
+
if (!list) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Lista "${listName}" não encontrada no quadro "${boardName}"`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cards = await this.getCardsUseCase.execute(list.id);
|
|
61
|
+
|
|
62
|
+
console.log(`📋 Cartões da lista "${listName}" no quadro "${boardName}":`);
|
|
63
|
+
if (cards.length === 0) {
|
|
64
|
+
console.log('📭 Esta lista está vazia.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
cards.forEach((card, index) => {
|
|
69
|
+
console.log(`${index + 1}. ${card.name}`);
|
|
70
|
+
console.log(` 🆔 ${card.id}\n`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getBoards() {
|
|
75
|
+
return await this.getBoardsUseCase.execute();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getLists(boardId: string) {
|
|
79
|
+
return await this.getListsUseCase.execute(boardId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getCards(listId: string) {
|
|
83
|
+
return await this.getCardsUseCase.execute(listId);
|
|
84
|
+
}
|
|
85
|
+
}
|