atl-fetch 1.0.0 → 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.
package/README.md CHANGED
@@ -16,6 +16,9 @@ Atlassian Cloud(Jira / Confluence)から情報を取得する Node.js CLI
16
16
  - **複数出力形式**: JSON / Markdown / YAML
17
17
  - **差分表示**: バージョン間の Unified diff 形式表示
18
18
  - **添付ファイルダウンロード**: 指定ディレクトリへの保存
19
+ - **親切なエラーメッセージ**: エラーコード、原因、解決策を表示
20
+ - **進捗表示**: スピナーによる処理状況の可視化
21
+ - **認証チェック**: `atl-fetch auth check` で設定状態を確認
19
22
 
20
23
  ## 動作要件
21
24
 
@@ -37,7 +40,6 @@ pnpm add -g atl-fetch
37
40
  ### 1. 環境変数の設定
38
41
 
39
42
  ```bash
40
- export ATLASSIAN_DOMAIN="your-domain.atlassian.net"
41
43
  export ATLASSIAN_EMAIL="your-email@example.com"
42
44
  export ATLASSIAN_API_TOKEN="your-api-token"
43
45
  ```
@@ -56,54 +58,63 @@ atl-fetch https://your-domain.atlassian.net/wiki/spaces/SPACE/pages/123456/Page+
56
58
  # Markdown 形式で出力
57
59
  atl-fetch https://your-domain.atlassian.net/browse/PROJECT-123 --format markdown
58
60
 
59
- # ファイルに保存
60
- atl-fetch https://your-domain.atlassian.net/browse/PROJECT-123 --output result.json
61
+ # 添付ファイルをダウンロード
62
+ atl-fetch https://your-domain.atlassian.net/browse/PROJECT-123 --download --dir ./output
63
+
64
+ # ファイルに保存(リダイレクト)
65
+ atl-fetch https://your-domain.atlassian.net/browse/PROJECT-123 > result.json
61
66
  ```
62
67
 
63
68
  ## CLI オプション
64
69
 
65
- | オプション | 短縮形 | 説明 | デフォルト |
66
- | ------------------------ | ---- | ---------------------------- | ------------- |
67
- | `--format` | `-f` | 出力形式(json / markdown / yaml) | json |
68
- | `--output` | `-o` | 出力ファイルパス | stdout |
69
- | `--include-comments` | `-c` | コメントを含める | false |
70
- | `--include-history` | `-h` | 変更履歴を含める | false |
71
- | `--include-attachments` | `-a` | 添付ファイルを含める | false |
72
- | `--download-attachments` | `-d` | 添付ファイルをダウンロード | false |
73
- | `--attachments-dir` | | 添付ファイルの保存先 | ./attachments |
74
- | `--diff` | | 指定バージョンとの差分を表示 | - |
75
- | `--help` | | ヘルプを表示 | - |
76
- | `--version` | | バージョンを表示 | - |
77
-
78
- ## ライブラリとしての使用
79
-
80
- ```typescript
81
- import { fetchJiraIssue, fetchConfluencePage } from 'atl-fetch';
82
-
83
- // Jira Issue を取得
84
- const jiraResult = await fetchJiraIssue({
85
- url: 'https://your-domain.atlassian.net/browse/PROJECT-123',
86
- includeComments: true,
87
- includeHistory: false,
88
- });
89
-
90
- if (jiraResult.isOk()) {
91
- console.log(jiraResult.value);
92
- } else {
93
- console.error(jiraResult.error);
94
- }
95
-
96
- // Confluence ページを取得
97
- const confluenceResult = await fetchConfluencePage({
98
- url: 'https://your-domain.atlassian.net/wiki/spaces/SPACE/pages/123456/Page+Title',
99
- includeHistory: true,
100
- });
101
-
102
- if (confluenceResult.isOk()) {
103
- console.log(confluenceResult.value);
104
- }
70
+ | オプション | 短縮形 | 説明 | デフォルト |
71
+ | ------------ | ---- | ---------------------------- | ----- |
72
+ | `--format` | `-f` | 出力形式(json / markdown / yaml) | json |
73
+ | `--download` | `-d` | 添付ファイルをダウンロード | false |
74
+ | `--dir` | `-o` | 保存先ディレクトリ | - |
75
+ | `--diff` | | 差分のみを出力 | false |
76
+ | `--color` | | カラー出力を有効化 | true |
77
+ | `--verbose` | `-v` | 詳細出力を有効化 | false |
78
+ | `--debug` | | デバッグ出力を有効化(開発者向け) | false |
79
+ | `--help` | `-h` | ヘルプを表示 | - |
80
+ | `--version` | `-V` | バージョンを表示 | - |
81
+
82
+ **注意**: `--dir` は `--download` と一緒に使用する必要があります。
83
+
84
+ ## サブコマンド
85
+
86
+ ### `atl-fetch auth check`
87
+
88
+ 認証情報の設定状態を確認します。
89
+
90
+ ```bash
91
+ atl-fetch auth check
105
92
  ```
106
93
 
