trello-cli-unofficial 0.7.6 → 0.8.0

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.
Files changed (31) hide show
  1. package/README.md +27 -0
  2. package/bun.lock +225 -2
  3. package/dist/main.js +26490 -25401
  4. package/main.ts +6 -3
  5. package/package.json +15 -3
  6. package/src/application/use-cases/AuthenticateUserUseCase.ts +7 -6
  7. package/src/application/use-cases/CreateBoardUseCase.ts +19 -0
  8. package/src/application/use-cases/CreateCardUseCase.ts +2 -1
  9. package/src/application/use-cases/CreateListUseCase.ts +19 -0
  10. package/src/application/use-cases/GetBoardDetailsUseCase.ts +41 -0
  11. package/src/application/use-cases/UpdateCardUseCase.ts +2 -1
  12. package/src/application/use-cases/index.ts +3 -0
  13. package/src/domain/entities/Board.ts +10 -2
  14. package/src/domain/entities/Card.ts +12 -1
  15. package/src/domain/entities/Config.ts +3 -1
  16. package/src/domain/entities/List.ts +14 -2
  17. package/src/domain/repositories/TrelloRepository.ts +4 -0
  18. package/src/i18n/index.ts +62 -5
  19. package/src/i18n/locales/en.json +154 -17
  20. package/src/i18n/locales/pt-BR.json +154 -17
  21. package/src/infrastructure/repositories/FileConfigRepository.ts +6 -3
  22. package/src/infrastructure/repositories/TrelloApiRepository.ts +155 -10
  23. package/src/presentation/cli/AuthController.ts +2 -1
  24. package/src/presentation/cli/BoardController.ts +160 -17
  25. package/src/presentation/cli/CardController.ts +169 -45
  26. package/src/presentation/cli/CommandController.ts +293 -27
  27. package/src/presentation/cli/ConfigController.ts +4 -3
  28. package/src/presentation/cli/TrelloCliController.ts +10 -2
  29. package/src/shared/ErrorHandler.ts +233 -0
  30. package/src/shared/OutputFormatter.ts +210 -0
  31. package/src/shared/index.ts +2 -0
@@ -1,6 +1,32 @@
1
1
  import type { CreateCardData, UpdateCardData } from '@domain/entities';
2
2
  import type { TrelloRepository } from '@domain/repositories';
3
3
  import { BoardEntity, CardEntity, ListEntity } from '@domain/entities';
