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.
- package/README.md +27 -0
- package/bun.lock +225 -2
- package/dist/main.js +26490 -25401
- package/main.ts +6 -3
- package/package.json +15 -3
- package/src/application/use-cases/AuthenticateUserUseCase.ts +7 -6
- package/src/application/use-cases/CreateBoardUseCase.ts +19 -0
- package/src/application/use-cases/CreateCardUseCase.ts +2 -1
- package/src/application/use-cases/CreateListUseCase.ts +19 -0
- package/src/application/use-cases/GetBoardDetailsUseCase.ts +41 -0
- package/src/application/use-cases/UpdateCardUseCase.ts +2 -1
- package/src/application/use-cases/index.ts +3 -0
- package/src/domain/entities/Board.ts +10 -2
- package/src/domain/entities/Card.ts +12 -1
- package/src/domain/entities/Config.ts +3 -1
- package/src/domain/entities/List.ts +14 -2
- package/src/domain/repositories/TrelloRepository.ts +4 -0
- package/src/i18n/index.ts +62 -5
- package/src/i18n/locales/en.json +154 -17
- package/src/i18n/locales/pt-BR.json +154 -17
- package/src/infrastructure/repositories/FileConfigRepository.ts +6 -3
- package/src/infrastructure/repositories/TrelloApiRepository.ts +155 -10
- package/src/presentation/cli/AuthController.ts +2 -1
- package/src/presentation/cli/BoardController.ts +160 -17
- package/src/presentation/cli/CardController.ts +169 -45
- package/src/presentation/cli/CommandController.ts +293 -27
- package/src/presentation/cli/ConfigController.ts +4 -3
- package/src/presentation/cli/TrelloCliController.ts +10 -2
- package/src/shared/ErrorHandler.ts +233 -0
- package/src/shared/OutputFormatter.ts +210 -0
- 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
|
-
|
|
14
|
-
|
|
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.
|
|
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: '
|
|
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: '
|
|
35
|
+
message: t('auth.authenticated'),
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
return {
|
|
39
40
|
success: false,
|
|
40
|
-
message: '
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
16
|
+
constructor(
|
|
17
|
+
public readonly id: string,
|
|
18
|
+
public readonly name: string,
|
|
19
|
+
) {}
|
|
8
20
|
|
|
9
|
-
static fromApiResponse(data:
|
|
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
|
-
|
|
3
|
-
|
|
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,
|
|
99
|
+
export function t(key: string, options?: Record<string, unknown>): string {
|
|
43
100
|
return i18next.t(key, options);
|
|
44
101
|
}
|
|
45
102
|
|