94
+ 出力例(設定済み):
95
+ ```
96
+ 認証情報チェック
97
+ ────────────────────────────────────────
98
+
99
+ ✓ 認証情報が正しく設定されています
100
+
101
+ 設定状態:
102
+ ATLASSIAN_EMAIL: user@example.com
103
+ ATLASSIAN_API_TOKEN: ***abcd
104
+ ```
105
+
106
+ ## エラーコード
107
+
108
+ 問題が発生した場合、以下のエラーコードと解決策が表示されます。
109
+
110
+ | コード | 説明 | 主な原因 |
111
+ | ------------ | ------------------ | ----------------------- |
112
+ | `ATL-URL-001` | URL の形式が不正 | サポートされていない URL 形式 |
113
+ | `ATL-AUTH-001` | 認証失敗 | API トークンが無効または期限切れ |
114
+ | `ATL-404-001` | リソースが見つからない | URL が間違っている、または削除済み |
115
+ | `ATL-403-001` | アクセス権限なし | 該当リソースへの権限がない |
116
+ | `ATL-NET-001` | ネットワークエラー | インターネット接続の問題 |
117
+
107
118
  ## ライセンス
108
119
 
109
120
  [MIT](LICENSE)
package/dist/cli/cli.d.ts CHANGED
@@ -19,6 +19,10 @@ export interface CliArgs {
19
19
  diff: boolean;
20
20
  /** カラー出力を有効にする */
21
21
  color: boolean;
22
+ /** 詳細出力を有効にする */
23
+ verbose: boolean;
24
+ /** デバッグ出力を有効にする */
25
+ debug: boolean;
22
26
  }