4
+ import { t } from '@/i18n';
5
+
6
+ // API Response types
7
+ interface TrelloBoardResponse {
8
+ id: string;
9
+ name: string;
10
+ url: string;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ interface TrelloListResponse {
15
+ id: string;
16
+ name: string;
17
+ idBoard: string;
18
+ pos: number;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ interface TrelloCardResponse {
23
+ id: string;
24
+ name: string;
25
+ desc?: string;
26
+ idList: string;
27
+ pos: number;
28
+ [key: string]: unknown;
29
+ }
4
30
 
5
31
  export class TrelloApiRepository implements TrelloRepository {
6
32
  private readonly baseUrl = 'https://api.trello.com/1';
@@ -10,7 +36,10 @@ export class TrelloApiRepository implements TrelloRepository {
10
36
  private readonly token: string,
11
37
  ) {}
12
38
 
13
- private async request(endpoint: string, options?: RequestInit): Promise<any> {
39
+ private async request(
40
+ endpoint: string,
41
+ options?: RequestInit,
42
+ ): Promise<unknown> {
14
43
  const url = `${this.baseUrl}${endpoint}?key=${this.apiKey}&token=${this.token}`;
15
44
 
16
45
  const response = await fetch(url, options);
@@ -18,26 +47,142 @@ export class TrelloApiRepository implements TrelloRepository {
18
47
  if (!response.ok) {
19
48
  const errorText = await response.text();
20
49
  throw new Error(
21
- `Trello API error: ${response.status} ${response.statusText}\n${errorText}`,
50
+ `${t('api.trelloError')} ${response.status} ${response.statusText}\n${errorText}`,
22
51
  );
23
52
  }
24
53
 
25
54
  return response.json();
26
55
  }
27
56
 
57
+ private async requestBoards(
58
+ endpoint: string,
59
+ options?: RequestInit,
60
+ ): Promise<TrelloBoardResponse[]> {
61
+ const data = await this.request(endpoint, options);
62
+ return data as TrelloBoardResponse[];
63
+ }
64
+
65
+ private async requestLists(
66
+ endpoint: string,
67
+ options?: RequestInit,
68
+ ): Promise<TrelloListResponse[]> {
69
+ const data = await this.request(endpoint, options);
70
+ return data as TrelloListResponse[];
71
+ }
72
+
73
+ private async requestCards(
74
+ endpoint: string,
75
+ options?: RequestInit,
76
+ ): Promise<TrelloCardResponse[]> {
77
+ const data = await this.request(endpoint, options);
78
+ return data as TrelloCardResponse[];
79
+ }
80
+
81
+ private async requestList(
82
+ endpoint: string,
83
+ options?: RequestInit,
84
+ ): Promise<TrelloListResponse> {
85
+ const data = await this.request(endpoint, options);
86
+ return data as TrelloListResponse;
87
+ }
88
+
28
89
  async getBoards(): Promise<BoardEntity[]> {
29
- const data = await this.request('/members/me/boards');
30
- return data.map((board: any) => BoardEntity.fromApiResponse(board));
90
+ const data = await this.requestBoards('/members/me/boards');
91
+ return data.map((board: TrelloBoardResponse) =>
92
+ BoardEntity.fromApiResponse(board),
93
+ );
94
+ }
95
+
96
+ async createBoard(name: string, description?: string): Promise<BoardEntity> {
97
+ const body = new URLSearchParams({
98
+ name,
99
+ desc: description || '',
100
+ key: this.apiKey,
101
+ token: this.token,
102
+ });
103
+
104
+ const data = await this.request('/boards', {
105
+ method: 'POST',
106
+ headers: {
107
+ 'Content-Type': 'application/x-www-form-urlencoded',
108
+ },
109
+ body: body.toString(),
110
+ });
111
+
112
+ return BoardEntity.fromApiResponse(data as TrelloBoardResponse);
31
113
  }
32
114
 
33
115
  async getLists(boardId: string): Promise<ListEntity[]> {
34
- const data = await this.request(`/boards/${boardId}/lists`);
35
- return data.map((list: any) => ListEntity.fromApiResponse(list));
116
+ const data = await this.requestLists(`/boards/${boardId}/lists`);
117
+ return data.map((list: TrelloListResponse) =>
118
+ ListEntity.fromApiResponse(list),
119
+ );
120
+ }
121
+
122
+ async createList(boardId: string, name: string): Promise<ListEntity> {
123
+ const body = new URLSearchParams({
124
+ idBoard: boardId,
125
+ name,
126
+ key: this.apiKey,
127
+ token: this.token,
128
+ });
129
+
130
+ const data = await this.request('/lists', {
131
+ method: 'POST',
132
+ headers: {
133
+ 'Content-Type': 'application/x-www-form-urlencoded',
134
+ },
135
+ body: body.toString(),
136
+ });
137
+
138
+ return ListEntity.fromApiResponse(data as TrelloListResponse);
139
+ }
140
+
141
+ async deleteList(listId: string): Promise<void> {
142
+ // Primeiro, fechar (arquivar) a lista
143
+ const closeBody = new URLSearchParams({
144
+ closed: 'true',
145
+ key: this.apiKey,
146
+ token: this.token,
147
+ });
148
+
149
+ await this.request(`/lists/${listId}`, {
150
+ method: 'PUT',
151
+ headers: {
152
+ 'Content-Type': 'application/x-www-form-urlencoded',
153
+ },
154
+ body: closeBody.toString(),
155
+ });
156
+
157
+ // Depois, deletar a lista
158
+ await this.request(`/lists/${listId}`, {
159
+ method: 'DELETE',
160
+ });
161
+ }
162
+
163
+ async moveList(listId: string, position: number): Promise<ListEntity> {
164
+ const body = new URLSearchParams({
165
+ pos: position.toString(),
166
+ key: this.apiKey,
167
+ token: this.token,
168
+ });
169
+
170
+ const data = await this.requestList(`/lists/${listId}`, {
171
+ method: 'PUT',
172
+ headers: {
173
+ 'Content-Type': 'application/x-www-form-urlencoded',
174
+ },
175
+ body: body.toString(),
176
+ });
177
+
178
+ return ListEntity.fromApiResponse(data);
36
179
  }
37
180
 
38
181
  async getCards(listId: string): Promise<CardEntity[]> {
39
- const data = await this.request(`/lists/${listId}/cards`);
40
- return data.map((card: any) => CardEntity.fromApiResponse(card));
182
+ const data = await this.requestCards(`/lists/${listId}/cards`);
183
+ return data.map((card: TrelloCardResponse) =>
184
+ CardEntity.fromApiResponse(card),
185
+ );
41
186
  }
42
187
 
43
188
  async createCard(cardData: CreateCardData): Promise<CardEntity> {
@@ -57,7 +202,7 @@ export class TrelloApiRepository implements TrelloRepository {
57
202
  body: body.toString(),
58
203
  });
59
204
 
60
- return CardEntity.fromApiResponse(data);
205
+ return CardEntity.fromApiResponse(data as TrelloCardResponse);
61
206
  }
62
207
 
63
208
  async updateCard(
@@ -78,7 +223,7 @@ export class TrelloApiRepository implements TrelloRepository {
78
223
  body: body.toString(),
79
224
  });
80
225
 
81
- return CardEntity.fromApiResponse(data);
226
+ return CardEntity.fromApiResponse(data as TrelloCardResponse);
82
227
  }
83
228
 
84
229
  async deleteCard(cardId: string): Promise<void> {
@@ -1,3 +1,4 @@
1
+ import type { ConfigEntity } from '@domain/entities';
1
2
  import type { ConfigRepository } from '@domain/repositories';
2
3
  import { AuthenticateUserUseCase } from '@application/use-cases';
3
4
  import inquirer from 'inquirer';
@@ -32,7 +33,7 @@ export class AuthController {
32
33
  console.log(result.success ? t('auth.tokenSaved') : result.message);
33
34
  }
34
35
 
35
- async getConfig() {
36
+ async getConfig(): Promise<ConfigEntity> {
36
37
  return await this.authenticateUseCase.getConfig();
37
38
  }
38
39
  }
@@ -1,30 +1,83 @@
1
+ import type { BoardEntity, CardEntity, ListEntity } from '@domain/entities';
1
2
  import type { TrelloRepository } from '@domain/repositories';
2
-
3
- import { GetBoardsUseCase, GetCardsUseCase, GetListsUseCase } from '@application/use-cases';
3
+ import type { OutputFormatter } from '@/shared';
4
+
5
+ import {
6
+ CreateBoardUseCase,
7
+ CreateListUseCase,
8
+ GetBoardDetailsUseCase,
9
+ GetBoardsUseCase,
10
+ GetCardsUseCase,
11
+ GetListsUseCase,
12
+ } from '@application/use-cases';
4
13
  import { t } from '@/i18n';
5
14
 
6
15
  export class BoardController {
7
16
  private getBoardsUseCase: GetBoardsUseCase;
8
17
  private getListsUseCase: GetListsUseCase;
9
18
  private getCardsUseCase: GetCardsUseCase;
10
-
11
- constructor(trelloRepository: TrelloRepository) {
19
+ private getBoardDetailsUseCase: GetBoardDetailsUseCase;
20
+ private createBoardUseCase: CreateBoardUseCase;
21
+ private createListUseCase: CreateListUseCase;
22
+ private trelloRepository: TrelloRepository;
23
+ private outputFormatter: OutputFormatter;
24
+
25
+ constructor(
26
+ trelloRepository: TrelloRepository,
27
+ outputFormatter: OutputFormatter,
28
+ ) {
29
+ this.trelloRepository = trelloRepository;
30
+ this.outputFormatter = outputFormatter;
12
31
  this.getBoardsUseCase = new GetBoardsUseCase(trelloRepository);
13
32
  this.getListsUseCase = new GetListsUseCase(trelloRepository);
14
33
  this.getCardsUseCase = new GetCardsUseCase(trelloRepository);
34
+ this.getBoardDetailsUseCase = new GetBoardDetailsUseCase(trelloRepository);
35
+ this.createBoardUseCase = new CreateBoardUseCase(trelloRepository);
36
+ this.createListUseCase = new CreateListUseCase(trelloRepository);
15
37
  }
16
38
 
17
39
  async showBoards(): Promise<void> {
18
40
  const boards = await this.getBoardsUseCase.execute();
19
41
 
20
- console.log(t('board.yourBoards'));
21
- boards.forEach((board, index) => {
22
- console.log(`${index + 1}. ${board.name}`);
23
- console.log(` 🔗 ${board.url}`);
24
- console.log(` 🆔 ${board.id}\n`);
42
+ this.outputFormatter.output(boards, {
43
+ fields: ['name', 'id', 'url'],
44
+ headers: ['Name', 'ID', 'URL'],
25
45
  });
26
46
  }
27
47
 
48
+ async showBoardDetails(boardId: string): Promise<void> {
49
+ const details = await this.getBoardDetailsUseCase.execute(boardId);
50
+
51
+ this.outputFormatter.message(
52
+ t('board.boardName', { name: details.board.name }),
53
+ );
54
+ this.outputFormatter.message(
55
+ t('board.boardUrl', { url: details.board.url }),
56
+ );
57
+ this.outputFormatter.message(t('board.boardId', { id: details.board.id }));
58
+ this.outputFormatter.message(
59
+ t('board.boardStats', {
60
+ lists: details.totalLists,
61
+ cards: details.totalCards,
62
+ }),
63
+ );
64
+ this.outputFormatter.message('');
65
+
66
+ if (details.lists.length > 0) {
67
+ this.outputFormatter.message(t('board.listsTitle'));
68
+ this.outputFormatter.output(details.lists, {
69
+ fields: ['name', 'id'],
70
+ headers: [t('board.listsHeaders.name'), t('board.listsHeaders.id')],
71
+ });
72
+ } else {
73
+ this.outputFormatter.message(t('board.listsEmpty'));
74
+ }
75
+ }
76
+
77
+ async getBoards(): Promise<BoardEntity[]> {
78
+ return this.getBoardsUseCase.execute();
79
+ }
80
+
28
81
  async showLists(boardName: string): Promise<void> {
29
82
  const boards = await this.getBoardsUseCase.execute();
30
83
  const board = boards.find(b => b.name === boardName);
@@ -38,10 +91,18 @@ export class BoardController {
38
91
  console.log(t('list.boardLists', { boardName }));
39
92
  lists.forEach((list, index) => {
40
93
  console.log(`${index + 1}. ${list.name}`);
41
- console.log(` 🆔 ${list.id}\n`);
94
+ console.log(` ${t('list.listId', { id: list.id })}\n`);
42
95
  });
43
96
  }
44
97
 
98
+ async getLists(boardId: string): Promise<ListEntity[]> {
99
+ return this.getListsUseCase.execute(boardId);
100
+ }
101
+
102
+ async getCards(listId: string): Promise<CardEntity[]> {
103
+ return this.getCardsUseCase.execute(listId);
104
+ }
105
+
45
106
  async showCards(boardName: string, listName: string): Promise<void> {
46
107
  const boards = await this.getBoardsUseCase.execute();
47
108
  const board = boards.find(b => b.name === boardName);
@@ -67,19 +128,101 @@ export class BoardController {
67
128
 
68
129
  cards.forEach((card, index) => {
69
130
  console.log(`${index + 1}. ${card.name}`);
70
- console.log(` 🆔 ${card.id}\n`);
131
+ console.log(` ${t('card.cardId', { id: card.id })}\n`);
132
+ });
133
+ }
134
+
135
+ async showListsById(boardId: string): Promise<void> {
136
+ const lists = await this.getListsUseCase.execute(boardId);
137
+
138
+ this.outputFormatter.output(lists, {
139
+ fields: ['name', 'id'],
140
+ headers: ['Name', 'ID'],
71
141
  });
72
142
  }
73
143
 
74
- async getBoards() {
75
- return await this.getBoardsUseCase.execute();
144
+ async showCardsByListId(listId: string): Promise<void> {
145
+ const cards = await this.getCardsUseCase.execute(listId);
146
+
147
+ if (cards.length === 0) {
148
+ this.outputFormatter.message(t('list.cardsEmpty'));
149
+ return;
150
+ }
151
+
152
+ this.outputFormatter.output(cards, {
153
+ fields: ['name', 'id'],
154
+ headers: ['Name', 'ID'],
155
+ });
76
156
  }
77
157
 
78
- async getLists(boardId: string) {
79
- return await this.getListsUseCase.execute(boardId);
158
+ async createBoard(name: string, description?: string): Promise<void> {
159
+ const board = await this.createBoardUseCase.execute(name, description);
160
+
161
+ console.log(t('board.created', { name: board.name }));
162
+ console.log(`🔗 ${board.url}`);
163
+ console.log(`🆔 ${board.id}`);
80
164
  }
81
165
 
82
- async getCards(listId: string) {
83
- return await this.getCardsUseCase.execute(listId);
166
+ async createList(boardId: string, name: string): Promise<void> {
167
+ const list = await this.createListUseCase.execute(boardId, name);
168
+
169
+ console.log(t('list.created', { name: list.name }));
170
+ console.log(`🆔 ${list.id}`);
171
+ }
172
+
173
+ async deleteList(listId: string): Promise<void> {
174
+ // Primeiro precisamos encontrar a lista para mostrar informações
175
+ const boards = await this.getBoardsUseCase.execute();
176
+ let list: ListEntity | undefined;
177
+
178
+ for (const board of boards) {
179
+ const lists = await this.getListsUseCase.execute(board.id);
180
+ list = lists.find((l: ListEntity) => l.id === listId);
181
+
182
+ if (list) {
183
+ break;
184
+ }
185
+ }
186
+
187
+ if (!list) {
188
+ throw new Error(t('list.notFound', { listId }));
189
+ }
190
+
191
+ // Verificar se a lista está vazia antes de deletar
192
+ const cards = await this.getCardsUseCase.execute(listId);
193
+ if (cards.length > 0) {
194
+ throw new Error(t('list.notEmpty', { listName: list.name }));
195
+ }
196
+
197
+ // Deletar a lista usando o repositório
198
+ await this.trelloRepository.deleteList(listId);
199
+
200
+ console.log(t('list.deleted', { name: list.name }));
201
+ console.log(`🆔 ${list.id}`);
202
+ }
203
+
204
+ async moveList(listId: string, position: number): Promise<void> {
205
+ // Primeiro precisamos encontrar a lista para mostrar informações
206
+ const boards = await this.getBoardsUseCase.execute();
207
+ let list: ListEntity | undefined;
208
+
209
+ for (const board of boards) {
210
+ const lists = await this.getListsUseCase.execute(board.id);
211
+ list = lists.find((l: ListEntity) => l.id === listId);
212
+
213
+ if (list) {
214
+ break;
215
+ }
216
+ }
217
+
218
+ if (!list) {
219
+ throw new Error(t('list.notFound', { listId }));
220
+ }
221
+
222
+ // Mover a lista para a nova posição
223
+ await this.trelloRepository.moveList(listId, position);
224
+
225
+ console.log(t('list.moved', { name: list.name, position }));
226
+ console.log(`🆔 ${list.id}`);
84
227
  }
85
228
  }