AG
z;8EZT8imMg*%&M9m}R?0&3&Mb@Wa)4T#pC2*fC4XE&A3YP8bnW)MhELZ0AOEFx-{nxv$|)hB3v$`z2i3b
z??ck~%ujFP*sTj2Rhn&+?T4OmLHK-LaAGEL*zux|FI!x!PhB-Imh*8XT>XFToqIgg
z+xo|)v46=aZK7Q+sTj;5#Yl7tr5(dC#*DjcnZb-921P`4JBTob3N>m5GZmU#Vnpn6
zm%RtcWj48Pmt1pei?im|>-^5^_wVn&!(Zd|eJ$TvYdz0e>v`Uv^~{uwvB*5?__2{Z
z5)Xt(<87jX(6|#GAfUVh8CO&~9|$6vC41LSQ&PYc{qZ`^#m;$vcFV{}?_JPTF0E0%
zG8mW|mSglavpGQiVajaude?jI3)$tKtC1CxmBGu4x2B$JHz=3gCH>-hbRub(Mu9TN
z+T650_myV2hXexg?2l_eY7t;)E4lda_M^-U-3Qj)SQD#M!j`2kix>OedV+Z1NV1Y%
zB8j^f=UkLJ)|Pl^aVhE2j=Tm|i|YL&2Z?+41DYWcQc@9an{0LX9YF=#{UoTI#qwA2
zDl+x?hlHkbE>pO^dmt21Ty+GO7&i>y;8
zC{T5NLV<2=K-)qL*2xNsEnQ=>J^n^q
z<8txn3(;_|v1diz-h`DTZ_QI)E44YDA<9oKy^lIRR{VNY%OK;9!BPc|-ulqMGi}Gg
zLj3-$aAf^m&oO0r{_T36gYmutDd6a1uu*p{#DJlZ99eFKsxZu`X)jR(KqLRRWix64
zWJ@Q30XrC-k;8kD6fy>uV&pf1m+5ZWpR){ls5VUq(3-|QUlg@hbBZ70sYhv=nC1Ccw-oN{suf)7e
z>P{WL+dlxU%ht{5y<8BWp`d5J1oqVA(b8dhL9to=c~dpwN#CmgKU3pC;4bqc^h!-;
ziA+z@u|J_oS&wY8z@#uebRXzsUaMDI@$S*y8l61r#^z`WK^H$|FuQ8o4@t8Iea`#y
zL!0WAKZE0xQiA$0SA%;{)rknaywKn?pcu8BD*DEDX;Blw@2g>U8D4D
zr;b5jI`lbwucN(-7pJQO;+;Hk*E*&|#`IfLyMd|H+L)o;_BMaOU*a@6RB~-&>^9rW
z9&{)*n1i9PMkYQZ_nGKjB0Z&r+4E&NmiZFWthZnJ8ztu8QZT|M0K@T+sLf})WnKQ@~v{-S6Aj8TZeWn!9Zhc3S@vp5z)k(y~;tLWcY=)1rF
zw}#WABD+9Ytq?x@ew?vBWy-*s$AYU_PW*s9t1!PKQw$)}`JnsK7>SPy@H=RqBkk~f
z(kujv4J*}QYGp>es~~^;Gi2YgWr2$5kzEA};meOt&^-v$l19Ch{_G&m>Y?gS`eL9j
z6f9KqGdTfbS-_d?of_5iIW*QWX%@E`u~rlsKONrlf{W)B?ugLoO#1%C?`U_7Jeg9Z
zQWM-PgFz{?8`^CI2-}K9^*2tAef@i1Ac5;Ztrc4M6S}jcxlvm(?r#H7jtshzS1U}~v
zW~rH@G{4~-8afFFuj<{gaw0!upSzRs@X6HJ(2l3DfW%$-2
zs$&^T56QR@UOEx2z=UTYdMz
zJ||PkfNG4ViK1bq&)QmEWX-J}kaoEh%y1MA(Jp6uu%?}r9}~atC7DjmYUvCb>+=u3
z<}OSfQik5Sfqo;-=tm%D6@W*L&as+blc*mwK*qiJm3ie#$j<(WvAe7FKYkLIc-n`!
zwwjlVW9|{X30DL4h(AvevM5fiHd80FhrwV^Zn!lqH%zA%4*PIK_6!sOr>?FC+}~(+
z;=Xw0{9sf8?z|GcbN|2F06ZNe6wWlEMa29zx;3qO9ytiz3UXi??BZFC2~u5-j5y@%Az9LdqI4g;-z_mC#)JR5qm`jR4IdiONh=-A+r%7dwhcgmiKN7-rPbX7L6mPX-Qj9z_spobj-s&Qmz=
z8b_)Ql&cmt;?bk|?fuvyBL
zPeM(7+DULspxgxwP^0Symy$~l4DKGj6AoRx_@+#o9`GiHt}^9Q50$4
zSkdqaVlwP;_ZK2*ncAQoEq6EzC5O49wG2(3m?`QHU2#IY97wczObm{wz?5reAZ4cy
zyQvnxt~rz^7OBBqW1IA1U<0qeycp5ZGPGh!67X^kZ3_zwwKm>Y4M|OqulrS8wK@n4
znXH)~8ZFE?*w5~a0?uvYVr7Qh$C8DIIMgwYIO6RWoJ!VDZK6||gWzXzX>R9sh
zu0R;?6n1IUHa~|s?MLfs4yLQ;$r%||GL(D7RNv@Ue|zA$$43$f$Eink^DoH8|GQMp
zEb3Q7VMj|yCL#ICuL5b<$pcs9&utdLYnqXnF$}0Z_CgwlvHZH)5#hyADwvUnB+>sVVsSYA@`rJ;L9?yj
z5TvNSt`>x+1#$h#SvO!uW)qP{(Kmtc09U!t(%$xTuRlyi&f$gcn|LQ+QFxI>nGFM1
z07BElRcvDzzoS9$KE-Mtd0nF*TYkE~k*RM0L#DB+D$y{Q;ZZC>9&_b-esgP)z7dmE
z5@RM1g@QTXh=_HU|1|QTXu(~@uDPlkf{jj|sMBk&GAfGji7qtO*WA_IeEk`ZFqB0)
z6*+xEXz&{#gZ(OIc$1i`F7f~e1pgac813-bh)g$WfabO)Mi^RYcf=v&oyKzZa++H^
z^=(88K?+0C7`lcDNJAOM7r0Ys&0X^hI063>-L?0`dyGddz|LR|S47??QPAD&QCGaF
z&~)bho?c;IHNW|$v~gVMfPcFO5z3ZWM{_Kar9{3!_^il%e*c@=D{x>N^m+Du
z7bf!I;=PIRBT5#~^h [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.
+
+
+
+#### 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.
+
+
+
+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/global.d.ts b/packages/ui/global.d.ts
index c5c4d67..0b90496 100644
--- a/packages/ui/global.d.ts
+++ b/packages/ui/global.d.ts
@@ -4,6 +4,8 @@ declare module "process" {
interface ProcessEnv {
MIRO_API_KEY: string;
NOTION_API_KEY: string;
+ TRELLO_API_KEY: string;
+ TRELLO_API_TOKEN: string;
}
}
}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 45fdb4d..abf25d9 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -35,6 +35,7 @@
"init": "tsx init.ts",
"dev:miro": "tsx scripts/generateDandoriMiroCards.ts",
"dev:notion": "tsx scripts/generateDandoriNotionPages.ts",
+ "dev:trello": "tsx scripts/generateDandoriTrelloCards.ts",
"test": "vitest run"
},
"keywords": [],
diff --git a/packages/ui/src/__tests__/trello.test.ts b/packages/ui/src/__tests__/trello.test.ts
new file mode 100644
index 0000000..5d7edb9
--- /dev/null
+++ b/packages/ui/src/__tests__/trello.test.ts
@@ -0,0 +1,136 @@
+import { describe, beforeEach, afterEach, vi, expect, it, Mock } from "vitest";
+import { DandoriTask } from "@dandori/core";
+import { generateDandoriTrelloCards } from "../index";
+import { runPromisesSequentially } from "@dandori/libs";
+import { TrelloClient } from "../trello/client";
+
+vi.mock("../trello/client", () => {
+ const TrelloClient = vi.fn();
+ TrelloClient.prototype = {
+ getLists: vi.fn(() => [
+ {
+ id: "1",
+ name: "TODO",
+ },
+ {
+ id: "2",
+ name: "DOING",
+ },
+ {
+ id: "3",
+ name: "done",
+ },
+ ]),
+ createCard: vi.fn(),
+ };
+ return { TrelloClient };
+});
+
+vi.mock("@dandori/libs", () => {
+ return {
+ runPromisesSequentially: vi.fn((runPromises, _runningLogPrefix) =>
+ Promise.all(runPromises.map((runPromise: () => any) => runPromise())),
+ ),
+ checkApiKey: vi.fn(),
+ };
+});
+
+const mockRunPromisesSequentially = runPromisesSequentially as Mock;
+
+describe("generateDandoriTrelloCards", () => {
+ let client: TrelloClient;
+
+ const todoWithStatus = "TODO";
+ const noStatus = "DOING";
+ const doneWithStatusButInvalidTrelloListName = "DONE";
+
+ const tasks: DandoriTask[] = [
+ {
+ id: "1",
+ name: todoWithStatus,
+ status: "todo",
+ fromTaskIdList: [],
+ },
+ {
+ id: "2",
+ name: noStatus,
+ fromTaskIdList: ["1"],
+ },
+ {
+ id: "3",
+ name: doneWithStatusButInvalidTrelloListName,
+ status: "done",
+ fromTaskIdList: ["2"],
+ },
+ ];
+ const boardId = "boardId";
+
+ const findPagePropertiesMockParam = (taskName: string) => {
+ const params = (client.createCard as Mock).mock.calls.flat();
+ return params.find(({ name }) => name === taskName);
+ };
+
+ beforeEach(() => {
+ client = new TrelloClient("key", "token");
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe("with status.todo mapping", () => {
+ beforeEach(async () => {
+ await generateDandoriTrelloCards(tasks, {
+ boardId,
+ trelloListPropertiesMap: {
+ "status.todo": todoWithStatus,
+ },
+ });
+ });
+
+ it("runPromisesSequentially called with creating pages log", () => {
+ expect(mockRunPromisesSequentially.mock.calls[0][1]).toBe(
+ "Creating Trello Cards",
+ );
+ });
+
+ it("called valid arguments", () => {
+ expect(findPagePropertiesMockParam(todoWithStatus)).toMatchObject({
+ name: todoWithStatus,
+ listId: "1",
+ });
+ });
+ });
+
+ describe("with status.doing mapping", () => {
+ beforeEach(async () => {
+ await generateDandoriTrelloCards(tasks, {
+ boardId,
+ trelloListPropertiesMap: {
+ "status.doing": noStatus,
+ },
+ });
+ });
+
+ it("no called valid arguments", () => {
+ expect(findPagePropertiesMockParam(noStatus)).toBeUndefined();
+ });
+ });
+
+ describe("with status.done mapping", () => {
+ beforeEach(async () => {
+ await generateDandoriTrelloCards(tasks, {
+ boardId,
+ trelloListPropertiesMap: {
+ "status.done": doneWithStatusButInvalidTrelloListName,
+ },
+ });
+ });
+
+ it("no called valid arguments", () => {
+ expect(
+ findPagePropertiesMockParam(doneWithStatusButInvalidTrelloListName),
+ ).toBeUndefined();
+ });
+ });
+});
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 97c12c1..9819ae0 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -1,2 +1,3 @@
export * from "./miro";
export * from "./notion";
+export * from "./trello";
diff --git a/packages/ui/src/trello/client.ts b/packages/ui/src/trello/client.ts
new file mode 100644
index 0000000..ff2454c
--- /dev/null
+++ b/packages/ui/src/trello/client.ts
@@ -0,0 +1,41 @@
+type ListResponse = {
+ id: string;
+ name: string;
+ closed: boolean;
+ pos: number;
+ softLimit: string;
+ idBoard: string;
+ subscribed: boolean;
+ limits: {
+ attachments: {
+ perBoard: any;
+ };
+ };
+};
+
+export class TrelloClient {
+ private readonly apiKey: string;
+ private readonly apiToken: string;
+
+ constructor(apiKey: string, apiToken: string) {
+ this.apiKey = apiKey;
+ this.apiToken = apiToken;
+ }
+
+ async getLists(boardId: string): Promise {
+ const res = await fetch(
+ `https://api.trello.com/1/boards/${boardId}/lists?key=${this.apiKey}&token=${this.apiToken}`,
+ );
+ return res.json();
+ }
+
+ 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",
+ },
+ );
+ return res.json();
+ }
+}
diff --git a/packages/ui/src/trello/index.ts b/packages/ui/src/trello/index.ts
new file mode 100644
index 0000000..e398b32
--- /dev/null
+++ b/packages/ui/src/trello/index.ts
@@ -0,0 +1 @@
+export * from "./trello";
diff --git a/packages/ui/src/trello/trello.ts b/packages/ui/src/trello/trello.ts
new file mode 100644
index 0000000..0995751
--- /dev/null
+++ b/packages/ui/src/trello/trello.ts
@@ -0,0 +1,77 @@
+import { DandoriTask, DandoriTaskStatus } from "@dandori/core";
+import { checkApiKey, runPromisesSequentially } from "@dandori/libs";
+import { TrelloClient } from "./client";
+
+type TrelloListPropertiesMap =
+ | {
+ "status.todo": string;
+ "status.doing"?: string;
+ "status.done"?: string;
+ }
+ | {
+ "status.todo"?: string;
+ "status.doing": string;
+ "status.done"?: string;
+ }
+ | {
+ "status.todo"?: string;
+ "status.doing"?: string;
+ "status.done": string;
+ };
+
+export type GenerateDandoriTrelloCardsOptions = {
+ boardId: string;
+ apiKey?: string;
+ apiToken?: string;
+ trelloListPropertiesMap: TrelloListPropertiesMap;
+};
+
+const targetName = "trello";
+
+export async function generateDandoriTrelloCards(
+ tasks: DandoriTask[],
+ options: GenerateDandoriTrelloCardsOptions,
+): Promise {
+ const key = checkApiKey(
+ `${targetName} api key`,
+ process.env.TRELLO_API_KEY,
+ options.apiKey,
+ );
+ const token = checkApiKey(
+ `${targetName} api token`,
+ process.env.TRELLO_API_TOKEN,
+ options.apiToken,
+ );
+ const trello = new TrelloClient(key, token);
+ const lists = await trello.getLists(options.boardId);
+ const { trelloListPropertiesMap } = options;
+ const statusNames = [
+ trelloListPropertiesMap["status.todo"],
+ trelloListPropertiesMap["status.doing"],
+ trelloListPropertiesMap["status.done"],
+ ];
+ const listIds = statusNames.map((statusName) => {
+ const list = lists.find((list) => list.name === statusName);
+ if (list) {
+ return list.id;
+ }
+ });
+ const dandoriTaskListIdMap: Record = {
+ todo: listIds[0],
+ doing: listIds[1],
+ done: listIds[2],
+ };
+ const createCards: (() => Promise)[] = [];
+ tasks.forEach((task) => {
+ const taskStatus = task.status;
+ if (!taskStatus) {
+ return;
+ }
+ const listId = dandoriTaskListIdMap[taskStatus];
+ if (!listId) {
+ return;
+ }
+ createCards.push(() => trello.createCard({ listId, name: task.name }));
+ });
+ await runPromisesSequentially(createCards, "Creating Trello Cards");
+}
diff --git a/packages/ui/templates/generateDandoriTrelloCards.ts b/packages/ui/templates/generateDandoriTrelloCards.ts
new file mode 100644
index 0000000..7116c82
--- /dev/null
+++ b/packages/ui/templates/generateDandoriTrelloCards.ts
@@ -0,0 +1,19 @@
+import { loadEnvFile } from "@dandori/libs";
+import { tasks } from "./mock";
+import { generateDandoriTrelloCards } from "../src/trello/trello";
+
+// set environment variables like access token
+loadEnvFile();
+
+// set trello settings
+const boardId = "";
+const trelloListPropertiesMap = {
+ "status.todo": "",
+ "status.doing": "",
+ "status.done": "",
+};
+
+await generateDandoriTrelloCards(tasks, {
+ boardId,
+ trelloListPropertiesMap,
+});