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
package/main.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { config } from 'dotenv';
3
+ import { t } from './src/i18n';
3
4
  import { CommandController } from './src/presentation';
4
5
 
5
6
  // Load environment variables
@@ -9,9 +10,11 @@ async function main() {
9
10
  try {
10
11
  const commandController = new CommandController();
11
12
  await commandController.run();
12
- }
13
- catch (error) {
14
- console.error(' Erro fatal:', (error as Error).message);
13
+ } catch (error) {
14
+ console.error(
15
+ t('errors.general', { message: (error as Error).message }),
16
+ (error as Error).message,
17
+ );
15
18
  process.exit(1);
16
19
  }
17
20
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trello-cli-unofficial",
3
3
  "type": "module",
4
- "version": "0.7.6",
4
+ "version": "0.8.0",
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/)",
@@ -63,6 +63,7 @@
63
63
  "lint": "eslint .",
64
64
  "lint:fix": "eslint . --fix",
65
65
  "typecheck": "tsc --noEmit",
66
+ "commitlint": "commitlint --edit",
66
67
  "validate": "bun run lint && bun run typecheck && bun run test:coverage:threshold",
67
68
  "version:patch": "bun version patch && git push --follow-tags",
68
69
  "version:minor": "bun version minor && git push --follow-tags",
@@ -82,15 +83,26 @@
82
83
  },
83
84
  "devDependencies": {
84
85
  "@antfu/eslint-config": "^6.2.0",
86
+ "@commitlint/cli": "^20.1.0",
87
+ "@commitlint/config-conventional": "^20.0.0",
85
88
  "@types/bun": "latest",
86
89
  "@types/fs-extra": "^11.0.4",
87
90
  "@types/inquirer": "^9.0.9",
88
91
  "eslint": "^9.39.1",
89
- "eslint-plugin-format": "^1.0.2"
92
+ "eslint-plugin-format": "^1.0.2",
93
+ "lint-staged": "^16.2.6"
90
94
  },
91
95
  "os": [
92
96
  "darwin",
93
97
  "linux",
94
98
  "win32"
95
- ]
99
+ ],
100
+ "gitHooks": {
101
+ "commit-msg": "bun run commitlint"
102
+ },
103
+ "lint-staged": {
104
+ "*.{js,ts,tsx}": [
105
+ "eslint --max-warnings 0"
106
+ ]
107
+ }
96
108
  }
@@ -1,5 +1,7 @@
1
+ import type { ConfigEntity } from '@domain/entities';
1
2
  import type { ConfigRepository } from '@domain/repositories';
2
3
  import { AuthenticationService } from '@domain/services';
4
+ import { t } from '@/i18n';
3
5
 
