@gitbeaker/requester-utils 43.5.0 → 43.6.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.md +1 -1
- package/README.md +7 -4
- package/dist/index.d.mts +11 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +21 -15
- package/dist/index.mjs +21 -15
- package/package.json +4 -3
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -26,11 +26,13 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<p align="center">
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<a href="https://codeclimate.com/github/jdalrymple/gitbeaker">
|
|
32
|
-
<img src="https://codeclimate.com/github/jdalrymple/gitbeaker/badges/gpa.svg" alt="Code Climate maintainability">
|
|
29
|
+
<a href="https://dl.circleci.com/status-badge/redirect/gh/jdalrymple/gitbeaker/tree/main">
|
|
30
|
+
<img alt="CircleCI" src="https://dl.circleci.com/status-badge/img/gh/jdalrymple/gitbeaker/tree/main.svg?style=svg" />
|
|
33
31
|
</a>
|
|
32
|
+
<a href="https://codecov.io/gh/jdalrymple/gitbeaker">
|
|
33
|
+
<img alt="Requester Utils Coverage" src="https://img.shields.io/codecov/c/github/jdalrymple/gitbeaker?flag=requester-utils&logo=codecov&label=coverage"/>
|
|
34
|
+
</a>
|
|
35
|
+
|
|
34
36
|
<a href="https://github.com/intuit/auto">
|
|
35
37
|
<img src="https://img.shields.io/badge/release-auto.svg?colorA=888888&colorB=9B065A&label=auto" alt="Auto">
|
|
36
38
|
</a>
|
|
@@ -230,6 +232,7 @@ import { RequesterUtils, BaseResource } from '@gitbeaker/requester-utils';
|
|
|
230
232
|
<td align="center" valign="top" width="0.33%"><a href="https://github.com/kayw-geek"><img src="https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/29700073?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="Kay W."/></td>
|
|
231
233
|
<td align="center" valign="top" width="0.33%"><a href="https://ffflorian.dev/"><img src="https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5497598?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="Florian Imdahl"/></td>
|
|
232
234
|
<td align="center" valign="top" width="0.33%"><a href="https://github.com/lanthier"><img src="https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/9666344?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="lanthier"/></td>
|
|
235
|
+
<td align="center" valign="top" width="0.33%"><a href="https://github.com/Teo-ShaoWei"><img src="https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/6925907?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt="ShaoWei Teo"/></td>
|
|
233
236
|
</tr>
|
|
234
237
|
</p>
|
|
235
238
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Agent } from 'http';
|
|
2
|
+
|
|
1
3
|
type RateLimiterFn = () => Promise<number>;
|
|
2
4
|
type RateLimiters = Record<string, RateLimiterFn | {
|
|
3
5
|
method: string;
|
|
@@ -24,9 +26,9 @@ type ResourceOptions = {
|
|
|
24
26
|
[authHeader: string]: () => Promise<string>;
|
|
25
27
|
};
|
|
26
28
|
url: string;
|
|
27
|
-
rejectUnauthorized: boolean;
|
|
28
29
|
rateLimits?: RateLimitOptions;
|
|
29
30
|
rateLimitDuration?: number;
|
|
31
|
+
agent?: Agent;
|
|
30
32
|
};
|
|
31
33
|
type DefaultRequestOptions = {
|
|
32
34
|
body?: FormData | Record<string, unknown>;
|
|
@@ -46,6 +48,7 @@ type RequestOptions = {
|
|
|
46
48
|
asStream?: boolean;
|
|
47
49
|
signal?: AbortSignal;
|
|
48
50
|
rateLimiters?: Record<string, RateLimiterFn>;
|
|
51
|
+
agent?: Agent;
|
|
49
52
|
};
|
|
50
53
|
interface RequesterType {
|
|
51
54
|
get<T extends ResponseBodyTypes>(endpoint: string, options?: DefaultRequestOptions): Promise<FormattedResponse<T>>;
|
|
@@ -55,20 +58,22 @@ interface RequesterType {
|
|
|
55
58
|
delete<T extends ResponseBodyTypes>(endpoint: string, options?: DefaultRequestOptions): Promise<FormattedResponse<T>>;
|
|
56
59
|
}
|
|
57
60
|
type RequestHandlerFn<T extends ResponseBodyTypes = ResponseBodyTypes> = (endpoint: string, options?: Record<string, unknown>) => Promise<FormattedResponse<T>>;
|
|
58
|
-
declare function generateRateLimiterFn(limit: number, interval: number): () => Promise<
|
|
61
|
+
declare function generateRateLimiterFn(limit: number, interval: number): () => Promise<any>;
|
|
59
62
|
declare function formatQuery(params?: Record<string, unknown>): string;
|
|
60
63
|
type OptionsHandlerFn = (serviceOptions: ResourceOptions, requestOptions: RequestOptions) => Promise<RequestOptions>;
|
|
61
64
|
declare function defaultOptionsHandler(resourceOptions: ResourceOptions, { body, searchParams, sudo, signal, asStream, method, }?: DefaultRequestOptions): Promise<RequestOptions>;
|
|
62
65
|
declare function createRateLimiters(rateLimitOptions?: RateLimitOptions, rateLimitDuration?: number): RateLimiters;
|
|
63
66
|
declare function createRequesterFn(optionsHandler: OptionsHandlerFn, requestHandler: RequestHandlerFn): (serviceOptions: ResourceOptions) => RequesterType;
|
|
64
|
-
|
|
67
|
+
type PresetConstructors<T> = {
|
|
68
|
+
[K in keyof T]: T[K];
|
|
69
|
+
};
|
|
70
|
+
declare function presetResourceArguments<T extends Record<string, any>>(resources: T, customConfig?: Record<string, unknown>): PresetConstructors<T>;
|
|
65
71
|
declare function getMatchingRateLimiter(endpoint: string, rateLimiters?: RateLimiters, method?: string): RateLimiterFn;
|
|
66
72
|
|
|
67
73
|
interface RootResourceOptions<C> {
|
|
68
74
|
requesterFn?: (resourceOptions: ResourceOptions) => RequesterType;
|
|
69
75
|
host?: string;
|
|
70
76
|
prefixUrl?: string;
|
|
71
|
-
rejectUnauthorized?: boolean;
|
|
72
77
|
camelize?: C;
|
|
73
78
|
queryTimeout?: number | null;
|
|
74
79
|
rateLimitDuration?: number;
|
|
@@ -76,6 +81,7 @@ interface RootResourceOptions<C> {
|
|
|
76
81
|
profileToken?: string;
|
|
77
82
|
profileMode?: 'execution' | 'memory';
|
|
78
83
|
rateLimits?: RateLimitOptions;
|
|
84
|
+
agent?: Agent;
|
|
79
85
|
}
|
|
80
86
|
type GitlabToken = string | (() => Promise<string>);
|
|
81
87
|
interface BaseRequestOptionsWithOAuthToken<C> extends RootResourceOptions<C> {
|
|
@@ -100,8 +106,7 @@ declare class BaseResource<C extends boolean = false> {
|
|
|
100
106
|
[authHeader: string]: () => Promise<string>;
|
|
101
107
|
};
|
|
102
108
|
readonly camelize: C | undefined;
|
|
103
|
-
|
|
104
|
-
constructor({ sudo, profileToken, camelize, requesterFn, profileMode, host, prefixUrl, rejectUnauthorized, queryTimeout, rateLimitDuration, rateLimits, ...tokens }: BaseResourceOptions<C>);
|
|
109
|
+
constructor({ sudo, profileToken, camelize, requesterFn, agent, profileMode, host, prefixUrl, queryTimeout, rateLimitDuration, rateLimits, ...tokens }: BaseResourceOptions<C>);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
declare class GitbeakerRequestError extends Error {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Agent } from 'http';
|
|
2
|
+
|
|
1
3
|
type RateLimiterFn = () => Promise<number>;
|
|
2
4
|
type RateLimiters = Record<string, RateLimiterFn | {
|
|
3
5
|
method: string;
|
|
@@ -24,9 +26,9 @@ type ResourceOptions = {
|
|
|
24
26
|
[authHeader: string]: () => Promise<string>;
|
|
25
27
|
};
|
|
26
28
|
url: string;
|
|
27
|
-
rejectUnauthorized: boolean;
|
|
28
29
|
rateLimits?: RateLimitOptions;
|
|
29
30
|
rateLimitDuration?: number;
|
|
31
|
+
agent?: Agent;
|
|
30
32
|
};
|
|
31
33
|
type DefaultRequestOptions = {
|
|
32
34
|
body?: FormData | Record<string, unknown>;
|
|
@@ -46,6 +48,7 @@ type RequestOptions = {
|
|
|
46
48
|
asStream?: boolean;
|
|
47
49
|
signal?: AbortSignal;
|
|
48
50
|
rateLimiters?: Record<string, RateLimiterFn>;
|
|
51
|
+
agent?: Agent;
|
|
49
52
|
};
|
|
50
53
|
interface RequesterType {
|
|
51
54
|
get<T extends ResponseBodyTypes>(endpoint: string, options?: DefaultRequestOptions): Promise<FormattedResponse<T>>;
|
|
@@ -55,20 +58,22 @@ interface RequesterType {
|
|
|
55
58
|
delete<T extends ResponseBodyTypes>(endpoint: string, options?: DefaultRequestOptions): Promise<FormattedResponse<T>>;
|
|
56
59
|
}
|
|
57
60
|
type RequestHandlerFn<T extends ResponseBodyTypes = ResponseBodyTypes> = (endpoint: string, options?: Record<string, unknown>) => Promise<FormattedResponse<T>>;
|
|
58
|
-
declare function generateRateLimiterFn(limit: number, interval: number): () => Promise<
|
|
61
|
+
declare function generateRateLimiterFn(limit: number, interval: number): () => Promise<any>;
|
|
59
62
|
declare function formatQuery(params?: Record<string, unknown>): string;
|
|
60
63
|
type OptionsHandlerFn = (serviceOptions: ResourceOptions, requestOptions: RequestOptions) => Promise<RequestOptions>;
|
|
61
64
|
declare function defaultOptionsHandler(resourceOptions: ResourceOptions, { body, searchParams, sudo, signal, asStream, method, }?: DefaultRequestOptions): Promise<RequestOptions>;
|
|
62
65
|
declare function createRateLimiters(rateLimitOptions?: RateLimitOptions, rateLimitDuration?: number): RateLimiters;
|
|
63
66
|
declare function createRequesterFn(optionsHandler: OptionsHandlerFn, requestHandler: RequestHandlerFn): (serviceOptions: ResourceOptions) => RequesterType;
|
|
64
|
-
|
|
67
|
+
type PresetConstructors<T> = {
|
|
68
|
+
[K in keyof T]: T[K];
|
|
69
|
+
};
|
|
70
|
+
declare function presetResourceArguments<T extends Record<string, any>>(resources: T, customConfig?: Record<string, unknown>): PresetConstructors<T>;
|
|
65
71
|
declare function getMatchingRateLimiter(endpoint: string, rateLimiters?: RateLimiters, method?: string): RateLimiterFn;
|
|
66
72
|
|
|
67
73
|
interface RootResourceOptions<C> {
|
|
68
74
|
requesterFn?: (resourceOptions: ResourceOptions) => RequesterType;
|
|
69
75
|
host?: string;
|
|
70
76
|
prefixUrl?: string;
|
|
71
|
-
rejectUnauthorized?: boolean;
|
|
72
77
|
camelize?: C;
|
|
73
78
|
queryTimeout?: number | null;
|
|
74
79
|
rateLimitDuration?: number;
|
|
@@ -76,6 +81,7 @@ interface RootResourceOptions<C> {
|
|
|
76
81
|
profileToken?: string;
|
|
77
82
|
profileMode?: 'execution' | 'memory';
|
|
78
83
|
rateLimits?: RateLimitOptions;
|
|
84
|
+
agent?: Agent;
|
|
79
85
|
}
|
|
80
86
|
type GitlabToken = string | (() => Promise<string>);
|
|
81
87
|
interface BaseRequestOptionsWithOAuthToken<C> extends RootResourceOptions<C> {
|
|
@@ -100,8 +106,7 @@ declare class BaseResource<C extends boolean = false> {
|
|
|
100
106
|
[authHeader: string]: () => Promise<string>;
|
|
101
107
|
};
|
|
102
108
|
readonly camelize: C | undefined;
|
|
103
|
-
|
|
104
|
-
constructor({ sudo, profileToken, camelize, requesterFn, profileMode, host, prefixUrl, rejectUnauthorized, queryTimeout, rateLimitDuration, rateLimits, ...tokens }: BaseResourceOptions<C>);
|
|
109
|
+
constructor({ sudo, profileToken, camelize, requesterFn, agent, profileMode, host, prefixUrl, queryTimeout, rateLimitDuration, rateLimits, ...tokens }: BaseResourceOptions<C>);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
declare class GitbeakerRequestError extends Error {
|
package/dist/index.js
CHANGED
|
@@ -29,12 +29,13 @@ async function defaultOptionsHandler(resourceOptions, {
|
|
|
29
29
|
asStream = false,
|
|
30
30
|
method = "GET"
|
|
31
31
|
} = {}) {
|
|
32
|
-
const { headers: preconfiguredHeaders, authHeaders, url } = resourceOptions;
|
|
32
|
+
const { headers: preconfiguredHeaders, authHeaders, url, agent } = resourceOptions;
|
|
33
33
|
const defaultOptions = {
|
|
34
34
|
method,
|
|
35
35
|
asStream,
|
|
36
36
|
signal,
|
|
37
|
-
prefixUrl: url
|
|
37
|
+
prefixUrl: url,
|
|
38
|
+
agent
|
|
38
39
|
};
|
|
39
40
|
defaultOptions.headers = { ...preconfiguredHeaders };
|
|
40
41
|
if (sudo) defaultOptions.headers.sudo = `${sudo}`;
|
|
@@ -88,20 +89,27 @@ function createRequesterFn(optionsHandler, requestHandler) {
|
|
|
88
89
|
return requester;
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
|
-
function
|
|
92
|
-
return class extends
|
|
93
|
-
constructor(...
|
|
94
|
-
const [config, ...
|
|
95
|
-
super({ ...
|
|
92
|
+
function createPresetConstructor(Constructor, presetConfig) {
|
|
93
|
+
return class extends Constructor {
|
|
94
|
+
constructor(...args) {
|
|
95
|
+
const [config, ...rest] = args;
|
|
96
|
+
super({ ...presetConfig, ...config }, ...rest);
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
function presetResourceArguments(resources, customConfig = {}) {
|
|
100
|
-
const
|
|
101
|
-
Object.entries(resources).
|
|
102
|
-
|
|
101
|
+
const result = {};
|
|
102
|
+
Object.entries(resources).forEach(([key, Constructor]) => {
|
|
103
|
+
if (typeof Constructor === "function") {
|
|
104
|
+
result[key] = createPresetConstructor(
|
|
105
|
+
Constructor,
|
|
106
|
+
customConfig
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
result[key] = Constructor;
|
|
110
|
+
}
|
|
103
111
|
});
|
|
104
|
-
return
|
|
112
|
+
return result;
|
|
105
113
|
}
|
|
106
114
|
function getMatchingRateLimiter(endpoint, rateLimiters = {}, method = "GET") {
|
|
107
115
|
const sortedEndpoints = Object.keys(rateLimiters).sort().reverse();
|
|
@@ -160,16 +168,15 @@ var BaseResource = class {
|
|
|
160
168
|
headers;
|
|
161
169
|
authHeaders;
|
|
162
170
|
camelize;
|
|
163
|
-
rejectUnauthorized;
|
|
164
171
|
constructor({
|
|
165
172
|
sudo,
|
|
166
173
|
profileToken,
|
|
167
174
|
camelize,
|
|
168
175
|
requesterFn,
|
|
176
|
+
agent,
|
|
169
177
|
profileMode = "execution",
|
|
170
178
|
host = "https://gitlab.com",
|
|
171
179
|
prefixUrl = "",
|
|
172
|
-
rejectUnauthorized = true,
|
|
173
180
|
queryTimeout = 3e5,
|
|
174
181
|
rateLimitDuration = 60,
|
|
175
182
|
rateLimits = DEFAULT_RATE_LIMITS,
|
|
@@ -179,7 +186,6 @@ var BaseResource = class {
|
|
|
179
186
|
this.url = [host, "api", "v4", prefixUrl].join("/");
|
|
180
187
|
this.headers = {};
|
|
181
188
|
this.authHeaders = {};
|
|
182
|
-
this.rejectUnauthorized = rejectUnauthorized;
|
|
183
189
|
this.camelize = camelize;
|
|
184
190
|
this.queryTimeout = queryTimeout;
|
|
185
191
|
if ("oauthToken" in tokens)
|
|
@@ -196,7 +202,7 @@ var BaseResource = class {
|
|
|
196
202
|
this.headers["X-Profile-Mode"] = profileMode;
|
|
197
203
|
}
|
|
198
204
|
if (sudo) this.headers.Sudo = `${sudo}`;
|
|
199
|
-
this.requester = requesterFn({ ...this, rateLimits, rateLimitDuration });
|
|
205
|
+
this.requester = requesterFn({ ...this, rateLimits, rateLimitDuration, agent });
|
|
200
206
|
}
|
|
201
207
|
};
|
|
202
208
|
|
package/dist/index.mjs
CHANGED
|
@@ -23,12 +23,13 @@ async function defaultOptionsHandler(resourceOptions, {
|
|
|
23
23
|
asStream = false,
|
|
24
24
|
method = "GET"
|
|
25
25
|
} = {}) {
|
|
26
|
-
const { headers: preconfiguredHeaders, authHeaders, url } = resourceOptions;
|
|
26
|
+
const { headers: preconfiguredHeaders, authHeaders, url, agent } = resourceOptions;
|
|
27
27
|
const defaultOptions = {
|
|
28
28
|
method,
|
|
29
29
|
asStream,
|
|
30
30
|
signal,
|
|
31
|
-
prefixUrl: url
|
|
31
|
+
prefixUrl: url,
|
|
32
|
+
agent
|
|
32
33
|
};
|
|
33
34
|
defaultOptions.headers = { ...preconfiguredHeaders };
|
|
34
35
|
if (sudo) defaultOptions.headers.sudo = `${sudo}`;
|
|
@@ -82,20 +83,27 @@ function createRequesterFn(optionsHandler, requestHandler) {
|
|
|
82
83
|
return requester;
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
|
-
function
|
|
86
|
-
return class extends
|
|
87
|
-
constructor(...
|
|
88
|
-
const [config, ...
|
|
89
|
-
super({ ...
|
|
86
|
+
function createPresetConstructor(Constructor, presetConfig) {
|
|
87
|
+
return class extends Constructor {
|
|
88
|
+
constructor(...args) {
|
|
89
|
+
const [config, ...rest] = args;
|
|
90
|
+
super({ ...presetConfig, ...config }, ...rest);
|
|
90
91
|
}
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
function presetResourceArguments(resources, customConfig = {}) {
|
|
94
|
-
const
|
|
95
|
-
Object.entries(resources).
|
|
96
|
-
|
|
95
|
+
const result = {};
|
|
96
|
+
Object.entries(resources).forEach(([key, Constructor]) => {
|
|
97
|
+
if (typeof Constructor === "function") {
|
|
98
|
+
result[key] = createPresetConstructor(
|
|
99
|
+
Constructor,
|
|
100
|
+
customConfig
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
result[key] = Constructor;
|
|
104
|
+
}
|
|
97
105
|
});
|
|
98
|
-
return
|
|
106
|
+
return result;
|
|
99
107
|
}
|
|
100
108
|
function getMatchingRateLimiter(endpoint, rateLimiters = {}, method = "GET") {
|
|
101
109
|
const sortedEndpoints = Object.keys(rateLimiters).sort().reverse();
|
|
@@ -154,16 +162,15 @@ var BaseResource = class {
|
|
|
154
162
|
headers;
|
|
155
163
|
authHeaders;
|
|
156
164
|
camelize;
|
|
157
|
-
rejectUnauthorized;
|
|
158
165
|
constructor({
|
|
159
166
|
sudo,
|
|
160
167
|
profileToken,
|
|
161
168
|
camelize,
|
|
162
169
|
requesterFn,
|
|
170
|
+
agent,
|
|
163
171
|
profileMode = "execution",
|
|
164
172
|
host = "https://gitlab.com",
|
|
165
173
|
prefixUrl = "",
|
|
166
|
-
rejectUnauthorized = true,
|
|
167
174
|
queryTimeout = 3e5,
|
|
168
175
|
rateLimitDuration = 60,
|
|
169
176
|
rateLimits = DEFAULT_RATE_LIMITS,
|
|
@@ -173,7 +180,6 @@ var BaseResource = class {
|
|
|
173
180
|
this.url = [host, "api", "v4", prefixUrl].join("/");
|
|
174
181
|
this.headers = {};
|
|
175
182
|
this.authHeaders = {};
|
|
176
|
-
this.rejectUnauthorized = rejectUnauthorized;
|
|
177
183
|
this.camelize = camelize;
|
|
178
184
|
this.queryTimeout = queryTimeout;
|
|
179
185
|
if ("oauthToken" in tokens)
|
|
@@ -190,7 +196,7 @@ var BaseResource = class {
|
|
|
190
196
|
this.headers["X-Profile-Mode"] = profileMode;
|
|
191
197
|
}
|
|
192
198
|
if (sudo) this.headers.Sudo = `${sudo}`;
|
|
193
|
-
this.requester = requesterFn({ ...this, rateLimits, rateLimitDuration });
|
|
199
|
+
this.requester = requesterFn({ ...this, rateLimits, rateLimitDuration, agent });
|
|
194
200
|
}
|
|
195
201
|
};
|
|
196
202
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitbeaker/requester-utils",
|
|
3
|
-
"version": "43.
|
|
3
|
+
"version": "43.6.0",
|
|
4
4
|
"description": "Utility functions for requester implementatons used in @gitbeaker",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -49,13 +49,14 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"picomatch-browser": "^2.2.6",
|
|
51
51
|
"qs": "^6.14.0",
|
|
52
|
-
"rate-limiter-flexible": "^
|
|
52
|
+
"rate-limiter-flexible": "^8.0.1",
|
|
53
53
|
"xcase": "^2.0.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^24.3.0",
|
|
57
|
+
"@types/qs": "^6.14.0",
|
|
57
58
|
"tsup": "^8.5.0",
|
|
58
59
|
"typescript": "^5.9.2"
|
|
59
60
|
},
|
|
60
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "bdafa8ad001658674492b09a948f02fb22cd45a9"
|
|
61
62
|
}
|