23
27
  /**
24
28
  * CLI 作成オプション
@@ -45,6 +49,10 @@ export declare function createCli(options?: CliOptions): import("yargs").Argv<{
45
49
  diff: boolean;
46
50
  } & {
47
51
  color: boolean;
52
+ } & {
53
+ verbose: boolean;
54
+ } & {
55
+ debug: boolean;
48
56
  }>;
49
57
  /**
50
58
  * CLI を実行する
package/dist/cli/cli.js CHANGED
@@ -6,7 +6,9 @@
6
6
  import { createRequire } from 'node:module';
7
7
  import yargs from 'yargs';
8
8
  import { hideBin } from 'yargs/helpers';
9
+ import { getCredentials } from '../services/auth/auth-service.js';
9
10
  import { fetchAndOutput, fetchAndSave } from '../services/fetch/fetch-service.js';
11
+ import { CliSpinner, colors, formatError, formatSuccess, formatWarn, SPINNER_STEPS } from './spinner.js';
10
12
  const require = createRequire(import.meta.url);
11
13
  const packageJson = require('../../package.json');
12
14
  /**
@@ -25,13 +27,16 @@ export function createCli(options = {}) {
25
27
  const { exitProcess = true } = options;
26
28
  return yargs()
27
29
  .scriptName('atl-fetch')
28
- .usage('Usage: $0 <url> [options]')
30
+ .usage('Usage: $0 <command> [options]')
29
31
  .command('$0 <url>', 'Atlassian Cloud の URL から情報を取得する', (yargs) => {
30
32
  return yargs.positional('url', {
31
33
  demandOption: true,
32
34
  describe: 'Atlassian Cloud URL (Jira Issue または Confluence ページ)',
33
35
  type: 'string',
34
36
  });
37
+ })
38
+ .command('auth', '認証情報を管理する', (yargs) => {
39
+ return yargs.command('check', '認証情報の設定状態を確認する', () => { }, runAuthCheck);
35
40
  })
36
41
  .option('format', {
37
42
  alias: 'f',
@@ -68,6 +73,39 @@ export function createCli(options = {}) {
68
73
  describe: 'カラー出力を有効にする',
69
74
  type: 'boolean',
70
75
  })
76
+ .option('verbose', {
77
+ alias: 'v',
78
+ default: false,
79
+ describe: '詳細出力を有効にする',
80
+ type: 'boolean',
81
+ })
82
+ .option('debug', {
83
+ default: false,
84
+ describe: 'デバッグ出力を有効にする(開発者向け)',
85
+ type: 'boolean',
86
+ })
87
+ .example([
88
+ ['$0 https://mycompany.atlassian.net/browse/PROJ-123', 'Jira Issue を JSON で取得'],
89
+ ['$0 https://mycompany.atlassian.net/browse/PROJ-123 -f markdown', 'Markdown 形式で取得'],
90
+ [
91
+ '$0 https://mycompany.atlassian.net/wiki/spaces/DOCS/pages/123456 -d -o ./output',
92
+ 'Confluence ページを添付ファイルごとダウンロード',
93
+ ],
94
+ ['$0 https://mycompany.atlassian.net/browse/PROJ-123 -v', '詳細モードで実行'],
95
+ ])
96
+ .epilogue(`環境変数:
97
+ ATLASSIAN_EMAIL Atlassian アカウントのメールアドレス(必須)
98
+ ATLASSIAN_API_TOKEN API トークン(必須)
99
+ https://id.atlassian.com/manage-profile/security/api-tokens で生成
100
+
101
+ エラーコード:
102
+ ATL-URL-001 URL の形式が不正
103
+ ATL-AUTH-001 認証失敗
104
+ ATL-404-001 リソースが見つからない
105
+ ATL-403-001 アクセス権限なし
106
+ ATL-NET-001 ネットワークエラー
107
+
108
+ 詳細: https://github.com/semba-yui/atl-fetch`)
71
109
  .help('help')
72
110
  .alias('h', 'help')
73
111
  .version(CLI_VERSION)
@@ -103,29 +141,144 @@ export async function runCli() {
103
141
  const download = argv['download'];
104
142
  const dir = argv['dir'];
105
143
  const colorEnabled = argv['color'];
144
+ const verbose = argv['verbose'];
145
+ const debug = argv['debug'];
146
+ const spinner = new CliSpinner(colorEnabled);
147
+ // verbose モードの場合、処理開始を表示
148
+ if (verbose) {
149
+ console.log(colors.dim(`atl-fetch v${CLI_VERSION}`));
150
+ console.log(colors.dim(`URL: ${url}`));
151
+ console.log(colors.dim(`形式: ${format}`));
152
+ if (download) {
153
+ console.log(colors.dim(`ダウンロードモード: 有効`));
154
+ console.log(colors.dim(`保存先: ${dir ?? process.cwd()}`));
155
+ }
156
+ console.log('');
157
+ }
106
158
  // ダウンロードモード
107
159
  if (download) {
108
160
  const baseDir = dir ?? process.cwd();
161
+ spinner.startStep(SPINNER_STEPS.FETCH_DATA);
109
162
  const result = await fetchAndSave(url, {
110
163
  baseDir,
111
164
  cliVersion: CLI_VERSION,
112
165
  sourceUrl: url,
113
166
  });
114
167
  if (result.isErr()) {
115
- console.error(`エラー: ${result.error.message}`);
168
+ spinner.fail();
169
+ console.error(formatErrorForKind(result.error.kind, result.error.message, url, debug));
116
170
  process.exit(1);
117
171
  }
118
- console.log(`保存完了: ${result.value.directory}`);
172
+ spinner.succeedStep(SPINNER_STEPS.SAVE_FILES);
173
+ console.log(formatSuccess('保存完了', {
174
+ URL: url,
175
+ 保存先: result.value.directory,
176
+ }));
119
177
  return;
120
178
  }
121
179
  // 通常モード(標準出力)
180
+ spinner.startStep(SPINNER_STEPS.FETCH_DATA);
122
181
  const result = await fetchAndOutput(url, {
123
182
  colorEnabled,
124
183
  format,
125
184
  });
126
185
  if (result.isErr()) {
127
- console.error(`エラー: ${result.error.message}`);
186
+ spinner.fail();
187
+ console.error(formatErrorForKind(result.error.kind, result.error.message, url, debug));
128
188
  process.exit(1);
129
189
  }
190
+ spinner.succeedStep(SPINNER_STEPS.FORMAT_OUTPUT);
130
191
  console.log(result.value);
131
192
  }
193
+ /**
194
+ * エラー種別に応じたエラーメッセージを生成する
195
+ *
196
+ * @param kind エラー種別
197
+ * @param message エラーメッセージ
198
+ * @param url 対象URL
199
+ * @param debug デバッグモードが有効かどうか
200
+ * @returns 整形されたエラーメッセージ
201
+ */
202
+ function formatErrorForKind(kind, message, url, debug) {
203
+ let result;
204
+ switch (kind) {
205
+ case 'URL_PARSE_ERROR':
206
+ result = formatError('ATL-URL-001', message, 'URLの形式が正しくありません', `サポートされるURL形式:\n - Jira: https://<site>.atlassian.net/browse/<KEY>\n - Confluence: https://<site>.atlassian.net/wiki/spaces/<SPACE>/pages/<ID>`);
207
+ break;
208
+ case 'AUTH_FAILED':
209
+ result = formatError('ATL-AUTH-001', message, 'APIトークンが無効または期限切れです', `環境変数を確認してください:\n - ATLASSIAN_EMAIL: Atlassianアカウントのメールアドレス\n - ATLASSIAN_API_TOKEN: https://id.atlassian.com/manage-profile/security/api-tokens で生成`);
210
+ break;
211
+ case 'NOT_FOUND':
212
+ result = formatError('ATL-404-001', message, '指定されたリソースが見つかりません', `URL: ${colors.cyan(url)}\n - URLが正しいか確認してください\n - リソースが削除されていないか確認してください`);
213
+ break;
214
+ case 'FORBIDDEN':
215
+ result = formatError('ATL-403-001', message, 'アクセス権限がありません', `URL: ${colors.cyan(url)}\n - 該当リソースへのアクセス権限があるか確認してください`);
216
+ break;
217
+ case 'NETWORK_ERROR':
218
+ result = formatError('ATL-NET-001', message, 'ネットワーク接続に問題があります', '- インターネット接続を確認してください\n - ファイアウォール/プロキシ設定を確認してください');
219
+ break;
220
+ default:
221
+ result = formatError('ATL-ERR-001', message, '予期しないエラーが発生しました', '--debug フラグで詳細を確認してください');
222
+ }
223
+ // デバッグモードの場合、追加情報を表示
224
+ if (debug) {
225
+ result += `\n\n${colors.dim('--- デバッグ情報 ---')}`;
226
+ result += `\n${colors.dim(`エラー種別: ${kind}`)}`;
227
+ result += `\n${colors.dim(`対象URL: ${url}`)}`;
228
+ result += `\n${colors.dim(`タイムスタンプ: ${new Date().toISOString()}`)}`;
229
+ }
230
+ return result;
231
+ }
232
+ /**
233
+ * 認証情報のチェックを実行する
234
+ *
235
+ * 環境変数が正しく設定されているかを確認し、結果を表示する。
236
+ */
237
+ function runAuthCheck() {
238
+ console.log(colors.bold('認証情報チェック'));
239
+ console.log(colors.dim('─'.repeat(40)));
240
+ console.log('');
241
+ const result = getCredentials();
242
+ if (result.isOk()) {
243
+ const email = result.value.email;
244
+ const maskedToken = `***${result.value.apiToken.slice(-4)}`;
245
+ console.log(colors.success('✓ 認証情報が正しく設定されています'));
246
+ console.log('');
247
+ console.log(colors.dim(' 設定状態:'));
248
+ console.log(colors.dim(` ATLASSIAN_EMAIL: ${colors.cyan(email)}`));
249
+ console.log(colors.dim(` ATLASSIAN_API_TOKEN: ${colors.cyan(maskedToken)}`));
250
+ console.log('');
251
+ console.log(colors.dim(' ※ 実際のAPI接続テストは行っていません。'));
252
+ console.log(colors.dim(' トークンが有効かどうかは、実際にURLを取得して確認してください。'));
253
+ }
254
+ else {
255
+ const error = result.error;
256
+ console.log(colors.error('✗ 認証情報の設定に問題があります'));
257
+ console.log('');
258
+ switch (error.kind) {
259
+ case 'MISSING_EMAIL':
260
+ console.log(formatWarn('ATLASSIAN_EMAIL が設定されていません'));
261
+ console.log('');
262
+ console.log(colors.dim(' 設定方法:'));
263
+ console.log(colors.dim(' export ATLASSIAN_EMAIL="your-email@example.com"'));
264
+ break;
265
+ case 'MISSING_TOKEN':
266
+ console.log(formatWarn('ATLASSIAN_API_TOKEN が設定されていません'));
267
+ console.log('');
268
+ console.log(colors.dim(' 設定方法:'));
269
+ console.log(colors.dim(' export ATLASSIAN_API_TOKEN="your-api-token"'));
270
+ console.log('');
271
+ console.log(colors.dim(' トークンの取得:'));
272
+ console.log(colors.dim(' https://id.atlassian.com/manage-profile/security/api-tokens'));
273
+ break;
274
+ case 'INVALID_EMAIL':
275
+ console.log(formatWarn('ATLASSIAN_EMAIL の形式が無効です'));
276
+ console.log('');
277
+ console.log(colors.dim(' 有効なメールアドレス形式で設定してください。'));
278
+ break;
279
+ default:
280
+ console.log(formatWarn(error.message));
281
+ }
282
+ process.exit(1);
283
+ }
284
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * CLI スピナー・進捗表示ユーティリティ
3
+ *
4
+ * ora と picocolors を使用した統一的な進捗表示を提供する。
5
+ */
6
+ /**
7
+ * スピナーのステップ定義
8
+ */
9
+ export interface SpinnerStep {
10
+ /** ステップ名(内部識別用) */
11
+ name: string;
12
+ /** 表示テキスト(進行中) */
13
+ text: string;
14
+ /** 完了時のテキスト */
15
+ successText?: string;
16
+ }
17
+ /**
18
+ * 事前定義されたスピナーステップ
19
+ */
20
+ export declare const SPINNER_STEPS: {
21
+ readonly AUTH_CHECK: {
22
+ readonly name: "auth_check";
23
+ readonly successText: "認証情報を確認しました";
24
+ readonly text: "認証情報を確認中...";
25
+ };
26
+ readonly DOWNLOAD_ATTACHMENTS: {
27
+ readonly name: "download_attachments";
28
+ readonly successText: "添付ファイルをダウンロードしました";
29
+ readonly text: "添付ファイルをダウンロード中...";
30
+ };
31
+ readonly FETCH_CONFLUENCE: {
32
+ readonly name: "fetch_confluence";
33
+ readonly successText: "Confluence ページを取得しました";
34
+ readonly text: "Confluence ページを取得中...";
35
+ };
36
+ readonly FETCH_DATA: {
37
+ readonly name: "fetch_data";
38
+ readonly successText: "データを取得しました";
39
+ readonly text: "データを取得中...";
40
+ };
41
+ readonly FETCH_JIRA: {
42
+ readonly name: "fetch_jira";
43
+ readonly successText: "Jira Issue を取得しました";
44
+ readonly text: "Jira Issue を取得中...";
45
+ };
46
+ readonly FORMAT_OUTPUT: {
47
+ readonly name: "format_output";
48
+ readonly successText: "出力を整形しました";
49
+ readonly text: "出力を整形中...";
50
+ };
51
+ readonly SAVE_FILES: {
52
+ readonly name: "save_files";
53
+ readonly successText: "ファイルを保存しました";
54
+ readonly text: "ファイルを保存中...";
55
+ };
56
+ readonly URL_PARSE: {
57
+ readonly name: "url_parse";
58
+ readonly successText: "URLを解析しました";
59
+ readonly text: "URLを解析中...";
60
+ };
61
+ };
62
+ /**
63
+ * CLI 出力のカラーヘルパー
64
+ */
65
+ export declare const colors: {
66
+ /** 太字 */
67
+ readonly bold: (text: string) => string;
68
+ /** シアン(ファイルパスなど) */
69
+ readonly cyan: (text: string) => string;
70
+ /** 薄いテキスト(グレー) */
71
+ readonly dim: (text: string) => string;
72
+ /** エラーメッセージ(赤) */
73
+ readonly error: (text: string) => string;
74
+ /** 情報メッセージ(青) */
75
+ readonly info: (text: string) => string;
76
+ /** マゼンタ(URL など) */
77
+ readonly magenta: (text: string) => string;
78
+ /** 成功メッセージ(緑) */
79
+ readonly success: (text: string) => string;
80
+ /** 警告メッセージ(黄) */
81
+ readonly warn: (text: string) => string;
82
+ };
83
+ /**
84
+ * CLI スピナー管理クラス
85
+ *
86
+ * 処理の進捗状況をユーザーに表示するためのスピナーを管理する。
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const spinner = new CliSpinner();
91
+ * spinner.start('データを取得中...');
92
+ * // ... 処理 ...
93
+ * spinner.succeed('データを取得しました');
94
+ * ```
95
+ */
96
+ export declare class CliSpinner {
97
+ private spinner;
98
+ private enabled;
99
+ /**
100
+ * CliSpinner インスタンスを作成する
101
+ *
102
+ * @param enabled スピナーを有効にするかどうか(CI環境などでは無効にする)
103
+ */
104
+ constructor(enabled?: boolean);
105
+ /**
106
+ * スピナーを開始する
107
+ *
108
+ * @param text 表示するテキスト
109
+ */
110
+ start(text: string): void;
111
+ /**
112
+ * スピナーのテキストを更新する
113
+ *
114
+ * @param text 新しいテキスト
115
+ */
116
+ update(text: string): void;
117
+ /**
118
+ * スピナーを成功状態で終了する
119
+ *
120
+ * @param text 成功時に表示するテキスト
121
+ */
122
+ succeed(text?: string): void;
123
+ /**
124
+ * スピナーを失敗状態で終了する
125
+ *
126
+ * @param text 失敗時に表示するテキスト
127
+ */
128
+ fail(text?: string): void;
129
+ /**
130
+ * スピナーを警告状態で終了する
131
+ *
132
+ * @param text 警告時に表示するテキスト
133
+ */
134
+ warn(text?: string): void;
135
+ /**
136
+ * スピナーを情報状態で終了する
137
+ *
138
+ * @param text 情報として表示するテキスト
139
+ */
140
+ info(text?: string): void;
141
+ /**
142
+ * スピナーを停止する(状態なし)
143
+ */
144
+ stop(): void;
145
+ /**
146
+ * 事前定義されたステップでスピナーを開始する
147
+ *
148
+ * @param step スピナーステップ定義
149
+ */
150
+ startStep(step: SpinnerStep): void;
151
+ /**
152
+ * 事前定義されたステップを成功状態で終了する
153
+ *
154
+ * @param step スピナーステップ定義
155
+ */
156
+ succeedStep(step: SpinnerStep): void;
157
+ }
158
+ /**
159
+ * エラーメッセージを整形して出力する
160
+ *
161
+ * @param code エラーコード
162
+ * @param message エラーメッセージ
163
+ * @param cause 原因の説明(オプション)
164
+ * @param solution 解決策の説明(オプション)
165
+ * @returns 整形されたエラーメッセージ
166
+ */
167
+ export declare function formatError(code: string, message: string, cause?: string, solution?: string): string;
168
+ /**
169
+ * 成功メッセージを整形する
170
+ *
171
+ * @param message メッセージ
172
+ * @param details 詳細情報(オプション)
173
+ * @returns 整形されたメッセージ
174
+ */
175
+ export declare function formatSuccess(message: string, details?: Record<string, string>): string;
176
+ /**
177
+ * 情報メッセージを整形する
178
+ *
179
+ * @param message メッセージ
180
+ * @returns 整形されたメッセージ
181
+ */
182
+ export declare function formatInfo(message: string): string;
183
+ /**
184
+ * 警告メッセージを整形する
185
+ *
186
+ * @param message メッセージ
187
+ * @returns 整形されたメッセージ
188
+ */
189
+ export declare function formatWarn(message: string): string;
@@ -0,0 +1,247 @@
1
+ /**
2
+ * CLI スピナー・進捗表示ユーティリティ
3
+ *
4
+ * ora と picocolors を使用した統一的な進捗表示を提供する。
5
+ */
6
+ import ora, {} from 'ora';
7
+ import pc from 'picocolors';
8
+ /**
9
+ * 事前定義されたスピナーステップ
10
+ */
11
+ export const SPINNER_STEPS = {
12
+ AUTH_CHECK: {
13
+ name: 'auth_check',
14
+ successText: '認証情報を確認しました',
15
+ text: '認証情報を確認中...',
16
+ },
17
+ DOWNLOAD_ATTACHMENTS: {
18
+ name: 'download_attachments',
19
+ successText: '添付ファイルをダウンロードしました',
20
+ text: '添付ファイルをダウンロード中...',
21
+ },
22
+ FETCH_CONFLUENCE: {
23
+ name: 'fetch_confluence',
24
+ successText: 'Confluence ページを取得しました',
25
+ text: 'Confluence ページを取得中...',
26
+ },
27
+ FETCH_DATA: {
28
+ name: 'fetch_data',
29
+ successText: 'データを取得しました',
30
+ text: 'データを取得中...',
31
+ },
32
+ FETCH_JIRA: {
33
+ name: 'fetch_jira',
34
+ successText: 'Jira Issue を取得しました',
35
+ text: 'Jira Issue を取得中...',
36
+ },
37
+ FORMAT_OUTPUT: {
38
+ name: 'format_output',
39
+ successText: '出力を整形しました',
40
+ text: '出力を整形中...',
41
+ },
42
+ SAVE_FILES: {
43
+ name: 'save_files',
44
+ successText: 'ファイルを保存しました',
45
+ text: 'ファイルを保存中...',
46
+ },
47
+ URL_PARSE: {
48
+ name: 'url_parse',
49
+ successText: 'URLを解析しました',
50
+ text: 'URLを解析中...',
51
+ },
52
+ };
53
+ /**
54
+ * CLI 出力のカラーヘルパー
55
+ */
56
+ export const colors = {
57
+ /** 太字 */
58
+ bold: (text) => pc.bold(text),
59
+ /** シアン(ファイルパスなど) */
60
+ cyan: (text) => pc.cyan(text),
61
+ /** 薄いテキスト(グレー) */
62
+ dim: (text) => pc.dim(text),
63
+ /** エラーメッセージ(赤) */
64
+ error: (text) => pc.red(text),
65
+ /** 情報メッセージ(青) */
66
+ info: (text) => pc.blue(text),
67
+ /** マゼンタ(URL など) */
68
+ magenta: (text) => pc.magenta(text),
69
+ /** 成功メッセージ(緑) */
70
+ success: (text) => pc.green(text),
71
+ /** 警告メッセージ(黄) */
72
+ warn: (text) => pc.yellow(text),
73
+ };
74
+ /**
75
+ * CLI スピナー管理クラス
76
+ *
77
+ * 処理の進捗状況をユーザーに表示するためのスピナーを管理する。
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const spinner = new CliSpinner();
82
+ * spinner.start('データを取得中...');
83
+ * // ... 処理 ...
84
+ * spinner.succeed('データを取得しました');
85
+ * ```
86
+ */
87
+ export class CliSpinner {
88
+ spinner = null;
89
+ enabled;
90
+ /**
91
+ * CliSpinner インスタンスを作成する
92
+ *
93
+ * @param enabled スピナーを有効にするかどうか(CI環境などでは無効にする)
94
+ */
95
+ constructor(enabled = true) {
96
+ // CI環境やテスト環境ではスピナーを無効化
97
+ this.enabled = enabled && !process.env['CI'] && process.stdout.isTTY === true;
98
+ }
99
+ /**
100
+ * スピナーを開始する
101
+ *
102
+ * @param text 表示するテキスト
103
+ */
104
+ start(text) {
105
+ if (!this.enabled) {
106
+ return;
107
+ }
108
+ this.spinner = ora({
109
+ spinner: 'dots',
110
+ text,
111
+ }).start();
112
+ }
113
+ /**
114
+ * スピナーのテキストを更新する
115
+ *
116
+ * @param text 新しいテキスト
117
+ */
118
+ update(text) {
119
+ if (this.spinner) {
120
+ this.spinner.text = text;
121
+ }
122
+ }
123
+ /**
124
+ * スピナーを成功状態で終了する
125
+ *
126
+ * @param text 成功時に表示するテキスト
127
+ */
128
+ succeed(text) {
129
+ if (this.spinner) {
130
+ this.spinner.succeed(text);
131
+ this.spinner = null;
132
+ }
133
+ }
134
+ /**
135
+ * スピナーを失敗状態で終了する
136
+ *
137
+ * @param text 失敗時に表示するテキスト
138
+ */
139
+ fail(text) {
140
+ if (this.spinner) {
141
+ this.spinner.fail(text);
142
+ this.spinner = null;
143
+ }
144
+ }
145
+ /**
146
+ * スピナーを警告状態で終了する
147
+ *
148
+ * @param text 警告時に表示するテキスト
149
+ */
150
+ warn(text) {
151
+ if (this.spinner) {
152
+ this.spinner.warn(text);
153
+ this.spinner = null;
154
+ }
155
+ }
156
+ /**
157
+ * スピナーを情報状態で終了する
158
+ *
159
+ * @param text 情報として表示するテキスト
160
+ */
161
+ info(text) {
162
+ if (this.spinner) {
163
+ this.spinner.info(text);
164
+ this.spinner = null;
165
+ }
166
+ }
167
+ /**
168
+ * スピナーを停止する(状態なし)
169
+ */
170
+ stop() {
171
+ if (this.spinner) {
172
+ this.spinner.stop();
173
+ this.spinner = null;
174
+ }
175
+ }
176
+ /**
177
+ * 事前定義されたステップでスピナーを開始する
178
+ *
179
+ * @param step スピナーステップ定義
180
+ */
181
+ startStep(step) {
182
+ this.start(step.text);
183
+ }
184
+ /**
185
+ * 事前定義されたステップを成功状態で終了する
186
+ *
187
+ * @param step スピナーステップ定義
188
+ */
189
+ succeedStep(step) {
190
+ this.succeed(step.successText ?? step.text);
191
+ }
192
+ }
193
+ /**
194
+ * エラーメッセージを整形して出力する
195
+ *
196
+ * @param code エラーコード
197
+ * @param message エラーメッセージ
198
+ * @param cause 原因の説明(オプション)
199
+ * @param solution 解決策の説明(オプション)
200
+ * @returns 整形されたエラーメッセージ
201
+ */
202
+ export function formatError(code, message, cause, solution) {
203
+ const lines = [];
204
+ lines.push(colors.error(`${colors.bold(code)}: ${message}`));
205
+ if (cause) {
206
+ lines.push(colors.dim(` 原因: ${cause}`));
207
+ }
208
+ if (solution) {
209
+ lines.push(colors.info(` 解決策: ${solution}`));
210
+ }
211
+ return lines.join('\n');
212
+ }
213
+ /**
214
+ * 成功メッセージを整形する
215
+ *
216
+ * @param message メッセージ
217
+ * @param details 詳細情報(オプション)
218
+ * @returns 整形されたメッセージ
219
+ */
220
+ export function formatSuccess(message, details) {
221
+ const lines = [];
222
+ lines.push(colors.success(`✓ ${message}`));
223
+ if (details) {
224
+ for (const [key, value] of Object.entries(details)) {
225
+ lines.push(colors.dim(` ${key}: ${colors.cyan(value)}`));
226
+ }
227
+ }
228
+ return lines.join('\n');
229
+ }
230
+ /**
231
+ * 情報メッセージを整形する
232
+ *
233
+ * @param message メッセージ
234
+ * @returns 整形されたメッセージ
235
+ */
236
+ export function formatInfo(message) {
237
+ return colors.info(`ℹ ${message}`);
238
+ }
239
+ /**
240
+ * 警告メッセージを整形する
241
+ *
242
+ * @param message メッセージ
243
+ * @returns 整形されたメッセージ
244
+ */
245
+ export function formatWarn(message) {
246
+ return colors.warn(`⚠ ${message}`);
247
+ }
package/package.json CHANGED
@@ -12,6 +12,8 @@
12
12
  "got": "14.6.6",
13
13
  "jsdom": "27.4.0",
14
14
  "neverthrow": "8.2.0",
15
+ "ora": "^9.0.0",
16
+ "picocolors": "^1.1.1",
15
17
  "turndown": "7.2.2",
16
18
  "turndown-plugin-gfm": "1.0.2",
17
19
  "yaml": "2.8.2",
@@ -80,7 +82,7 @@
80
82
  },
