backlog-exporter 0.7.3 → 1.1.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.
Files changed (121) hide show
  1. package/README.md +109 -1
  2. package/bin/dev.js +6 -2
  3. package/dist/commands/all/index.d.ts +3 -0
  4. package/dist/commands/all/index.js +69 -127
  5. package/dist/commands/document/index.d.ts +1 -0
  6. package/dist/commands/document/index.js +26 -12
  7. package/dist/commands/issue/index.d.ts +1 -0
  8. package/dist/commands/issue/index.js +29 -12
  9. package/dist/commands/prune/index.d.ts +16 -0
  10. package/dist/commands/prune/index.js +86 -0
  11. package/dist/commands/update/index.d.ts +4 -7
  12. package/dist/commands/update/index.js +33 -260
  13. package/dist/commands/wiki/index.d.ts +1 -0
  14. package/dist/commands/wiki/index.js +25 -10
  15. package/dist/composition/backlog-repositories.d.ts +16 -0
  16. package/dist/composition/backlog-repositories.js +15 -0
  17. package/dist/modules/all/domain/content-selection.d.ts +4 -0
  18. package/dist/modules/all/domain/content-selection.js +16 -0
  19. package/dist/modules/all/use-case/export-all.d.ts +25 -0
  20. package/dist/modules/all/use-case/export-all.js +84 -0
  21. package/dist/modules/document/domain/document-markdown.d.ts +2 -0
  22. package/dist/modules/document/domain/document-markdown.js +40 -0
  23. package/dist/modules/document/domain/document-path.d.ts +23 -0
  24. package/dist/modules/document/domain/document-path.js +57 -0
  25. package/dist/modules/document/domain/document-repository.d.ts +7 -0
  26. package/dist/modules/document/domain/document-repository.js +1 -0
  27. package/dist/modules/document/domain/document-save-plan.d.ts +9 -0
  28. package/dist/modules/document/domain/document-save-plan.js +15 -0
  29. package/dist/modules/document/domain/document.d.ts +50 -0
  30. package/dist/modules/document/domain/document.js +1 -0
  31. package/dist/modules/document/repository/backlog-document-repository.d.ts +3 -0
  32. package/dist/modules/document/repository/backlog-document-repository.js +35 -0
  33. package/dist/modules/document/use-case/export-documents.d.ts +17 -0
  34. package/dist/modules/document/use-case/export-documents.js +107 -0
  35. package/dist/modules/issue/domain/issue-filter.d.ts +2 -0
  36. package/dist/modules/issue/domain/issue-filter.js +7 -0
  37. package/dist/modules/issue/domain/issue-markdown.d.ts +5 -0
  38. package/dist/modules/issue/domain/issue-markdown.js +127 -0
  39. package/dist/modules/issue/domain/issue-path.d.ts +34 -0
  40. package/dist/modules/issue/domain/issue-path.js +42 -0
  41. package/dist/modules/issue/domain/issue-repository.d.ts +12 -0
  42. package/dist/modules/issue/domain/issue-repository.js +1 -0
  43. package/dist/modules/issue/domain/issue.d.ts +53 -0
  44. package/dist/modules/issue/domain/issue.js +1 -0
  45. package/dist/modules/issue/repository/backlog-issue-repository.d.ts +3 -0
  46. package/dist/modules/issue/repository/backlog-issue-repository.js +41 -0
  47. package/dist/modules/issue/use-case/export-issues.d.ts +19 -0
  48. package/dist/modules/issue/use-case/export-issues.js +113 -0
  49. package/dist/modules/project/domain/project-repository.d.ts +3 -0
  50. package/dist/modules/project/domain/project-repository.js +1 -0
  51. package/dist/modules/project/repository/backlog-project-repository.d.ts +3 -0
  52. package/dist/modules/project/repository/backlog-project-repository.js +17 -0
  53. package/dist/modules/prune/domain/expected-paths.d.ts +4 -0
  54. package/dist/modules/prune/domain/expected-paths.js +1 -0
  55. package/dist/modules/prune/domain/prune-target.d.ts +3 -0
  56. package/dist/modules/prune/domain/prune-target.js +21 -0
  57. package/dist/modules/prune/repository/prune-walker.d.ts +7 -0
  58. package/dist/modules/prune/repository/prune-walker.js +44 -0
  59. package/dist/modules/prune/use-case/prune-directories.d.ts +28 -0
  60. package/dist/modules/prune/use-case/prune-directories.js +74 -0
  61. package/dist/modules/prune/use-case/prune-exports.d.ts +27 -0
  62. package/dist/modules/prune/use-case/prune-exports.js +74 -0
  63. package/dist/modules/settings/domain/settings.d.ts +16 -0
  64. package/dist/modules/settings/domain/settings.js +6 -0
  65. package/dist/modules/settings/repository/settings-store.d.ts +6 -0
  66. package/dist/modules/settings/repository/settings-store.js +56 -0
  67. package/dist/modules/update/domain/update-plan.d.ts +38 -0
  68. package/dist/modules/update/domain/update-plan.js +70 -0
  69. package/dist/modules/update/use-case/update-exports.d.ts +21 -0
  70. package/dist/modules/update/use-case/update-exports.js +139 -0
  71. package/dist/modules/wiki/domain/wiki-filter.d.ts +9 -0
  72. package/dist/modules/wiki/domain/wiki-filter.js +11 -0
  73. package/dist/modules/wiki/domain/wiki-markdown.d.ts +6 -0
  74. package/dist/modules/wiki/domain/wiki-markdown.js +19 -0
  75. package/dist/modules/wiki/domain/wiki-path.d.ts +7 -0
  76. package/dist/modules/wiki/domain/wiki-path.js +37 -0
  77. package/dist/modules/wiki/domain/wiki-repository.d.ts +6 -0
  78. package/dist/modules/wiki/domain/wiki-repository.js +1 -0
  79. package/dist/modules/wiki/domain/wiki.d.ts +16 -0
  80. package/dist/modules/wiki/domain/wiki.js +1 -0
  81. package/dist/modules/wiki/repository/backlog-wiki-repository.d.ts +3 -0
  82. package/dist/modules/wiki/repository/backlog-wiki-repository.js +13 -0
  83. package/dist/modules/wiki/use-case/export-wikis.d.ts +15 -0
  84. package/dist/modules/wiki/use-case/export-wikis.js +71 -0
  85. package/dist/shared/attachment.d.ts +8 -0
  86. package/dist/shared/attachment.js +37 -0
  87. package/dist/shared/backlog/http-client.d.ts +20 -0
  88. package/dist/shared/backlog/http-client.js +93 -0
  89. package/dist/shared/backlog/sleep.d.ts +1 -0
  90. package/dist/shared/backlog/sleep.js +3 -0
  91. package/dist/shared/backlog-url.d.ts +1 -0
  92. package/dist/shared/backlog-url.js +5 -0
  93. package/dist/shared/config/env.d.ts +3 -0
  94. package/dist/shared/config/env.js +21 -0
  95. package/dist/shared/console/progress.d.ts +1 -0
  96. package/dist/shared/console/progress.js +11 -0
  97. package/dist/shared/console/prompt.d.ts +3 -0
  98. package/dist/shared/console/prompt.js +19 -0
  99. package/dist/shared/file-name.d.ts +3 -0
  100. package/dist/shared/file-name.js +26 -0
  101. package/dist/shared/markdown/body-marker.d.ts +3 -0
  102. package/dist/shared/markdown/body-marker.js +5 -0
  103. package/dist/shared/ports.d.ts +4 -0
  104. package/dist/shared/ports.js +1 -0
  105. package/dist/shared/storage/markdown-store.d.ts +7 -0
  106. package/dist/shared/storage/markdown-store.js +32 -0
  107. package/dist/shared/storage/update-log.d.ts +1 -0
  108. package/dist/{utils/log.js → shared/storage/update-log.js} +1 -5
  109. package/oclif.manifest.json +160 -27
  110. package/package.json +15 -19
  111. package/dist/utils/backlog-api.d.ts +0 -74
  112. package/dist/utils/backlog-api.js +0 -453
  113. package/dist/utils/backlog.d.ts +0 -16
  114. package/dist/utils/backlog.js +0 -34
  115. package/dist/utils/common.d.ts +0 -25
  116. package/dist/utils/common.js +0 -54
  117. package/dist/utils/log.d.ts +0 -6
  118. package/dist/utils/settings.d.ts +0 -45
  119. package/dist/utils/settings.js +0 -63
  120. package/dist/utils/sleep.d.ts +0 -28
  121. package/dist/utils/sleep.js +0 -42