4
6
  export class AuthenticateUserUseCase {
5
7
  private authService: AuthenticationService;
@@ -15,15 +17,14 @@ export class AuthenticateUserUseCase {
15
17
  if (!this.authService.validateToken(token)) {
16
18
  return {
17
19
  success: false,
18
- message:
19
- '❌ Token inválido. Deve começar com ATTA e ter pelo menos 10 caracteres.',
20
+ message: t('auth.tokenInvalid'),
20
21
  };
21
22
  }
22
23
 
23
24
  await this.authService.saveToken(token);
24
25
  return {
25
26
  success: true,
26
- message: '✅ Token configurado com sucesso!',
27
+ message: t('auth.tokenSaved'),
27
28
  };
28
29
  }
29
30
 
@@ -31,17 +32,17 @@ export class AuthenticateUserUseCase {
31
32
  if (isAuthenticated) {
32
33
  return {
33
34
  success: true,
34
- message: 'Usuário já autenticado.',
35
+ message: t('auth.authenticated'),
35
36
  };
36
37
  }
37
38
 
38
39
  return {
39
40
  success: false,
40
- message: '🔐 Você precisa configurar seu token do Trello primeiro.',
41
+ message: t('auth.notAuthenticated'),
41
42
  };
42
43
  }
43
44
 
44
- async getConfig() {
45
+ async getConfig(): Promise<ConfigEntity> {
45
46
  return await this.authService.getConfig();
46
47
  }
47
48
  }
@@ -0,0 +1,19 @@
1
+ import type { BoardEntity } from '@domain/entities';
2
+ import type { TrelloRepository } from '@domain/repositories';
3
+ import { t } from '@/i18n';
4
+
5
+ export class CreateBoardUseCase {
6
+ constructor(private trelloRepository: TrelloRepository) {}
7
+
8
+ async execute(name: string, description?: string): Promise<BoardEntity> {
9
+ if (!name || name.trim().length === 0) {
10
+ throw new Error(t('board.validation.nameRequired'));
11
+ }
12
+
13
+ if (name.length > 16384) {
14
+ throw new Error(t('board.validation.nameTooLong'));
15
+ }
16
+
17
+ return await this.trelloRepository.createBoard(name.trim(), description);
18
+ }
19
+ }
@@ -1,12 +1,13 @@
1
1
  import type { CardEntity, CreateCardData } from '@domain/entities';
2
2
  import type { TrelloRepository } from '@domain/repositories';
3
+ import { t } from '@/i18n';
3
4
 
4
5
  export class CreateCardUseCase {
5
6
  constructor(private trelloRepository: TrelloRepository) {}
6
7
 
7
8
  async execute(cardData: CreateCardData): Promise<CardEntity> {
8
9
  if (!cardData.name.trim()) {
9
- throw new Error('Nome do cartão é obrigatório');
10
+ throw new Error(t('card.validation.requiredCardName'));
10
11
  }
11
12
 
12
13
  return await this.trelloRepository.createCard(cardData);
@@ -0,0 +1,19 @@
1
+ import type { ListEntity } from '@domain/entities';
2
+ import type { TrelloRepository } from '@domain/repositories';
3
+ import { t } from '@/i18n';
4
+
5
+ export class CreateListUseCase {
6
+ constructor(private readonly trelloRepository: TrelloRepository) {}
7
+
8
+ async execute(boardId: string, name: string): Promise<ListEntity> {
9
+ if (!name || name.trim().length === 0) {
10
+ throw new Error(t('list.validation.nameCannotBeEmpty'));
11
+ }
12
+
13
+ if (name.length > 16384) {
14
+ throw new Error(t('list.validation.nameTooLong'));
15
+ }
16
+
17
+ return this.trelloRepository.createList(boardId, name.trim());
18
+ }
19
+ }
@@ -0,0 +1,41 @@
1
+ import type { BoardEntity, ListEntity } from '@domain/entities';
2
+ import type { TrelloRepository } from '@domain/repositories';
3
+ import { t } from '@/i18n';
4
+
5
+ export interface BoardDetails {
6
+ board: BoardEntity;
7
+ lists: ListEntity[];
8
+ totalLists: number;
9
+ totalCards: number;
10
+ }
11
+
12
+ export class GetBoardDetailsUseCase {
13
+ constructor(private trelloRepository: TrelloRepository) {}
14
+
15
+ async execute(boardId: string): Promise<BoardDetails> {
16
+ // Get board info
17
+ const boards = await this.trelloRepository.getBoards();
18
+ const board = boards.find(b => b.id === boardId);
19
+
20
+ if (!board) {
21
+ throw new Error(t('board.notFoundById', { id: boardId }));
22
+ }
23
+
24
+ // Get lists for this board
25
+ const lists = await this.trelloRepository.getLists(boardId);
26
+
27
+ // Get cards for each list and count total
28
+ let totalCards = 0;
29
+ for (const list of lists) {
30
+ const cards = await this.trelloRepository.getCards(list.id);
31
+ totalCards += cards.length;
32
+ }
33
+
34
+ return {
35
+ board,
36
+ lists,
37
+ totalLists: lists.length,
38
+ totalCards,
39
+ };
40
+ }
41
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CardEntity, UpdateCardData } from '@domain/entities';
2
2
  import type { TrelloRepository } from '@domain/repositories';
3
+ import { t } from '@/i18n';
3
4
 
4
5
  export class UpdateCardUseCase {
5
6
  constructor(private trelloRepository: TrelloRepository) {}
@@ -9,7 +10,7 @@ export class UpdateCardUseCase {
9
10
  updateData: UpdateCardData,
10
11
  ): Promise<CardEntity> {
11
12
  if (updateData.name !== undefined && !updateData.name.trim()) {
12
- throw new Error('Nome do cartão não pode estar vazio');
13
+ throw new Error(t('card.validation.cardNameCannotBeEmpty'));
13
14
  }
14
15
 
15
16
  return await this.trelloRepository.updateCard(cardId, updateData);
@@ -1,6 +1,9 @@
1
1
  export * from './AuthenticateUserUseCase';
2
+ export * from './CreateBoardUseCase';
2
3
  export * from './CreateCardUseCase';
4
+ export * from './CreateListUseCase';
3
5
  export * from './DeleteCardUseCase';
6
+ export * from './GetBoardDetailsUseCase';
4
7
  export * from './GetBoardsUseCase';
5
8
  export * from './GetCardsUseCase';
6
9
  export * from './GetListsUseCase';
@@ -4,14 +4,22 @@ export interface Board {
4
4
  url: string;
5
5
  }
6
6
 
7
- export class BoardEntity implements Board {
7
+ // API Response types
8
+ interface TrelloBoardResponse {
9
+ id: string;
10
+ name: string;
11
+ url: string;
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ export class BoardEntity {
8
16
  constructor(
9
17
  public readonly id: string,
10
18
  public readonly name: string,
11
19
  public readonly url: string,
12
20
  ) {}
13
21
 
14
- static fromApiResponse(data: any): BoardEntity {
22
+ static fromApiResponse(data: TrelloBoardResponse): BoardEntity {
15
23
  return new BoardEntity(data.id, data.name, data.url);
16
24
  }
17
25
  }
@@ -18,6 +18,17 @@ export interface UpdateCardData {
18
18
  idList?: string;
19
19
  }
20
20
 
21
+ // API Response types
22
+ interface TrelloCardResponse {
23
+ id: string;
24
+ name: string;
25
+ desc?: string;
26
+ idList: string;
27
+ pos: number;
28
+ url?: string;
29
+ [key: string]: unknown;
30
+ }
31
+
21
32
  export class CardEntity implements Card {
22
33
  constructor(
23
34
  public readonly id: string,
@@ -27,7 +38,7 @@ export class CardEntity implements Card {
27
38
  public readonly url?: string,
28
39
  ) {}
29
40
 
30
- static fromApiResponse(data: any): CardEntity {
41
+ static fromApiResponse(data: TrelloCardResponse): CardEntity {
31
42
  return new CardEntity(data.id, data.name, data.idList, data.desc, data.url);
32
43
  }
33
44
 
@@ -7,7 +7,9 @@ export class ConfigEntity implements Config {
7
7
  constructor(public readonly apiKey: string, public readonly token?: string) {}
8
8
 
9
9
  static createDefault(): ConfigEntity {
10
- return new ConfigEntity('630a01228b85df706aa520f3611e6490');
10
+ return new ConfigEntity(
11
+ process.env.TRELLO_API_KEY || '630a01228b85df706aa520f3611e6490',
12
+ );
11
13
  }
12
14
 
13
15
  withToken(token: string): ConfigEntity {
@@ -3,10 +3,22 @@ export interface List {
3
3
  name: string;
4
4
  }
5
5
 
6
+ // API Response types
7
+ interface TrelloListResponse {
8
+ id: string;
9
+ name: string;
10
+ idBoard: string;
11
+ pos: number;
12
+ [key: string]: unknown;
13
+ }
14
+
6
15
  export class ListEntity implements List {
7
- constructor(public readonly id: string, public readonly name: string) {}
16
+ constructor(
17
+ public readonly id: string,
18
+ public readonly name: string,
19
+ ) {}
8
20
 
9
- static fromApiResponse(data: any): ListEntity {
21
+ static fromApiResponse(data: TrelloListResponse): ListEntity {
10
22
  return new ListEntity(data.id, data.name);
11
23
  }
12
24
  }
@@ -8,7 +8,11 @@ import type {
8
8
 
9
9
  export interface TrelloRepository {
10
10
  getBoards: () => Promise<BoardEntity[]>;
11
+ createBoard: (name: string, description?: string) => Promise<BoardEntity>;
11
12
  getLists: (boardId: string) => Promise<ListEntity[]>;
13
+ createList: (boardId: string, name: string) => Promise<ListEntity>;
14
+ deleteList: (listId: string) => Promise<void>;
15
+ moveList: (listId: string, position: number) => Promise<ListEntity>;
12
16
  getCards: (listId: string) => Promise<CardEntity[]>;
13
17
  createCard: (data: CreateCardData) => Promise<CardEntity>;
14
18
  updateCard: (cardId: string, data: UpdateCardData) => Promise<CardEntity>;
package/src/i18n/index.ts CHANGED
@@ -1,6 +1,23 @@
1
+ /* eslint-disable ts/no-require-imports */
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
1
4
  import i18next from 'i18next';
2
- import en from './locales/en.json';
3
- import ptBR from './locales/pt-BR.json';
5
+
6
+ // Get the directory name for ES modules
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ // Import translations statically - this will only execute at runtime, not during lint
11
+ // The linting process doesn't actually execute this code, so no warning will be shown
12
+ const enTranslations
13
+ = process.env.NODE_ENV !== 'lint'
14
+ ? require('./locales/en.json')
15
+ : { common: { yes: 'Yes', no: 'No' } };
16
+ const ptBRTranslations
17
+ = process.env.NODE_ENV !== 'lint'
18
+ ? require('./locales/pt-BR.json')
19
+ : { common: { yes: 'Sim', no: 'Não' } };
20
+ /* eslint-enable ts/no-require-imports */
4
21
 
5
22
  /**
6
23
  * Detecta o idioma do sistema
@@ -9,6 +26,11 @@ import ptBR from './locales/pt-BR.json';
9
26
  function detectLanguage(): string {
10
27
  const lang = process.env.LANG || process.env.LANGUAGE || 'en_US';
11
28
 
29
+ // Durante testes, sempre usar português para manter consistência
30
+ if (process.env.NODE_ENV === 'test') {
31
+ return 'pt-BR';
32
+ }
33
+
12
34
  // Mapeia common locales para nossos idiomas suportados
13
35
  if (lang.startsWith('pt')) {
14
36
  return 'pt-BR';
@@ -17,16 +39,51 @@ function detectLanguage(): string {
17
39
  return 'en';
18
40
  }
19
41
 
42
+ // Load translations from JSON files (lazy loaded)
43
+ let cachedTranslations: {
44
+ en: Record<string, unknown>;
45
+ ptBR: Record<string, unknown>;
46
+ } | null = null;
47
+
48
+ function loadTranslations() {
49
+ if (cachedTranslations) {
50
+ return cachedTranslations;
51
+ }
52
+
53
+ // Skip file system access during static analysis/linting
54
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'lint') {
55
+ cachedTranslations = {
56
+ en: { common: { yes: 'Yes', no: 'No' } },
57
+ ptBR: { common: { yes: 'Sim', no: 'Não' } },
58
+ };
59
+ return cachedTranslations;
60
+ }
61
+
62
+ try {
63
+ cachedTranslations = { en: enTranslations, ptBR: ptBRTranslations };
64
+ return cachedTranslations;
65
+ } catch (error) {
66
+ console.error(t('errors.translationLoadError'), error);
67
+ cachedTranslations = {
68
+ en: { common: { yes: 'Yes', no: 'No' } },
69
+ ptBR: { common: { yes: 'Sim', no: 'Não' } },
70
+ };
71
+ return cachedTranslations;
72
+ }
73
+ }
74
+
20
75
  // Inicializa i18next
76
+ const translations = loadTranslations();
77
+
21
78
  i18next.init({
22
79
  lng: detectLanguage(),
23
80
  fallbackLng: 'en',
24
81
  resources: {
25
82
  'pt-BR': {
26
- translation: ptBR,
83
+ translation: translations.ptBR,
27
84
  },
28
85
  'en': {
29
- translation: en,
86
+ translation: translations.en,
30
87
  },
31
88
  },
32
89
  interpolation: {
@@ -39,7 +96,7 @@ i18next.init({
39
96
  * @param key - Chave da tradução (ex: 'auth.notAuthenticated')
40
97
  * @param options - Opções de interpolação (ex: { name: 'João' })
41
98
  */
42
- export function t(key: string, options?: Record<string, any>): string {
99
+ export function t(key: string, options?: Record<string, unknown>): string {
43
100
  return i18next.t(key, options);
44
101
  }
45
102