backlog-exporter 0.2.3 → 0.5.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 +146 -20
- package/dist/commands/all/index.js +29 -5
- package/dist/commands/document/index.d.ts +13 -0
- package/dist/commands/document/index.js +83 -0
- package/dist/commands/update/index.d.ts +2 -0
- package/dist/commands/update/index.js +84 -19
- package/dist/utils/backlog-api.d.ts +13 -0
- package/dist/utils/backlog-api.js +127 -0
- package/dist/utils/log.d.ts +6 -0
- package/dist/utils/log.js +18 -0
- package/dist/utils/settings.d.ts +1 -0
- package/dist/utils/settings.js +1 -0
- package/oclif.manifest.json +80 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,7 +13,8 @@ Backlog のデータをエクスポートするためのコマンドラインツ
|
|
|
13
13
|
* [使用方法](#使用方法)
|
|
14
14
|
* [課題のエクスポート](#課題のエクスポート)
|
|
15
15
|
* [Wikiのエクスポート](#wikiのエクスポート)
|
|
16
|
-
* [
|
|
16
|
+
* [ドキュメントのエクスポート](#ドキュメントのエクスポート)
|
|
17
|
+
* [課題・Wiki・ドキュメントの一括エクスポート](#課題wikiドキュメントの一括エクスポート)
|
|
17
18
|
* [データの更新](#データの更新)
|
|
18
19
|
* [npxを使った課題のエクスポート](#npxを使った課題のエクスポート)
|
|
19
20
|
* [npxを使ったデータの更新](#npxを使ったデータの更新)
|
|
@@ -23,7 +24,11 @@ Backlog のデータをエクスポートするためのコマンドラインツ
|
|
|
23
24
|
* [Wiki のエクスポート](#wiki-のエクスポート)
|
|
24
25
|
* [基本的な使用方法](#基本的な使用方法)
|
|
25
26
|
* [出力先を指定](#出力先を指定)
|
|
26
|
-
* [
|
|
27
|
+
* [ドキュメント のエクスポート](#ドキュメント-のエクスポート)
|
|
28
|
+
* [基本的な使用方法](#基本的な使用方法)
|
|
29
|
+
* [出力先を指定](#出力先を指定)
|
|
30
|
+
* [キーワード検索](#キーワード検索)
|
|
31
|
+
* [課題・Wiki・ドキュメント の一括エクスポート](#課題wikiドキュメント-の一括エクスポート)
|
|
27
32
|
* [基本的な使用方法](#基本的な使用方法)
|
|
28
33
|
* [出力先を指定](#出力先を指定)
|
|
29
34
|
* [データの更新](#データの更新)
|
|
@@ -32,11 +37,13 @@ Backlog のデータをエクスポートするためのコマンドラインツ
|
|
|
32
37
|
* [確認プロンプトをスキップして更新](#確認プロンプトをスキップして更新)
|
|
33
38
|
* [課題のみを更新](#課題のみを更新)
|
|
34
39
|
* [Wikiのみを更新](#wikiのみを更新)
|
|
40
|
+
* [ドキュメントのみを更新](#ドキュメントのみを更新)
|
|
35
41
|
* [APIキーを指定して更新](#apiキーを指定して更新)
|
|
36
42
|
* [コマンド](#コマンド)
|
|
37
43
|
* [出力形式](#出力形式)
|
|
38
44
|
* [課題のタイトル](#課題のタイトル)
|
|
39
45
|
* [Wiki のタイトル](#wiki-のタイトル)
|
|
46
|
+
* [ドキュメントのタイトル](#ドキュメントのタイトル)
|
|
40
47
|
* [その他の特徴](#その他の特徴)
|
|
41
48
|
<!-- tocstop -->
|
|
42
49
|
|
|
@@ -47,7 +54,8 @@ backlog-exporter は、Backlog のデータをローカルにエクスポート
|
|
|
47
54
|
|
|
48
55
|
- **課題(Issue)のエクスポート**:Backlog の課題を Markdown ファイルとして保存
|
|
49
56
|
- **Wiki 記事のエクスポート**:Backlog の Wiki 記事を Markdown ファイルとして保存
|
|
50
|
-
-
|
|
57
|
+
- **ドキュメントのエクスポート**:Backlog のドキュメントを Markdown ファイルとして保存
|
|
58
|
+
- **一括エクスポート**:課題・Wiki・ドキュメントを同時に取得する機能
|
|
51
59
|
- **データの更新**:既存のエクスポートデータを最新の状態に更新する機能
|
|
52
60
|
|
|
53
61
|
# インストール
|
|
@@ -58,7 +66,7 @@ $ npm install -g backlog-exporter
|
|
|
58
66
|
$ backlog-exporter COMMAND
|
|
59
67
|
running command...
|
|
60
68
|
$ backlog-exporter (--version)
|
|
61
|
-
backlog-exporter/0.
|
|
69
|
+
backlog-exporter/0.5.0 linux-x64 node-v24.2.0
|
|
62
70
|
$ backlog-exporter --help [COMMAND]
|
|
63
71
|
USAGE
|
|
64
72
|
$ backlog-exporter COMMAND
|
|
@@ -85,7 +93,10 @@ $ backlog-exporter issue --domain example.backlog.jp --projectIdOrKey PROJECT_KE
|
|
|
85
93
|
# Wikiのエクスポート
|
|
86
94
|
$ backlog-exporter wiki --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./wiki
|
|
87
95
|
|
|
88
|
-
#
|
|
96
|
+
# ドキュメントのエクスポート
|
|
97
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./documents
|
|
98
|
+
|
|
99
|
+
# 課題・Wiki・ドキュメントの一括エクスポート
|
|
89
100
|
$ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./backlog-data
|
|
90
101
|
|
|
91
102
|
# データの更新
|
|
@@ -130,9 +141,56 @@ $ backlog-exporter wiki --domain example.backlog.jp --projectIdOrKey PROJECT_KEY
|
|
|
130
141
|
|
|
131
142
|
エクスポートされた Wiki は、指定したディレクトリ内に Markdown ファイルとして保存されます。Wiki の階層構造は保持され、ディレクトリ構造として再現されます。
|
|
132
143
|
|
|
133
|
-
#
|
|
144
|
+
# ドキュメント のエクスポート
|
|
145
|
+
|
|
146
|
+
`document`コマンドを使用すると、Backlog のドキュメントページを Markdown ファイルとしてエクスポートできます。
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
# 基本的な使用方法
|
|
150
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY
|
|
151
|
+
|
|
152
|
+
# 出力先を指定
|
|
153
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./documents
|
|
154
|
+
|
|
155
|
+
# キーワード検索
|
|
156
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --keyword 仕様書
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## ドキュメントの出力方法
|
|
160
|
+
|
|
161
|
+
ドキュメントは **ツリー構造を保持** してエクスポートされます:
|
|
134
162
|
|
|
135
|
-
|
|
163
|
+
### ディレクトリ構造の保持
|
|
164
|
+
- Backlogのドキュメントツリーの階層構造がそのままローカルディレクトリ構造として再現されます
|
|
165
|
+
- フォルダはディレクトリとして作成され、ドキュメントはMarkdownファイルとして保存されます
|
|
166
|
+
|
|
167
|
+
### 出力例
|
|
168
|
+
```
|
|
169
|
+
documents/
|
|
170
|
+
├── プロジェクト概要/
|
|
171
|
+
│ ├── 要件定義書.md
|
|
172
|
+
│ └── 仕様書.md
|
|
173
|
+
├── 設計書/
|
|
174
|
+
│ ├── システム設計/
|
|
175
|
+
│ │ ├── アーキテクチャ設計.md
|
|
176
|
+
│ │ └── データベース設計.md
|
|
177
|
+
│ └── UI設計/
|
|
178
|
+
│ ├── 画面設計書.md
|
|
179
|
+
│ └── ワイヤーフレーム.md
|
|
180
|
+
└── 運用手順書.md
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 特徴
|
|
184
|
+
- **階層構造の完全再現**: Backlogのフォルダ階層がそのまま保持されます
|
|
185
|
+
- **重複処理の防止**: 同じドキュメントが複数回処理されることを防ぎます
|
|
186
|
+
- **ファイル名の自動サニタイズ**: 不正な文字を自動的に除去して安全なファイル名を生成します
|
|
187
|
+
- **メタデータの保持**: 作成者、更新者、タグ、添付ファイル情報なども含めて保存されます
|
|
188
|
+
|
|
189
|
+
エクスポートされたドキュメントは、指定したディレクトリ内にツリー構造を保持したMarkdownファイルとして保存されます。
|
|
190
|
+
|
|
191
|
+
# 課題・Wiki・ドキュメント の一括エクスポート
|
|
192
|
+
|
|
193
|
+
`all`コマンドを使用すると、課題・Wiki・ドキュメントを一度に取得できます。
|
|
136
194
|
|
|
137
195
|
```sh
|
|
138
196
|
# 基本的な使用方法
|
|
@@ -142,7 +200,7 @@ $ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY
|
|
|
142
200
|
$ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./backlog-data
|
|
143
201
|
```
|
|
144
202
|
|
|
145
|
-
一括エクスポートでは、課題は`issues`ディレクトリに、Wiki は`wiki`ディレクトリに保存されます。
|
|
203
|
+
一括エクスポートでは、課題は`issues`ディレクトリに、Wiki は`wiki`ディレクトリに、ドキュメントは`documents`ディレクトリに保存されます。
|
|
146
204
|
|
|
147
205
|
# データの更新
|
|
148
206
|
|
|
@@ -164,16 +222,20 @@ $ backlog-exporter update --issuesOnly
|
|
|
164
222
|
# Wikiのみを更新
|
|
165
223
|
$ backlog-exporter update --wikisOnly
|
|
166
224
|
|
|
225
|
+
# ドキュメントのみを更新
|
|
226
|
+
$ backlog-exporter update --documentsOnly
|
|
227
|
+
|
|
167
228
|
# APIキーを指定して更新
|
|
168
229
|
$ backlog-exporter update --apiKey YOUR_API_KEY
|
|
169
230
|
```
|
|
170
231
|
|
|
171
|
-
|
|
232
|
+
更新コマンドは、各ディレクトリの設定ファイルに基づいて、課題・Wiki・ドキュメントを自動的に更新します。設定ファイルが見つかったディレクトリでは、そのディレクトリ内のファイルが直接更新されます(サブフォルダは作成されません)。
|
|
172
233
|
|
|
173
234
|
# コマンド
|
|
174
235
|
|
|
175
236
|
<!-- commands -->
|
|
176
237
|
* [`backlog-exporter all`](#backlog-exporter-all)
|
|
238
|
+
* [`backlog-exporter document`](#backlog-exporter-document)
|
|
177
239
|
* [`backlog-exporter help [COMMAND]`](#backlog-exporter-help-command)
|
|
178
240
|
* [`backlog-exporter issue`](#backlog-exporter-issue)
|
|
179
241
|
* [`backlog-exporter plugins`](#backlog-exporter-plugins)
|
|
@@ -191,7 +253,7 @@ $ backlog-exporter update --apiKey YOUR_API_KEY
|
|
|
191
253
|
|
|
192
254
|
## `backlog-exporter all`
|
|
193
255
|
|
|
194
|
-
Backlog
|
|
256
|
+
Backlogから課題・Wiki・ドキュメントを取得してMarkdownファイルとして保存する
|
|
195
257
|
|
|
196
258
|
```
|
|
197
259
|
USAGE
|
|
@@ -206,20 +268,52 @@ FLAGS
|
|
|
206
268
|
--projectIdOrKey=<value> (required) Backlog project ID or key
|
|
207
269
|
|
|
208
270
|
DESCRIPTION
|
|
209
|
-
Backlog
|
|
271
|
+
Backlogから課題・Wiki・ドキュメントを取得してMarkdownファイルとして保存する
|
|
210
272
|
|
|
211
273
|
EXAMPLES
|
|
212
274
|
$ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY
|
|
213
|
-
|
|
275
|
+
課題・Wiki・ドキュメントをMarkdownファイルとして保存する
|
|
214
276
|
|
|
215
277
|
$ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-project
|
|
216
|
-
|
|
278
|
+
指定したディレクトリに課題・Wiki・ドキュメントを保存する
|
|
217
279
|
|
|
218
280
|
$ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --maxCount 1000
|
|
219
|
-
最大1000件の課題を取得する(デフォルトは
|
|
281
|
+
最大1000件の課題を取得する(デフォルトは5000件)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
_See code: [src/commands/all/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.5.0/src/commands/all/index.ts)_
|
|
285
|
+
|
|
286
|
+
## `backlog-exporter document`
|
|
287
|
+
|
|
288
|
+
Backlogからドキュメントを取得してMarkdownファイルとして保存する
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
USAGE
|
|
292
|
+
$ backlog-exporter document --domain <value> --projectIdOrKey <value> [--apiKey <value>] [--keyword <value>]
|
|
293
|
+
[-o <value>]
|
|
294
|
+
|
|
295
|
+
FLAGS
|
|
296
|
+
-o, --output=<value> 出力ディレクトリパス
|
|
297
|
+
--apiKey=<value> Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)
|
|
298
|
+
--domain=<value> (required) Backlog domain (e.g. example.backlog.jp)
|
|
299
|
+
--keyword=<value> 検索キーワード
|
|
300
|
+
--projectIdOrKey=<value> (required) Backlog project ID or key
|
|
301
|
+
|
|
302
|
+
DESCRIPTION
|
|
303
|
+
Backlogからドキュメントを取得してMarkdownファイルとして保存する
|
|
304
|
+
|
|
305
|
+
EXAMPLES
|
|
306
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY
|
|
307
|
+
ドキュメントをMarkdownファイルとして保存する
|
|
308
|
+
|
|
309
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-documents
|
|
310
|
+
指定したディレクトリにドキュメントを保存する
|
|
311
|
+
|
|
312
|
+
$ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --keyword 仕様書
|
|
313
|
+
キーワード「仕様書」を含むドキュメントのみを取得する
|
|
220
314
|
```
|
|
221
315
|
|
|
222
|
-
_See code: [src/commands/
|
|
316
|
+
_See code: [src/commands/document/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.5.0/src/commands/document/index.ts)_
|
|
223
317
|
|
|
224
318
|
## `backlog-exporter help [COMMAND]`
|
|
225
319
|
|
|
@@ -275,7 +369,7 @@ EXAMPLES
|
|
|
275
369
|
最大10000件の課題を取得する(デフォルトは5000件)
|
|
276
370
|
```
|
|
277
371
|
|
|
278
|
-
_See code: [src/commands/issue/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.
|
|
372
|
+
_See code: [src/commands/issue/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.5.0/src/commands/issue/index.ts)_
|
|
279
373
|
|
|
280
374
|
## `backlog-exporter plugins`
|
|
281
375
|
|
|
@@ -573,8 +667,8 @@ Backlogから最新データを取得して更新する
|
|
|
573
667
|
|
|
574
668
|
```
|
|
575
669
|
USAGE
|
|
576
|
-
$ backlog-exporter update [DIRECTORY] [--apiKey <value>] [--domain <value>] [-f]
|
|
577
|
-
[--projectIdOrKey <value>] [--wikisOnly]
|
|
670
|
+
$ backlog-exporter update [DIRECTORY] [--apiKey <value>] [--documentsOnly] [--domain <value>] [-f]
|
|
671
|
+
[--issuesOnly] [--projectIdOrKey <value>] [--wikisOnly]
|
|
578
672
|
|
|
579
673
|
ARGUMENTS
|
|
580
674
|
DIRECTORY 更新対象のディレクトリ(設定ファイルが保存されている場所)
|
|
@@ -582,6 +676,7 @@ ARGUMENTS
|
|
|
582
676
|
FLAGS
|
|
583
677
|
-f, --force 確認プロンプトをスキップする
|
|
584
678
|
--apiKey=<value> Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)
|
|
679
|
+
--documentsOnly ドキュメントのみを更新する
|
|
585
680
|
--domain=<value> Backlog domain (e.g. example.backlog.jp)
|
|
586
681
|
--issuesOnly 課題のみを更新する
|
|
587
682
|
--projectIdOrKey=<value> Backlog project ID or key
|
|
@@ -604,7 +699,7 @@ EXAMPLES
|
|
|
604
699
|
指定したディレクトリの設定を使用して更新する
|
|
605
700
|
```
|
|
606
701
|
|
|
607
|
-
_See code: [src/commands/update/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.
|
|
702
|
+
_See code: [src/commands/update/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.5.0/src/commands/update/index.ts)_
|
|
608
703
|
|
|
609
704
|
## `backlog-exporter wiki`
|
|
610
705
|
|
|
@@ -631,7 +726,7 @@ EXAMPLES
|
|
|
631
726
|
指定したディレクトリにWikiを保存する
|
|
632
727
|
```
|
|
633
728
|
|
|
634
|
-
_See code: [src/commands/wiki/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.
|
|
729
|
+
_See code: [src/commands/wiki/index.ts](https://github.com/ShuntaToda/backlog-exporter/blob/v0.5.0/src/commands/wiki/index.ts)_
|
|
635
730
|
<!-- commandsstop -->
|
|
636
731
|
|
|
637
732
|
# 出力形式
|
|
@@ -689,6 +784,35 @@ Wiki は以下の形式で Markdown ファイルとして保存されます:
|
|
|
689
784
|
Backlog の書式がそのまま保持されます。
|
|
690
785
|
```
|
|
691
786
|
|
|
787
|
+
## ドキュメント の出力形式
|
|
788
|
+
|
|
789
|
+
ドキュメントは以下の形式で Markdown ファイルとして保存されます:
|
|
790
|
+
|
|
791
|
+
```markdown
|
|
792
|
+
# ドキュメントのタイトル
|
|
793
|
+
|
|
794
|
+
[Backlog Document Link](https://example.backlog.jp/document/DOC-ID)
|
|
795
|
+
|
|
796
|
+
**ステータス**: 1 🎉
|
|
797
|
+
**作成者**: 山田太郎
|
|
798
|
+
**作成日時**: 2023/01/01 10:00:00
|
|
799
|
+
**更新者**: 佐藤次郎
|
|
800
|
+
**更新日時**: 2023/01/02 15:30:45
|
|
801
|
+
|
|
802
|
+
## 内容
|
|
803
|
+
|
|
804
|
+
ここにドキュメントの本文内容が入ります。
|
|
805
|
+
Backlog の書式がそのまま保持されます。
|
|
806
|
+
|
|
807
|
+
## 添付ファイル
|
|
808
|
+
- **資料.pdf** (1024.5 KB) - 作成者: 山田太郎, 作成日時: 2023/01/01 10:00:00
|
|
809
|
+
- **画像.png** (256.3 KB) - 作成者: 佐藤次郎, 作成日時: 2023/01/01 10:30:00
|
|
810
|
+
|
|
811
|
+
## タグ
|
|
812
|
+
- 仕様書
|
|
813
|
+
- 設計書
|
|
814
|
+
```
|
|
815
|
+
|
|
692
816
|
# その他の特徴
|
|
693
817
|
|
|
694
818
|
- **環境変数サポート**: 環境変数 `BACKLOG_API_KEY` を使用して API キーを設定可能
|
|
@@ -696,3 +820,5 @@ Backlog の書式がそのまま保持されます。
|
|
|
696
820
|
- **並列処理**: 並列処理による高速なダウンロード
|
|
697
821
|
- **ファイル名サニタイズ**: ファイル名の自動サニタイズ(不正な文字の除去)
|
|
698
822
|
- **階層構造の保持**: Wiki の階層構造を保持したエクスポート
|
|
823
|
+
- **キーワード検索**: ドキュメントの検索キーワード対応
|
|
824
|
+
- **差分更新**: 前回の更新以降に変更されたデータのみを更新
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import * as dotenv from 'dotenv';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { downloadIssues, downloadWikis } from '../../utils/backlog-api.js';
|
|
4
|
+
import { downloadDocuments, downloadIssues, downloadWikis } from '../../utils/backlog-api.js';
|
|
5
5
|
import { validateAndGetProjectId } from '../../utils/backlog.js';
|
|
6
6
|
import { createOutputDirectory, getApiKey } from '../../utils/common.js';
|
|
7
7
|
import { FolderType, updateSettings } from '../../utils/settings.js';
|
|
8
8
|
// .envファイルを読み込む
|
|
9
9
|
dotenv.config();
|
|
10
10
|
export default class All extends Command {
|
|
11
|
-
static description = 'Backlog
|
|
11
|
+
static description = 'Backlogから課題・Wiki・ドキュメントを取得してMarkdownファイルとして保存する';
|
|
12
12
|
static examples = [
|
|
13
13
|
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY
|
|
14
|
-
|
|
14
|
+
課題・Wiki・ドキュメントをMarkdownファイルとして保存する
|
|
15
15
|
`,
|
|
16
16
|
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-project
|
|
17
|
-
|
|
17
|
+
指定したディレクトリに課題・Wiki・ドキュメントを保存する
|
|
18
18
|
`,
|
|
19
19
|
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --maxCount 1000
|
|
20
|
-
最大1000件の課題を取得する(デフォルトは
|
|
20
|
+
最大1000件の課題を取得する(デフォルトは5000件)
|
|
21
21
|
`,
|
|
22
22
|
];
|
|
23
23
|
static flags = {
|
|
@@ -62,6 +62,9 @@ export default class All extends Command {
|
|
|
62
62
|
// Wikiの出力ディレクトリ
|
|
63
63
|
const wikiOutput = path.join(outputRoot, 'wiki');
|
|
64
64
|
await createOutputDirectory(wikiOutput);
|
|
65
|
+
// ドキュメントの出力ディレクトリ
|
|
66
|
+
const documentOutput = path.join(outputRoot, 'documents');
|
|
67
|
+
await createOutputDirectory(documentOutput);
|
|
65
68
|
// 課題フォルダに設定ファイルを保存
|
|
66
69
|
await updateSettings(issueOutput, {
|
|
67
70
|
apiKey,
|
|
@@ -78,6 +81,14 @@ export default class All extends Command {
|
|
|
78
81
|
outputDir: wikiOutput,
|
|
79
82
|
projectIdOrKey,
|
|
80
83
|
});
|
|
84
|
+
// ドキュメントフォルダに設定ファイルを保存
|
|
85
|
+
await updateSettings(documentOutput, {
|
|
86
|
+
apiKey,
|
|
87
|
+
domain,
|
|
88
|
+
folderType: FolderType.DOCUMENT,
|
|
89
|
+
outputDir: documentOutput,
|
|
90
|
+
projectIdOrKey,
|
|
91
|
+
});
|
|
81
92
|
// 課題の取得と保存
|
|
82
93
|
this.log('課題の取得を開始します...');
|
|
83
94
|
await downloadIssues(this, {
|
|
@@ -105,6 +116,19 @@ export default class All extends Command {
|
|
|
105
116
|
lastUpdated: new Date().toISOString(),
|
|
106
117
|
});
|
|
107
118
|
this.log('Wikiの取得が完了しました');
|
|
119
|
+
// ドキュメントの取得と保存
|
|
120
|
+
this.log('ドキュメントの取得を開始します...');
|
|
121
|
+
await downloadDocuments(this, {
|
|
122
|
+
apiKey,
|
|
123
|
+
domain,
|
|
124
|
+
outputDir: documentOutput,
|
|
125
|
+
projectId,
|
|
126
|
+
});
|
|
127
|
+
// ドキュメントフォルダの最終更新日時を更新
|
|
128
|
+
await updateSettings(documentOutput, {
|
|
129
|
+
lastUpdated: new Date().toISOString(),
|
|
130
|
+
});
|
|
131
|
+
this.log('ドキュメントの取得が完了しました');
|
|
108
132
|
this.log('すべてのデータの取得が完了しました!');
|
|
109
133
|
}
|
|
110
134
|
catch (error) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Document extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
apiKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
keyword: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
projectIdOrKey: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import { downloadDocuments } from '../../utils/backlog-api.js';
|
|
4
|
+
import { validateAndGetProjectId } from '../../utils/backlog.js';
|
|
5
|
+
import { createOutputDirectory, getApiKey } from '../../utils/common.js';
|
|
6
|
+
import { FolderType, updateSettings } from '../../utils/settings.js';
|
|
7
|
+
// .envファイルを読み込む
|
|
8
|
+
dotenv.config();
|
|
9
|
+
export default class Document extends Command {
|
|
10
|
+
static description = 'Backlogからドキュメントを取得してMarkdownファイルとして保存する';
|
|
11
|
+
static examples = [
|
|
12
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY
|
|
13
|
+
ドキュメントをMarkdownファイルとして保存する
|
|
14
|
+
`,
|
|
15
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-documents
|
|
16
|
+
指定したディレクトリにドキュメントを保存する
|
|
17
|
+
`,
|
|
18
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --keyword 仕様書
|
|
19
|
+
キーワード「仕様書」を含むドキュメントのみを取得する
|
|
20
|
+
`,
|
|
21
|
+
];
|
|
22
|
+
static flags = {
|
|
23
|
+
apiKey: Flags.string({
|
|
24
|
+
description: 'Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)',
|
|
25
|
+
required: false,
|
|
26
|
+
}),
|
|
27
|
+
domain: Flags.string({
|
|
28
|
+
description: 'Backlog domain (e.g. example.backlog.jp)',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
keyword: Flags.string({
|
|
32
|
+
description: '検索キーワード',
|
|
33
|
+
required: false,
|
|
34
|
+
}),
|
|
35
|
+
output: Flags.string({
|
|
36
|
+
char: 'o',
|
|
37
|
+
description: '出力ディレクトリパス',
|
|
38
|
+
required: false,
|
|
39
|
+
}),
|
|
40
|
+
projectIdOrKey: Flags.string({
|
|
41
|
+
description: 'Backlog project ID or key',
|
|
42
|
+
required: true,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { flags } = await this.parse(Document);
|
|
47
|
+
try {
|
|
48
|
+
const { domain, keyword, projectIdOrKey } = flags;
|
|
49
|
+
const apiKey = flags.apiKey || getApiKey(this);
|
|
50
|
+
const outputDir = flags.output || './backlog-documents';
|
|
51
|
+
// 出力ディレクトリの作成
|
|
52
|
+
await createOutputDirectory(outputDir);
|
|
53
|
+
// プロジェクトキーからプロジェクトIDを取得
|
|
54
|
+
const projectId = await validateAndGetProjectId(domain, projectIdOrKey, apiKey);
|
|
55
|
+
this.log(`プロジェクトID: ${projectId} を使用します`);
|
|
56
|
+
// 設定ファイルを保存
|
|
57
|
+
await updateSettings(outputDir, {
|
|
58
|
+
apiKey,
|
|
59
|
+
domain,
|
|
60
|
+
folderType: FolderType.DOCUMENT,
|
|
61
|
+
outputDir,
|
|
62
|
+
projectIdOrKey,
|
|
63
|
+
});
|
|
64
|
+
// ドキュメントの取得と保存
|
|
65
|
+
await downloadDocuments(this, {
|
|
66
|
+
apiKey,
|
|
67
|
+
domain,
|
|
68
|
+
keyword,
|
|
69
|
+
outputDir,
|
|
70
|
+
projectId,
|
|
71
|
+
});
|
|
72
|
+
// 最終更新日時を更新
|
|
73
|
+
await updateSettings(outputDir, {
|
|
74
|
+
lastUpdated: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
this.log('ドキュメントの取得が完了しました!');
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
80
|
+
this.error(`ドキュメントの取得に失敗しました: ${errorMessage}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -7,6 +7,7 @@ export default class Update extends Command {
|
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
9
|
apiKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
documentsOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
11
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
issuesOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -18,6 +19,7 @@ export default class Update extends Command {
|
|
|
18
19
|
private determineUpdateTargets;
|
|
19
20
|
private findAndUpdateSettings;
|
|
20
21
|
private updateDirectory;
|
|
22
|
+
private updateDocuments;
|
|
21
23
|
private updateIssues;
|
|
22
24
|
private updateWikis;
|
|
23
25
|
}
|
|
@@ -2,7 +2,7 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import * as dotenv from 'dotenv';
|
|
3
3
|
import { access, readdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { downloadIssues, downloadWikis } from '../../utils/backlog-api.js';
|
|
5
|
+
import { downloadDocuments, downloadIssues, downloadWikis } from '../../utils/backlog-api.js';
|
|
6
6
|
import { validateAndGetProjectId } from '../../utils/backlog.js';
|
|
7
7
|
import { createOutputDirectory, getApiKey } from '../../utils/common.js';
|
|
8
8
|
import { FolderType, getSettingsFilePath, loadSettings, updateSettings } from '../../utils/settings.js';
|
|
@@ -35,6 +35,10 @@ export default class Update extends Command {
|
|
|
35
35
|
description: 'Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)',
|
|
36
36
|
required: false,
|
|
37
37
|
}),
|
|
38
|
+
documentsOnly: Flags.boolean({
|
|
39
|
+
description: 'ドキュメントのみを更新する',
|
|
40
|
+
required: false,
|
|
41
|
+
}),
|
|
38
42
|
domain: Flags.string({
|
|
39
43
|
description: 'Backlog domain (e.g. example.backlog.jp)',
|
|
40
44
|
required: false,
|
|
@@ -81,7 +85,14 @@ export default class Update extends Command {
|
|
|
81
85
|
if (options.folderType) {
|
|
82
86
|
this.log(`- フォルダタイプ: ${options.folderType}`);
|
|
83
87
|
}
|
|
84
|
-
|
|
88
|
+
const updateTargets = [];
|
|
89
|
+
if (options.updateIssues)
|
|
90
|
+
updateTargets.push('課題');
|
|
91
|
+
if (options.updateWikis)
|
|
92
|
+
updateTargets.push('Wiki');
|
|
93
|
+
if (options.updateDocuments)
|
|
94
|
+
updateTargets.push('ドキュメント');
|
|
95
|
+
this.log(`- 更新対象: ${updateTargets.join('・')}`);
|
|
85
96
|
// 確認プロンプトを表示
|
|
86
97
|
this.log('更新を実行しますか? (y/n)');
|
|
87
98
|
process.stdin.resume();
|
|
@@ -99,20 +110,32 @@ export default class Update extends Command {
|
|
|
99
110
|
return response;
|
|
100
111
|
}
|
|
101
112
|
// 更新対象の決定
|
|
102
|
-
determineUpdateTargets(folderType, issuesOnly, wikisOnly) {
|
|
103
|
-
let updateIssues = !wikisOnly;
|
|
104
|
-
let updateWikis = !issuesOnly;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
determineUpdateTargets(folderType, documentsOnly, issuesOnly, wikisOnly) {
|
|
114
|
+
let updateIssues = !wikisOnly && !documentsOnly;
|
|
115
|
+
let updateWikis = !issuesOnly && !documentsOnly;
|
|
116
|
+
let updateDocuments = !issuesOnly && !wikisOnly;
|
|
117
|
+
// フォルダタイプに応じて更新対象を決定
|
|
118
|
+
switch (folderType) {
|
|
119
|
+
case FolderType.DOCUMENT: {
|
|
120
|
+
updateIssues = false;
|
|
121
|
+
updateWikis = false;
|
|
122
|
+
updateDocuments = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case FolderType.ISSUE: {
|
|
126
|
+
updateIssues = true;
|
|
127
|
+
updateWikis = false;
|
|
128
|
+
updateDocuments = false;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case FolderType.WIKI: {
|
|
132
|
+
updateIssues = false;
|
|
133
|
+
updateWikis = true;
|
|
134
|
+
updateDocuments = false;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { updateDocuments, updateIssues, updateWikis };
|
|
116
139
|
}
|
|
117
140
|
// 設定ファイルを探索して更新を実行
|
|
118
141
|
async findAndUpdateSettings(targetDir, flags) {
|
|
@@ -150,11 +173,10 @@ export default class Update extends Command {
|
|
|
150
173
|
// 設定ファイルを読み込む
|
|
151
174
|
const settings = await loadSettings(targetDir);
|
|
152
175
|
// コマンドライン引数と設定ファイルを組み合わせて使用する値を決定
|
|
153
|
-
const apiKey = flags.apiKey || settings.apiKey || getApiKey(this);
|
|
154
176
|
const domain = flags.domain || settings.domain;
|
|
155
177
|
const projectIdOrKey = flags.projectIdOrKey || settings.projectIdOrKey;
|
|
156
178
|
const { folderType } = settings;
|
|
157
|
-
const { force, issuesOnly, wikisOnly } = flags;
|
|
179
|
+
const { documentsOnly, force, issuesOnly, wikisOnly } = flags;
|
|
158
180
|
// 必須パラメータの検証
|
|
159
181
|
if (!domain) {
|
|
160
182
|
this.warn(`${targetDir}: ドメインが指定されていません。スキップします。`);
|
|
@@ -164,8 +186,17 @@ export default class Update extends Command {
|
|
|
164
186
|
this.warn(`${targetDir}: プロジェクトIDまたはキーが指定されていません。スキップします。`);
|
|
165
187
|
return;
|
|
166
188
|
}
|
|
189
|
+
// APIキーの検証
|
|
190
|
+
let apiKey;
|
|
191
|
+
try {
|
|
192
|
+
apiKey = flags.apiKey || settings.apiKey || getApiKey(this);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
this.warn(`${targetDir}: APIキーが指定されていません。--apiKey フラグまたは BACKLOG_API_KEY 環境変数で設定してください。`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
167
198
|
// 更新対象の決定
|
|
168
|
-
const { updateIssues, updateWikis } = this.determineUpdateTargets(folderType, issuesOnly, wikisOnly);
|
|
199
|
+
const { updateDocuments, updateIssues, updateWikis } = this.determineUpdateTargets(folderType, documentsOnly, issuesOnly, wikisOnly);
|
|
169
200
|
// 更新前の確認
|
|
170
201
|
const confirmed = await this.confirmUpdate({
|
|
171
202
|
domain,
|
|
@@ -173,6 +204,7 @@ export default class Update extends Command {
|
|
|
173
204
|
force: force || false,
|
|
174
205
|
projectIdOrKey,
|
|
175
206
|
targetDir,
|
|
207
|
+
updateDocuments,
|
|
176
208
|
updateIssues,
|
|
177
209
|
updateWikis,
|
|
178
210
|
});
|
|
@@ -202,8 +234,41 @@ export default class Update extends Command {
|
|
|
202
234
|
targetDir,
|
|
203
235
|
});
|
|
204
236
|
}
|
|
237
|
+
// ドキュメントの更新
|
|
238
|
+
if (updateDocuments) {
|
|
239
|
+
await this.updateDocuments({
|
|
240
|
+
apiKey,
|
|
241
|
+
domain,
|
|
242
|
+
projectId,
|
|
243
|
+
projectIdOrKey,
|
|
244
|
+
targetDir,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
205
247
|
this.log(`${targetDir} の更新が完了しました!`);
|
|
206
248
|
}
|
|
249
|
+
// ドキュメントの更新
|
|
250
|
+
async updateDocuments(options) {
|
|
251
|
+
this.log('ドキュメントの更新を開始します...');
|
|
252
|
+
// 設定ファイルから前回の更新日時を取得
|
|
253
|
+
const { lastUpdated } = await loadSettings(options.targetDir);
|
|
254
|
+
await downloadDocuments(this, {
|
|
255
|
+
apiKey: options.apiKey,
|
|
256
|
+
domain: options.domain,
|
|
257
|
+
lastUpdated,
|
|
258
|
+
outputDir: options.targetDir,
|
|
259
|
+
projectId: options.projectId,
|
|
260
|
+
});
|
|
261
|
+
// 設定ファイルを更新
|
|
262
|
+
await updateSettings(options.targetDir, {
|
|
263
|
+
apiKey: options.apiKey,
|
|
264
|
+
domain: options.domain,
|
|
265
|
+
folderType: FolderType.DOCUMENT,
|
|
266
|
+
lastUpdated: new Date().toISOString(),
|
|
267
|
+
outputDir: options.targetDir,
|
|
268
|
+
projectIdOrKey: options.projectIdOrKey,
|
|
269
|
+
});
|
|
270
|
+
this.log('ドキュメントの更新が完了しました');
|
|
271
|
+
}
|
|
207
272
|
// 課題の更新
|
|
208
273
|
async updateIssues(options) {
|
|
209
274
|
this.log('課題の更新を開始します...');
|
|
@@ -25,3 +25,16 @@ export declare function downloadWikis(command: Command, options: {
|
|
|
25
25
|
outputDir: string;
|
|
26
26
|
projectIdOrKey: string;
|
|
27
27
|
}): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Backlogからドキュメントをダウンロードする
|
|
30
|
+
* @param command コマンドインスタンス
|
|
31
|
+
* @param options ドキュメントダウンロードのオプション
|
|
32
|
+
*/
|
|
33
|
+
export declare function downloadDocuments(command: Command, options: {
|
|
34
|
+
apiKey: string;
|
|
35
|
+
domain: string;
|
|
36
|
+
keyword?: string;
|
|
37
|
+
lastUpdated?: string;
|
|
38
|
+
outputDir: string;
|
|
39
|
+
projectId: number;
|
|
40
|
+
}): Promise<void>;
|
|
@@ -3,6 +3,7 @@ import * as fs from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { sanitizeFileName, sanitizeWikiFileName } from './common.js';
|
|
6
|
+
import { appendLog } from './log.js';
|
|
6
7
|
import { RateLimiter } from './sleep.js';
|
|
7
8
|
/**
|
|
8
9
|
* Backlogから課題をダウンロードする
|
|
@@ -130,6 +131,9 @@ export async function downloadIssues(command, options) {
|
|
|
130
131
|
${issue.description || '詳細情報なし'}${commentsSection}`;
|
|
131
132
|
// eslint-disable-next-line no-await-in-loop
|
|
132
133
|
await fs.writeFile(issueFilePath, markdownContent);
|
|
134
|
+
// ログに記録
|
|
135
|
+
// eslint-disable-next-line no-await-in-loop
|
|
136
|
+
await appendLog(options.outputDir, `課題「${issue.summary}」を更新しました: ${backlogIssueUrl}`);
|
|
133
137
|
}
|
|
134
138
|
catch (error) {
|
|
135
139
|
command.warn(`課題 ${issue.issueKey} の保存に失敗しました: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -238,6 +242,9 @@ export async function downloadWikis(command, options) {
|
|
|
238
242
|
const markdownContent = `# ${wiki.name}\n\n[Backlog Wiki Link](${backlogWikiUrl})\n\n${content}`;
|
|
239
243
|
// eslint-disable-next-line no-await-in-loop
|
|
240
244
|
await fs.writeFile(wikiFilePath, markdownContent);
|
|
245
|
+
// ログに記録
|
|
246
|
+
// eslint-disable-next-line no-await-in-loop
|
|
247
|
+
await appendLog(options.outputDir, `Wiki「${wiki.name}」を更新しました: ${backlogWikiUrl}`);
|
|
241
248
|
// 進捗状況を一行で更新
|
|
242
249
|
const wikiIndex = filteredWikis.indexOf(wiki) + 1;
|
|
243
250
|
process.stdout.write(`\rWikiを保存中... (${wikiIndex}/${filteredWikis.length}件)`);
|
|
@@ -248,3 +255,123 @@ export async function downloadWikis(command, options) {
|
|
|
248
255
|
}
|
|
249
256
|
command.log('\nWikiのダウンロードが完了しました!');
|
|
250
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Backlogからドキュメントをダウンロードする
|
|
260
|
+
* @param command コマンドインスタンス
|
|
261
|
+
* @param options ドキュメントダウンロードのオプション
|
|
262
|
+
*/
|
|
263
|
+
export async function downloadDocuments(command, options) {
|
|
264
|
+
const baseUrl = `https://${options.domain}/api/v2`;
|
|
265
|
+
command.log('ドキュメントの取得を開始します...');
|
|
266
|
+
// APIリクエスト数をカウントするためのRateLimiterを作成
|
|
267
|
+
const rateLimiter = new RateLimiter(command);
|
|
268
|
+
// ドキュメントツリーの取得
|
|
269
|
+
command.log('ドキュメントツリーを取得しています...');
|
|
270
|
+
// APIリクエスト数をインクリメント
|
|
271
|
+
await rateLimiter.increment();
|
|
272
|
+
const documentTree = await ky
|
|
273
|
+
.get(`${baseUrl}/documents/tree?projectIdOrKey=${options.projectId}&apiKey=${options.apiKey}`)
|
|
274
|
+
.json();
|
|
275
|
+
command.log('アクティブなドキュメントツリーを処理します...');
|
|
276
|
+
// ツリー構造をトラバースして、各ドキュメントの詳細を取得・保存
|
|
277
|
+
const processedDocuments = [];
|
|
278
|
+
/**
|
|
279
|
+
* ドキュメントノードを再帰的に処理する
|
|
280
|
+
*/
|
|
281
|
+
const processDocumentNode = async (node, currentPath) => {
|
|
282
|
+
// フォルダの場合
|
|
283
|
+
if (node.children && node.children.length > 0) {
|
|
284
|
+
// フォルダを作成
|
|
285
|
+
const folderPath = path.join(options.outputDir, currentPath, sanitizeFileName(node.name));
|
|
286
|
+
await fs.mkdir(folderPath, { recursive: true });
|
|
287
|
+
// 子ノードを処理
|
|
288
|
+
for (const child of node.children) {
|
|
289
|
+
// eslint-disable-next-line no-await-in-loop
|
|
290
|
+
await processDocumentNode(child, path.join(currentPath, sanitizeFileName(node.name)));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
// ドキュメントファイルの場合
|
|
295
|
+
try {
|
|
296
|
+
// 既に処理済みのドキュメントはスキップ
|
|
297
|
+
if (processedDocuments.includes(node.id)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
processedDocuments.push(node.id);
|
|
301
|
+
// APIリクエスト数をインクリメント
|
|
302
|
+
await rateLimiter.increment();
|
|
303
|
+
// 進捗状況を表示
|
|
304
|
+
process.stdout.write(`\rドキュメント「${node.name}」を処理中...`);
|
|
305
|
+
// ドキュメント詳細を取得
|
|
306
|
+
const documentDetail = await ky.get(`${baseUrl}/documents/${node.id}?apiKey=${options.apiKey}`).json();
|
|
307
|
+
// 前回の更新日時チェック
|
|
308
|
+
if (options.lastUpdated) {
|
|
309
|
+
const lastUpdatedDate = new Date(options.lastUpdated);
|
|
310
|
+
const documentUpdatedDate = new Date(documentDetail.updated);
|
|
311
|
+
if (documentUpdatedDate <= lastUpdatedDate) {
|
|
312
|
+
return; // 更新が必要ない場合はスキップ
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// ファイルパスを構築
|
|
316
|
+
const sanitizedTitle = sanitizeFileName(documentDetail.title);
|
|
317
|
+
const documentFileName = `${sanitizedTitle}.md`;
|
|
318
|
+
const documentFilePath = path.join(options.outputDir, currentPath, documentFileName);
|
|
319
|
+
// ディレクトリを作成(必要に応じて)
|
|
320
|
+
const dirPath = path.dirname(documentFilePath);
|
|
321
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
322
|
+
// Backlogのドキュメントへのリンクを作成
|
|
323
|
+
const backlogDocumentUrl = `https://${options.domain}/document/${node.id}`;
|
|
324
|
+
// 添付ファイルリストの作成
|
|
325
|
+
let attachmentsSection = '';
|
|
326
|
+
if (documentDetail.attachments && documentDetail.attachments.length > 0) {
|
|
327
|
+
attachmentsSection = '\n\n## 添付ファイル\n';
|
|
328
|
+
for (const attachment of documentDetail.attachments) {
|
|
329
|
+
const attachmentDate = new Date(attachment.created).toLocaleString('ja-JP');
|
|
330
|
+
const fileSize = (attachment.size / 1024).toFixed(1);
|
|
331
|
+
attachmentsSection += `- **${attachment.name}** (${fileSize} KB) - 作成者: ${attachment.createdUser.name}, 作成日時: ${attachmentDate}\n`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// タグリストの作成
|
|
335
|
+
let tagsSection = '';
|
|
336
|
+
if (documentDetail.tags && documentDetail.tags.length > 0) {
|
|
337
|
+
tagsSection = '\n\n## タグ\n';
|
|
338
|
+
for (const tag of documentDetail.tags) {
|
|
339
|
+
tagsSection += `- ${tag.name}\n`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// 作成者・更新者情報
|
|
343
|
+
const createdDate = new Date(documentDetail.created).toLocaleString('ja-JP');
|
|
344
|
+
const updatedDate = new Date(documentDetail.updated).toLocaleString('ja-JP');
|
|
345
|
+
// Markdownファイルに書き込む
|
|
346
|
+
const markdownContent = `# ${documentDetail.title}
|
|
347
|
+
|
|
348
|
+
[Backlog Document Link](${backlogDocumentUrl})
|
|
349
|
+
|
|
350
|
+
**ステータス**: ${documentDetail.statusId}${documentDetail.emoji ? ` ${documentDetail.emoji}` : ''}
|
|
351
|
+
**作成者**: ${documentDetail.createdUser.name}
|
|
352
|
+
**作成日時**: ${createdDate}
|
|
353
|
+
**更新者**: ${documentDetail.updatedUser.name}
|
|
354
|
+
**更新日時**: ${updatedDate}
|
|
355
|
+
|
|
356
|
+
## 内容
|
|
357
|
+
|
|
358
|
+
${documentDetail.plain || '(内容なし)'}${attachmentsSection}${tagsSection}`;
|
|
359
|
+
await fs.writeFile(documentFilePath, markdownContent);
|
|
360
|
+
// ログに記録
|
|
361
|
+
await appendLog(options.outputDir, `ドキュメント「${documentDetail.title}」を更新しました: ${backlogDocumentUrl}`);
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
command.warn(`ドキュメント ${node.name} の取得に失敗しました: ${error instanceof Error ? error.message : String(error)}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
// アクティブツリーのルートから処理開始
|
|
369
|
+
if (documentTree.activeTree.children && documentTree.activeTree.children.length > 0) {
|
|
370
|
+
for (const rootNode of documentTree.activeTree.children) {
|
|
371
|
+
// eslint-disable-next-line no-await-in-loop
|
|
372
|
+
await processDocumentNode(rootNode, '');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
command.log(`\n合計 ${processedDocuments.length}件のドキュメントが処理されました。`);
|
|
376
|
+
command.log('ドキュメントのダウンロードが完了しました!');
|
|
377
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* 更新ログを記録する
|
|
5
|
+
* @param outputDir 出力ディレクトリ
|
|
6
|
+
* @param message ログメッセージ
|
|
7
|
+
*/
|
|
8
|
+
export async function appendLog(outputDir, message) {
|
|
9
|
+
const logPath = path.join(outputDir, 'backlog-update.log');
|
|
10
|
+
const timestamp = new Date().toLocaleString('ja-JP');
|
|
11
|
+
const logMessage = `[${timestamp}] ${message}\n`;
|
|
12
|
+
try {
|
|
13
|
+
await fs.appendFile(logPath, logMessage, 'utf8');
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error(`ログの記録に失敗しました: ${error instanceof Error ? error.message : String(error)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/utils/settings.d.ts
CHANGED
package/dist/utils/settings.js
CHANGED
package/oclif.manifest.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"all": {
|
|
4
4
|
"aliases": [],
|
|
5
5
|
"args": {},
|
|
6
|
-
"description": "Backlog
|
|
6
|
+
"description": "Backlogから課題・Wiki・ドキュメントを取得してMarkdownファイルとして保存する",
|
|
7
7
|
"examples": [
|
|
8
|
-
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY\n
|
|
9
|
-
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-project\n
|
|
10
|
-
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --maxCount 1000\n最大1000件の課題を取得する(デフォルトは
|
|
8
|
+
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY\n課題・Wiki・ドキュメントをMarkdownファイルとして保存する\n",
|
|
9
|
+
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-project\n指定したディレクトリに課題・Wiki・ドキュメントを保存する\n",
|
|
10
|
+
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --maxCount 1000\n最大1000件の課題を取得する(デフォルトは5000件)\n"
|
|
11
11
|
],
|
|
12
12
|
"flags": {
|
|
13
13
|
"apiKey": {
|
|
@@ -70,6 +70,74 @@
|
|
|
70
70
|
"index.js"
|
|
71
71
|
]
|
|
72
72
|
},
|
|
73
|
+
"document": {
|
|
74
|
+
"aliases": [],
|
|
75
|
+
"args": {},
|
|
76
|
+
"description": "Backlogからドキュメントを取得してMarkdownファイルとして保存する",
|
|
77
|
+
"examples": [
|
|
78
|
+
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY\nドキュメントをMarkdownファイルとして保存する\n",
|
|
79
|
+
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-documents\n指定したディレクトリにドキュメントを保存する\n",
|
|
80
|
+
"<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --keyword 仕様書\nキーワード「仕様書」を含むドキュメントのみを取得する\n"
|
|
81
|
+
],
|
|
82
|
+
"flags": {
|
|
83
|
+
"apiKey": {
|
|
84
|
+
"description": "Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)",
|
|
85
|
+
"name": "apiKey",
|
|
86
|
+
"required": false,
|
|
87
|
+
"hasDynamicHelp": false,
|
|
88
|
+
"multiple": false,
|
|
89
|
+
"type": "option"
|
|
90
|
+
},
|
|
91
|
+
"domain": {
|
|
92
|
+
"description": "Backlog domain (e.g. example.backlog.jp)",
|
|
93
|
+
"name": "domain",
|
|
94
|
+
"required": true,
|
|
95
|
+
"hasDynamicHelp": false,
|
|
96
|
+
"multiple": false,
|
|
97
|
+
"type": "option"
|
|
98
|
+
},
|
|
99
|
+
"keyword": {
|
|
100
|
+
"description": "検索キーワード",
|
|
101
|
+
"name": "keyword",
|
|
102
|
+
"required": false,
|
|
103
|
+
"hasDynamicHelp": false,
|
|
104
|
+
"multiple": false,
|
|
105
|
+
"type": "option"
|
|
106
|
+
},
|
|
107
|
+
"output": {
|
|
108
|
+
"char": "o",
|
|
109
|
+
"description": "出力ディレクトリパス",
|
|
110
|
+
"name": "output",
|
|
111
|
+
"required": false,
|
|
112
|
+
"hasDynamicHelp": false,
|
|
113
|
+
"multiple": false,
|
|
114
|
+
"type": "option"
|
|
115
|
+
},
|
|
116
|
+
"projectIdOrKey": {
|
|
117
|
+
"description": "Backlog project ID or key",
|
|
118
|
+
"name": "projectIdOrKey",
|
|
119
|
+
"required": true,
|
|
120
|
+
"hasDynamicHelp": false,
|
|
121
|
+
"multiple": false,
|
|
122
|
+
"type": "option"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"hasDynamicHelp": false,
|
|
126
|
+
"hiddenAliases": [],
|
|
127
|
+
"id": "document",
|
|
128
|
+
"pluginAlias": "backlog-exporter",
|
|
129
|
+
"pluginName": "backlog-exporter",
|
|
130
|
+
"pluginType": "core",
|
|
131
|
+
"strict": true,
|
|
132
|
+
"enableJsonFlag": false,
|
|
133
|
+
"isESM": true,
|
|
134
|
+
"relativePath": [
|
|
135
|
+
"dist",
|
|
136
|
+
"commands",
|
|
137
|
+
"document",
|
|
138
|
+
"index.js"
|
|
139
|
+
]
|
|
140
|
+
},
|
|
73
141
|
"issue": {
|
|
74
142
|
"aliases": [],
|
|
75
143
|
"args": {},
|
|
@@ -174,6 +242,13 @@
|
|
|
174
242
|
"multiple": false,
|
|
175
243
|
"type": "option"
|
|
176
244
|
},
|
|
245
|
+
"documentsOnly": {
|
|
246
|
+
"description": "ドキュメントのみを更新する",
|
|
247
|
+
"name": "documentsOnly",
|
|
248
|
+
"required": false,
|
|
249
|
+
"allowNo": false,
|
|
250
|
+
"type": "boolean"
|
|
251
|
+
},
|
|
177
252
|
"domain": {
|
|
178
253
|
"description": "Backlog domain (e.g. example.backlog.jp)",
|
|
179
254
|
"name": "domain",
|
|
@@ -289,5 +364,5 @@
|
|
|
289
364
|
]
|
|
290
365
|
}
|
|
291
366
|
},
|
|
292
|
-
"version": "0.
|
|
367
|
+
"version": "0.5.0"
|
|
293
368
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backlog-exporter",
|
|
3
3
|
"description": "A new CLI generated with oclif",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"author": "ShuntaToda",
|
|
6
6
|
"bin": {
|
|
7
7
|
"backlog-exporter": "bin/run.js"
|
|
@@ -76,4 +76,4 @@
|
|
|
76
76
|
"version": "oclif readme && git add README.md"
|
|
77
77
|
},
|
|
78
78
|
"types": "dist/index.d.ts"
|
|
79
|
-
}
|
|
79
|
+
}
|