81
83
  "type": "module",
82
84
  "types": "./dist/index.d.ts",
83
- "version": "1.0.0",
85
+ "version": "1.1.0",
84
86
  "scripts": {
85
87
  "build": "tsc",
86
88
  "dev": "tsc --watch",
@@ -1,104 +0,0 @@
1
- /**
2
- * Result 型 - 成功または失敗を表現する discriminated union
3
- *
4
- * @example
5
- * ```typescript
6
- * function divide(a: number, b: number): Result<number, DivisionError> {
7
- * if (b === 0) {
8
- * return err({ kind: 'DIVISION_BY_ZERO', message: 'Cannot divide by zero' });
9
- * }
10
- * return ok(a / b);
11
- * }
12
- * ```
13
- */
14
- /**
15
- * 成功を表す型
16
- */
17
- export interface Ok<T> {
18
- readonly success: true;
19
- readonly value: T;
20
- }
21
- /**
22
- * 失敗を表す型
23
- */
24
- export interface Err<E> {
25
- readonly success: false;
26
- readonly error: E;
27
- }
28
- /**
29
- * Result 型 - 成功(Ok)または失敗(Err)のどちらかを表す
30
- */
31
- export type Result<T, E> = Ok<T> | Err<E>;
32
- /**
33
- * 成功 Result を作成する
34
- *
35
- * @param value - 成功値
36
- * @returns 成功 Result
37
- */
38
- export declare function ok<T>(value: T): Ok<T>;
39
- /**
40
- * 失敗 Result を作成する
41
- *
42
- * @param error - エラー値
43
- * @returns 失敗 Result
44
- */
45
- export declare function err<E>(error: E): Err<E>;
46
- /**
47
- * Result が成功かどうかを判定する型ガード
48
- *
49
- * @param result - 判定対象の Result
50
- * @returns 成功の場合 true
51
- */
52
- export declare function isOk<T, E>(result: Result<T, E>): result is Ok<T>;
53
- /**
54
- * Result が失敗かどうかを判定する型ガード
55
- *
56
- * @param result - 判定対象の Result
57
- * @returns 失敗の場合 true
58
- */
59
- export declare function isErr<T, E>(result: Result<T, E>): result is Err<E>;
60
- /**
61
- * 成功 Result から値を取り出す
62
- * 失敗 Result の場合はエラーをスローする
63
- *
64
- * @param result - 対象の Result
65
- * @returns 成功値
66
- * @throws 失敗 Result の場合
67
- */
68
- export declare function unwrap<T, E>(result: Result<T, E>): T;
69
- /**
70
- * 失敗 Result からエラー値を取り出す
71
- * 成功 Result の場合はエラーをスローする
72
- *
73
- * @param result - 対象の Result
74
- * @returns エラー値
75
- * @throws 成功 Result の場合
76
- */
77
- export declare function unwrapErr<T, E>(result: Result<T, E>): E;
78
- /**
79
- * 成功 Result から値を取り出す
80
- * 失敗 Result の場合はデフォルト値を返す
81
- *
82
- * @param result - 対象の Result
83
- * @param defaultValue - 失敗時のデフォルト値
84
- * @returns 成功値またはデフォルト値
85
- */
86
- export declare function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T;
87
- /**
88
- * 成功 Result の値を変換する
89
- * 失敗 Result の場合はそのまま返す
90
- *
91
- * @param result - 対象の Result
92
- * @param fn - 変換関数
93
- * @returns 変換された Result
94
- */
95
- export declare function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
96
- /**
97
- * 失敗 Result のエラー値を変換する
98
- * 成功 Result の場合はそのまま返す
99
- *
100
- * @param result - 対象の Result
101
- * @param fn - 変換関数
102
- * @returns 変換された Result
103
- */
104
- export declare function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;
@@ -1,119 +0,0 @@
1
- /**
2
- * Result 型 - 成功または失敗を表現する discriminated union
3
- *
4
- * @example
5
- * ```typescript
6
- * function divide(a: number, b: number): Result<number, DivisionError> {
7
- * if (b === 0) {
8
- * return err({ kind: 'DIVISION_BY_ZERO', message: 'Cannot divide by zero' });
9
- * }
10
- * return ok(a / b);
11
- * }
12
- * ```
13
- */
14
- /**
15
- * 成功 Result を作成する
16
- *
17
- * @param value - 成功値
18
- * @returns 成功 Result
19
- */
20
- export function ok(value) {
21
- return { success: true, value };
22
- }
23
- /**
24
- * 失敗 Result を作成する
25
- *
26
- * @param error - エラー値
27
- * @returns 失敗 Result
28
- */
29
- export function err(error) {
30
- return { error, success: false };
31
- }
32
- /**
33
- * Result が成功かどうかを判定する型ガード
34
- *
35
- * @param result - 判定対象の Result
36
- * @returns 成功の場合 true
37
- */
38
- export function isOk(result) {
39
- return result.success;
40
- }
41
- /**
42
- * Result が失敗かどうかを判定する型ガード
43
- *
44
- * @param result - 判定対象の Result
45
- * @returns 失敗の場合 true
46
- */
47
- export function isErr(result) {
48
- return !result.success;
49
- }
50
- /**
51
- * 成功 Result から値を取り出す
52
- * 失敗 Result の場合はエラーをスローする
53
- *
54
- * @param result - 対象の Result
55
- * @returns 成功値
56
- * @throws 失敗 Result の場合
57
- */
58
- export function unwrap(result) {
59
- if (isOk(result)) {
60
- return result.value;
61
- }
62
- throw new Error(`Attempted to unwrap an Err value: ${String(result.error)}`);
63
- }
64
- /**
65
- * 失敗 Result からエラー値を取り出す
66
- * 成功 Result の場合はエラーをスローする
67
- *
68
- * @param result - 対象の Result
69
- * @returns エラー値
70
- * @throws 成功 Result の場合
71
- */
72
- export function unwrapErr(result) {
73
- if (isErr(result)) {
74
- return result.error;
75
- }
76
- throw new Error('Attempted to unwrapErr an Ok value');
77
- }
78
- /**
79
- * 成功 Result から値を取り出す
80
- * 失敗 Result の場合はデフォルト値を返す
81
- *
82
- * @param result - 対象の Result
83
- * @param defaultValue - 失敗時のデフォルト値
84
- * @returns 成功値またはデフォルト値
85
- */
86
- export function unwrapOr(result, defaultValue) {
87
- if (isOk(result)) {
88
- return result.value;
89
- }
90
- return defaultValue;
91
- }
92
- /**
93
- * 成功 Result の値を変換する
94
- * 失敗 Result の場合はそのまま返す
95
- *
96
- * @param result - 対象の Result
97
- * @param fn - 変換関数
98
- * @returns 変換された Result
99
- */
100
- export function mapResult(result, fn) {
101
- if (isOk(result)) {
102
- return ok(fn(result.value));
103
- }
104
- return result;
105
- }
106
- /**
107
- * 失敗 Result のエラー値を変換する
108
- * 成功 Result の場合はそのまま返す
109
- *
110
- * @param result - 対象の Result
111
- * @param fn - 変換関数
112
- * @returns 変換された Result
113
- */
114
- export function mapErr(result, fn) {
115
- if (isErr(result)) {
116
- return err(fn(result.error));
117
- }
118
- return result;
119
- }