backlog-exporter 0.7.2 → 1.0.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 +87 -1
- package/bin/dev.js +6 -2
- package/dist/commands/all/index.d.ts +2 -0
- package/dist/commands/all/index.js +62 -126
- package/dist/commands/document/index.js +18 -11
- package/dist/commands/issue/index.js +18 -11
- package/dist/commands/prune/index.d.ts +16 -0
- package/dist/commands/prune/index.js +86 -0
- package/dist/commands/update/index.d.ts +3 -7
- package/dist/commands/update/index.js +28 -260
- package/dist/commands/wiki/index.js +17 -9
- package/dist/composition/backlog-repositories.d.ts +16 -0
- package/dist/composition/backlog-repositories.js +15 -0
- package/dist/modules/all/domain/content-selection.d.ts +4 -0
- package/dist/modules/all/domain/content-selection.js +16 -0
- package/dist/modules/all/use-case/export-all.d.ts +24 -0
- package/dist/modules/all/use-case/export-all.js +78 -0
- package/dist/modules/document/domain/document-markdown.d.ts +2 -0
- package/dist/modules/document/domain/document-markdown.js +37 -0
- package/dist/modules/document/domain/document-path.d.ts +15 -0
- package/dist/modules/document/domain/document-path.js +48 -0
- package/dist/modules/document/domain/document-repository.d.ts +6 -0
- package/dist/modules/document/domain/document-repository.js +1 -0
- package/dist/modules/document/domain/document-save-plan.d.ts +9 -0
- package/dist/modules/document/domain/document-save-plan.js +15 -0
- package/dist/modules/document/domain/document.d.ts +50 -0
- package/dist/modules/document/domain/document.js +1 -0
- package/dist/modules/document/repository/backlog-document-repository.d.ts +3 -0
- package/dist/modules/document/repository/backlog-document-repository.js +31 -0
- package/dist/modules/document/use-case/export-documents.d.ts +16 -0
- package/dist/modules/document/use-case/export-documents.js +82 -0
- package/dist/modules/issue/domain/issue-filter.d.ts +2 -0
- package/dist/modules/issue/domain/issue-filter.js +7 -0
- package/dist/modules/issue/domain/issue-markdown.d.ts +4 -0
- package/dist/modules/issue/domain/issue-markdown.js +112 -0
- package/dist/modules/issue/domain/issue-path.d.ts +23 -0
- package/dist/modules/issue/domain/issue-path.js +30 -0
- package/dist/modules/issue/domain/issue-repository.d.ts +11 -0
- package/dist/modules/issue/domain/issue-repository.js +1 -0
- package/dist/modules/issue/domain/issue.d.ts +47 -0
- package/dist/modules/issue/domain/issue.js +1 -0
- package/dist/modules/issue/repository/backlog-issue-repository.d.ts +3 -0
- package/dist/modules/issue/repository/backlog-issue-repository.js +38 -0
- package/dist/modules/issue/use-case/export-issues.d.ts +18 -0
- package/dist/modules/issue/use-case/export-issues.js +87 -0
- package/dist/modules/project/domain/project-repository.d.ts +3 -0
- package/dist/modules/project/domain/project-repository.js +1 -0
- package/dist/modules/project/repository/backlog-project-repository.d.ts +3 -0
- package/dist/modules/project/repository/backlog-project-repository.js +17 -0
- package/dist/modules/prune/domain/expected-paths.d.ts +4 -0
- package/dist/modules/prune/domain/expected-paths.js +1 -0
- package/dist/modules/prune/domain/prune-target.d.ts +3 -0
- package/dist/modules/prune/domain/prune-target.js +21 -0
- package/dist/modules/prune/repository/prune-walker.d.ts +7 -0
- package/dist/modules/prune/repository/prune-walker.js +39 -0
- package/dist/modules/prune/use-case/prune-directories.d.ts +28 -0
- package/dist/modules/prune/use-case/prune-directories.js +74 -0
- package/dist/modules/prune/use-case/prune-exports.d.ts +27 -0
- package/dist/modules/prune/use-case/prune-exports.js +74 -0
- package/dist/modules/settings/domain/settings.d.ts +15 -0
- package/dist/modules/settings/domain/settings.js +6 -0
- package/dist/modules/settings/repository/settings-store.d.ts +6 -0
- package/dist/modules/settings/repository/settings-store.js +56 -0
- package/dist/modules/update/domain/update-plan.d.ts +36 -0
- package/dist/modules/update/domain/update-plan.js +69 -0
- package/dist/modules/update/use-case/update-exports.d.ts +21 -0
- package/dist/modules/update/use-case/update-exports.js +136 -0
- package/dist/modules/wiki/domain/wiki-filter.d.ts +9 -0
- package/dist/modules/wiki/domain/wiki-filter.js +11 -0
- package/dist/modules/wiki/domain/wiki-markdown.d.ts +1 -0
- package/dist/modules/wiki/domain/wiki-markdown.js +4 -0
- package/dist/modules/wiki/domain/wiki-path.d.ts +4 -0
- package/dist/modules/wiki/domain/wiki-path.js +25 -0
- package/dist/modules/wiki/domain/wiki-repository.d.ts +5 -0
- package/dist/modules/wiki/domain/wiki-repository.js +1 -0
- package/dist/modules/wiki/domain/wiki.d.ts +10 -0
- package/dist/modules/wiki/domain/wiki.js +1 -0
- package/dist/modules/wiki/repository/backlog-wiki-repository.d.ts +3 -0
- package/dist/modules/wiki/repository/backlog-wiki-repository.js +10 -0
- package/dist/modules/wiki/use-case/export-wikis.d.ts +14 -0
- package/dist/modules/wiki/use-case/export-wikis.js +43 -0
- package/dist/shared/backlog/http-client.d.ts +17 -0
- package/dist/shared/backlog/http-client.js +62 -0
- package/dist/shared/backlog/rate-limiter.d.ts +9 -0
- package/dist/shared/backlog/rate-limiter.js +25 -0
- package/dist/shared/backlog-url.d.ts +1 -0
- package/dist/shared/backlog-url.js +5 -0
- package/dist/shared/config/env.d.ts +3 -0
- package/dist/shared/config/env.js +21 -0
- package/dist/shared/console/progress.d.ts +1 -0
- package/dist/shared/console/progress.js +4 -0
- package/dist/shared/console/prompt.d.ts +3 -0
- package/dist/shared/console/prompt.js +19 -0
- package/dist/shared/file-name.d.ts +2 -0
- package/dist/shared/file-name.js +15 -0
- package/dist/shared/markdown/body-marker.d.ts +3 -0
- package/dist/shared/markdown/body-marker.js +5 -0
- package/dist/shared/ports.d.ts +4 -0
- package/dist/shared/ports.js +1 -0
- package/dist/shared/storage/markdown-store.d.ts +5 -0
- package/dist/shared/storage/markdown-store.js +21 -0
- package/dist/shared/storage/update-log.d.ts +1 -0
- package/dist/{utils/log.js → shared/storage/update-log.js} +1 -5
- package/oclif.manifest.json +118 -26
- package/package.json +15 -19
- package/dist/utils/backlog-api.d.ts +0 -74
- package/dist/utils/backlog-api.js +0 -448
- package/dist/utils/backlog.d.ts +0 -16
- package/dist/utils/backlog.js +0 -34
- package/dist/utils/common.d.ts +0 -25
- package/dist/utils/common.js +0 -54
- package/dist/utils/log.d.ts +0 -6
- package/dist/utils/settings.d.ts +0 -45
- package/dist/utils/settings.js +0 -63
- package/dist/utils/sleep.d.ts +0 -28
- package/dist/utils/sleep.js +0 -42
package/README.md
CHANGED
|
@@ -16,6 +16,12 @@ Backlog のデータをエクスポートするためのコマンドラインツ
|
|
|
16
16
|
* [ドキュメント のエクスポート](#ドキュメント-のエクスポート)
|
|
17
17
|
* [課題・Wiki・ドキュメント の一括エクスポート](#課題wikiドキュメント-の一括エクスポート)
|
|
18
18
|
* [データの更新](#データの更新)
|
|
19
|
+
* [特定の課題だけを再取得(課題キーまたは課題ID)](#特定の課題だけを再取得課題キーまたは課題id)
|
|
20
|
+
* [特定のWikiだけを再取得(Wiki ID)](#特定のwikiだけを再取得wiki-id)
|
|
21
|
+
* [特定のドキュメントだけを再取得(ドキュメントID)](#特定のドキュメントだけを再取得ドキュメントid)
|
|
22
|
+
* [カレントディレクトリ配下の設定ファイルを探索して実行](#カレントディレクトリ配下の設定ファイルを探索して実行)
|
|
23
|
+
* [対象ディレクトリを指定](#対象ディレクトリを指定)
|
|
24
|
+
* [確認プロンプトをスキップ](#確認プロンプトをスキップ)
|
|
19
25
|
* [コマンド](#コマンド)
|
|
20
26
|
* [出力形式](#出力形式)
|
|
21
27
|
* [課題のタイトル](#課題のタイトル)
|
|
@@ -43,7 +49,7 @@ $ npm install -g backlog-exporter
|
|
|
43
49
|
$ backlog-exporter COMMAND
|
|
44
50
|
running command...
|
|
45
51
|
$ backlog-exporter (--version)
|
|
46
|
-
backlog-exporter/0.
|
|
52
|
+
backlog-exporter/1.0.0 linux-x64 node-v26.4.0
|
|
47
53
|
$ backlog-exporter --help [COMMAND]
|
|
48
54
|
USAGE
|
|
49
55
|
$ backlog-exporter COMMAND
|
|
@@ -227,12 +233,14 @@ $ backlog-exporter document --domain example.backlog.jp --projectIdOrKey PROJECT
|
|
|
227
233
|
|
|
228
234
|
- Backlogのドキュメントツリーの階層構造がそのままローカルディレクトリ構造として再現されます
|
|
229
235
|
- フォルダはディレクトリとして作成され、ドキュメントはMarkdownファイルとして保存されます
|
|
236
|
+
- 子を持つ親ドキュメント自身が本文を持つ場合は、フォルダ内の `00_index.md` として保存されます(本文が空の親はファイルを作成しません)
|
|
230
237
|
|
|
231
238
|
### 出力例
|
|
232
239
|
|
|
233
240
|
```
|
|
234
241
|
documents/
|
|
235
242
|
├── プロジェクト概要/
|
|
243
|
+
│ ├── 00_index.md ← 親ドキュメント「プロジェクト概要」自身の本文
|
|
236
244
|
│ ├── 要件定義書.md
|
|
237
245
|
│ └── 仕様書.md
|
|
238
246
|
├── 設計書/
|
|
@@ -248,6 +256,7 @@ documents/
|
|
|
248
256
|
### 特徴
|
|
249
257
|
|
|
250
258
|
- **階層構造の完全再現**: Backlogのフォルダ階層がそのまま保持されます
|
|
259
|
+
- **親ドキュメント本文の保存**: 子を持つ親ドキュメントの本文もフォルダ内の `00_index.md` として保存されます
|
|
251
260
|
- **重複処理の防止**: 同じドキュメントが複数回処理されることを防ぎます
|
|
252
261
|
- **ファイル名の自動サニタイズ**: 不正な文字を自動的に除去して安全なファイル名を生成します
|
|
253
262
|
- **メタデータの保持**: 作成者、更新者、タグ、添付ファイル情報なども含めて保存されます
|
|
@@ -286,6 +295,15 @@ $ backlog-exporter all --domain example.backlog.jp --projectIdOrKey PROJECT_KEY
|
|
|
286
295
|
|
|
287
296
|
## エクスポート対象の制御
|
|
288
297
|
|
|
298
|
+
`--only` / `--exclude` を指定せずに対話端末で実行した場合は、Wiki・ドキュメントのどちらを取得するかを選択できます(BacklogはWikiからドキュメントへの移行を予定しています):
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
BacklogのWikiとドキュメントのどちらを取得しますか?(BacklogはWikiからドキュメントへの移行を予定しています)
|
|
302
|
+
[1] 両方(デフォルト) [2] Wikiのみ [3] ドキュメントのみ
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
非対話環境(CI・パイプ実行)では従来どおり両方を取得します。
|
|
306
|
+
|
|
289
307
|
`all`コマンドでは、以下のフラグを使用してエクスポート対象を制御できます:
|
|
290
308
|
|
|
291
309
|
### --only フラグ
|
|
@@ -368,8 +386,51 @@ $ backlog-exporter update --documentsOnly
|
|
|
368
386
|
$ backlog-exporter update --apiKey YOUR_API_KEY
|
|
369
387
|
```
|
|
370
388
|
|
|
389
|
+
**指定した項目(課題・Wiki・ドキュメント)のみを再取得する**
|
|
390
|
+
|
|
391
|
+
```sh
|
|
392
|
+
# 特定の課題だけを再取得(課題キーまたは課題ID)
|
|
393
|
+
$ backlog-exporter update --issueIdOrKey PROJECT-1,PROJECT-2
|
|
394
|
+
|
|
395
|
+
# 特定のWikiだけを再取得(Wiki ID)
|
|
396
|
+
$ backlog-exporter update --wikiId 12345,12346
|
|
397
|
+
|
|
398
|
+
# 特定のドキュメントだけを再取得(ドキュメントID)
|
|
399
|
+
$ backlog-exporter update --documentId abc123,def456
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
`update` は通常、設定ファイルの最終更新日時(`lastUpdated`)以降に更新された項目を差分取得します。一方で「特定の項目だけを取り直したい」場合は、上記のID指定フラグを使います。
|
|
403
|
+
|
|
404
|
+
- `--issueIdOrKey`: 課題キー(`PROJECT-1`)または課題ID(数値)。Wiki・ドキュメントは数値IDのみのため `--wikiId` / `--documentId` を使います
|
|
405
|
+
- いずれもカンマ区切りで複数指定できます
|
|
406
|
+
- 指定したフラグに対応する項目のみを再取得し、それ以外の種別の更新は行いません(例: `--wikiId` のみ指定時は課題・ドキュメントを更新しません)
|
|
407
|
+
- 指定した項目以外のローカルファイルには影響しません
|
|
408
|
+
|
|
409
|
+
> **Note**: これらのID指定フラグは全件差分更新ではないため、設定ファイルの最終更新日時(`lastUpdated`)は更新されません。そのため、次回の通常の差分更新に影響を与えません。
|
|
410
|
+
|
|
371
411
|
更新コマンドは、各ディレクトリの設定ファイルに基づいて、課題・Wiki・ドキュメントを自動的に更新します。設定ファイルが見つかったディレクトリでは、そのディレクトリ内のファイルが直接更新されます(サブフォルダは作成されません)。
|
|
372
412
|
|
|
413
|
+
**Backlog上で削除・移動された課題・ドキュメント・Wikiのローカルファイルを削除する(prune)**
|
|
414
|
+
|
|
415
|
+
```sh
|
|
416
|
+
# カレントディレクトリ配下の設定ファイルを探索して実行
|
|
417
|
+
$ backlog-exporter prune
|
|
418
|
+
|
|
419
|
+
# 対象ディレクトリを指定
|
|
420
|
+
$ backlog-exporter prune ./backlog-documents
|
|
421
|
+
|
|
422
|
+
# 確認プロンプトをスキップ
|
|
423
|
+
$ backlog-exporter prune --force
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
`update`(および各取得コマンド)は増分更新(追加・上書き)のため、Backlog上で削除された課題・ドキュメント・Wikiや別の場所へ移動されたものは、ローカルにファイルが残り続けます。`prune` コマンドは、Backlogに存在しないローカルの `.md` ファイルと、空になったディレクトリを削除してBacklogと同じ状態に揃えます。
|
|
427
|
+
|
|
428
|
+
- 対象は**課題・ドキュメント・Wikiフォルダ**です(設定ファイルの `folderType` で判定します)
|
|
429
|
+
- ファイルを削除する破壊的な操作のため、`update` とは独立したコマンドとして分離しています(実行時に確認プロンプトを表示。`--force` でスキップ可能)
|
|
430
|
+
- 削除対象は `.md` ファイルのみ。`backlog-settings.json`・`backlog-update.log`・`.md` 以外のファイルには触れません。ただし、対象フォルダ内にユーザーが独自に置いた `.md` ファイルや空のディレクトリは、Backlog上に存在しないものとして削除されるため注意してください
|
|
431
|
+
- ファイル名は保存時と同じロジックで比較します(ドキュメントは一覧APIの `title` 基準、課題は設定ファイルの `issueKeyFileName`/`issueKeyFolder` を反映)。サニタイズ差異やツリー名との差異による誤削除は起きません。ドキュメント情報の取得に失敗した場合は、誤削除を防ぐため何も削除せずに中止します
|
|
432
|
+
- 削除したファイルは `backlog-update.log` に記録されます
|
|
433
|
+
|
|
373
434
|
# コマンド
|
|
374
435
|
|
|
375
436
|
## `backlog-exporter help [COMMAND]`
|
|
@@ -686,6 +747,25 @@ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/
|
|
|
686
747
|
|
|
687
748
|
# 出力形式
|
|
688
749
|
|
|
750
|
+
## 本文マーカー
|
|
751
|
+
|
|
752
|
+
課題の詳細・Wikiの本文・ドキュメントの内容といった「本文」部分は、種別を問わず以下のHTMLコメントマーカーで囲まれて出力されます。
|
|
753
|
+
|
|
754
|
+
```markdown
|
|
755
|
+
<!-- backlog-exporter:body:start -->
|
|
756
|
+
ここが本文(## 見出しなどを含んでもよい)
|
|
757
|
+
<!-- backlog-exporter:body:end -->
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
- マーカーはMarkdownのレンダリング時には表示されません(HTMLコメント)
|
|
761
|
+
- 本文自体が `##` 見出しなどを含んでいても、開始・終了マーカー間を本文として機械的に抽出・差し替えできます
|
|
762
|
+
- 本文をBacklogへ書き戻す(API反映する)ツールなどが、どこからどこまでが本文かを一意に判定するために使えます
|
|
763
|
+
- 子を持つ親ドキュメントの本文(`00_index.md`)にも同じマーカーが付きます
|
|
764
|
+
|
|
765
|
+
**抽出時の規約**: 本文自体がマーカーと同じ文字列を含む可能性があるため(例: このツールの使い方をBacklog上にメモした場合)、抽出ツールは「ファイル内で**最初に現れる開始マーカー**から**最後に現れる終了マーカー**まで」を本文とみなしてください。
|
|
766
|
+
|
|
767
|
+
以降の出力例では、この本文マーカーを含めた形を示します。
|
|
768
|
+
|
|
689
769
|
## 課題の出力形式
|
|
690
770
|
|
|
691
771
|
課題は以下の形式で Markdown ファイルとして保存されます:
|
|
@@ -714,7 +794,9 @@ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/
|
|
|
714
794
|
|
|
715
795
|
## 詳細
|
|
716
796
|
|
|
797
|
+
<!-- backlog-exporter:body:start -->
|
|
717
798
|
ここに課題の詳細説明が入ります。
|
|
799
|
+
<!-- backlog-exporter:body:end -->
|
|
718
800
|
|
|
719
801
|
## コメント
|
|
720
802
|
|
|
@@ -744,8 +826,10 @@ Wiki は以下の形式で Markdown ファイルとして保存されます:
|
|
|
744
826
|
|
|
745
827
|
[Backlog Wiki Link](https://example.backlog.jp/alias/wiki/12345)
|
|
746
828
|
|
|
829
|
+
<!-- backlog-exporter:body:start -->
|
|
747
830
|
ここに Wiki の本文内容が入ります。
|
|
748
831
|
Backlog の書式がそのまま保持されます。
|
|
832
|
+
<!-- backlog-exporter:body:end -->
|
|
749
833
|
```
|
|
750
834
|
|
|
751
835
|
## ドキュメント の出力形式
|
|
@@ -765,8 +849,10 @@ Backlog の書式がそのまま保持されます。
|
|
|
765
849
|
|
|
766
850
|
## 内容
|
|
767
851
|
|
|
852
|
+
<!-- backlog-exporter:body:start -->
|
|
768
853
|
ここにドキュメントの本文内容が入ります。
|
|
769
854
|
Backlog の書式がそのまま保持されます。
|
|
855
|
+
<!-- backlog-exporter:body:end -->
|
|
770
856
|
|
|
771
857
|
## 添付ファイル
|
|
772
858
|
|
package/bin/dev.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --
|
|
1
|
+
#!/usr/bin/env -S node --import tsx --disable-warning=ExperimentalWarning
|
|
2
2
|
|
|
3
3
|
import {execute} from '@oclif/core'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// execute({development: true}) はエラー時のスタックトレース表示も有効化してしまうため、
|
|
6
|
+
// srcのTS解決に必要な NODE_ENV のみを設定する
|
|
7
|
+
process.env.NODE_ENV = 'development'
|
|
8
|
+
|
|
9
|
+
await execute({dir: import.meta.url})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { FolderType, updateSettings } from '../../utils/settings.js';
|
|
2
|
+
import { createBacklogRepositories } from '../../composition/backlog-repositories.js';
|
|
3
|
+
import { applyContentSelection, parseContentSelection } from '../../modules/all/domain/content-selection.js';
|
|
4
|
+
import { exportAll } from '../../modules/all/use-case/export-all.js';
|
|
5
|
+
import { API_KEY_NOT_FOUND_MESSAGE, loadDotenv, resolveApiKey } from '../../shared/config/env.js';
|
|
6
|
+
import { isInteractiveStdin, readLine } from '../../shared/console/prompt.js';
|
|
8
7
|
// .envファイルを読み込む
|
|
9
|
-
|
|
8
|
+
loadDotenv();
|
|
9
|
+
const VALID_TARGETS = ['issues', 'wiki', 'documents'];
|
|
10
10
|
export default class All extends Command {
|
|
11
11
|
static description = 'Backlogから課題・Wiki・ドキュメントを取得してMarkdownファイルとして保存する';
|
|
12
12
|
static examples = [
|
|
@@ -80,131 +80,67 @@ export default class All extends Command {
|
|
|
80
80
|
const { flags } = await this.parse(All);
|
|
81
81
|
try {
|
|
82
82
|
const { domain, exclude, issueKeyFileName, issueKeyFolder, maxCount, only, projectIdOrKey } = flags;
|
|
83
|
-
const apiKey = flags.apiKey
|
|
83
|
+
const apiKey = resolveApiKey(flags.apiKey, () => this.log('環境変数 BACKLOG_API_KEY からAPIキーを使用します')) ??
|
|
84
|
+
this.error(API_KEY_NOT_FOUND_MESSAGE);
|
|
84
85
|
const outputRoot = flags.output || './backlog-data';
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
let targets = this.determineTargets(only, exclude);
|
|
87
|
+
// --only / --exclude 未指定の対話実行では、Wiki・ドキュメントのどちらを取得するか選択できるようにする
|
|
88
|
+
if (!only && !exclude && isInteractiveStdin()) {
|
|
89
|
+
targets = applyContentSelection(targets, await this.promptContentSelection());
|
|
88
90
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (!validTargets.includes(target)) {
|
|
107
|
-
this.error(`Invalid target '${target}'. Available targets are: ${validTargets.join(', ')}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// Check if any targets remain after exclusion
|
|
111
|
-
if (targets.length === 0) {
|
|
112
|
-
this.error('No targets remaining after exclusion. Please specify valid targets to export.');
|
|
113
|
-
}
|
|
114
|
-
// 出力ディレクトリの作成
|
|
115
|
-
await createOutputDirectory(outputRoot);
|
|
116
|
-
// プロジェクトキーからプロジェクトIDを取得
|
|
117
|
-
const projectId = await validateAndGetProjectId(domain, projectIdOrKey, apiKey);
|
|
118
|
-
this.log(`プロジェクトID: ${projectId} を使用します`);
|
|
119
|
-
if (targets.includes('issues')) {
|
|
120
|
-
// 課題の出力ディレクトリ
|
|
121
|
-
const issueOutput = path.join(outputRoot, 'issues');
|
|
122
|
-
await createOutputDirectory(issueOutput);
|
|
123
|
-
// 課題フォルダに設定ファイルを保存
|
|
124
|
-
await updateSettings(issueOutput, {
|
|
125
|
-
apiKey,
|
|
126
|
-
domain,
|
|
127
|
-
folderType: FolderType.ISSUE,
|
|
128
|
-
issueKeyFileName,
|
|
129
|
-
issueKeyFolder,
|
|
130
|
-
outputDir: issueOutput,
|
|
131
|
-
projectIdOrKey,
|
|
132
|
-
});
|
|
133
|
-
// 課題の取得と保存
|
|
134
|
-
this.log('課題の取得を開始します...');
|
|
135
|
-
await downloadIssues(this, {
|
|
136
|
-
apiKey,
|
|
137
|
-
count: maxCount,
|
|
138
|
-
domain,
|
|
139
|
-
issueKeyFileName,
|
|
140
|
-
issueKeyFolder,
|
|
141
|
-
outputDir: issueOutput,
|
|
142
|
-
projectId,
|
|
143
|
-
});
|
|
144
|
-
// 課題フォルダの最終更新日時を更新
|
|
145
|
-
await updateSettings(issueOutput, {
|
|
146
|
-
lastUpdated: new Date().toISOString(),
|
|
147
|
-
});
|
|
148
|
-
this.log('課題の取得が完了しました');
|
|
149
|
-
}
|
|
150
|
-
if (targets.includes('wiki')) {
|
|
151
|
-
// Wikiの出力ディレクトリ
|
|
152
|
-
const wikiOutput = path.join(outputRoot, 'wiki');
|
|
153
|
-
await createOutputDirectory(wikiOutput);
|
|
154
|
-
// Wikiフォルダに設定ファイルを保存
|
|
155
|
-
await updateSettings(wikiOutput, {
|
|
156
|
-
apiKey,
|
|
157
|
-
domain,
|
|
158
|
-
folderType: FolderType.WIKI,
|
|
159
|
-
outputDir: wikiOutput,
|
|
160
|
-
projectIdOrKey,
|
|
161
|
-
});
|
|
162
|
-
// Wikiの取得と保存
|
|
163
|
-
this.log('Wikiの取得を開始します...');
|
|
164
|
-
await downloadWikis(this, {
|
|
165
|
-
apiKey,
|
|
166
|
-
domain,
|
|
167
|
-
outputDir: wikiOutput,
|
|
168
|
-
projectIdOrKey,
|
|
169
|
-
});
|
|
170
|
-
// Wikiフォルダの最終更新日時を更新
|
|
171
|
-
await updateSettings(wikiOutput, {
|
|
172
|
-
lastUpdated: new Date().toISOString(),
|
|
173
|
-
});
|
|
174
|
-
this.log('Wikiの取得が完了しました');
|
|
175
|
-
}
|
|
176
|
-
if (targets.includes('documents')) {
|
|
177
|
-
// ドキュメントの出力ディレクトリ
|
|
178
|
-
const documentOutput = path.join(outputRoot, 'documents');
|
|
179
|
-
await createOutputDirectory(documentOutput);
|
|
180
|
-
// ドキュメントフォルダに設定ファイルを保存
|
|
181
|
-
await updateSettings(documentOutput, {
|
|
182
|
-
apiKey,
|
|
183
|
-
domain,
|
|
184
|
-
folderType: FolderType.DOCUMENT,
|
|
185
|
-
outputDir: documentOutput,
|
|
186
|
-
projectIdOrKey,
|
|
187
|
-
});
|
|
188
|
-
// ドキュメントの取得と保存
|
|
189
|
-
this.log('ドキュメントの取得を開始します...');
|
|
190
|
-
await downloadDocuments(this, {
|
|
191
|
-
apiKey,
|
|
192
|
-
domain,
|
|
193
|
-
outputDir: documentOutput,
|
|
194
|
-
projectId,
|
|
195
|
-
projectIdOrKey,
|
|
196
|
-
});
|
|
197
|
-
// ドキュメントフォルダの最終更新日時を更新
|
|
198
|
-
await updateSettings(documentOutput, {
|
|
199
|
-
lastUpdated: new Date().toISOString(),
|
|
200
|
-
});
|
|
201
|
-
this.log('ドキュメントの取得が完了しました');
|
|
202
|
-
}
|
|
203
|
-
this.log('すべてのデータの取得が完了しました!');
|
|
91
|
+
this.log(`取得対象: ${targets.join(', ')}`);
|
|
92
|
+
const logger = { log: (message) => this.log(message), warn: (message) => this.warn(message) };
|
|
93
|
+
const repositories = createBacklogRepositories({
|
|
94
|
+
apiKey,
|
|
95
|
+
domain,
|
|
96
|
+
onRateLimitWait: () => this.log('レート制限を回避するため15秒間待機します...'),
|
|
97
|
+
});
|
|
98
|
+
await exportAll({ ...repositories, logger }, {
|
|
99
|
+
apiKey,
|
|
100
|
+
domain,
|
|
101
|
+
issueKeyFileName,
|
|
102
|
+
issueKeyFolder,
|
|
103
|
+
maxCount,
|
|
104
|
+
outputRoot,
|
|
105
|
+
projectIdOrKey,
|
|
106
|
+
targets,
|
|
107
|
+
});
|
|
204
108
|
}
|
|
205
109
|
catch (error) {
|
|
206
110
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
207
111
|
this.error(`データの取得に失敗しました: ${errorMessage}`);
|
|
208
112
|
}
|
|
209
113
|
}
|
|
114
|
+
determineTargets(only, exclude) {
|
|
115
|
+
if (only && exclude) {
|
|
116
|
+
this.error('Cannot use both --only and --exclude flags together. Please use only one.');
|
|
117
|
+
}
|
|
118
|
+
// 指定された値の検証
|
|
119
|
+
const inputTargets = only ? only.split(',') : exclude ? exclude.split(',') : [];
|
|
120
|
+
for (const target of inputTargets) {
|
|
121
|
+
if (!VALID_TARGETS.includes(target)) {
|
|
122
|
+
this.error(`Invalid target '${target}'. Available targets are: ${VALID_TARGETS.join(', ')}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let targets;
|
|
126
|
+
if (only) {
|
|
127
|
+
targets = only.split(',');
|
|
128
|
+
}
|
|
129
|
+
else if (exclude) {
|
|
130
|
+
const excludeTargets = exclude.split(',');
|
|
131
|
+
targets = VALID_TARGETS.filter((target) => !excludeTargets.includes(target));
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
targets = [...VALID_TARGETS];
|
|
135
|
+
}
|
|
136
|
+
if (targets.length === 0) {
|
|
137
|
+
this.error('No targets remaining after exclusion. Please specify valid targets to export.');
|
|
138
|
+
}
|
|
139
|
+
return targets;
|
|
140
|
+
}
|
|
141
|
+
async promptContentSelection() {
|
|
142
|
+
this.log('BacklogのWikiとドキュメントのどちらを取得しますか?(BacklogはWikiからドキュメントへの移行を予定しています)');
|
|
143
|
+
this.log(' [1] 両方(デフォルト) [2] Wikiのみ [3] ドキュメントのみ');
|
|
144
|
+
return parseContentSelection(await readLine());
|
|
145
|
+
}
|
|
210
146
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { createBacklogRepositories } from '../../composition/backlog-repositories.js';
|
|
3
|
+
import { exportDocuments } from '../../modules/document/use-case/export-documents.js';
|
|
4
|
+
import { FolderType } from '../../modules/settings/domain/settings.js';
|
|
5
|
+
import { updateSettings } from '../../modules/settings/repository/settings-store.js';
|
|
6
|
+
import { API_KEY_NOT_FOUND_MESSAGE, loadDotenv, resolveApiKey } from '../../shared/config/env.js';
|
|
7
|
+
import { ensureDirectory } from '../../shared/storage/markdown-store.js';
|
|
7
8
|
// .envファイルを読み込む
|
|
8
|
-
|
|
9
|
+
loadDotenv();
|
|
9
10
|
export default class Document extends Command {
|
|
10
11
|
static description = 'Backlogからドキュメントを取得してMarkdownファイルとして保存する';
|
|
11
12
|
static examples = [
|
|
@@ -46,12 +47,19 @@ export default class Document extends Command {
|
|
|
46
47
|
const { flags } = await this.parse(Document);
|
|
47
48
|
try {
|
|
48
49
|
const { domain, keyword, projectIdOrKey } = flags;
|
|
49
|
-
const apiKey = flags.apiKey
|
|
50
|
+
const apiKey = resolveApiKey(flags.apiKey, () => this.log('環境変数 BACKLOG_API_KEY からAPIキーを使用します')) ??
|
|
51
|
+
this.error(API_KEY_NOT_FOUND_MESSAGE);
|
|
50
52
|
const outputDir = flags.output || './backlog-documents';
|
|
53
|
+
const logger = { log: (message) => this.log(message), warn: (message) => this.warn(message) };
|
|
54
|
+
const { documentRepository, projectRepository } = createBacklogRepositories({
|
|
55
|
+
apiKey,
|
|
56
|
+
domain,
|
|
57
|
+
onRateLimitWait: () => this.log('レート制限を回避するため15秒間待機します...'),
|
|
58
|
+
});
|
|
51
59
|
// 出力ディレクトリの作成
|
|
52
|
-
await
|
|
60
|
+
await ensureDirectory(outputDir);
|
|
53
61
|
// プロジェクトキーからプロジェクトIDを取得
|
|
54
|
-
const projectId = await
|
|
62
|
+
const projectId = await projectRepository.resolveProjectId(projectIdOrKey);
|
|
55
63
|
this.log(`プロジェクトID: ${projectId} を使用します`);
|
|
56
64
|
// 設定ファイルを保存
|
|
57
65
|
await updateSettings(outputDir, {
|
|
@@ -62,8 +70,7 @@ export default class Document extends Command {
|
|
|
62
70
|
projectIdOrKey,
|
|
63
71
|
});
|
|
64
72
|
// ドキュメントの取得と保存
|
|
65
|
-
await
|
|
66
|
-
apiKey,
|
|
73
|
+
await exportDocuments({ documentRepository, logger }, {
|
|
67
74
|
domain,
|
|
68
75
|
keyword,
|
|
69
76
|
outputDir,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { createBacklogRepositories } from '../../composition/backlog-repositories.js';
|
|
3
|
+
import { exportIssues } from '../../modules/issue/use-case/export-issues.js';
|
|
4
|
+
import { FolderType } from '../../modules/settings/domain/settings.js';
|
|
5
|
+
import { updateSettings } from '../../modules/settings/repository/settings-store.js';
|
|
6
|
+
import { API_KEY_NOT_FOUND_MESSAGE, loadDotenv, resolveApiKey } from '../../shared/config/env.js';
|
|
7
|
+
import { ensureDirectory } from '../../shared/storage/markdown-store.js';
|
|
7
8
|
// .envファイルを読み込む
|
|
8
|
-
|
|
9
|
+
loadDotenv();
|
|
9
10
|
export default class Issue extends Command {
|
|
10
11
|
static description = 'Backlogから課題を取得してMarkdownファイルとして保存する';
|
|
11
12
|
static examples = [
|
|
@@ -72,12 +73,19 @@ export default class Issue extends Command {
|
|
|
72
73
|
const { flags } = await this.parse(Issue);
|
|
73
74
|
try {
|
|
74
75
|
const { domain, issueKeyFileName, issueKeyFolder, maxCount, projectIdOrKey, statusId } = flags;
|
|
75
|
-
const apiKey = flags.apiKey
|
|
76
|
+
const apiKey = resolveApiKey(flags.apiKey, () => this.log('環境変数 BACKLOG_API_KEY からAPIキーを使用します')) ??
|
|
77
|
+
this.error(API_KEY_NOT_FOUND_MESSAGE);
|
|
76
78
|
const outputDir = flags.output || './backlog-issues';
|
|
79
|
+
const logger = { log: (message) => this.log(message), warn: (message) => this.warn(message) };
|
|
80
|
+
const { issueRepository, projectRepository } = createBacklogRepositories({
|
|
81
|
+
apiKey,
|
|
82
|
+
domain,
|
|
83
|
+
onRateLimitWait: () => this.log('レート制限を回避するため15秒間待機します...'),
|
|
84
|
+
});
|
|
77
85
|
// 出力ディレクトリの作成
|
|
78
|
-
await
|
|
86
|
+
await ensureDirectory(outputDir);
|
|
79
87
|
// プロジェクトキーからプロジェクトIDを取得
|
|
80
|
-
const projectId = await
|
|
88
|
+
const projectId = await projectRepository.resolveProjectId(projectIdOrKey);
|
|
81
89
|
this.log(`プロジェクトID: ${projectId} を使用します`);
|
|
82
90
|
// 設定ファイルを保存
|
|
83
91
|
await updateSettings(outputDir, {
|
|
@@ -90,8 +98,7 @@ export default class Issue extends Command {
|
|
|
90
98
|
projectIdOrKey,
|
|
91
99
|
});
|
|
92
100
|
// 課題の取得と保存
|
|
93
|
-
await
|
|
94
|
-
apiKey,
|
|
101
|
+
await exportIssues({ issueRepository, logger }, {
|
|
95
102
|
count: maxCount,
|
|
96
103
|
domain,
|
|
97
104
|
issueKeyFileName,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Prune extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
directory: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
apiKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
projectIdOrKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
private confirmPrune;
|
|
16
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { createBacklogRepositories } from '../../composition/backlog-repositories.js';
|
|
4
|
+
import { pruneDirectories } from '../../modules/prune/use-case/prune-directories.js';
|
|
5
|
+
import { loadDotenv } from '../../shared/config/env.js';
|
|
6
|
+
import { isInteractiveStdin, readYesNo } from '../../shared/console/prompt.js';
|
|
7
|
+
// .envファイルを読み込む
|
|
8
|
+
loadDotenv();
|
|
9
|
+
export default class Prune extends Command {
|
|
10
|
+
static args = {
|
|
11
|
+
directory: Args.string({
|
|
12
|
+
description: '対象ディレクトリ(設定ファイルが保存されている場所)',
|
|
13
|
+
required: false,
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
static description = 'Backlog上で削除・移動された課題・ドキュメント・Wikiのローカルファイルを削除し、Backlogと同じ状態に揃える';
|
|
17
|
+
static examples = [
|
|
18
|
+
`<%= config.bin %> <%= command.id %>
|
|
19
|
+
カレントディレクトリ配下の設定ファイルを探索し、Backlog上に存在しないドキュメントファイルを削除する
|
|
20
|
+
`,
|
|
21
|
+
`<%= config.bin %> <%= command.id %> ./backlog-documents
|
|
22
|
+
指定したディレクトリを対象にする
|
|
23
|
+
`,
|
|
24
|
+
`<%= config.bin %> <%= command.id %> --force
|
|
25
|
+
確認プロンプトをスキップして削除する
|
|
26
|
+
`,
|
|
27
|
+
];
|
|
28
|
+
static flags = {
|
|
29
|
+
apiKey: Flags.string({
|
|
30
|
+
description: 'Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)',
|
|
31
|
+
required: false,
|
|
32
|
+
}),
|
|
33
|
+
domain: Flags.string({
|
|
34
|
+
description: 'Backlog domain (e.g. example.backlog.jp)',
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
force: Flags.boolean({
|
|
38
|
+
char: 'f',
|
|
39
|
+
description: '確認プロンプトをスキップする',
|
|
40
|
+
required: false,
|
|
41
|
+
}),
|
|
42
|
+
projectIdOrKey: Flags.string({
|
|
43
|
+
description: 'Backlog project ID or key',
|
|
44
|
+
required: false,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
async run() {
|
|
48
|
+
const { args, flags } = await this.parse(Prune);
|
|
49
|
+
const targetDir = args.directory || process.cwd();
|
|
50
|
+
const logger = { log: (message) => this.log(message), warn: (message) => this.warn(message) };
|
|
51
|
+
try {
|
|
52
|
+
await pruneDirectories({ createRepositories: createBacklogRepositories, logger }, {
|
|
53
|
+
confirmDirectory: (directory) => this.confirmPrune(directory, flags.force ?? false),
|
|
54
|
+
flags: {
|
|
55
|
+
apiKey: flags.apiKey,
|
|
56
|
+
domain: flags.domain,
|
|
57
|
+
projectIdOrKey: flags.projectIdOrKey,
|
|
58
|
+
},
|
|
59
|
+
rootDir: targetDir,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
64
|
+
this.error(`pruneに失敗しました: ${errorMessage}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 削除実行前の確認プロンプト(--force 時はスキップ)
|
|
68
|
+
async confirmPrune(targetDir, force) {
|
|
69
|
+
if (force) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
// 非対話環境(CI・パイプ入力など)では 'data' イベントが発火せず永久に待機してしまうため、
|
|
73
|
+
// プロンプトを出さずにエラーとして終了する
|
|
74
|
+
if (!isInteractiveStdin()) {
|
|
75
|
+
this.error('対話的な確認ができない環境のため中止しました(標準入力が端末ではありません)。--force フラグを付けると確認をスキップして実行できます。');
|
|
76
|
+
}
|
|
77
|
+
this.log('以下のディレクトリで、Backlog上に存在しない課題・ドキュメント・Wiki(.mdファイル)を削除します:');
|
|
78
|
+
this.log(`- ディレクトリ: ${targetDir}`);
|
|
79
|
+
this.log('削除を実行しますか? (y/n)');
|
|
80
|
+
const response = await readYesNo();
|
|
81
|
+
if (!response) {
|
|
82
|
+
this.log('pruneをキャンセルしました');
|
|
83
|
+
}
|
|
84
|
+
return response;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -7,21 +7,17 @@ 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
|
+
documentId: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
documentsOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
issueIdOrKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
15
|
issueKeyFileName: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
16
|
issueKeyFolder: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
17
|
issuesOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
18
|
projectIdOrKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
wikiId: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
20
|
wikisOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
21
|
};
|
|
19
22
|
run(): Promise<void>;
|
|
20
|
-
private confirmUpdate;
|
|
21
|
-
private determineUpdateTargets;
|
|
22
|
-
private findAndUpdateSettings;
|
|
23
|
-
private updateDirectory;
|
|
24
|
-
private updateDocuments;
|
|
25
|
-
private updateIssues;
|
|
26
|
-
private updateWikis;
|
|
27
23
|
}
|