backlog-exporter 0.7.0 → 0.7.2
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 +120 -82
- package/dist/commands/all/index.d.ts +17 -0
- package/dist/commands/all/index.js +210 -0
- package/dist/commands/document/index.d.ts +13 -0
- package/dist/commands/document/index.js +84 -0
- package/dist/commands/issue/index.d.ts +16 -0
- package/dist/commands/issue/index.js +114 -0
- package/dist/commands/update/index.d.ts +27 -0
- package/dist/commands/update/index.js +344 -0
- package/dist/commands/wiki/index.d.ts +12 -0
- package/dist/commands/wiki/index.js +71 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/backlog-api.d.ts +74 -0
- package/dist/utils/backlog-api.js +448 -0
- package/dist/utils/backlog.d.ts +16 -0
- package/dist/utils/backlog.js +34 -0
- package/dist/utils/common.d.ts +25 -0
- package/dist/utils/common.js +54 -0
- package/dist/utils/log.d.ts +6 -0
- package/dist/utils/log.js +18 -0
- package/dist/utils/settings.d.ts +45 -0
- package/dist/utils/settings.js +63 -0
- package/dist/utils/sleep.d.ts +28 -0
- package/dist/utils/sleep.js +42 -0
- package/oclif.manifest.json +435 -2
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Issue 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
|
+
issueKeyFileName: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
issueKeyFolder: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
maxCount: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
projectIdOrKey: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
statusId: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import { downloadIssues } 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 Issue 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-issues
|
|
16
|
+
指定したディレクトリに課題を保存する
|
|
17
|
+
`,
|
|
18
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --statusId 1,2,3
|
|
19
|
+
指定したステータスIDの課題のみを取得する
|
|
20
|
+
`,
|
|
21
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --maxCount 10000
|
|
22
|
+
最大10000件の課題を取得する(デフォルトは5000件)
|
|
23
|
+
`,
|
|
24
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFileName
|
|
25
|
+
ファイル名を課題キーにする
|
|
26
|
+
`,
|
|
27
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFolder
|
|
28
|
+
課題キーでフォルダを作成する
|
|
29
|
+
`,
|
|
30
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --issueKeyFileName --issueKeyFolder
|
|
31
|
+
課題キーでフォルダを作成し、ファイル名も課題キーにする
|
|
32
|
+
`,
|
|
33
|
+
];
|
|
34
|
+
static flags = {
|
|
35
|
+
apiKey: Flags.string({
|
|
36
|
+
description: 'Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
domain: Flags.string({
|
|
40
|
+
description: 'Backlog domain (e.g. example.backlog.jp)',
|
|
41
|
+
required: true,
|
|
42
|
+
}),
|
|
43
|
+
issueKeyFileName: Flags.boolean({
|
|
44
|
+
description: 'ファイル名を課題キーにする',
|
|
45
|
+
required: false,
|
|
46
|
+
}),
|
|
47
|
+
issueKeyFolder: Flags.boolean({
|
|
48
|
+
description: '課題キーでフォルダを作成する',
|
|
49
|
+
required: false,
|
|
50
|
+
}),
|
|
51
|
+
maxCount: Flags.integer({
|
|
52
|
+
char: 'm',
|
|
53
|
+
default: 5000,
|
|
54
|
+
description: '一度に取得する課題の最大数(デフォルト: 5000)',
|
|
55
|
+
required: false,
|
|
56
|
+
}),
|
|
57
|
+
output: Flags.string({
|
|
58
|
+
char: 'o',
|
|
59
|
+
description: '出力ディレクトリパス',
|
|
60
|
+
required: false,
|
|
61
|
+
}),
|
|
62
|
+
projectIdOrKey: Flags.string({
|
|
63
|
+
description: 'Backlog project ID or key',
|
|
64
|
+
required: true,
|
|
65
|
+
}),
|
|
66
|
+
statusId: Flags.string({
|
|
67
|
+
description: 'ステータスID(カンマ区切りで複数指定可能)',
|
|
68
|
+
required: false,
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
async run() {
|
|
72
|
+
const { flags } = await this.parse(Issue);
|
|
73
|
+
try {
|
|
74
|
+
const { domain, issueKeyFileName, issueKeyFolder, maxCount, projectIdOrKey, statusId } = flags;
|
|
75
|
+
const apiKey = flags.apiKey || getApiKey(this);
|
|
76
|
+
const outputDir = flags.output || './backlog-issues';
|
|
77
|
+
// 出力ディレクトリの作成
|
|
78
|
+
await createOutputDirectory(outputDir);
|
|
79
|
+
// プロジェクトキーからプロジェクトIDを取得
|
|
80
|
+
const projectId = await validateAndGetProjectId(domain, projectIdOrKey, apiKey);
|
|
81
|
+
this.log(`プロジェクトID: ${projectId} を使用します`);
|
|
82
|
+
// 設定ファイルを保存
|
|
83
|
+
await updateSettings(outputDir, {
|
|
84
|
+
apiKey,
|
|
85
|
+
domain,
|
|
86
|
+
folderType: FolderType.ISSUE,
|
|
87
|
+
issueKeyFileName,
|
|
88
|
+
issueKeyFolder,
|
|
89
|
+
outputDir,
|
|
90
|
+
projectIdOrKey,
|
|
91
|
+
});
|
|
92
|
+
// 課題の取得と保存
|
|
93
|
+
await downloadIssues(this, {
|
|
94
|
+
apiKey,
|
|
95
|
+
count: maxCount,
|
|
96
|
+
domain,
|
|
97
|
+
issueKeyFileName,
|
|
98
|
+
issueKeyFolder,
|
|
99
|
+
outputDir,
|
|
100
|
+
projectId,
|
|
101
|
+
statusId,
|
|
102
|
+
});
|
|
103
|
+
// 最終更新日時を更新
|
|
104
|
+
await updateSettings(outputDir, {
|
|
105
|
+
lastUpdated: new Date().toISOString(),
|
|
106
|
+
});
|
|
107
|
+
this.log('課題の取得が完了しました!');
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
111
|
+
this.error(`課題の取得に失敗しました: ${errorMessage}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Update 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
|
+
documentsOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
issueKeyFileName: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
issueKeyFolder: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
issuesOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
projectIdOrKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
wikisOnly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
};
|
|
19
|
+
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
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import { access, readdir } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { downloadDocuments, downloadIssues, downloadWikis } from '../../utils/backlog-api.js';
|
|
6
|
+
import { validateAndGetProjectId } from '../../utils/backlog.js';
|
|
7
|
+
import { createOutputDirectory, getApiKey } from '../../utils/common.js';
|
|
8
|
+
import { FolderType, getSettingsFilePath, loadSettings, updateSettings } from '../../utils/settings.js';
|
|
9
|
+
// .envファイルを読み込む
|
|
10
|
+
dotenv.config();
|
|
11
|
+
export default class Update extends Command {
|
|
12
|
+
static args = {
|
|
13
|
+
directory: Args.string({
|
|
14
|
+
description: '更新対象のディレクトリ(設定ファイルが保存されている場所)',
|
|
15
|
+
required: false,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static description = 'Backlogから最新データを取得して更新する';
|
|
19
|
+
static examples = [
|
|
20
|
+
`<%= config.bin %> <%= command.id %>
|
|
21
|
+
カレントディレクトリの設定を使用して更新する
|
|
22
|
+
`,
|
|
23
|
+
`<%= config.bin %> <%= command.id %> --force
|
|
24
|
+
確認プロンプトをスキップする
|
|
25
|
+
`,
|
|
26
|
+
`<%= config.bin %> <%= command.id %> --apiKey YOUR_API_KEY --domain example.backlog.jp --projectIdOrKey PROJECT_KEY
|
|
27
|
+
指定したパラメータで更新する(設定ファイルが存在する場合は上書きされます)
|
|
28
|
+
`,
|
|
29
|
+
`<%= config.bin %> <%= command.id %> ./my-project
|
|
30
|
+
指定したディレクトリの設定を使用して更新する
|
|
31
|
+
`,
|
|
32
|
+
`<%= config.bin %> <%= command.id %> --issueKeyFileName
|
|
33
|
+
ファイル名を課題キーにする
|
|
34
|
+
`,
|
|
35
|
+
`<%= config.bin %> <%= command.id %> --issueKeyFolder
|
|
36
|
+
課題キーでフォルダを作成する
|
|
37
|
+
`,
|
|
38
|
+
`<%= config.bin %> <%= command.id %> --issueKeyFileName --issueKeyFolder
|
|
39
|
+
課題キーでフォルダを作成し、ファイル名も課題キーにする
|
|
40
|
+
`,
|
|
41
|
+
];
|
|
42
|
+
static flags = {
|
|
43
|
+
apiKey: Flags.string({
|
|
44
|
+
description: 'Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)',
|
|
45
|
+
required: false,
|
|
46
|
+
}),
|
|
47
|
+
documentsOnly: Flags.boolean({
|
|
48
|
+
description: 'ドキュメントのみを更新する',
|
|
49
|
+
required: false,
|
|
50
|
+
}),
|
|
51
|
+
domain: Flags.string({
|
|
52
|
+
description: 'Backlog domain (e.g. example.backlog.jp)',
|
|
53
|
+
required: false,
|
|
54
|
+
}),
|
|
55
|
+
force: Flags.boolean({
|
|
56
|
+
char: 'f',
|
|
57
|
+
description: '確認プロンプトをスキップする',
|
|
58
|
+
required: false,
|
|
59
|
+
}),
|
|
60
|
+
issueKeyFileName: Flags.boolean({
|
|
61
|
+
description: 'ファイル名を課題キーにする',
|
|
62
|
+
required: false,
|
|
63
|
+
}),
|
|
64
|
+
issueKeyFolder: Flags.boolean({
|
|
65
|
+
description: '課題キーでフォルダを作成する',
|
|
66
|
+
required: false,
|
|
67
|
+
}),
|
|
68
|
+
issuesOnly: Flags.boolean({
|
|
69
|
+
description: '課題のみを更新する',
|
|
70
|
+
required: false,
|
|
71
|
+
}),
|
|
72
|
+
projectIdOrKey: Flags.string({
|
|
73
|
+
description: 'Backlog project ID or key',
|
|
74
|
+
required: false,
|
|
75
|
+
}),
|
|
76
|
+
wikisOnly: Flags.boolean({
|
|
77
|
+
description: 'Wikiのみを更新する',
|
|
78
|
+
required: false,
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
async run() {
|
|
82
|
+
const { args, flags } = await this.parse(Update);
|
|
83
|
+
// 更新対象のディレクトリを決定(指定がなければカレントディレクトリ)
|
|
84
|
+
const targetDir = args.directory || process.cwd();
|
|
85
|
+
try {
|
|
86
|
+
// 設定ファイルを探索して更新を実行
|
|
87
|
+
await this.findAndUpdateSettings(targetDir, flags);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
91
|
+
this.error(`更新に失敗しました: ${errorMessage}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 確認プロンプトの表示
|
|
95
|
+
async confirmUpdate(options) {
|
|
96
|
+
if (options.force)
|
|
97
|
+
return true;
|
|
98
|
+
this.log(`以下の設定で更新を実行します:`);
|
|
99
|
+
this.log(`- ディレクトリ: ${options.targetDir}`);
|
|
100
|
+
this.log(`- ドメイン: ${options.domain}`);
|
|
101
|
+
this.log(`- プロジェクト: ${options.projectIdOrKey}`);
|
|
102
|
+
if (options.folderType) {
|
|
103
|
+
this.log(`- フォルダタイプ: ${options.folderType}`);
|
|
104
|
+
}
|
|
105
|
+
const updateTargets = [];
|
|
106
|
+
if (options.updateIssues)
|
|
107
|
+
updateTargets.push('課題');
|
|
108
|
+
if (options.updateWikis)
|
|
109
|
+
updateTargets.push('Wiki');
|
|
110
|
+
if (options.updateDocuments)
|
|
111
|
+
updateTargets.push('ドキュメント');
|
|
112
|
+
this.log(`- 更新対象: ${updateTargets.join('・')}`);
|
|
113
|
+
// 確認プロンプトを表示
|
|
114
|
+
this.log('更新を実行しますか? (y/n)');
|
|
115
|
+
process.stdin.resume();
|
|
116
|
+
process.stdin.setEncoding('utf8');
|
|
117
|
+
const response = await new Promise((resolve) => {
|
|
118
|
+
process.stdin.once('data', (data) => {
|
|
119
|
+
const input = data.toString().trim().toLowerCase();
|
|
120
|
+
resolve(input === 'y' || input === 'yes');
|
|
121
|
+
process.stdin.pause();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
if (!response) {
|
|
125
|
+
this.log('更新をキャンセルしました');
|
|
126
|
+
}
|
|
127
|
+
return response;
|
|
128
|
+
}
|
|
129
|
+
// 更新対象の決定
|
|
130
|
+
determineUpdateTargets(folderType, documentsOnly, issuesOnly, wikisOnly) {
|
|
131
|
+
let updateIssues = !wikisOnly && !documentsOnly;
|
|
132
|
+
let updateWikis = !issuesOnly && !documentsOnly;
|
|
133
|
+
let updateDocuments = !issuesOnly && !wikisOnly;
|
|
134
|
+
// フォルダタイプに応じて更新対象を決定
|
|
135
|
+
switch (folderType) {
|
|
136
|
+
case FolderType.DOCUMENT: {
|
|
137
|
+
updateIssues = false;
|
|
138
|
+
updateWikis = false;
|
|
139
|
+
updateDocuments = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case FolderType.ISSUE: {
|
|
143
|
+
updateIssues = true;
|
|
144
|
+
updateWikis = false;
|
|
145
|
+
updateDocuments = false;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case FolderType.WIKI: {
|
|
149
|
+
updateIssues = false;
|
|
150
|
+
updateWikis = true;
|
|
151
|
+
updateDocuments = false;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { updateDocuments, updateIssues, updateWikis };
|
|
156
|
+
}
|
|
157
|
+
// 設定ファイルを探索して更新を実行
|
|
158
|
+
async findAndUpdateSettings(targetDir, flags) {
|
|
159
|
+
// 現在のディレクトリに設定ファイルがあるか確認
|
|
160
|
+
const settingsPath = getSettingsFilePath(targetDir);
|
|
161
|
+
let hasSettings = false;
|
|
162
|
+
try {
|
|
163
|
+
await access(settingsPath);
|
|
164
|
+
hasSettings = true;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// 設定ファイルが存在しない場合は何もしない
|
|
168
|
+
}
|
|
169
|
+
if (hasSettings) {
|
|
170
|
+
// 設定ファイルが存在する場合は更新を実行
|
|
171
|
+
await this.updateDirectory(targetDir, flags);
|
|
172
|
+
}
|
|
173
|
+
// サブディレクトリを探索
|
|
174
|
+
try {
|
|
175
|
+
const entries = await readdir(targetDir, { withFileTypes: true });
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
const subDir = join(targetDir, entry.name);
|
|
179
|
+
// eslint-disable-next-line no-await-in-loop
|
|
180
|
+
await this.findAndUpdateSettings(subDir, flags);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
this.warn(`ディレクトリの読み取りに失敗しました: ${targetDir}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// 指定されたディレクトリの更新を実行
|
|
189
|
+
async updateDirectory(targetDir, flags) {
|
|
190
|
+
// 設定ファイルを読み込む
|
|
191
|
+
const settings = await loadSettings(targetDir);
|
|
192
|
+
// コマンドライン引数と設定ファイルを組み合わせて使用する値を決定
|
|
193
|
+
const domain = flags.domain || settings.domain;
|
|
194
|
+
const projectIdOrKey = flags.projectIdOrKey || settings.projectIdOrKey;
|
|
195
|
+
const { folderType } = settings;
|
|
196
|
+
const { documentsOnly, force, issuesOnly, wikisOnly } = flags;
|
|
197
|
+
// 設定ファイルからオプションを読み込み、コマンドライン引数で上書き
|
|
198
|
+
const issueKeyFileName = flags.issueKeyFileName ?? settings.issueKeyFileName ?? false;
|
|
199
|
+
const issueKeyFolder = flags.issueKeyFolder ?? settings.issueKeyFolder ?? false;
|
|
200
|
+
// 必須パラメータの検証
|
|
201
|
+
if (!domain) {
|
|
202
|
+
this.warn(`${targetDir}: ドメインが指定されていません。スキップします。`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!projectIdOrKey) {
|
|
206
|
+
this.warn(`${targetDir}: プロジェクトIDまたはキーが指定されていません。スキップします。`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// APIキーの検証
|
|
210
|
+
let apiKey;
|
|
211
|
+
try {
|
|
212
|
+
apiKey = flags.apiKey || settings.apiKey || getApiKey(this);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
this.warn(`${targetDir}: APIキーが指定されていません。--apiKey フラグまたは BACKLOG_API_KEY 環境変数で設定してください。`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// 更新対象の決定
|
|
219
|
+
const { updateDocuments, updateIssues, updateWikis } = this.determineUpdateTargets(folderType, documentsOnly, issuesOnly, wikisOnly);
|
|
220
|
+
// 更新前の確認
|
|
221
|
+
const confirmed = await this.confirmUpdate({
|
|
222
|
+
domain,
|
|
223
|
+
folderType,
|
|
224
|
+
force: force || false,
|
|
225
|
+
projectIdOrKey,
|
|
226
|
+
targetDir,
|
|
227
|
+
updateDocuments,
|
|
228
|
+
updateIssues,
|
|
229
|
+
updateWikis,
|
|
230
|
+
});
|
|
231
|
+
if (!confirmed)
|
|
232
|
+
return;
|
|
233
|
+
// 出力ディレクトリの作成
|
|
234
|
+
await createOutputDirectory(targetDir);
|
|
235
|
+
// プロジェクトキーからプロジェクトIDを取得
|
|
236
|
+
const projectId = await validateAndGetProjectId(domain, projectIdOrKey, apiKey);
|
|
237
|
+
this.log(`プロジェクトID: ${projectId} を使用します`);
|
|
238
|
+
// 課題の更新
|
|
239
|
+
if (updateIssues) {
|
|
240
|
+
await this.updateIssues({
|
|
241
|
+
apiKey,
|
|
242
|
+
domain,
|
|
243
|
+
issueKeyFileName,
|
|
244
|
+
issueKeyFolder,
|
|
245
|
+
projectId,
|
|
246
|
+
projectIdOrKey,
|
|
247
|
+
targetDir,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// Wikiの更新
|
|
251
|
+
if (updateWikis) {
|
|
252
|
+
await this.updateWikis({
|
|
253
|
+
apiKey,
|
|
254
|
+
domain,
|
|
255
|
+
projectIdOrKey,
|
|
256
|
+
targetDir,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// ドキュメントの更新
|
|
260
|
+
if (updateDocuments) {
|
|
261
|
+
await this.updateDocuments({
|
|
262
|
+
apiKey,
|
|
263
|
+
domain,
|
|
264
|
+
projectId,
|
|
265
|
+
projectIdOrKey,
|
|
266
|
+
targetDir,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
this.log(`${targetDir} の更新が完了しました!`);
|
|
270
|
+
}
|
|
271
|
+
// ドキュメントの更新
|
|
272
|
+
async updateDocuments(options) {
|
|
273
|
+
this.log('ドキュメントの更新を開始します...');
|
|
274
|
+
// 設定ファイルから前回の更新日時を取得
|
|
275
|
+
const { lastUpdated } = await loadSettings(options.targetDir);
|
|
276
|
+
await downloadDocuments(this, {
|
|
277
|
+
apiKey: options.apiKey,
|
|
278
|
+
domain: options.domain,
|
|
279
|
+
lastUpdated,
|
|
280
|
+
outputDir: options.targetDir,
|
|
281
|
+
projectId: options.projectId,
|
|
282
|
+
projectIdOrKey: options.projectIdOrKey,
|
|
283
|
+
});
|
|
284
|
+
// 設定ファイルを更新
|
|
285
|
+
await updateSettings(options.targetDir, {
|
|
286
|
+
apiKey: options.apiKey,
|
|
287
|
+
domain: options.domain,
|
|
288
|
+
folderType: FolderType.DOCUMENT,
|
|
289
|
+
lastUpdated: new Date().toISOString(),
|
|
290
|
+
outputDir: options.targetDir,
|
|
291
|
+
projectIdOrKey: options.projectIdOrKey,
|
|
292
|
+
});
|
|
293
|
+
this.log('ドキュメントの更新が完了しました');
|
|
294
|
+
}
|
|
295
|
+
// 課題の更新
|
|
296
|
+
async updateIssues(options) {
|
|
297
|
+
this.log('課題の更新を開始します...');
|
|
298
|
+
// 設定ファイルから前回の更新日時を取得
|
|
299
|
+
const { lastUpdated } = await loadSettings(options.targetDir);
|
|
300
|
+
await downloadIssues(this, {
|
|
301
|
+
apiKey: options.apiKey,
|
|
302
|
+
count: 100,
|
|
303
|
+
domain: options.domain,
|
|
304
|
+
issueKeyFileName: options.issueKeyFileName,
|
|
305
|
+
issueKeyFolder: options.issueKeyFolder,
|
|
306
|
+
lastUpdated,
|
|
307
|
+
outputDir: options.targetDir,
|
|
308
|
+
projectId: options.projectId,
|
|
309
|
+
});
|
|
310
|
+
// 設定ファイルを更新
|
|
311
|
+
await updateSettings(options.targetDir, {
|
|
312
|
+
apiKey: options.apiKey,
|
|
313
|
+
domain: options.domain,
|
|
314
|
+
folderType: FolderType.ISSUE,
|
|
315
|
+
lastUpdated: new Date().toISOString(),
|
|
316
|
+
outputDir: options.targetDir,
|
|
317
|
+
projectIdOrKey: options.projectIdOrKey,
|
|
318
|
+
});
|
|
319
|
+
this.log('課題の更新が完了しました');
|
|
320
|
+
}
|
|
321
|
+
// Wikiの更新
|
|
322
|
+
async updateWikis(options) {
|
|
323
|
+
this.log('Wikiの更新を開始します...');
|
|
324
|
+
// 設定ファイルから前回の更新日時を取得
|
|
325
|
+
const { lastUpdated } = await loadSettings(options.targetDir);
|
|
326
|
+
await downloadWikis(this, {
|
|
327
|
+
apiKey: options.apiKey,
|
|
328
|
+
domain: options.domain,
|
|
329
|
+
lastUpdated,
|
|
330
|
+
outputDir: options.targetDir,
|
|
331
|
+
projectIdOrKey: options.projectIdOrKey,
|
|
332
|
+
});
|
|
333
|
+
// 設定ファイルを更新
|
|
334
|
+
await updateSettings(options.targetDir, {
|
|
335
|
+
apiKey: options.apiKey,
|
|
336
|
+
domain: options.domain,
|
|
337
|
+
folderType: FolderType.WIKI,
|
|
338
|
+
lastUpdated: new Date().toISOString(),
|
|
339
|
+
outputDir: options.targetDir,
|
|
340
|
+
projectIdOrKey: options.projectIdOrKey,
|
|
341
|
+
});
|
|
342
|
+
this.log('Wikiの更新が完了しました');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Wiki 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
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
projectIdOrKey: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import { downloadWikis } from '../../utils/backlog-api.js';
|
|
4
|
+
import { createOutputDirectory, getApiKey } from '../../utils/common.js';
|
|
5
|
+
import { FolderType, updateSettings } from '../../utils/settings.js';
|
|
6
|
+
// .envファイルを読み込む
|
|
7
|
+
dotenv.config();
|
|
8
|
+
export default class Wiki extends Command {
|
|
9
|
+
static description = 'Backlogから Wiki を取得してMarkdownファイルとして保存する';
|
|
10
|
+
static examples = [
|
|
11
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY
|
|
12
|
+
Wikiをダウンロードする
|
|
13
|
+
`,
|
|
14
|
+
`<%= config.bin %> <%= command.id %> --domain example.backlog.jp --projectIdOrKey PROJECT_KEY --apiKey YOUR_API_KEY --output ./my-project
|
|
15
|
+
指定したディレクトリにWikiを保存する
|
|
16
|
+
`,
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
apiKey: Flags.string({
|
|
20
|
+
description: 'Backlog API key (環境変数 BACKLOG_API_KEY からも自動読み取り可能)',
|
|
21
|
+
required: false,
|
|
22
|
+
}),
|
|
23
|
+
domain: Flags.string({
|
|
24
|
+
description: 'Backlog domain (e.g. example.backlog.jp)',
|
|
25
|
+
required: true,
|
|
26
|
+
}),
|
|
27
|
+
output: Flags.string({
|
|
28
|
+
char: 'o',
|
|
29
|
+
description: '出力ディレクトリパス',
|
|
30
|
+
required: false,
|
|
31
|
+
}),
|
|
32
|
+
projectIdOrKey: Flags.string({
|
|
33
|
+
description: 'Backlog project ID or key',
|
|
34
|
+
required: true,
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
async run() {
|
|
38
|
+
const { flags } = await this.parse(Wiki);
|
|
39
|
+
try {
|
|
40
|
+
const { domain, projectIdOrKey } = flags;
|
|
41
|
+
const apiKey = flags.apiKey || getApiKey(this);
|
|
42
|
+
const outputDir = flags.output || './wiki';
|
|
43
|
+
// 出力ディレクトリの作成
|
|
44
|
+
await createOutputDirectory(outputDir);
|
|
45
|
+
// 設定ファイルを保存
|
|
46
|
+
await updateSettings(outputDir, {
|
|
47
|
+
apiKey,
|
|
48
|
+
domain,
|
|
49
|
+
folderType: FolderType.WIKI,
|
|
50
|
+
outputDir,
|
|
51
|
+
projectIdOrKey,
|
|
52
|
+
});
|
|
53
|
+
// Wikiの取得と保存
|
|
54
|
+
await downloadWikis(this, {
|
|
55
|
+
apiKey,
|
|
56
|
+
domain,
|
|
57
|
+
outputDir,
|
|
58
|
+
projectIdOrKey,
|
|
59
|
+
});
|
|
60
|
+
// 最終更新日時を更新
|
|
61
|
+
await updateSettings(outputDir, {
|
|
62
|
+
lastUpdated: new Date().toISOString(),
|
|
63
|
+
});
|
|
64
|
+
this.log('Wikiの取得が完了しました!');
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
68
|
+
this.error(`Wikiの取得に失敗しました: ${errorMessage}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* カスタム属性セクションを作成する
|
|
4
|
+
* @param customFields カスタム属性の配列
|
|
5
|
+
* @returns Markdownテーブル形式のカスタム属性セクション
|
|
6
|
+
*/
|
|
7
|
+
export declare function createCustomFieldsSection(customFields?: Array<{
|
|
8
|
+
id: number;
|
|
9
|
+
name: string;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}>): string;
|
|
12
|
+
/**
|
|
13
|
+
* Backlogから課題をダウンロードする
|
|
14
|
+
* @param command コマンドインスタンス
|
|
15
|
+
* @param options 課題ダウンロードのオプション
|
|
16
|
+
* @param options.apiKey Backlog API key
|
|
17
|
+
* @param options.count 取得する課題の最大数
|
|
18
|
+
* @param options.domain Backlogのドメイン
|
|
19
|
+
* @param options.lastUpdated 最終更新日時
|
|
20
|
+
* @param options.outputDir 出力ディレクトリ
|
|
21
|
+
* @param options.projectId プロジェクトID
|
|
22
|
+
* @param options.statusId ステータスID
|
|
23
|
+
* @param options.issueKeyFileName ファイル名を課題キーにするかどうか
|
|
24
|
+
* @param options.issueKeyFolder 課題キーでフォルダを作成するかどうか
|
|
25
|
+
*/
|
|
26
|
+
export declare function downloadIssues(command: Command, options: {
|
|
27
|
+
apiKey: string;
|
|
28
|
+
count?: number;
|
|
29
|
+
domain: string;
|
|
30
|
+
issueKeyFileName?: boolean;
|
|
31
|
+
issueKeyFolder?: boolean;
|
|
32
|
+
lastUpdated?: string;
|
|
33
|
+
outputDir: string;
|
|
34
|
+
projectId: number;
|
|
35
|
+
statusId?: string;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* BacklogからWikiをダウンロードする
|
|
39
|
+
* @param command コマンドインスタンス
|
|
40
|
+
* @param options Wikiダウンロードのオプション
|
|
41
|
+
* @param options.apiKey Backlog API key
|
|
42
|
+
* @param options.domain Backlogのドメイン
|
|
43
|
+
* @param options.lastUpdated 最終更新日時
|
|
44
|
+
* @param options.outputDir 出力ディレクトリ
|
|
45
|
+
* @param options.projectIdOrKey プロジェクトIDまたはキー
|
|
46
|
+
*/
|
|
47
|
+
export declare function downloadWikis(command: Command, options: {
|
|
48
|
+
apiKey: string;
|
|
49
|
+
domain: string;
|
|
50
|
+
lastUpdated?: string;
|
|
51
|
+
outputDir: string;
|
|
52
|
+
projectIdOrKey: string;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Backlogからドキュメントをダウンロードする
|
|
56
|
+
* @param command コマンドインスタンス
|
|
57
|
+
* @param options ドキュメントダウンロードのオプション
|
|
58
|
+
* @param options.apiKey Backlog API key
|
|
59
|
+
* @param options.domain Backlogのドメイン
|
|
60
|
+
* @param options.keyword キーワードフィルター
|
|
61
|
+
* @param options.lastUpdated 最終更新日時
|
|
62
|
+
* @param options.outputDir 出力ディレクトリ
|
|
63
|
+
* @param options.projectId プロジェクトID
|
|
64
|
+
* @param options.projectIdOrKey プロジェクトIDまたはキー
|
|
65
|
+
*/
|
|
66
|
+
export declare function downloadDocuments(command: Command, options: {
|
|
67
|
+
apiKey: string;
|
|
68
|
+
domain: string;
|
|
69
|
+
keyword?: string;
|
|
70
|
+
lastUpdated?: string;
|
|
71
|
+
outputDir: string;
|
|
72
|
+
projectId: number;
|
|
73
|
+
projectIdOrKey: string;
|
|
74
|
+
}): Promise<void>;
|