stitchkit 0.114.1 → 0.114.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/CHANGELOG.md +8 -0
- package/dist/entrypoints/cli/publish.js +1 -1
- package/dist/entrypoints/cli.js +2 -2
- package/dist/entrypoints/tools.js +2 -2
- package/dist/{index-kdnfe0sr.js → index-912sza5h.js} +1 -1
- package/dist/{index-t65erk4z.js → index-mgb7r8b8.js} +122 -62
- package/dist/{index-km2qw407.js → index-tnxsq7cw.js} +1 -1
- package/dist/internal/pinned-http.d.ts +27 -0
- package/dist/internal/pinned-http.d.ts.map +1 -0
- package/dist/internal/secure-fetch.d.ts +26 -6
- package/dist/internal/secure-fetch.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,14 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
|
|
|
15
15
|
|
|
16
16
|
## [Unreleased]
|
|
17
17
|
|
|
18
|
+
## [0.114.2] - 2026-10-31
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `stitchkit/tools`, `stitchkit/cli` — the guarded fetch behind `view_file`, `mountDownload` and the CLI `--output-dir` download pins every hop to the address that passed the public-address check. It resolved a hostname, validated the answer, then let `fetch` resolve the same name again, so a resolver that answers public and then private could open a private or loopback socket (SSRF rebinding). The connection is now opened to the validated address with the URL's `Host` header and, over TLS, its server name, on every redirect hop; a URL carrying credentials is refused rather than moved to whatever host a redirect names. Streaming, backpressure, the header/body deadlines, the body cap and output-dir containment are unchanged; `allowPrivateHosts` / `allowPrivateDownloadHosts` still widen the address check without a second lookup.
|
|
23
|
+
**Affects:** `stitchkit/tools` behaviour; `stitchkit/cli` behaviour
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
## [0.114.1] - 2026-10-31
|
|
19
27
|
|
|
20
28
|
### Added
|
package/dist/entrypoints/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
renderCliView2,
|
|
17
17
|
pollUntilDone2,
|
|
18
18
|
createCli2
|
|
19
|
-
} from "../index-
|
|
19
|
+
} from "../index-tnxsq7cw.js";
|
|
20
20
|
import"../index-k42ckyzx.js";
|
|
21
21
|
import"../index-ct75c3at.js";
|
|
22
22
|
import"../index-rhzkndr5.js";
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
checkCliUpdate2,
|
|
41
41
|
applyCliUpdate2,
|
|
42
42
|
rollbackCliUpdate2
|
|
43
|
-
} from "../index-
|
|
43
|
+
} from "../index-912sza5h.js";
|
|
44
44
|
import"../index-86nbfysb.js";
|
|
45
45
|
import"../index-ega3yz4c.js";
|
|
46
46
|
import {
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
WaitTimeoutError,
|
|
75
75
|
runWaitOperation,
|
|
76
76
|
createCli2
|
|
77
|
-
} from "../index-
|
|
77
|
+
} from "../index-tnxsq7cw.js";
|
|
78
78
|
import {
|
|
79
79
|
ManagedFilePathSchema2,
|
|
80
80
|
ManagedFileRefSchema2
|
|
@@ -110,7 +110,7 @@ import {
|
|
|
110
110
|
fetchGuarded,
|
|
111
111
|
fetchPinnedDocument,
|
|
112
112
|
readCapped
|
|
113
|
-
} from "../index-
|
|
113
|
+
} from "../index-mgb7r8b8.js";
|
|
114
114
|
import"../index-1bx1xv1k.js";
|
|
115
115
|
import {
|
|
116
116
|
readBoundedChunks,
|
|
@@ -7,9 +7,88 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/internal/secure-fetch.ts
|
|
9
9
|
import { lookup } from "node:dns/promises";
|
|
10
|
+
import { isIP } from "node:net";
|
|
11
|
+
|
|
12
|
+
// src/internal/pinned-http.ts
|
|
10
13
|
import { request as httpRequest } from "node:http";
|
|
11
14
|
import { request as httpsRequest } from "node:https";
|
|
12
|
-
|
|
15
|
+
async function requestPinned(url, resolved, signal, headers = {}) {
|
|
16
|
+
const transport = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
17
|
+
const incoming = await new Promise((resolve, reject) => {
|
|
18
|
+
let answered = false;
|
|
19
|
+
const request = transport({
|
|
20
|
+
protocol: url.protocol,
|
|
21
|
+
hostname: resolved.address,
|
|
22
|
+
family: resolved.family,
|
|
23
|
+
port: url.port || undefined,
|
|
24
|
+
path: `${url.pathname}${url.search}`,
|
|
25
|
+
method: "GET",
|
|
26
|
+
signal,
|
|
27
|
+
...url.protocol === "https:" ? { servername: url.hostname } : {},
|
|
28
|
+
headers: { host: url.host, accept: "*/*", ...headers }
|
|
29
|
+
}, (response) => {
|
|
30
|
+
answered = true;
|
|
31
|
+
resolve(response);
|
|
32
|
+
});
|
|
33
|
+
request.once("error", (error) => {
|
|
34
|
+
if (answered)
|
|
35
|
+
return;
|
|
36
|
+
reject(signal?.aborted && signal.reason !== undefined ? signal.reason : error);
|
|
37
|
+
});
|
|
38
|
+
request.end();
|
|
39
|
+
});
|
|
40
|
+
return pinnedResponse(incoming);
|
|
41
|
+
}
|
|
42
|
+
function pinnedResponse(incoming) {
|
|
43
|
+
const status = incoming.statusCode ?? 0;
|
|
44
|
+
const headers = new Headers;
|
|
45
|
+
for (const [name, value] of Object.entries(incoming.headers)) {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
for (const item of value)
|
|
48
|
+
headers.append(name, item);
|
|
49
|
+
} else if (value !== undefined) {
|
|
50
|
+
headers.set(name, value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (status === 204 || status === 205 || status === 304) {
|
|
54
|
+
incoming.resume();
|
|
55
|
+
return new Response(null, { status, statusText: incoming.statusMessage, headers });
|
|
56
|
+
}
|
|
57
|
+
let done = false;
|
|
58
|
+
const body = new ReadableStream({
|
|
59
|
+
start(controller) {
|
|
60
|
+
incoming.on("data", (chunk) => {
|
|
61
|
+
if (done)
|
|
62
|
+
return;
|
|
63
|
+
controller.enqueue(chunk);
|
|
64
|
+
if ((controller.desiredSize ?? 1) <= 0)
|
|
65
|
+
incoming.pause();
|
|
66
|
+
});
|
|
67
|
+
incoming.once("end", () => {
|
|
68
|
+
if (done)
|
|
69
|
+
return;
|
|
70
|
+
done = true;
|
|
71
|
+
controller.close();
|
|
72
|
+
});
|
|
73
|
+
incoming.once("error", (error) => {
|
|
74
|
+
if (done)
|
|
75
|
+
return;
|
|
76
|
+
done = true;
|
|
77
|
+
controller.error(error);
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
pull() {
|
|
81
|
+
incoming.resume();
|
|
82
|
+
},
|
|
83
|
+
cancel() {
|
|
84
|
+
done = true;
|
|
85
|
+
incoming.destroy();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return new Response(body, { status, statusText: incoming.statusMessage, headers });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/internal/secure-fetch.ts
|
|
13
92
|
var NUMERIC_HOST = /^(0x[0-9a-f]+|[0-9.]+)$/i;
|
|
14
93
|
var MAX_REDIRECTS = 5;
|
|
15
94
|
function assertHttpUrl(url) {
|
|
@@ -102,9 +181,10 @@ function isPrivateIp(ip) {
|
|
|
102
181
|
}
|
|
103
182
|
return false;
|
|
104
183
|
}
|
|
105
|
-
async
|
|
106
|
-
await
|
|
107
|
-
|
|
184
|
+
var defaultLookup = async (host) => {
|
|
185
|
+
const records = await lookup(host, { all: true });
|
|
186
|
+
return Array.isArray(records) ? records : [records];
|
|
187
|
+
};
|
|
108
188
|
async function fetchGuarded(start, allowPrivate, options = {}) {
|
|
109
189
|
const timeoutMs = options.timeoutMs ?? 15000;
|
|
110
190
|
const bodyTimeoutMs = options.bodyTimeoutMs ?? 120000;
|
|
@@ -120,15 +200,16 @@ async function fetchGuarded(start, allowPrivate, options = {}) {
|
|
|
120
200
|
controller.abort(new Error(`fetch did not produce response headers within ${timeoutMs}ms`));
|
|
121
201
|
}, timeoutMs);
|
|
122
202
|
headerTimer.unref?.();
|
|
203
|
+
const transport = options.transport ?? requestPinned;
|
|
123
204
|
let settled = false;
|
|
124
205
|
try {
|
|
125
206
|
let url = start;
|
|
126
207
|
for (let hop = 0;hop <= MAX_REDIRECTS; hop++) {
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const res = await
|
|
208
|
+
if (url.username || url.password) {
|
|
209
|
+
throw new Error("refusing to fetch a URL that carries credentials");
|
|
210
|
+
}
|
|
211
|
+
const resolved = await resolveAddress(url, allowPrivate, signal, options.dns);
|
|
212
|
+
const res = await transport(url, resolved, signal);
|
|
132
213
|
const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
|
|
133
214
|
if (location === null) {
|
|
134
215
|
settled = true;
|
|
@@ -148,11 +229,27 @@ async function fetchGuarded(start, allowPrivate, options = {}) {
|
|
|
148
229
|
clearTimeout(headerTimer);
|
|
149
230
|
}
|
|
150
231
|
}
|
|
232
|
+
async function resolveAddress(url, allowPrivate, signal, dns) {
|
|
233
|
+
if (!allowPrivate)
|
|
234
|
+
return resolvePublicAddress(url, signal, dns);
|
|
235
|
+
assertHttpUrl(url);
|
|
236
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
237
|
+
if (!host)
|
|
238
|
+
throw new Error("refusing to fetch a URL with no host");
|
|
239
|
+
const ipFamily = isIP(host);
|
|
240
|
+
if (ipFamily)
|
|
241
|
+
return { address: host, family: ipFamily === 6 ? 6 : 4 };
|
|
242
|
+
const records = await (dns ?? defaultLookup)(host, { all: true });
|
|
243
|
+
const record = records[0];
|
|
244
|
+
if (!record)
|
|
245
|
+
throw new Error("host resolved to no addresses");
|
|
246
|
+
return { address: record.address, family: record.family === 6 ? 6 : 4 };
|
|
247
|
+
}
|
|
151
248
|
|
|
152
249
|
class PrivateAddressRefusal extends Error {
|
|
153
250
|
name = "PrivateAddressRefusal";
|
|
154
251
|
}
|
|
155
|
-
async function resolvePublicAddress(url, signal) {
|
|
252
|
+
async function resolvePublicAddress(url, signal, dns) {
|
|
156
253
|
assertHttpUrl(url);
|
|
157
254
|
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
158
255
|
if (!host)
|
|
@@ -169,7 +266,7 @@ async function resolvePublicAddress(url, signal) {
|
|
|
169
266
|
if (host === "localhost" || host.endsWith(".local") || host.endsWith(".internal")) {
|
|
170
267
|
throw new PrivateAddressRefusal("refusing to fetch an internal host");
|
|
171
268
|
}
|
|
172
|
-
const pending =
|
|
269
|
+
const pending = (dns ?? defaultLookup)(host, { all: true });
|
|
173
270
|
const records = signal ? await raceAbort(pending, signal) : await pending;
|
|
174
271
|
for (const record of records) {
|
|
175
272
|
if (isPrivateIp(record.address)) {
|
|
@@ -202,61 +299,24 @@ async function fetchPinnedDocument(start, options) {
|
|
|
202
299
|
throw new Error("document URL cannot contain credentials");
|
|
203
300
|
}
|
|
204
301
|
const resolved = await resolvePublicAddress(url, signal);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
protocol: url.protocol,
|
|
209
|
-
hostname: resolved.address,
|
|
210
|
-
family: resolved.family,
|
|
211
|
-
port: url.port || undefined,
|
|
212
|
-
path: `${url.pathname}${url.search}`,
|
|
213
|
-
method: "GET",
|
|
214
|
-
signal,
|
|
215
|
-
servername: url.hostname,
|
|
216
|
-
headers: {
|
|
217
|
-
host: url.host,
|
|
218
|
-
accept: "application/json",
|
|
219
|
-
...options.headers
|
|
220
|
-
}
|
|
221
|
-
}, (incoming) => {
|
|
222
|
-
const headers = new Headers;
|
|
223
|
-
for (const [name, value] of Object.entries(incoming.headers)) {
|
|
224
|
-
if (Array.isArray(value)) {
|
|
225
|
-
for (const item of value)
|
|
226
|
-
headers.append(name, item);
|
|
227
|
-
} else if (value !== undefined) {
|
|
228
|
-
headers.set(name, value);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
const chunks = [];
|
|
232
|
-
let total = 0;
|
|
233
|
-
incoming.on("data", (chunk) => {
|
|
234
|
-
total += chunk.byteLength;
|
|
235
|
-
if (total > options.maxBytes) {
|
|
236
|
-
incoming.destroy(new Error("response body exceeds configured byte limit"));
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
chunks.push(chunk);
|
|
240
|
-
});
|
|
241
|
-
incoming.once("error", reject);
|
|
242
|
-
incoming.once("end", () => {
|
|
243
|
-
const body = new Uint8Array(total);
|
|
244
|
-
let offset = 0;
|
|
245
|
-
for (const chunk of chunks) {
|
|
246
|
-
body.set(chunk, offset);
|
|
247
|
-
offset += chunk.byteLength;
|
|
248
|
-
}
|
|
249
|
-
resolve({ status: incoming.statusCode ?? 0, headers, body, url });
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
request.once("error", reject);
|
|
253
|
-
request.end();
|
|
302
|
+
const response = await requestPinned(url, resolved, signal, {
|
|
303
|
+
accept: "application/json",
|
|
304
|
+
...options.headers
|
|
254
305
|
});
|
|
306
|
+
const body = await readCapped(response, options.maxBytes);
|
|
307
|
+
if (body === null)
|
|
308
|
+
throw new Error("response body exceeds configured byte limit");
|
|
309
|
+
const document = {
|
|
310
|
+
status: response.status,
|
|
311
|
+
headers: response.headers,
|
|
312
|
+
body: new Uint8Array(body),
|
|
313
|
+
url
|
|
314
|
+
};
|
|
255
315
|
if (response.status < 300 || response.status >= 400)
|
|
256
|
-
return
|
|
316
|
+
return document;
|
|
257
317
|
const location = response.headers.get("location");
|
|
258
318
|
if (!location)
|
|
259
|
-
return
|
|
319
|
+
return document;
|
|
260
320
|
url = new URL(location, url);
|
|
261
321
|
}
|
|
262
322
|
throw new Error("too many redirects");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One hop over a socket opened to the address the guard validated.
|
|
3
|
+
*
|
|
4
|
+
* The address is given, never looked up here: that is what makes a validated
|
|
5
|
+
* answer the one the connection uses. The `Host` header keeps the URL's host
|
|
6
|
+
* and, over TLS, the certificate is verified against it (`servername`), so a
|
|
7
|
+
* pinned connection is not a weaker one. The body is a Fetch stream over the
|
|
8
|
+
* socket, with backpressure and cancellation preserved.
|
|
9
|
+
*/
|
|
10
|
+
import type { IncomingMessage } from 'node:http';
|
|
11
|
+
/** One hop, already resolved: open the connection to exactly `address`. */
|
|
12
|
+
export type PinnedTransport = (url: URL, resolved: {
|
|
13
|
+
address: string;
|
|
14
|
+
family: 4 | 6;
|
|
15
|
+
}, signal: AbortSignal | undefined) => Promise<Response>;
|
|
16
|
+
/**
|
|
17
|
+
* One hop over a socket opened to `resolved.address`: the `Host` header keeps
|
|
18
|
+
* the URL's host and, for TLS, the certificate is verified against it
|
|
19
|
+
* (`servername`), so a pinned connection is not a weaker connection.
|
|
20
|
+
*/
|
|
21
|
+
export declare function requestPinned(url: URL, resolved: {
|
|
22
|
+
address: string;
|
|
23
|
+
family: 4 | 6;
|
|
24
|
+
}, signal: AbortSignal | undefined, headers?: Record<string, string>): Promise<Response>;
|
|
25
|
+
/** An `IncomingMessage` as a streaming Fetch response, keeping backpressure and cancellation. */
|
|
26
|
+
export declare function pinnedResponse(incoming: IncomingMessage): Response;
|
|
27
|
+
//# sourceMappingURL=pinned-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinned-http.d.ts","sourceRoot":"","sources":["../../src/internal/pinned-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAIjD,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,EAC5C,MAAM,EAAE,WAAW,GAAG,SAAS,KAC5B,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,EAC5C,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,OAAO,CAAC,QAAQ,CAAC,CAgCnB;AAED,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,QAAQ,CA6ClE"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PinnedTransport } from './pinned-http.js';
|
|
1
2
|
/** True unless the literal belongs to the globally routable unicast Internet. */
|
|
2
3
|
export declare function isPrivateIp(ip: string): boolean;
|
|
3
4
|
/**
|
|
@@ -14,13 +15,21 @@ export declare function assertPublicUrl(url: URL, signal?: AbortSignal): Promise
|
|
|
14
15
|
/**
|
|
15
16
|
* Fetch a URL with the SSRF guard enforced on the initial URL and on every
|
|
16
17
|
* redirect hop — a public host can otherwise `302` to an internal address or a
|
|
17
|
-
* non-http(s) scheme
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
18
|
+
* non-http(s) scheme — and with the connection pinned to the address that
|
|
19
|
+
* passed the check, so a resolver cannot answer public and then connect
|
|
20
|
+
* somewhere private.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The resolver seam: one `lookup` call per hop. Production uses `node:dns`;
|
|
24
|
+
* a test supplies a controlled answer to prove which address the socket went
|
|
25
|
+
* to, and that a second answer cannot open another one.
|
|
23
26
|
*/
|
|
27
|
+
export type GuardedDnsLookup = (host: string, options: {
|
|
28
|
+
all: true;
|
|
29
|
+
}) => Promise<Array<{
|
|
30
|
+
address: string;
|
|
31
|
+
family: number;
|
|
32
|
+
}>>;
|
|
24
33
|
export interface GuardedFetchOptions {
|
|
25
34
|
/**
|
|
26
35
|
* Deadline for producing the FINAL response headers — DNS, connects and every
|
|
@@ -36,6 +45,17 @@ export interface GuardedFetchOptions {
|
|
|
36
45
|
bodyTimeoutMs?: number;
|
|
37
46
|
/** Optional caller cancellation, combined with both deadlines. */
|
|
38
47
|
signal?: AbortSignal;
|
|
48
|
+
/**
|
|
49
|
+
* Test seam: the resolver whose answer the connection is pinned to. Default
|
|
50
|
+
* `node:dns`. It replaces the name lookup, never the address check.
|
|
51
|
+
*/
|
|
52
|
+
dns?: GuardedDnsLookup;
|
|
53
|
+
/**
|
|
54
|
+
* Test seam: how one resolved hop is opened. Default the pinned
|
|
55
|
+
* `http`/`https` request. Only the redirect tests replace it; production and
|
|
56
|
+
* the rebinding test use the default, which is where pinning happens.
|
|
57
|
+
*/
|
|
58
|
+
transport?: PinnedTransport;
|
|
39
59
|
}
|
|
40
60
|
export declare function fetchGuarded(start: URL, allowPrivate: boolean, options?: GuardedFetchOptions): Promise<Response>;
|
|
41
61
|
export interface PinnedDocumentFetchOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-fetch.d.ts","sourceRoot":"","sources":["../../src/internal/secure-fetch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secure-fetch.d.ts","sourceRoot":"","sources":["../../src/internal/secure-fetch.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAmDrD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA8D/C;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED;;;;;;GAMG;AACH;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,GAAG,EAAE,IAAI,CAAA;CAAE,KACnB,OAAO,CAAC,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAQzD,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,GAAG,CAAC,EAAE,gBAAgB,CAAC;IACvB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,GAAG,EACV,YAAY,EAAE,OAAO,EACrB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,QAAQ,CAAC,CAuDnB;AAqBD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;CACV;AAED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,2BAA2B;CACzC;AA+CD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,sBAAsB,CAAC,CA2CjC;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGnF"}
|
package/package.json
CHANGED