-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathapi.ts
More file actions
123 lines (107 loc) · 3.04 KB
/
api.ts
File metadata and controls
123 lines (107 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import path from 'path';
import chalk from 'chalk';
import { pickBy } from 'lodash';
import fetch from 'node-fetch';
import qs from 'query-string';
import { getConfig } from '../config';
const API_DEBUG = process.env.API_DEBUG === 'true';
let version = 'unknown';
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
version = require(path.resolve(__dirname, '../../package.json')).version;
} catch (e) {}
export class ApiError extends Error {
constructor(
public status: number,
public body: {
error: string;
message?: string;
invalidFields?: { path: string[]; message: string; type: string }[];
},
) {
super();
if (body?.message) {
this.message = body.message;
}
}
}
export function debugLog(...args: any[]) {
if (!API_DEBUG) return;
console.log(chalk.dim(new Date().toISOString()), chalk.cyan`[DEBUG]`, ...args);
}
export async function api<T = any>({
method,
path,
data,
query = {},
auth,
responseType = 'json',
abortController,
}: {
method: 'get' | 'post' | 'put' | 'delete' | 'patch';
path: string;
query?: Record<string, string | string[] | boolean | number | undefined>;
data?: unknown;
auth?: { apiUrl: string; credentials: string };
responseType?: 'json' | 'stream';
abortController?: AbortController;
}): Promise<{ body: T }> {
const config = auth || getConfig();
const sanitizedQuery = pickBy(query, (v) => v);
const queryString = Object.keys(sanitizedQuery).length ? `?${qs.stringify(sanitizedQuery)}` : '';
const url = !path.startsWith('https') ? `${config.apiUrl}${path}${queryString}` : `${path}${queryString}`;
const headers = {
'Content-Type': 'application/json',
authorization: `token ${config.credentials}`,
'X-CLI-Version': version,
};
if (API_DEBUG) {
console.log(
chalk.dim(new Date().toISOString()),
chalk.cyan`[HTTP REQUEST]`,
chalk.dim(method?.toUpperCase()),
url,
chalk.dim(JSON.stringify({ headers })),
);
if (data) {
console.log(chalk.dim(JSON.stringify(data)));
}
}
const response = await fetch(url, {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
timeout: responseType === 'stream' ? 0 : undefined,
signal: abortController ? (abortController.signal as any) : undefined,
});
let body;
if (responseType === 'json') {
const rawBody = await response.text();
try {
body = responseType === 'json' ? JSON.parse(rawBody) : response.body;
} catch (e) {
body = rawBody;
}
} else {
body = response.body;
}
if (API_DEBUG) {
console.log(
chalk.dim(new Date().toISOString()),
chalk.cyan`[HTTP RESPONSE]`,
url,
chalk.cyan(response.status),
chalk.dim(JSON.stringify({ headers: response.headers })),
);
if (body && responseType === 'json') {
console.log(chalk.dim(JSON.stringify(body, null, 2)));
}
}
switch (response.status) {
case 200:
case 201:
return { body };
default:
throw new ApiError(response.status, body as any);
}
}