atl-fetch 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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/cli/cli.d.ts +61 -0
- package/dist/cli/cli.js +131 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +4 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/ports/file/file-port.d.ts +89 -0
- package/dist/ports/file/file-port.js +155 -0
- package/dist/ports/file/index.d.ts +1 -0
- package/dist/ports/file/index.js +1 -0
- package/dist/ports/http/http-port.d.ts +107 -0
- package/dist/ports/http/http-port.js +238 -0
- package/dist/ports/http/index.d.ts +1 -0
- package/dist/ports/http/index.js +1 -0
- package/dist/services/auth/auth-service.d.ts +79 -0
- package/dist/services/auth/auth-service.js +158 -0
- package/dist/services/auth/index.d.ts +1 -0
- package/dist/services/auth/index.js +1 -0
- package/dist/services/confluence/confluence-service.d.ts +152 -0
- package/dist/services/confluence/confluence-service.js +510 -0
- package/dist/services/confluence/index.d.ts +1 -0
- package/dist/services/confluence/index.js +1 -0
- package/dist/services/diff/diff-service.d.ts +84 -0
- package/dist/services/diff/diff-service.js +881 -0
- package/dist/services/diff/index.d.ts +1 -0
- package/dist/services/diff/index.js +1 -0
- package/dist/services/fetch/fetch-service.d.ts +112 -0
- package/dist/services/fetch/fetch-service.js +302 -0
- package/dist/services/fetch/index.d.ts +1 -0
- package/dist/services/fetch/index.js +1 -0
- package/dist/services/jira/index.d.ts +1 -0
- package/dist/services/jira/index.js +1 -0
- package/dist/services/jira/jira-service.d.ts +100 -0
- package/dist/services/jira/jira-service.js +354 -0
- package/dist/services/output/index.d.ts +4 -0
- package/dist/services/output/index.js +4 -0
- package/dist/services/output/output-service.d.ts +67 -0
- package/dist/services/output/output-service.js +228 -0
- package/dist/services/storage/index.d.ts +6 -0
- package/dist/services/storage/index.js +6 -0
- package/dist/services/storage/storage-service.d.ts +77 -0
- package/dist/services/storage/storage-service.js +738 -0
- package/dist/services/text-converter/index.d.ts +1 -0
- package/dist/services/text-converter/index.js +1 -0
- package/dist/services/text-converter/text-converter.d.ts +35 -0
- package/dist/services/text-converter/text-converter.js +681 -0
- package/dist/services/url-parser/index.d.ts +1 -0
- package/dist/services/url-parser/index.js +1 -0
- package/dist/services/url-parser/url-parser.d.ts +43 -0
- package/dist/services/url-parser/url-parser.js +283 -0
- package/dist/types/auth.d.ts +25 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/confluence.d.ts +68 -0
- package/dist/types/confluence.js +1 -0
- package/dist/types/diff.d.ts +77 -0
- package/dist/types/diff.js +7 -0
- package/dist/types/fetch.d.ts +65 -0
- package/dist/types/fetch.js +1 -0
- package/dist/types/file.d.ts +22 -0
- package/dist/types/file.js +1 -0
- package/dist/types/http.d.ts +45 -0
- package/dist/types/http.js +1 -0
- package/dist/types/jira.d.ts +90 -0
- package/dist/types/jira.js +1 -0
- package/dist/types/output.d.ts +55 -0
- package/dist/types/output.js +7 -0
- package/dist/types/result.d.ts +104 -0
- package/dist/types/result.js +119 -0
- package/dist/types/storage.d.ts +209 -0
- package/dist/types/storage.js +6 -0
- package/dist/types/url-parser.d.ts +46 -0
- package/dist/types/url-parser.js +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type Result } from 'neverthrow';
|
|
2
|
+
import type { HttpError, HttpRequestOptions, HttpResponse, ProgressCallback } from '../../types/http.js';
|
|
3
|
+
/**
|
|
4
|
+
* HTTP リクエストを実行する
|
|
5
|
+
*
|
|
6
|
+
* got ライブラリを使用して HTTP リクエストを実行し、Result 型で結果を返す。
|
|
7
|
+
* 認証ヘッダーの付与、リトライ、タイムアウト処理をサポート。
|
|
8
|
+
*
|
|
9
|
+
* @typeParam T - レスポンスデータの型
|
|
10
|
+
* @param url - リクエスト先の URL
|
|
11
|
+
* @param options - リクエストオプション(メソッド、ヘッダー、ボディ、タイムアウト)
|
|
12
|
+
* @returns 成功時は {@link HttpResponse} を含む Ok、失敗時は {@link HttpError} を含む Err
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // GET リクエスト
|
|
17
|
+
* const result = await httpRequest<{ id: string }>('https://api.example.com/data');
|
|
18
|
+
* if (result.isOk()) {
|
|
19
|
+
* console.log(result.value.data.id);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // 認証付き GET リクエスト
|
|
26
|
+
* const result = await httpRequest<{ user: string }>('https://api.example.com/me', {
|
|
27
|
+
* headers: { Authorization: 'Basic xxx' }
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // POST リクエスト
|
|
34
|
+
* const result = await httpRequest<{ id: string }>('https://api.example.com/create', {
|
|
35
|
+
* method: 'POST',
|
|
36
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
* body: JSON.stringify({ name: 'test' }),
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function httpRequest<T>(url: string, options?: HttpRequestOptions): Promise<Result<HttpResponse<T>, HttpError>>;
|
|
42
|
+
/**
|
|
43
|
+
* エラーを HttpError にマッピングする
|
|
44
|
+
*
|
|
45
|
+
* @param error - キャッチしたエラー
|
|
46
|
+
* @returns 対応する HttpError
|
|
47
|
+
*
|
|
48
|
+
* @internal テスト用に export
|
|
49
|
+
*/
|
|
50
|
+
export declare function mapErrorToHttpError(error: unknown): HttpError;
|
|
51
|
+
/**
|
|
52
|
+
* HTTP ステータスコードに対応するテキストを取得する
|
|
53
|
+
*
|
|
54
|
+
* @param status - HTTP ステータスコード
|
|
55
|
+
* @returns ステータステキスト
|
|
56
|
+
*
|
|
57
|
+
* @internal テスト用に export
|
|
58
|
+
*/
|
|
59
|
+
export declare function getStatusText(status: number): string;
|
|
60
|
+
/**
|
|
61
|
+
* ファイルをダウンロードして指定パスに保存する
|
|
62
|
+
*
|
|
63
|
+
* got ライブラリを使用してストリームでファイルをダウンロードし、
|
|
64
|
+
* 指定したパスに保存する。大きなファイルでもメモリを節約できる。
|
|
65
|
+
*
|
|
66
|
+
* @param url - ダウンロード対象の URL
|
|
67
|
+
* @param destPath - 保存先ファイルパス
|
|
68
|
+
* @param headers - リクエストヘッダー(認証ヘッダー等)
|
|
69
|
+
* @param onProgress - 進捗コールバック関数
|
|
70
|
+
* @param timeout - タイムアウト(ミリ秒)
|
|
71
|
+
* @returns 成功時は void を含む Ok、失敗時は {@link HttpError} を含む Err
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // 基本的なダウンロード
|
|
76
|
+
* const result = await httpDownload(
|
|
77
|
+
* 'https://example.com/file.pdf',
|
|
78
|
+
* '/path/to/save/file.pdf'
|
|
79
|
+
* );
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // 認証付きダウンロード
|
|
85
|
+
* const result = await httpDownload(
|
|
86
|
+
* 'https://api.example.com/attachments/123',
|
|
87
|
+
* '/path/to/save/attachment.pdf',
|
|
88
|
+
* { Authorization: 'Basic xxx' }
|
|
89
|
+
* );
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* // 進捗コールバック付きダウンロード
|
|
95
|
+
* const result = await httpDownload(
|
|
96
|
+
* 'https://example.com/large-file.zip',
|
|
97
|
+
* '/path/to/save/large-file.zip',
|
|
98
|
+
* undefined,
|
|
99
|
+
* (transferred, total) => {
|
|
100
|
+
* if (total) {
|
|
101
|
+
* console.log(`Progress: ${(transferred / total * 100).toFixed(1)}%`);
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
* );
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function httpDownload(url: string, destPath: string, headers?: Record<string, string>, onProgress?: ProgressCallback, timeout?: number): Promise<Result<void, HttpError>>;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
|
+
import got, { HTTPError, RequestError, TimeoutError } from 'got';
|
|
6
|
+
import { err, ok } from 'neverthrow';
|
|
7
|
+
/**
|
|
8
|
+
* デフォルトのタイムアウト(ミリ秒)
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
11
|
+
/**
|
|
12
|
+
* リトライ対象のステータスコード
|
|
13
|
+
*/
|
|
14
|
+
const RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
15
|
+
/**
|
|
16
|
+
* リトライ回数
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_RETRY_LIMIT = 2;
|
|
19
|
+
/**
|
|
20
|
+
* HTTP リクエストを実行する
|
|
21
|
+
*
|
|
22
|
+
* got ライブラリを使用して HTTP リクエストを実行し、Result 型で結果を返す。
|
|
23
|
+
* 認証ヘッダーの付与、リトライ、タイムアウト処理をサポート。
|
|
24
|
+
*
|
|
25
|
+
* @typeParam T - レスポンスデータの型
|
|
26
|
+
* @param url - リクエスト先の URL
|
|
27
|
+
* @param options - リクエストオプション(メソッド、ヘッダー、ボディ、タイムアウト)
|
|
28
|
+
* @returns 成功時は {@link HttpResponse} を含む Ok、失敗時は {@link HttpError} を含む Err
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // GET リクエスト
|
|
33
|
+
* const result = await httpRequest<{ id: string }>('https://api.example.com/data');
|
|
34
|
+
* if (result.isOk()) {
|
|
35
|
+
* console.log(result.value.data.id);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // 認証付き GET リクエスト
|
|
42
|
+
* const result = await httpRequest<{ user: string }>('https://api.example.com/me', {
|
|
43
|
+
* headers: { Authorization: 'Basic xxx' }
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // POST リクエスト
|
|
50
|
+
* const result = await httpRequest<{ id: string }>('https://api.example.com/create', {
|
|
51
|
+
* method: 'POST',
|
|
52
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
* body: JSON.stringify({ name: 'test' }),
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export async function httpRequest(url, options = {}) {
|
|
58
|
+
const { method = 'GET', headers = {}, body, timeout = DEFAULT_TIMEOUT } = options;
|
|
59
|
+
try {
|
|
60
|
+
const response = await got(url, {
|
|
61
|
+
body,
|
|
62
|
+
headers,
|
|
63
|
+
method,
|
|
64
|
+
responseType: 'json',
|
|
65
|
+
retry: {
|
|
66
|
+
limit: DEFAULT_RETRY_LIMIT,
|
|
67
|
+
statusCodes: RETRY_STATUS_CODES,
|
|
68
|
+
},
|
|
69
|
+
timeout: {
|
|
70
|
+
request: timeout,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
// ヘッダーを Record<string, string> に変換
|
|
74
|
+
const responseHeaders = {};
|
|
75
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
76
|
+
if (typeof value === 'string') {
|
|
77
|
+
responseHeaders[key] = value;
|
|
78
|
+
}
|
|
79
|
+
else if (Array.isArray(value)) {
|
|
80
|
+
responseHeaders[key] = value.join(', ');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return ok({
|
|
84
|
+
data: response.body,
|
|
85
|
+
headers: responseHeaders,
|
|
86
|
+
status: response.statusCode,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return err(mapErrorToHttpError(error));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* エラーを HttpError にマッピングする
|
|
95
|
+
*
|
|
96
|
+
* @param error - キャッチしたエラー
|
|
97
|
+
* @returns 対応する HttpError
|
|
98
|
+
*
|
|
99
|
+
* @internal テスト用に export
|
|
100
|
+
*/
|
|
101
|
+
export function mapErrorToHttpError(error) {
|
|
102
|
+
// タイムアウトエラー
|
|
103
|
+
if (error instanceof TimeoutError) {
|
|
104
|
+
return {
|
|
105
|
+
kind: 'TIMEOUT',
|
|
106
|
+
message: `リクエストがタイムアウトしました: ${error.message}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// HTTP エラー(4xx, 5xx)
|
|
110
|
+
if (error instanceof HTTPError) {
|
|
111
|
+
const status = error.response.statusCode;
|
|
112
|
+
const statusText = getStatusText(status);
|
|
113
|
+
return {
|
|
114
|
+
kind: 'HTTP_ERROR',
|
|
115
|
+
message: `HTTP ${status} ${statusText}: ${error.message}`,
|
|
116
|
+
status,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// その他の got エラー(ネットワークエラー等)
|
|
120
|
+
if (error instanceof RequestError) {
|
|
121
|
+
return {
|
|
122
|
+
kind: 'NETWORK_ERROR',
|
|
123
|
+
message: `ネットワークエラーが発生しました: ${error.message}`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// 不明なエラー
|
|
127
|
+
const message = error instanceof Error ? error.message : '不明なエラーが発生しました';
|
|
128
|
+
return {
|
|
129
|
+
kind: 'NETWORK_ERROR',
|
|
130
|
+
message: `リクエストエラー: ${message}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* HTTP ステータスコードに対応するテキストを取得する
|
|
135
|
+
*
|
|
136
|
+
* @param status - HTTP ステータスコード
|
|
137
|
+
* @returns ステータステキスト
|
|
138
|
+
*
|
|
139
|
+
* @internal テスト用に export
|
|
140
|
+
*/
|
|
141
|
+
export function getStatusText(status) {
|
|
142
|
+
const statusTexts = {
|
|
143
|
+
400: 'Bad Request',
|
|
144
|
+
401: 'Unauthorized',
|
|
145
|
+
403: 'Forbidden',
|
|
146
|
+
404: 'Not Found',
|
|
147
|
+
405: 'Method Not Allowed',
|
|
148
|
+
408: 'Request Timeout',
|
|
149
|
+
429: 'Too Many Requests',
|
|
150
|
+
500: 'Internal Server Error',
|
|
151
|
+
502: 'Bad Gateway',
|
|
152
|
+
503: 'Service Unavailable',
|
|
153
|
+
504: 'Gateway Timeout',
|
|
154
|
+
};
|
|
155
|
+
return statusTexts[status] ?? 'Error';
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* ファイルをダウンロードして指定パスに保存する
|
|
159
|
+
*
|
|
160
|
+
* got ライブラリを使用してストリームでファイルをダウンロードし、
|
|
161
|
+
* 指定したパスに保存する。大きなファイルでもメモリを節約できる。
|
|
162
|
+
*
|
|
163
|
+
* @param url - ダウンロード対象の URL
|
|
164
|
+
* @param destPath - 保存先ファイルパス
|
|
165
|
+
* @param headers - リクエストヘッダー(認証ヘッダー等)
|
|
166
|
+
* @param onProgress - 進捗コールバック関数
|
|
167
|
+
* @param timeout - タイムアウト(ミリ秒)
|
|
168
|
+
* @returns 成功時は void を含む Ok、失敗時は {@link HttpError} を含む Err
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* // 基本的なダウンロード
|
|
173
|
+
* const result = await httpDownload(
|
|
174
|
+
* 'https://example.com/file.pdf',
|
|
175
|
+
* '/path/to/save/file.pdf'
|
|
176
|
+
* );
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // 認証付きダウンロード
|
|
182
|
+
* const result = await httpDownload(
|
|
183
|
+
* 'https://api.example.com/attachments/123',
|
|
184
|
+
* '/path/to/save/attachment.pdf',
|
|
185
|
+
* { Authorization: 'Basic xxx' }
|
|
186
|
+
* );
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* // 進捗コールバック付きダウンロード
|
|
192
|
+
* const result = await httpDownload(
|
|
193
|
+
* 'https://example.com/large-file.zip',
|
|
194
|
+
* '/path/to/save/large-file.zip',
|
|
195
|
+
* undefined,
|
|
196
|
+
* (transferred, total) => {
|
|
197
|
+
* if (total) {
|
|
198
|
+
* console.log(`Progress: ${(transferred / total * 100).toFixed(1)}%`);
|
|
199
|
+
* }
|
|
200
|
+
* }
|
|
201
|
+
* );
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export async function httpDownload(url, destPath, headers, onProgress, timeout = DEFAULT_TIMEOUT) {
|
|
205
|
+
try {
|
|
206
|
+
// 保存先ディレクトリを作成
|
|
207
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
208
|
+
// ダウンロードストリームを作成
|
|
209
|
+
const downloadStream = got.stream(url, {
|
|
210
|
+
...(headers && { headers }),
|
|
211
|
+
retry: {
|
|
212
|
+
limit: DEFAULT_RETRY_LIMIT,
|
|
213
|
+
statusCodes: RETRY_STATUS_CODES,
|
|
214
|
+
},
|
|
215
|
+
timeout: {
|
|
216
|
+
request: timeout,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
// 進捗追跡
|
|
220
|
+
let transferred = 0;
|
|
221
|
+
let total;
|
|
222
|
+
downloadStream.on('downloadProgress', (progress) => {
|
|
223
|
+
transferred = progress.transferred;
|
|
224
|
+
total = progress.total;
|
|
225
|
+
if (onProgress) {
|
|
226
|
+
onProgress(transferred, total);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// ファイル書き込みストリームを作成
|
|
230
|
+
const writeStream = createWriteStream(destPath);
|
|
231
|
+
// ストリームをパイプラインで接続
|
|
232
|
+
await pipeline(downloadStream, writeStream);
|
|
233
|
+
return ok(undefined);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
return err(mapErrorToHttpError(error));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { httpDownload, httpRequest } from './http-port.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { httpDownload, httpRequest } from './http-port.js';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type Result } from 'neverthrow';
|
|
2
|
+
import { type ZodIssue } from 'zod';
|
|
3
|
+
import type { AuthError, Credentials } from '../../types/auth.js';
|
|
4
|
+
/**
|
|
5
|
+
* Zod の issues 配列からエラーの種別を判定し、AuthError を生成する
|
|
6
|
+
*
|
|
7
|
+
* @t3-oss/env-core は onValidationError で ZodError ではなく issues 配列を渡す
|
|
8
|
+
*
|
|
9
|
+
* @param issues - Zod バリデーションエラーの issues 配列
|
|
10
|
+
* @returns 適切な AuthError
|
|
11
|
+
*
|
|
12
|
+
* @internal テスト用に export
|
|
13
|
+
*/
|
|
14
|
+
export declare function mapZodIssuesToAuthError(issues: ZodIssue[]): AuthError;
|
|
15
|
+
/**
|
|
16
|
+
* 環境変数から Atlassian Cloud の認証情報を取得する
|
|
17
|
+
*
|
|
18
|
+
* @t3-oss/env-core と zod を使用して環境変数をバリデーションし、
|
|
19
|
+
* 型安全な認証情報を返す。
|
|
20
|
+
*
|
|
21
|
+
* 必要な環境変数:
|
|
22
|
+
* - `ATLASSIAN_EMAIL`: Atlassian アカウントのメールアドレス(有効なメール形式)
|
|
23
|
+
* - `ATLASSIAN_API_TOKEN`: Atlassian API トークン(空でない文字列)
|
|
24
|
+
*
|
|
25
|
+
* @returns 成功時は {@link Credentials} を含む Ok、失敗時は {@link AuthError} を含す Err
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // 成功例
|
|
30
|
+
* process.env.ATLASSIAN_EMAIL = 'user@example.com';
|
|
31
|
+
* process.env.ATLASSIAN_API_TOKEN = 'your-api-token';
|
|
32
|
+
* const result = getCredentials();
|
|
33
|
+
* if (result.isOk()) {
|
|
34
|
+
* console.log(result.value.email); // 'user@example.com'
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // 失敗例: 環境変数未設定
|
|
41
|
+
* delete process.env.ATLASSIAN_EMAIL;
|
|
42
|
+
* const result = getCredentials();
|
|
43
|
+
* if (result.isErr()) {
|
|
44
|
+
* console.log(result.error.kind); // 'MISSING_EMAIL'
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function getCredentials(): Result<Credentials, AuthError>;
|
|
49
|
+
/**
|
|
50
|
+
* Basic Auth ヘッダーを生成する
|
|
51
|
+
*
|
|
52
|
+
* 環境変数から認証情報を取得し、`Authorization: Basic <base64>` 形式の
|
|
53
|
+
* ヘッダー値を生成する。email:token を base64 エンコードする。
|
|
54
|
+
*
|
|
55
|
+
* @returns 成功時は `Basic <base64>` 形式の文字列を含む Ok、失敗時は {@link AuthError} を含む Err
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // 成功例
|
|
60
|
+
* process.env.ATLASSIAN_EMAIL = 'user@example.com';
|
|
61
|
+
* process.env.ATLASSIAN_API_TOKEN = 'your-api-token';
|
|
62
|
+
* const result = getAuthHeader();
|
|
63
|
+
* if (result.isOk()) {
|
|
64
|
+
* // result.value は 'Basic dXNlckBleGFtcGxlLmNvbTp5b3VyLWFwaS10b2tlbg=='
|
|
65
|
+
* fetch(url, { headers: { Authorization: result.value } });
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // 失敗例: 環境変数未設定
|
|
72
|
+
* delete process.env.ATLASSIAN_EMAIL;
|
|
73
|
+
* const result = getAuthHeader();
|
|
74
|
+
* if (result.isErr()) {
|
|
75
|
+
* console.error(result.error.message);
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function getAuthHeader(): Result<string, AuthError>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createEnv } from '@t3-oss/env-core';
|
|
2
|
+
import { err, ok } from 'neverthrow';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/**
|
|
5
|
+
* 環境変数スキーマの定義
|
|
6
|
+
*
|
|
7
|
+
* @t3-oss/env-core と zod を使用して環境変数をバリデーション
|
|
8
|
+
*/
|
|
9
|
+
const envSchema = {
|
|
10
|
+
ATLASSIAN_API_TOKEN: z.string().min(1, 'API トークンは必須です'),
|
|
11
|
+
ATLASSIAN_EMAIL: z.string().min(1, 'メールアドレスは必須です').email('有効なメールアドレスを指定してください'),
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Zod の issues 配列からエラーの種別を判定し、AuthError を生成する
|
|
15
|
+
*
|
|
16
|
+
* @t3-oss/env-core は onValidationError で ZodError ではなく issues 配列を渡す
|
|
17
|
+
*
|
|
18
|
+
* @param issues - Zod バリデーションエラーの issues 配列
|
|
19
|
+
* @returns 適切な AuthError
|
|
20
|
+
*
|
|
21
|
+
* @internal テスト用に export
|
|
22
|
+
*/
|
|
23
|
+
export function mapZodIssuesToAuthError(issues) {
|
|
24
|
+
// 最初のエラーを取得
|
|
25
|
+
const firstIssue = issues[0];
|
|
26
|
+
if (!firstIssue) {
|
|
27
|
+
return {
|
|
28
|
+
kind: 'INVALID_CREDENTIALS',
|
|
29
|
+
message: '認証情報のバリデーションに失敗しました。',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const path = firstIssue.path[0];
|
|
33
|
+
const code = firstIssue.code;
|
|
34
|
+
// ATLASSIAN_EMAIL のエラー
|
|
35
|
+
if (path === 'ATLASSIAN_EMAIL') {
|
|
36
|
+
// 未設定または空の場合
|
|
37
|
+
if (code === 'invalid_type' || code === 'too_small') {
|
|
38
|
+
return {
|
|
39
|
+
kind: 'MISSING_EMAIL',
|
|
40
|
+
message: '環境変数 ATLASSIAN_EMAIL が設定されていません。Atlassian アカウントのメールアドレスを設定してください。',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// メール形式が無効
|
|
44
|
+
return {
|
|
45
|
+
kind: 'INVALID_EMAIL',
|
|
46
|
+
message: `無効なメールアドレス形式です。有効なメールアドレスを ATLASSIAN_EMAIL に設定してください。`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// ATLASSIAN_API_TOKEN のエラー
|
|
50
|
+
if (path === 'ATLASSIAN_API_TOKEN') {
|
|
51
|
+
return {
|
|
52
|
+
kind: 'MISSING_TOKEN',
|
|
53
|
+
message: '環境変数 ATLASSIAN_API_TOKEN が設定されていません。Atlassian API トークンを設定してください。トークンは https://id.atlassian.com/manage-profile/security/api-tokens で作成できます。',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
kind: 'INVALID_CREDENTIALS',
|
|
58
|
+
message: `認証情報のバリデーションに失敗しました: ${firstIssue.message}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 環境変数から Atlassian Cloud の認証情報を取得する
|
|
63
|
+
*
|
|
64
|
+
* @t3-oss/env-core と zod を使用して環境変数をバリデーションし、
|
|
65
|
+
* 型安全な認証情報を返す。
|
|
66
|
+
*
|
|
67
|
+
* 必要な環境変数:
|
|
68
|
+
* - `ATLASSIAN_EMAIL`: Atlassian アカウントのメールアドレス(有効なメール形式)
|
|
69
|
+
* - `ATLASSIAN_API_TOKEN`: Atlassian API トークン(空でない文字列)
|
|
70
|
+
*
|
|
71
|
+
* @returns 成功時は {@link Credentials} を含む Ok、失敗時は {@link AuthError} を含す Err
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // 成功例
|
|
76
|
+
* process.env.ATLASSIAN_EMAIL = 'user@example.com';
|
|
77
|
+
* process.env.ATLASSIAN_API_TOKEN = 'your-api-token';
|
|
78
|
+
* const result = getCredentials();
|
|
79
|
+
* if (result.isOk()) {
|
|
80
|
+
* console.log(result.value.email); // 'user@example.com'
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* // 失敗例: 環境変数未設定
|
|
87
|
+
* delete process.env.ATLASSIAN_EMAIL;
|
|
88
|
+
* const result = getCredentials();
|
|
89
|
+
* if (result.isErr()) {
|
|
90
|
+
* console.log(result.error.kind); // 'MISSING_EMAIL'
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function getCredentials() {
|
|
95
|
+
let validationError = null;
|
|
96
|
+
try {
|
|
97
|
+
const env = createEnv({
|
|
98
|
+
onValidationError: (issues) => {
|
|
99
|
+
// @t3-oss/env-core は issues 配列を渡す
|
|
100
|
+
validationError = mapZodIssuesToAuthError(issues);
|
|
101
|
+
throw new Error('Validation failed');
|
|
102
|
+
},
|
|
103
|
+
runtimeEnv: process.env,
|
|
104
|
+
server: envSchema,
|
|
105
|
+
});
|
|
106
|
+
return ok({
|
|
107
|
+
apiToken: env.ATLASSIAN_API_TOKEN,
|
|
108
|
+
email: env.ATLASSIAN_EMAIL,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
if (validationError) {
|
|
113
|
+
return err(validationError);
|
|
114
|
+
}
|
|
115
|
+
// 予期しないエラー
|
|
116
|
+
return err({
|
|
117
|
+
kind: 'INVALID_CREDENTIALS',
|
|
118
|
+
message: '認証情報の取得中に予期しないエラーが発生しました。',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Basic Auth ヘッダーを生成する
|
|
124
|
+
*
|
|
125
|
+
* 環境変数から認証情報を取得し、`Authorization: Basic <base64>` 形式の
|
|
126
|
+
* ヘッダー値を生成する。email:token を base64 エンコードする。
|
|
127
|
+
*
|
|
128
|
+
* @returns 成功時は `Basic <base64>` 形式の文字列を含む Ok、失敗時は {@link AuthError} を含む Err
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* // 成功例
|
|
133
|
+
* process.env.ATLASSIAN_EMAIL = 'user@example.com';
|
|
134
|
+
* process.env.ATLASSIAN_API_TOKEN = 'your-api-token';
|
|
135
|
+
* const result = getAuthHeader();
|
|
136
|
+
* if (result.isOk()) {
|
|
137
|
+
* // result.value は 'Basic dXNlckBleGFtcGxlLmNvbTp5b3VyLWFwaS10b2tlbg=='
|
|
138
|
+
* fetch(url, { headers: { Authorization: result.value } });
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* // 失敗例: 環境変数未設定
|
|
145
|
+
* delete process.env.ATLASSIAN_EMAIL;
|
|
146
|
+
* const result = getAuthHeader();
|
|
147
|
+
* if (result.isErr()) {
|
|
148
|
+
* console.error(result.error.message);
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function getAuthHeader() {
|
|
153
|
+
return getCredentials().map((credentials) => {
|
|
154
|
+
const rawCredentials = `${credentials.email}:${credentials.apiToken}`;
|
|
155
|
+
const base64Encoded = Buffer.from(rawCredentials).toString('base64');
|
|
156
|
+
return `Basic ${base64Encoded}`;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getAuthHeader, getCredentials } from './auth-service.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getAuthHeader, getCredentials } from './auth-service.js';
|