diff --git a/.changeset/swift-bugs-appear.md b/.changeset/swift-bugs-appear.md new file mode 100644 index 0000000..b4d8674 --- /dev/null +++ b/.changeset/swift-bugs-appear.md @@ -0,0 +1,6 @@ +--- +"@dandori/cli": patch +"@dandori/ui": patch +--- + +Add trello diff --git a/README.md b/README.md index 04622b5..2d11c3f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,21 @@ Today's My Tasks notion output example +## Example 3 + +### Input + +```text +Today's My Tasks +* [todo] Send Email to John +* [doing] Write a blog +* [done] Report to Boss +``` + +### Output(Trello) + +notion output example + ## Usage This project is monorepo. You can choose the following ways to use it. diff --git a/media/trello_example.png b/media/trello_example.png new file mode 100644 index 0000000..dc50d7b Binary files /dev/null and b/media/trello_example.png differ diff --git a/packages/cli/README.md b/packages/cli/README.md index ce9c6cd..86393bc 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -114,4 +114,31 @@ Options: ```bash pnpm --package=@dandori/cli dlx dandori-miro your_tasks.txt -d your_database_id -o status --status 'Status' --status-todo 'ToDo' --status-doing 'Doing' --status-done 'Done 🙌' +``` + +### dandori-trello + +This command is to execute `generateDandoriTrelloCards` of `@dandori/ui`. + +```bash +% pnpm --package=@dandori/cli dlx dandori-trello -h + +Usage: @dandori/cli [options] + +Options: + -V, --version output the version number + -e, --env-file env file path + -m, --model Chat GPT model which supports function_calling + -o, --optional-task-props optional output task props which delimiter is a comma + -b, --board-id trello board id + --status-todo trello list status todo name + --status-doing trello list status doing name + --status-done trello list status done name + -h, --help display help for command +``` + +#### Example of the command + +```bash +pnpm --package=@dandori/cli dlx dandori-trello your_tasks.txt -d your_board_id -o status --status-todo 'Todo' --status-doing 'Doing' --status-done 'Done' ``` \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index a4b9ab2..68462b4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,13 +34,15 @@ "build": "tsup --config ../../tsup.config.ts", "test": "vitest run", "dev:core": "tsx src/core/cli.ts", - "dev:miro": "tsx src/miro/cli.ts ./tmp/tmp.txt", - "dev:notion": "tsx src/notion/cli.ts" + "dev:miro": "tsx src/miro/cli.ts", + "dev:notion": "tsx src/notion/cli.ts", + "dev:trello": "tsx src/trello/cli.ts -h" }, "bin": { "dandori-core": "./dist/core/cli.js", "dandori-miro": "./dist/miro/cli.js", - "dandori-notion": "./dist/notion/cli.js" + "dandori-notion": "./dist/notion/cli.js", + "dandori-trello": "./dist/trello/cli.js" }, "keywords": [], "author": "Hiroki Miyaji", diff --git a/packages/cli/src/notion/__tests__/index.test.ts b/packages/cli/src/notion/__tests__/index.test.ts index 59d02b8..40d23a0 100644 --- a/packages/cli/src/notion/__tests__/index.test.ts +++ b/packages/cli/src/notion/__tests__/index.test.ts @@ -11,7 +11,7 @@ import { } from "vitest"; import { DandoriTask } from "@dandori/core"; import { generateDandoriNotionPages } from "@dandori/ui"; -import DandoriMiroCli from "../index"; +import DandoriNotionCli from "../index"; import { rm, writeFile } from "fs/promises"; const tasks: DandoriTask[] = [ @@ -60,7 +60,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["-d", databaseId]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with database id", () => { @@ -75,7 +75,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--name", name]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.name", () => { @@ -92,7 +92,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--deadline", deadline]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.deadline", () => { @@ -109,7 +109,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status", status]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status", () => { @@ -126,7 +126,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status-todo", statusTodo]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status.todo", () => { @@ -143,7 +143,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status-doing", statusDoing]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status.doing", () => { @@ -160,7 +160,7 @@ describe("DandoriNotionCli", () => { beforeEach(async () => { loadProcessArgv(["--status-done", statusDone]); - await new DandoriMiroCli().run(); + await new DandoriNotionCli().run(); }); it("call generateDandoriNotionPages with databasePropertiesMap.status.done", () => { diff --git a/packages/cli/src/trello/__tests__/index.test.ts b/packages/cli/src/trello/__tests__/index.test.ts new file mode 100644 index 0000000..64af7b9 --- /dev/null +++ b/packages/cli/src/trello/__tests__/index.test.ts @@ -0,0 +1,123 @@ +import { + describe, + beforeEach, + afterEach, + vi, + expect, + it, + beforeAll, + afterAll, + Mock, +} from "vitest"; +import { DandoriTask } from "@dandori/core"; +import { generateDandoriTrelloCards } from "@dandori/ui"; +import DandoriTrelloCli from "../index"; +import { rm, writeFile } from "fs/promises"; + +const tasks: DandoriTask[] = [ + { + id: "1", + name: "task1", + deadline: "2021-01-01", + description: "task1-description", + fromTaskIdList: [], + status: "todo", + }, +]; + +vi.mock("@dandori/core", () => ({ + default: vi.fn(() => tasks), +})); + +vi.mock("@dandori/ui", () => ({ + generateDandoriTrelloCards: vi.fn(), +})); + +const mockGenerateDandoriTrelloCards = generateDandoriTrelloCards as Mock; + +describe("DandoriTrelloCli", () => { + const inputFileName = "DandoriTrelloCli.txt"; + const inputFileText = "DandoriTrelloCli"; + const loadProcessArgv = (options: string[]) => { + process.argv = ["node", "cli.js", inputFileName, ...options]; + }; + + beforeAll(async () => { + await writeFile(inputFileName, inputFileText); + }); + + afterAll(async () => { + await rm(inputFileName); + }); + + afterEach(() => { + process.argv = []; + vi.clearAllMocks(); + }); + + describe("with -b option", () => { + const boardId = "boardId"; + + beforeEach(async () => { + loadProcessArgv(["-b", boardId]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with board id", () => { + expect(mockGenerateDandoriTrelloCards.mock.lastCall[1]).toMatchObject({ + boardId, + }); + }); + }); + + describe("with --status-todo option", () => { + const statusTodo = "ToDo"; + + beforeEach(async () => { + loadProcessArgv(["--status-todo", statusTodo]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with trelloListPropertiesMap.status.todo", () => { + expect( + mockGenerateDandoriTrelloCards.mock.lastCall[1].trelloListPropertiesMap, + ).toMatchObject({ + "status.todo": statusTodo, + }); + }); + }); + + describe("with --status-doing option", () => { + const statusDoing = "Doing"; + + beforeEach(async () => { + loadProcessArgv(["--status-doing", statusDoing]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with trelloListPropertiesMap.status.doing", () => { + expect( + mockGenerateDandoriTrelloCards.mock.lastCall[1].trelloListPropertiesMap, + ).toMatchObject({ + "status.doing": statusDoing, + }); + }); + }); + + describe("with --status-done option", () => { + const statusDone = "Done"; + + beforeEach(async () => { + loadProcessArgv(["--status-done", statusDone]); + await new DandoriTrelloCli().run(); + }); + + it("call generateDandoriTrelloCards with trelloListPropertiesMap.status.done", () => { + expect( + mockGenerateDandoriTrelloCards.mock.lastCall[1].trelloListPropertiesMap, + ).toMatchObject({ + "status.done": statusDone, + }); + }); + }); +}); diff --git a/packages/cli/src/trello/cli.ts b/packages/cli/src/trello/cli.ts new file mode 100644 index 0000000..e672ca6 --- /dev/null +++ b/packages/cli/src/trello/cli.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +import DandoriTrelloCli from "./index"; + +const cli = new DandoriTrelloCli(); +void cli.run(); diff --git a/packages/cli/src/trello/index.ts b/packages/cli/src/trello/index.ts new file mode 100644 index 0000000..f9c8b6c --- /dev/null +++ b/packages/cli/src/trello/index.ts @@ -0,0 +1,26 @@ +import { generateDandoriTrelloCards } from "@dandori/ui"; +import DandoriCoreCli from "../core"; + +export default class DandoriTrelloCli extends DandoriCoreCli { + override async run(): Promise { + const tasks = await this.generateDandoriTasks(); + const opts = this.program.opts(); + await generateDandoriTrelloCards(tasks, { + boardId: opts.boardId, + trelloListPropertiesMap: { + "status.todo": opts.statusTodo, + "status.doing": opts.statusDoing, + "status.done": opts.statusDone, + }, + }); + } + + protected override buildCommand() { + return super + .buildCommand() + .option("-b, --board-id ", "trello board id") + .option("--status-todo ", "trello list status todo name") + .option("--status-doing ", "trello list status doing name") + .option("--status-done ", "trello list status done name"); + } +} diff --git a/packages/ui/README.md b/packages/ui/README.md index a45dc66..0a4e834 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -39,6 +39,7 @@ await generateDandoriMiroCards(tasks, { * [Miro](https://miro.com/) * [Notion](https://www.notion.so/) +* [Trello](https://trello.com/) ## API @@ -116,7 +117,7 @@ The tasks which are generated by `generateDandoriTasks` of `@dandori/core`. interface GenerateDandoriNotionPagesOptions { databaseId: string; databasePropertiesMap?: DatabasePropertiesMap; - apuKey?: string; + apiKey?: string; } ``` @@ -181,4 +182,94 @@ For more details about database properties, please see [Notion API](https://deve * apiKey -The api key of miro. You can also set `NOTION_API_KEY` environment variable instead of this option. \ No newline at end of file +The api key of notion. You can also set `NOTION_API_KEY` environment variable instead of this option. + +### generateDandoriTrelloCards + +```ts +async function generateDandoriTrelloCards( + tasks: DandoriTask[], + options?: GenerateDandoriTrelloCardsOptions, +): Promise {} +``` + +`generateDandoriTrelloCards` creates trello cards from `generateDandoriTasks` result. + +trello output example + +#### Parameters + +##### tasks + +The tasks which are generated by `generateDandoriTasks` of `@dandori/core`. + +##### options + +```ts +interface GenerateDandoriTrelloCardsOptions { + boardId: string; + trelloListPropertiesMap?: TrelloListPropertiesMap; + apiKey?: string; + apiToken?: string; +} +``` + +* boardId + +The existing board id of trello. + +You can get the board id from your trello url. + +For example, if the url is `https://trello.com/b/ABCDE/boardTitle`, the board id is `ABCDE`. + +* trelloListPropertiesMap + +The map which key is defined by dandori and value is your trello list properties. + +You can set the key like belows. + +```ts +const trelloListPropertiesMap = { + "status.todo": "", + "status.doing": "", + "status.done": "", +}; +``` + +```ts +import generateDandoriTasks from '@dandori/core'; +import { generateDandoriTrelloCards } from "@dandori/ui"; + +const text = ` +Today's My Tasks +* Send Email to John +* Send Email to Mary +* Report to Boss after sending emails +`; + +const trelloListPropertiesMap = { + "status.todo": "Todo", + "status.doing": "Doing", + "status.done": "Done", +}; + +const tasks = await generateDandoriTasks(text); +await generateDandoriNotionPages(tasks, { + boardId: 'ABCDE', + trelloListPropertiesMap, +}); +``` + +This is an example. In this case, the output is like belows. + +notion output example + +For more details about database properties, please see [Notion API](https://developers.notion.com/reference/page#page-property-value). + +* apiKey + +The api key of trello. You can also set `TRELLO_API_KEY` environment variable instead of this option. + +* apiToken + +The api token of trello which you can get through OAuth. You can also set `TRELLO_API_TOKEN` environment variable instead of this option. \ No newline at end of file diff --git a/packages/ui/src/trello/client.ts b/packages/ui/src/trello/client.ts index 84f07e1..ff2454c 100644 --- a/packages/ui/src/trello/client.ts +++ b/packages/ui/src/trello/client.ts @@ -22,18 +22,20 @@ export class TrelloClient { this.apiToken = apiToken; } - getLists(boardId: string): Promise { - return fetch( + async getLists(boardId: string): Promise { + const res = await fetch( `https://api.trello.com/1/boards/${boardId}/lists?key=${this.apiKey}&token=${this.apiToken}`, - ).then((res) => res.json()); + ); + return res.json(); } - createCard(param: { listId: string; name: string }): Promise { - return fetch( + async createCard(param: { listId: string; name: string }): Promise { + const res = await fetch( `https://api.trello.com/1/cards?key=${this.apiKey}&token=${this.apiToken}&idList=${param.listId}&name=${param.name}`, { method: "POST", }, - ).then((res) => res.json()); + ); + return res.json(); } }