@@ -0,0 +1,93 @@
1
+ import { backlogOrigin } from '../backlog-url.js';
2
+ import { sleep } from './sleep.js';
3
+ // fetchは4xx/5xxでthrowしないため、response.okでない場合にこれを投げる
4
+ export class HttpError extends Error {
5
+ status;
6
+ constructor(status, statusText, url) {
7
+ super(`HTTP ${status} ${statusText}: GET ${url}`);
8
+ this.status = status;
9
+ this.name = 'HttpError';
10
+ }
11
+ }
12
+ const RETRYABLE_STATUSES = new Set([408, 500, 502, 503, 504]);
13
+ const MAX_RETRIES = 2;
14
+ // 429待機は1回ごとにレート制限ウィンドウ(1分)が更新されるため、通常リトライとは別枠で上限を設ける
15
+ const MAX_RATE_LIMIT_WAITS = 5;
16
+ const RETRY_BASE_DELAY_MS = 300;
17
+ const RATE_LIMIT_MIN_WAIT_MS = 1000;
18
+ const RATE_LIMIT_FALLBACK_WAIT_MS = 60_000;
19
+ const RATE_LIMIT_MAX_WAIT_MS = 120_000;
20
+ // レート制限(429)はウィンドウが1分単位のため、短いbackoffではなく
21
+ // X-RateLimit-Reset(UNIX秒)まで待つ。ヘッダーが無い/不正な場合は1分待つ
22
+ export function rateLimitWaitMs(resetHeader, nowMs) {
23
+ const resetSeconds = Number(resetHeader);
24
+ if (!resetHeader || !Number.isFinite(resetSeconds))
25
+ return RATE_LIMIT_FALLBACK_WAIT_MS;
26
+ // リセット直後の境界ずれで再度429にならないよう1秒の余裕を持たせる
27
+ const waitMs = resetSeconds * 1000 - nowMs + 1000;
28
+ return Math.min(Math.max(waitMs, RATE_LIMIT_MIN_WAIT_MS), RATE_LIMIT_MAX_WAIT_MS);
29
+ }
30
+ // apiKey付与・429時のX-RateLimit-Resetまでの待機・一時エラーのリトライを担うBacklog APIクライアント
31
+ export class BacklogHttpClient {
32
+ apiKey;
33
+ baseUrl;
34
+ onRateLimitExceeded;
35
+ constructor(options) {
36
+ this.apiKey = options.apiKey;
37
+ this.baseUrl = `${backlogOrigin(options.domain)}/api/v2`;
38
+ this.onRateLimitExceeded = options.onRateLimitExceeded;
39
+ }
40
+ async getBinary(pathname, params = {}) {
41
+ const response = await this.fetchWithRetry(this.requestUrl(pathname, params));
42
+ return response.arrayBuffer();
43
+ }
44
+ async getJson(pathname, params = {}) {
45
+ const response = await this.fetchWithRetry(this.requestUrl(pathname, params));
46
+ return (await response.json());
47
+ }
48
+ // リトライのため、ループ内のawaitは意図したもの
49
+ /* eslint-disable no-await-in-loop */
50
+ async fetchWithRetry(url) {
51
+ let rateLimitWaits = 0;
52
+ for (let attempt = 0;;) {
53
+ let response;
54
+ try {
55
+ response = await fetch(url);
56
+ }
57
+ catch (error) {
58
+ // ネットワークエラー
59
+ if (attempt < MAX_RETRIES) {
60
+ await sleep(RETRY_BASE_DELAY_MS * 2 ** attempt);
61
+ attempt++;
62
+ continue;
63
+ }
64
+ throw error;
65
+ }
66
+ if (response.ok) {
67
+ return response;
68
+ }
69
+ // 429は待機すれば解消する見込みが高いため、通常のリトライ回数を消費しない
70
+ if (response.status === 429 && rateLimitWaits < MAX_RATE_LIMIT_WAITS) {
71
+ const waitMs = rateLimitWaitMs(response.headers.get('x-ratelimit-reset'), Date.now());
72
+ this.onRateLimitExceeded?.(Math.ceil(waitMs / 1000));
73
+ await sleep(waitMs);
74
+ rateLimitWaits++;
75
+ continue;
76
+ }
77
+ if (attempt < MAX_RETRIES && RETRYABLE_STATUSES.has(response.status)) {
78
+ await sleep(RETRY_BASE_DELAY_MS * 2 ** attempt);
79
+ attempt++;
80
+ continue;
81
+ }
82
+ throw new HttpError(response.status, response.statusText, this.maskApiKey(url));
83
+ }
84
+ }
85
+ /* eslint-enable no-await-in-loop */
86
+ maskApiKey(url) {
87
+ return url.replace(/apiKey=[^&]*/, 'apiKey=***');
88
+ }
89
+ requestUrl(pathname, params) {
90
+ const searchParams = new URLSearchParams({ apiKey: this.apiKey, ...params });
91
+ return `${this.baseUrl}${pathname}?${searchParams.toString()}`;
92
+ }
93
+ }
@@ -0,0 +1 @@
1
+ export declare const sleep: (ms: number) => Promise<void>;
@@ -0,0 +1,3 @@
1
+ export const sleep = (ms) => new Promise((resolve) => {
2
+ setTimeout(resolve, ms);
3
+ });
@@ -0,0 +1 @@
1
+ export declare function backlogOrigin(domain: string): string;
@@ -0,0 +1,5 @@
1
+ // 通常はBacklogドメイン(example.backlog.jp)を https:// として扱う。
2
+ // テストのモックサーバやセルフホスト向けにプロトコル付き(http://127.0.0.1:8080 等)も許可する
3
+ export function backlogOrigin(domain) {
4
+ return /^https?:\/\//.test(domain) ? domain : `https://${domain}`;
5
+ }
@@ -0,0 +1,3 @@
1
+ export declare function loadDotenv(): void;
2
+ export declare function resolveApiKey(provided?: string, onEnvUsed?: () => void): string | undefined;
3
+ export declare const API_KEY_NOT_FOUND_MESSAGE = "API\u30AD\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002--apiKey \u30D5\u30E9\u30B0\u307E\u305F\u306F BACKLOG_API_KEY \u74B0\u5883\u5909\u6570\u3067\u63D0\u4F9B\u3057\u3066\u304F\u3060\u3055\u3044";
@@ -0,0 +1,21 @@
1
+ import process from 'node:process';
2
+ export function loadDotenv() {
3
+ try {
4
+ process.loadEnvFile();
5
+ }
6
+ catch {
7
+ // .env が存在しない場合は何もしない
8
+ }
9
+ }
10
+ export function resolveApiKey(provided, onEnvUsed) {
11
+ if (provided) {
12
+ return provided;
13
+ }
14
+ const envApiKey = process.env.BACKLOG_API_KEY;
15
+ if (envApiKey) {
16
+ onEnvUsed?.();
17
+ return envApiKey;
18
+ }
19
+ return undefined;
20
+ }
21
+ export const API_KEY_NOT_FOUND_MESSAGE = 'APIキーが見つかりません。--apiKey フラグまたは BACKLOG_API_KEY 環境変数で提供してください';
@@ -0,0 +1 @@
1
+ export declare function writeProgress(message: string): void;
@@ -0,0 +1,11 @@
1
+ import process from 'node:process';
2
+ export function writeProgress(message) {
3
+ if (process.stdout.isTTY) {
4
+ // ESC[K でカーソル位置から行末まで消去し、前の長いメッセージの残骸を防ぐ
5
+ process.stdout.write(`\r\u001B[K${message}`);
6
+ }
7
+ else {
8
+ // 非TTY(CIログ・パイプ)では \r が効かず1行に連結されるため改行して出力する
9
+ process.stdout.write(`${message}\n`);
10
+ }
11
+ }
@@ -0,0 +1,3 @@
1
+ export declare function isInteractiveStdin(): boolean;
2
+ export declare function readLine(): Promise<string>;
3
+ export declare function readYesNo(): Promise<boolean>;
@@ -0,0 +1,19 @@
1
+ import process from 'node:process';
2
+ // 非対話環境では'data'イベントが発火せず確認プロンプトが永久に待機するため、表示前に必ず確認する
3
+ export function isInteractiveStdin() {
4
+ return Boolean(process.stdin.isTTY);
5
+ }
6
+ export async function readLine() {
7
+ process.stdin.resume();
8
+ process.stdin.setEncoding('utf8');
9
+ return new Promise((resolve) => {
10
+ process.stdin.once('data', (data) => {
11
+ resolve(data.toString().trim());
12
+ process.stdin.pause();
13
+ });
14
+ });
15
+ }
16
+ export async function readYesNo() {
17
+ const input = (await readLine()).toLowerCase();
18
+ return input === 'y' || input === 'yes';
19
+ }
@@ -0,0 +1,3 @@
1
+ export declare function sanitizeFileName(name: string): string;
2
+ export declare function sanitizeAttachmentFileName(name: string): string;
3
+ export declare function sanitizeWikiFileName(name: string): string;
@@ -0,0 +1,26 @@
1
+ export function sanitizeFileName(name) {
2
+ return name
3
+ .replaceAll(/[\\/:*?"<>|]/g, '_') // Windowsで使用できない文字を置換
4
+ .replaceAll(/\s+/g, '_') // スペースをアンダースコアに置換
5
+ .replaceAll('.', '_') // ドットを置換
6
+ .slice(0, 200); // 長すぎるファイル名を防ぐために200文字に制限
7
+ }
8
+ // 添付ファイル用: sanitizeFileNameと異なり、拡張子が消えないようドットを保持する
9
+ export function sanitizeAttachmentFileName(name) {
10
+ const sanitized = name.replaceAll(/[\\/:*?"<>|]/g, '_').replaceAll(/\s+/g, '_');
11
+ if (sanitized.length <= 200)
12
+ return sanitized;
13
+ const dotIndex = sanitized.lastIndexOf('.');
14
+ const extension = dotIndex > 0 ? sanitized.slice(dotIndex) : '';
15
+ if (extension.length === 0 || extension.length >= 200)
16
+ return sanitized.slice(0, 200);
17
+ return sanitized.slice(0, 200 - extension.length) + extension;
18
+ }
19
+ export function sanitizeWikiFileName(name) {
20
+ const invalidChars = ['\\', ':', '*', '?', '"', '<', '>', '|'];
21
+ let sanitizedName = name;
22
+ for (const char of invalidChars) {
23
+ sanitizedName = sanitizedName.replaceAll(char, '_');
24
+ }
25
+ return sanitizedName;
26
+ }
@@ -0,0 +1,3 @@
1
+ export declare const BODY_START_MARKER = "<!-- backlog-exporter:body:start -->";
2
+ export declare const BODY_END_MARKER = "<!-- backlog-exporter:body:end -->";
3
+ export declare function wrapBody(body: string): string;
@@ -0,0 +1,5 @@
1
+ export const BODY_START_MARKER = '<!-- backlog-exporter:body:start -->';
2
+ export const BODY_END_MARKER = '<!-- backlog-exporter:body:end -->';
3
+ export function wrapBody(body) {
4
+ return `${BODY_START_MARKER}\n${body}\n${BODY_END_MARKER}`;
5
+ }
@@ -0,0 +1,4 @@
1
+ export interface Logger {
2
+ log(message: string): void;
3
+ warn(message: string): void;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export declare function ensureDirectory(directory: string): Promise<void>;
2
+ export declare function fileExists(filePath: string): Promise<boolean>;
3
+ export declare function writeMarkdownFile(filePath: string, content: string): Promise<void>;
4
+ export declare function writeBinaryFile(filePath: string, data: ArrayBuffer): Promise<void>;
5
+ export declare function fileSize(filePath: string): Promise<null | number>;
6
+ export declare function deleteFile(filePath: string): Promise<void>;
7
+ export declare function assertDirectoryExists(directory: string): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function ensureDirectory(directory) {
4
+ await fs.mkdir(directory, { recursive: true });
5
+ }
6
+ export async function fileExists(filePath) {
7
+ return fs.access(filePath).then(() => true, () => false);
8
+ }
9
+ export async function writeMarkdownFile(filePath, content) {
10
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
11
+ await fs.writeFile(filePath, content);
12
+ }
13
+ // 中断で壊れたファイルが完成品として残らないよう、一時ファイルに書いてからrenameする
14
+ export async function writeBinaryFile(filePath, data) {
15
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
16
+ const tempPath = `${filePath}.tmp`;
17
+ await fs.writeFile(tempPath, new Uint8Array(data));
18
+ await fs.rename(tempPath, filePath);
19
+ }
20
+ export async function fileSize(filePath) {
21
+ const stats = await fs.stat(filePath).catch(() => null);
22
+ return stats?.size ?? null;
23
+ }
24
+ export async function deleteFile(filePath) {
25
+ await fs.unlink(filePath);
26
+ }
27
+ export async function assertDirectoryExists(directory) {
28
+ const stats = await fs.stat(directory).catch(() => { });
29
+ if (!stats?.isDirectory()) {
30
+ throw new Error(`指定されたディレクトリが存在しません: ${directory}`);
31
+ }
32
+ }
@@ -0,0 +1 @@
1
+ export declare function appendLog(outputDir: string, message: string): Promise<void>;
@@ -1,10 +1,6 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- /**
4
- * 更新ログを記録する
5
- * @param outputDir 出力ディレクトリ
6
- * @param message ログメッセージ
7
- */
3
+ // backlog-update.logは監査用途のため、実行環境のロケールに依存せず日本語固定とする
8
4
  export async function appendLog(outputDir, message) {
9
5
  const logPath = path.join(outputDir, 'backlog-update.log');
10
6
  const timestamp = new Date().toLocaleString('ja-JP');
@@ -31,6 +31,14 @@
31
31
  "multiple": false,
32
32
  "type": "option"
33
33
  },
34
+ "downloadAttachments": {
35
+ "char": "d",
36
+ "description": "課題の添付ファイルもダウンロードする",
37
+ "name": "downloadAttachments",
38
+ "required": false,
39
+ "allowNo": false,
40
+ "type": "boolean"
41
+ },
34
42
  "exclude": {
35
43
  "description": "Exclude the specified types, separated by commas (e.g., 'documents,wiki')",
36
44
  "name": "exclude",
@@ -105,6 +113,82 @@
105
113
  "index.js"
106
114
  ]
107
115
  },
116
+ "document": {
117
+ "aliases": [],
118
+ "args": {},
119
+ "description": "Backlogからドキュメントを取得してMarkdownファイルとして保存する",
120
+ "examples": [
121
+ "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY\nドキュメントをMarkdownファイルとして保存する\n",
122
+ "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-documents\n指定したディレクトリにドキュメントを保存する\n",
123
+ "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --keyword 仕様書\nキーワード「仕様書」を含むドキュメントのみを取得する\n"
124
+ ],
125
+ "flags": {
126
+ "apiKey": {
127
+ "description": "Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)",
128
+ "name": "apiKey",
129
+ "required": false,
130
+ "hasDynamicHelp": false,
131
+ "multiple": false,
132
+ "type": "option"
133
+ },
134
+ "domain": {
135
+ "description": "Backlog domain (e.g. example.backlog.jp)",
136
+ "name": "domain",
137
+ "required": true,
138
+ "hasDynamicHelp": false,
139
+ "multiple": false,
140
+ "type": "option"
141
+ },
142
+ "downloadAttachments": {
143
+ "char": "d",
144
+ "description": "添付ファイルもダウンロードする",
145
+ "name": "downloadAttachments",
146
+ "required": false,
147
+ "allowNo": false,
148
+ "type": "boolean"
149
+ },
150
+ "keyword": {
151
+ "description": "検索キーワード",
152
+ "name": "keyword",
153
+ "required": false,
154
+ "hasDynamicHelp": false,
155
+ "multiple": false,
156
+ "type": "option"
157
+ },
158
+ "output": {
159
+ "char": "o",
160
+ "description": "出力ディレクトリパス",
161
+ "name": "output",
162
+ "required": false,
163
+ "hasDynamicHelp": false,
164
+ "multiple": false,
165
+ "type": "option"
166
+ },
167
+ "projectIdOrKey": {
168
+ "description": "Backlog project ID or key",
169
+ "name": "projectIdOrKey",
170
+ "required": true,
171
+ "hasDynamicHelp": false,
172
+ "multiple": false,
173
+ "type": "option"
174
+ }
175
+ },
176
+ "hasDynamicHelp": false,
177
+ "hiddenAliases": [],
178
+ "id": "document",
179
+ "pluginAlias": "backlog-exporter",
180
+ "pluginName": "backlog-exporter",
181
+ "pluginType": "core",
182
+ "strict": true,
183
+ "enableJsonFlag": false,
184
+ "isESM": true,
185
+ "relativePath": [
186
+ "dist",
187
+ "commands",
188
+ "document",
189
+ "index.js"
190
+ ]
191
+ },
108
192
  "issue": {
109
193
  "aliases": [],
110
194
  "args": {},
@@ -116,7 +200,8 @@
116
200
  "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --maxCount 10000\n最大10000件の課題を取得する(デフォルトは5000件)\n",
117
201
  "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFileName\nファイル名を課題キーにする\n",
118
202
  "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFolder\n課題キーでフォルダを作成する\n",
119
- "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFileName --issueKeyFolder\n課題キーでフォルダを作成し、ファイル名も課題キーにする\n"
203
+ "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFileName --issueKeyFolder\n課題キーでフォルダを作成し、ファイル名も課題キーにする\n",
204
+ "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --downloadAttachments\n課題の添付ファイルもダウンロードする\n"
120
205
  ],
121
206
  "flags": {
122
207
  "apiKey": {
@@ -135,6 +220,14 @@
135
220
  "multiple": false,
136
221
  "type": "option"
137
222
  },
223
+ "downloadAttachments": {
224
+ "char": "d",
225
+ "description": "課題の添付ファイルもダウンロードする",
226
+ "name": "downloadAttachments",
227
+ "required": false,
228
+ "allowNo": false,
229
+ "type": "boolean"
230
+ },
138
231
  "issueKeyFileName": {
139
232
  "description": "ファイル名を課題キーにする",
140
233
  "name": "issueKeyFileName",
@@ -201,14 +294,20 @@
201
294
  "index.js"
202
295
  ]
203
296
  },
204
- "document": {
297
+ "prune": {
205
298
  "aliases": [],
206
- "args": {},
207
- "description": "Backlogからドキュメントを取得してMarkdownファイルとして保存する",
299
+ "args": {
300
+ "directory": {
301
+ "description": "対象ディレクトリ(設定ファイルが保存されている場所)",
302
+ "name": "directory",
303
+ "required": false
304
+ }
305
+ },
306
+ "description": "Backlog上で削除・移動された課題・ドキュメント・Wikiのローカルファイルを削除し、Backlogと同じ状態に揃える",
208
307
  "examples": [
209
- "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY\nドキュメントをMarkdownファイルとして保存する\n",
210
- "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-documents\n指定したディレクトリにドキュメントを保存する\n",
211
- "<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --keyword 仕様書\nキーワード「仕様書」を含むドキュメントのみを取得する\n"
308
+ "<%= config.bin %> <%= command.id %>\nカレントディレクトリ配下の設定ファイルを探索し、Backlog上に存在しないドキュメントファイルを削除する\n",
309
+ "<%= config.bin %> <%= command.id %> ./backlog-documents\n指定したディレクトリを対象にする\n",
310
+ "<%= config.bin %> <%= command.id %> --force\n確認プロンプトをスキップして削除する\n"
212
311
  ],
213
312
  "flags": {
214
313
  "apiKey": {
@@ -222,32 +321,23 @@
222
321
  "domain": {
223
322
  "description": "Backlog domain (e.g. example.backlog.jp)",
224
323
  "name": "domain",
225
- "required": true,
226
- "hasDynamicHelp": false,
227
- "multiple": false,
228
- "type": "option"
229
- },
230
- "keyword": {
231
- "description": "検索キーワード",
232
- "name": "keyword",
233
324
  "required": false,
234
325
  "hasDynamicHelp": false,
235
326
  "multiple": false,
236
327
  "type": "option"
237
328
  },
238
- "output": {
239
- "char": "o",
240
- "description": "出力ディレクトリパス",
241
- "name": "output",
329
+ "force": {
330
+ "char": "f",
331
+ "description": "確認プロンプトをスキップする",
332
+ "name": "force",
242
333
  "required": false,
243
- "hasDynamicHelp": false,
244
- "multiple": false,
245
- "type": "option"
334
+ "allowNo": false,
335
+ "type": "boolean"
246
336
  },
247
337
  "projectIdOrKey": {
248
338
  "description": "Backlog project ID or key",
249
339
  "name": "projectIdOrKey",
250
- "required": true,
340
+ "required": false,
251
341
  "hasDynamicHelp": false,
252
342
  "multiple": false,
253
343
  "type": "option"
@@ -255,7 +345,7 @@
255
345
  },
256
346
  "hasDynamicHelp": false,
257
347
  "hiddenAliases": [],
258
- "id": "document",
348
+ "id": "prune",
259
349
  "pluginAlias": "backlog-exporter",
260
350
  "pluginName": "backlog-exporter",
261
351
  "pluginType": "core",
@@ -265,7 +355,7 @@
265
355
  "relativePath": [
266
356
  "dist",
267
357
  "commands",
268
- "document",
358
+ "prune",
269
359
  "index.js"
270
360
  ]
271
361
  },
@@ -286,7 +376,10 @@
286
376
  "<%= config.bin %> <%= command.id %> ./my-project\n指定したディレクトリの設定を使用して更新する\n",
287
377
  "<%= config.bin %> <%= command.id %> --issueKeyFileName\nファイル名を課題キーにする\n",
288
378
  "<%= config.bin %> <%= command.id %> --issueKeyFolder\n課題キーでフォルダを作成する\n",
289
- "<%= config.bin %> <%= command.id %> --issueKeyFileName --issueKeyFolder\n課題キーでフォルダを作成し、ファイル名も課題キーにする\n"
379
+ "<%= config.bin %> <%= command.id %> --issueKeyFileName --issueKeyFolder\n課題キーでフォルダを作成し、ファイル名も課題キーにする\n",
380
+ "<%= config.bin %> <%= command.id %> --issueIdOrKey PROJECT-1,PROJECT-2\n指定した課題(IDまたはキー)のみを再取得する(全件差分更新は行わない)\n",
381
+ "<%= config.bin %> <%= command.id %> --wikiId 12345,12346\n指定したWiki(ID)のみを再取得する(全件差分更新は行わない)\n",
382
+ "<%= config.bin %> <%= command.id %> --documentId abc123,def456\n指定したドキュメント(ID)のみを再取得する(全件差分更新は行わない)\n"
290
383
  ],
291
384
  "flags": {
292
385
  "apiKey": {
@@ -297,6 +390,14 @@
297
390
  "multiple": false,
298
391
  "type": "option"
299
392
  },
393
+ "documentId": {
394
+ "description": "指定したドキュメント(ID)のみを再取得する(カンマ区切りで複数指定可能)",
395
+ "name": "documentId",
396
+ "required": false,
397
+ "hasDynamicHelp": false,
398
+ "multiple": false,
399
+ "type": "option"
400
+ },
300
401
  "documentsOnly": {
301
402
  "description": "ドキュメントのみを更新する",
302
403
  "name": "documentsOnly",
@@ -312,6 +413,14 @@
312
413
  "multiple": false,
313
414
  "type": "option"
314
415
  },
416
+ "downloadAttachments": {
417
+ "char": "d",
418
+ "description": "課題の添付ファイルもダウンロードする(未指定時は設定ファイルの値を使用)",
419
+ "name": "downloadAttachments",
420
+ "required": false,
421
+ "allowNo": false,
422
+ "type": "boolean"
423
+ },
315
424
  "force": {
316
425
  "char": "f",
317
426
  "description": "確認プロンプトをスキップする",
@@ -320,6 +429,14 @@
320
429
  "allowNo": false,
321
430
  "type": "boolean"
322
431
  },
432
+ "issueIdOrKey": {
433
+ "description": "指定した課題(IDまたはキー)のみを再取得する(カンマ区切りで複数指定可能)",
434
+ "name": "issueIdOrKey",
435
+ "required": false,
436
+ "hasDynamicHelp": false,
437
+ "multiple": false,
438
+ "type": "option"
439
+ },
323
440
  "issueKeyFileName": {
324
441
  "description": "ファイル名を課題キーにする",
325
442
  "name": "issueKeyFileName",
@@ -349,6 +466,14 @@
349
466
  "multiple": false,
350
467
  "type": "option"
351
468
  },
469
+ "wikiId": {
470
+ "description": "指定したWiki(ID)のみを再取得する(カンマ区切りで複数指定可能)",
471
+ "name": "wikiId",
472
+ "required": false,
473
+ "hasDynamicHelp": false,
474
+ "multiple": false,
475
+ "type": "option"
476
+ },
352
477
  "wikisOnly": {
353
478
  "description": "Wikiのみを更新する",
354
479
  "name": "wikisOnly",
@@ -398,6 +523,14 @@
398
523
  "multiple": false,
399
524
  "type": "option"
400
525
  },
526
+ "downloadAttachments": {
527
+ "char": "d",
528
+ "description": "添付ファイルもダウンロードする",
529
+ "name": "downloadAttachments",
530
+ "required": false,
531
+ "allowNo": false,
532
+ "type": "boolean"
533
+ },
401
534
  "output": {
402
535
  "char": "o",
403
536
  "description": "出力ディレクトリパス",
@@ -433,5 +566,5 @@
433
566
  ]
434
567
  }
435
568
  },
436
- "version": "0.7.3"
569
+ "version": "1.1.0"
437
570
  }