@pithy-sh/cloudflare 0.1.1 → 0.1.3
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/dist/ai/aiManager.d.ts +99 -0
- package/dist/ai/aiManager.d.ts.map +1 -0
- package/dist/ai/aiManager.js +148 -0
- package/dist/ai/vectorizeManager.d.ts +67 -0
- package/dist/ai/vectorizeManager.d.ts.map +1 -0
- package/dist/ai/vectorizeManager.js +101 -0
- package/dist/ai/vectorizeProvisioner.d.ts +123 -0
- package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
- package/dist/ai/vectorizeProvisioner.js +195 -0
- package/dist/client/accounts.d.ts +48 -0
- package/dist/client/accounts.d.ts.map +1 -0
- package/dist/client/accounts.js +55 -0
- package/dist/client/clients.d.ts +122 -0
- package/dist/client/clients.d.ts.map +1 -0
- package/dist/client/clients.js +216 -0
- package/dist/client/errors.d.ts +73 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +98 -0
- package/dist/client/manager.d.ts +45 -0
- package/dist/client/manager.d.ts.map +1 -0
- package/dist/client/manager.js +51 -0
- package/dist/d1/d1Manager.d.ts +74 -0
- package/dist/d1/d1Manager.d.ts.map +1 -0
- package/dist/d1/d1Manager.js +128 -0
- package/dist/d1/d1PreparedStatement.d.ts +24 -0
- package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
- package/dist/d1/d1PreparedStatement.js +91 -0
- package/dist/d1/d1Provisioner.d.ts +28 -0
- package/dist/d1/d1Provisioner.d.ts.map +1 -0
- package/dist/d1/d1Provisioner.js +67 -0
- package/dist/email/emailRoutingManager.d.ts +36 -0
- package/dist/email/emailRoutingManager.d.ts.map +1 -0
- package/dist/email/emailRoutingManager.js +124 -0
- package/dist/email/emailSendManager.d.ts +45 -0
- package/dist/email/emailSendManager.d.ts.map +1 -0
- package/dist/email/emailSendManager.js +52 -0
- package/dist/env/devVars.d.ts +65 -0
- package/dist/env/devVars.d.ts.map +1 -0
- package/dist/env/devVars.js +84 -0
- package/dist/hostnames/customHostnamesManager.d.ts +51 -0
- package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
- package/dist/hostnames/customHostnamesManager.js +84 -0
- package/dist/kv/kvManager.d.ts +76 -0
- package/dist/kv/kvManager.d.ts.map +1 -0
- package/dist/kv/kvManager.js +135 -0
- package/dist/kv/kvProvisioner.d.ts +28 -0
- package/dist/kv/kvProvisioner.d.ts.map +1 -0
- package/dist/kv/kvProvisioner.js +71 -0
- package/dist/media/assetSeeder.d.ts +31 -0
- package/dist/media/assetSeeder.d.ts.map +1 -0
- package/dist/media/assetSeeder.js +70 -0
- package/dist/media/imageManager.d.ts +34 -0
- package/dist/media/imageManager.d.ts.map +1 -0
- package/dist/media/imageManager.js +103 -0
- package/dist/media/ownership.d.ts +40 -0
- package/dist/media/ownership.d.ts.map +1 -0
- package/dist/media/ownership.js +46 -0
- package/dist/media/streamManager.d.ts +83 -0
- package/dist/media/streamManager.d.ts.map +1 -0
- package/dist/media/streamManager.js +145 -0
- package/dist/queue/queueManager.d.ts +84 -0
- package/dist/queue/queueManager.d.ts.map +1 -0
- package/dist/queue/queueManager.js +114 -0
- package/dist/r2/r2Credentials.d.ts +12 -0
- package/dist/r2/r2Credentials.d.ts.map +1 -0
- package/dist/r2/r2Credentials.js +13 -0
- package/dist/r2/r2Manager.d.ts +226 -0
- package/dist/r2/r2Manager.d.ts.map +1 -0
- package/dist/r2/r2Manager.js +405 -0
- package/dist/r2/r2Provisioner.d.ts +39 -0
- package/dist/r2/r2Provisioner.d.ts.map +1 -0
- package/dist/r2/r2Provisioner.js +86 -0
- package/dist/secrets/secretsStoreManager.d.ts +82 -0
- package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
- package/dist/secrets/secretsStoreManager.js +147 -0
- package/dist/secrets/secretsStores.d.ts +47 -0
- package/dist/secrets/secretsStores.d.ts.map +1 -0
- package/dist/secrets/secretsStores.js +65 -0
- package/dist/test-utils/emailRoutingRules.d.ts +39 -0
- package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
- package/dist/test-utils/emailRoutingRules.js +89 -0
- package/dist/test-utils/fixtureReportSetup.d.ts +25 -0
- package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
- package/dist/test-utils/fixtureReportSetup.js +30 -0
- package/dist/test-utils/fixtures.d.ts +224 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/fixtures.js +277 -0
- package/dist/test-utils/harness.d.ts +192 -0
- package/dist/test-utils/harness.d.ts.map +1 -0
- package/dist/test-utils/harness.js +351 -0
- package/dist/test-utils/inboundRecorder.d.ts +91 -0
- package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
- package/dist/test-utils/inboundRecorder.js +141 -0
- package/dist/test-utils/integrationSetup.d.ts +22 -0
- package/dist/test-utils/integrationSetup.d.ts.map +1 -0
- package/dist/test-utils/integrationSetup.js +37 -0
- package/dist/test-utils/reap.d.ts +112 -0
- package/dist/test-utils/reap.d.ts.map +1 -0
- package/dist/test-utils/reap.js +185 -0
- package/dist/tokens/accountTokensManager.d.ts +157 -0
- package/dist/tokens/accountTokensManager.d.ts.map +1 -0
- package/dist/tokens/accountTokensManager.js +280 -0
- package/dist/tokens/permissions.d.ts +36 -0
- package/dist/tokens/permissions.d.ts.map +1 -0
- package/dist/tokens/permissions.js +50 -0
- package/dist/tokens/profiles.d.ts +102 -0
- package/dist/tokens/profiles.d.ts.map +1 -0
- package/dist/tokens/profiles.js +137 -0
- package/dist/turnstile/turnstileManager.d.ts +75 -0
- package/dist/turnstile/turnstileManager.d.ts.map +1 -0
- package/dist/turnstile/turnstileManager.js +138 -0
- package/dist/user/userManager.d.ts +36 -0
- package/dist/user/userManager.d.ts.map +1 -0
- package/dist/user/userManager.js +58 -0
- package/dist/workers/buildsManager.d.ts +116 -0
- package/dist/workers/buildsManager.d.ts.map +1 -0
- package/dist/workers/buildsManager.js +237 -0
- package/dist/workers/buildsTypes.d.ts +102 -0
- package/dist/workers/buildsTypes.d.ts.map +1 -0
- package/dist/workers/buildsTypes.js +87 -0
- package/dist/workers/workersBuildEvent.d.ts +64 -0
- package/dist/workers/workersBuildEvent.d.ts.map +1 -0
- package/dist/workers/workersBuildEvent.js +47 -0
- package/dist/workers/workersManager.d.ts +136 -0
- package/dist/workers/workersManager.d.ts.map +1 -0
- package/dist/workers/workersManager.js +312 -0
- package/dist/workers/workersProvisioner.d.ts +80 -0
- package/dist/workers/workersProvisioner.d.ts.map +1 -0
- package/dist/workers/workersProvisioner.js +106 -0
- package/dist/workflows/stepFailure.d.ts +180 -0
- package/dist/workflows/stepFailure.d.ts.map +1 -0
- package/dist/workflows/stepFailure.js +227 -0
- package/dist/workflows/workflowsClient.d.ts +87 -0
- package/dist/workflows/workflowsClient.d.ts.map +1 -0
- package/dist/workflows/workflowsClient.js +156 -0
- package/dist/zones/zonesManager.d.ts +50 -0
- package/dist/zones/zonesManager.d.ts.map +1 -0
- package/dist/zones/zonesManager.js +74 -0
- package/package.json +18 -9
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Widget, WidgetListResponse } from "cloudflare/resources/turnstile/widgets";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CloudflareManager } from "../client/manager";
|
|
4
|
+
/**
|
|
5
|
+
* The two widget modes Pithy provisions. `managed` is Cloudflare's *visible* managed widget (CF decides
|
|
6
|
+
* whether to show interaction) — used where a challenge should be seen (a login page). `invisible` runs
|
|
7
|
+
* silently — used where a form should not interrupt (a lead capture). One of each per domain, max.
|
|
8
|
+
*/
|
|
9
|
+
export type TurnstileWidgetMode = "managed" | "invisible";
|
|
10
|
+
/**
|
|
11
|
+
* The server-side response from Turnstile's `/siteverify` endpoint. Pithy validates the raw JSON
|
|
12
|
+
* through this object before trusting it — a humanity check is a security boundary (CLAUDE.md §HTTP:
|
|
13
|
+
* Turnstile is composable middleware). `challenge_ts` is an ISO-8601 string on the wire, decoded to
|
|
14
|
+
* a `Date` via the `JsonDate` codec.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TurnstileVerification: z.ZodObject<{
|
|
17
|
+
success: z.ZodBoolean;
|
|
18
|
+
"error-codes": z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
19
|
+
messages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
|
+
challenge_ts: z.ZodOptional<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
|
|
21
|
+
hostname: z.ZodOptional<z.ZodString>;
|
|
22
|
+
action: z.ZodOptional<z.ZodString>;
|
|
23
|
+
cdata: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type TurnstileVerification = z.output<typeof TurnstileVerification>;
|
|
26
|
+
/** Options for a server-side `verify` call. */
|
|
27
|
+
export interface TurnstileVerifyOptions {
|
|
28
|
+
/** The end-user IP the token was issued to, for an extra integrity check. */
|
|
29
|
+
remoteIp?: string;
|
|
30
|
+
/** The expected action label, asserted by Cloudflare against the token. */
|
|
31
|
+
action?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Out-of-Worker Turnstile access over the REST API: server-side token verification against
|
|
35
|
+
* `/siteverify`, plus widget management (create/list/update/rotate/delete) via the SDK. Inside a
|
|
36
|
+
* Worker the same siteverify call works over `fetch`; this manager is the CLI/CI/provisioning
|
|
37
|
+
* counterpart, addressed by account.
|
|
38
|
+
*/
|
|
39
|
+
export declare class CloudflareTurnstileManager extends CloudflareManager {
|
|
40
|
+
/**
|
|
41
|
+
* Verify a Turnstile token server-side against `/siteverify`. The secret is the widget's secret
|
|
42
|
+
* key (never the sitekey). The raw response is validated through `TurnstileVerification`; a
|
|
43
|
+
* malformed body throws `cloudflare/invalid_response`, a network failure is wrapped as
|
|
44
|
+
* `cloudflare/request_failed`.
|
|
45
|
+
*/
|
|
46
|
+
verify(token: string, secret: string, options?: TurnstileVerifyOptions): Promise<TurnstileVerification>;
|
|
47
|
+
/** Find a widget by name. Returns null when no widget matches; never throws on a missing match. */
|
|
48
|
+
getTurnstile(name: string): Promise<WidgetListResponse | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Every widget claiming `domain`, matched case-insensitively (hostnames are). The sibling of
|
|
51
|
+
* {@link getTurnstile}, keyed on the domain instead of the name — and **plural**, deliberately:
|
|
52
|
+
* Cloudflare's widget names are not unique and a domain may legitimately appear on several widgets,
|
|
53
|
+
* so returning one would hide the rest from a caller deciding whether the domain is free.
|
|
54
|
+
*
|
|
55
|
+
* Exact hostname equality only. Cloudflare treats a widget's domain as covering its subdomains, but
|
|
56
|
+
* this is the input to a *refusal*, and a refusal inferred from a subdomain relationship would block
|
|
57
|
+
* `app.example.com` because someone once made a widget for `example.com`.
|
|
58
|
+
*/
|
|
59
|
+
listTurnstilesByDomain(domain: string): Promise<WidgetListResponse[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Create a widget for the given domains in the requested mode — `managed` (visible) or `invisible`
|
|
62
|
+
* (silent). Defaults to `invisible` so existing callers keep their behavior.
|
|
63
|
+
*/
|
|
64
|
+
addTurnstile(name: string, domains: string[], mode?: TurnstileWidgetMode): Promise<Widget>;
|
|
65
|
+
/** Delete a widget by its sitekey. */
|
|
66
|
+
deleteTurnstile(siteKey: string): Promise<void>;
|
|
67
|
+
/** Replace the allowed domains on an existing widget (kept in invisible mode). */
|
|
68
|
+
updateTurnstileDomains(siteKey: string, domains: string[], name: string): Promise<Widget>;
|
|
69
|
+
/** Rotate a widget's secret key, invalidating the old secret immediately. */
|
|
70
|
+
rotateTurnstile(siteKey: string): Promise<Widget>;
|
|
71
|
+
getServiceType(): string;
|
|
72
|
+
/** Prove access by listing widgets. Never throws. */
|
|
73
|
+
validateServiceAccess(): Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=turnstileManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnstileManager.d.ts","sourceRoot":"","sources":["../../src/turnstile/turnstileManager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AACzF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAQtD;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,WAAW,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;iBAUsD,CAAC;AACzF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE3E,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,iBAAiB;IAC/D;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA4B5G;IAED,mGAAmG;IAC7F,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAOnE;IAED;;;;;;;;;OASG;IACG,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAS1E;IAED;;;OAGG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,mBAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAI5G;IAED,sCAAsC;IAChC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpD;IAED,kFAAkF;IAC5E,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ9F;IAED,6EAA6E;IACvE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQtD;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,qDAAqD;IAC/C,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;CACF"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { CloudflareInvalidResponseError, cloudflareRequest } from "../client/errors.js";
|
|
2
|
+
import { CloudflareManager } from "../client/manager.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { JsonDate } from "@pithy-sh/core/src/data/codecs";
|
|
5
|
+
//#region src/turnstile/turnstileManager.ts
|
|
6
|
+
/** The siteverify endpoint Turnstile tokens are validated against (server-side, not the SDK). */
|
|
7
|
+
const SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
8
|
+
/** Per-call SDK timeout + retry budget for widget management operations. */
|
|
9
|
+
const requestOptions = {
|
|
10
|
+
timeout: 1e4,
|
|
11
|
+
maxRetries: 3
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The server-side response from Turnstile's `/siteverify` endpoint. Pithy validates the raw JSON
|
|
15
|
+
* through this object before trusting it — a humanity check is a security boundary (CLAUDE.md §HTTP:
|
|
16
|
+
* Turnstile is composable middleware). `challenge_ts` is an ISO-8601 string on the wire, decoded to
|
|
17
|
+
* a `Date` via the `JsonDate` codec.
|
|
18
|
+
*/
|
|
19
|
+
const TurnstileVerification = z.object({
|
|
20
|
+
success: z.boolean().describe("Whether the token passed the humanity challenge."),
|
|
21
|
+
"error-codes": z.array(z.string()).default([]).describe("Machine-readable failure reasons; empty on success."),
|
|
22
|
+
messages: z.array(z.string()).optional().describe("Optional human-readable notices from Cloudflare."),
|
|
23
|
+
challenge_ts: JsonDate.optional().describe("When the challenge was solved (ISO-8601 on the wire, Date in JS)."),
|
|
24
|
+
hostname: z.string().optional().describe("The hostname the challenge was solved on."),
|
|
25
|
+
action: z.string().optional().describe("The customer-supplied action label bound to the token."),
|
|
26
|
+
cdata: z.string().optional().describe("The customer-supplied context data bound to the token.")
|
|
27
|
+
}).describe("Server-side Turnstile siteverify response from challenges.cloudflare.com.");
|
|
28
|
+
/**
|
|
29
|
+
* Out-of-Worker Turnstile access over the REST API: server-side token verification against
|
|
30
|
+
* `/siteverify`, plus widget management (create/list/update/rotate/delete) via the SDK. Inside a
|
|
31
|
+
* Worker the same siteverify call works over `fetch`; this manager is the CLI/CI/provisioning
|
|
32
|
+
* counterpart, addressed by account.
|
|
33
|
+
*/
|
|
34
|
+
var CloudflareTurnstileManager = class extends CloudflareManager {
|
|
35
|
+
/**
|
|
36
|
+
* Verify a Turnstile token server-side against `/siteverify`. The secret is the widget's secret
|
|
37
|
+
* key (never the sitekey). The raw response is validated through `TurnstileVerification`; a
|
|
38
|
+
* malformed body throws `cloudflare/invalid_response`, a network failure is wrapped as
|
|
39
|
+
* `cloudflare/request_failed`.
|
|
40
|
+
*/
|
|
41
|
+
async verify(token, secret, options) {
|
|
42
|
+
const body = new URLSearchParams({
|
|
43
|
+
secret,
|
|
44
|
+
response: token
|
|
45
|
+
});
|
|
46
|
+
if (options?.remoteIp) body.set("remoteip", options.remoteIp);
|
|
47
|
+
if (options?.action) body.set("action", options.action);
|
|
48
|
+
const raw = await cloudflareRequest("Turnstile siteverify", async () => {
|
|
49
|
+
const response = await fetch(SITEVERIFY_URL, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
52
|
+
body
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) throw new CloudflareInvalidResponseError({
|
|
55
|
+
message: "Turnstile siteverify returned a non-OK status.",
|
|
56
|
+
detail: `siteverify responded ${response.status} ${response.statusText}.`
|
|
57
|
+
});
|
|
58
|
+
return await response.json();
|
|
59
|
+
});
|
|
60
|
+
const parsed = TurnstileVerification.safeParse(raw);
|
|
61
|
+
if (!parsed.success) throw new CloudflareInvalidResponseError({
|
|
62
|
+
message: "Turnstile siteverify response had an unexpected shape.",
|
|
63
|
+
detail: parsed.error.message
|
|
64
|
+
});
|
|
65
|
+
return parsed.data;
|
|
66
|
+
}
|
|
67
|
+
/** Find a widget by name. Returns null when no widget matches; never throws on a missing match. */
|
|
68
|
+
async getTurnstile(name) {
|
|
69
|
+
return cloudflareRequest(`Turnstile get widget '${name}'`, async () => {
|
|
70
|
+
for await (const widget of this.getClient().turnstile.widgets.list({ account_id: this.accountId })) if (widget.name === name) return widget;
|
|
71
|
+
return null;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every widget claiming `domain`, matched case-insensitively (hostnames are). The sibling of
|
|
76
|
+
* {@link getTurnstile}, keyed on the domain instead of the name — and **plural**, deliberately:
|
|
77
|
+
* Cloudflare's widget names are not unique and a domain may legitimately appear on several widgets,
|
|
78
|
+
* so returning one would hide the rest from a caller deciding whether the domain is free.
|
|
79
|
+
*
|
|
80
|
+
* Exact hostname equality only. Cloudflare treats a widget's domain as covering its subdomains, but
|
|
81
|
+
* this is the input to a *refusal*, and a refusal inferred from a subdomain relationship would block
|
|
82
|
+
* `app.example.com` because someone once made a widget for `example.com`.
|
|
83
|
+
*/
|
|
84
|
+
async listTurnstilesByDomain(domain) {
|
|
85
|
+
const wanted = domain.toLowerCase();
|
|
86
|
+
return cloudflareRequest(`Turnstile list widgets for '${domain}'`, async () => {
|
|
87
|
+
const matches = [];
|
|
88
|
+
for await (const widget of this.getClient().turnstile.widgets.list({ account_id: this.accountId })) if (widget.domains?.some((each) => each.toLowerCase() === wanted)) matches.push(widget);
|
|
89
|
+
return matches;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create a widget for the given domains in the requested mode — `managed` (visible) or `invisible`
|
|
94
|
+
* (silent). Defaults to `invisible` so existing callers keep their behavior.
|
|
95
|
+
*/
|
|
96
|
+
async addTurnstile(name, domains, mode = "invisible") {
|
|
97
|
+
return cloudflareRequest(`Turnstile create widget '${name}'`, () => this.getClient().turnstile.widgets.create({
|
|
98
|
+
account_id: this.accountId,
|
|
99
|
+
domains,
|
|
100
|
+
mode,
|
|
101
|
+
name
|
|
102
|
+
}, requestOptions));
|
|
103
|
+
}
|
|
104
|
+
/** Delete a widget by its sitekey. */
|
|
105
|
+
async deleteTurnstile(siteKey) {
|
|
106
|
+
await cloudflareRequest(`Turnstile delete widget '${siteKey}'`, () => this.getClient().turnstile.widgets.delete(siteKey, { account_id: this.accountId }, requestOptions));
|
|
107
|
+
}
|
|
108
|
+
/** Replace the allowed domains on an existing widget (kept in invisible mode). */
|
|
109
|
+
async updateTurnstileDomains(siteKey, domains, name) {
|
|
110
|
+
return cloudflareRequest(`Turnstile update domains for '${siteKey}'`, () => this.getClient().turnstile.widgets.update(siteKey, {
|
|
111
|
+
account_id: this.accountId,
|
|
112
|
+
domains,
|
|
113
|
+
mode: "invisible",
|
|
114
|
+
name
|
|
115
|
+
}, requestOptions));
|
|
116
|
+
}
|
|
117
|
+
/** Rotate a widget's secret key, invalidating the old secret immediately. */
|
|
118
|
+
async rotateTurnstile(siteKey) {
|
|
119
|
+
return cloudflareRequest(`Turnstile rotate secret for '${siteKey}'`, () => this.getClient().turnstile.widgets.rotateSecret(siteKey, {
|
|
120
|
+
account_id: this.accountId,
|
|
121
|
+
invalidate_immediately: true
|
|
122
|
+
}, requestOptions));
|
|
123
|
+
}
|
|
124
|
+
getServiceType() {
|
|
125
|
+
return "Cloudflare Turnstile";
|
|
126
|
+
}
|
|
127
|
+
/** Prove access by listing widgets. Never throws. */
|
|
128
|
+
async validateServiceAccess() {
|
|
129
|
+
try {
|
|
130
|
+
await this.getClient().turnstile.widgets.list({ account_id: this.accountId });
|
|
131
|
+
return true;
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
138
|
+
export { CloudflareTurnstileManager, TurnstileVerification };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CloudflareManager } from "../client/manager";
|
|
3
|
+
/**
|
|
4
|
+
* Out-of-Worker read access to the **user/token** identity behind a CF API token — `GET /user` and
|
|
5
|
+
* `GET /user/tokens/verify`/`/user/tokens/:id`. The audit CLI emitter uses it to attribute a
|
|
6
|
+
* control-plane action (`pithy migrate`, `pithy deploy`, …) to the right actor: a user token
|
|
7
|
+
* (`cfut_*`) resolves to the developer's email, an account token (`cfat_*`) to the token's name.
|
|
8
|
+
*
|
|
9
|
+
* These are *user-scoped* endpoints, so they need a user-bound token — CLAUDE.md prefers
|
|
10
|
+
* account-owned tokens for provisioning, but actor attribution is exactly the case where the token's
|
|
11
|
+
* own identity is the point. Reads only; never mints, never logs a token value.
|
|
12
|
+
*/
|
|
13
|
+
/** The CF user behind a user token. The SDK omits `email` from its type, but the API returns it. */
|
|
14
|
+
export declare const CfUserIdentity: z.ZodObject<{
|
|
15
|
+
id: z.ZodOptional<z.ZodString>;
|
|
16
|
+
email: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type CfUserIdentity = z.output<typeof CfUserIdentity>;
|
|
19
|
+
/** The result of verifying the calling token: its id and lifecycle status. */
|
|
20
|
+
export declare const CfTokenVerification: z.ZodObject<{
|
|
21
|
+
id: z.ZodString;
|
|
22
|
+
status: z.ZodString;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export type CfTokenVerification = z.output<typeof CfTokenVerification>;
|
|
25
|
+
export declare class CloudflareUserManager extends CloudflareManager {
|
|
26
|
+
getServiceType(): string;
|
|
27
|
+
/** Prove access by reading the user record. Never throws. */
|
|
28
|
+
validateServiceAccess(): Promise<boolean>;
|
|
29
|
+
/** The Cloudflare user behind a user (`cfut_*`) token — `GET /user`. */
|
|
30
|
+
getUser(): Promise<CfUserIdentity>;
|
|
31
|
+
/** Verify the calling token and read its id + status — `GET /user/tokens/verify`. */
|
|
32
|
+
verifyToken(): Promise<CfTokenVerification>;
|
|
33
|
+
/** The name of a token by id, or `null` if it can't be read — `GET /user/tokens/:id`. */
|
|
34
|
+
getTokenName(tokenId: string): Promise<string | null>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=userManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userManager.d.ts","sourceRoot":"","sources":["../../src/user/userManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;;;GASG;AAEH,oGAAoG;AACpG,eAAO,MAAM,cAAc;;;iBAKiE,CAAC;AAC7F,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB;;;iBAK4D,CAAC;AAC7F,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAKvE,qBAAa,qBAAsB,SAAQ,iBAAiB;IAC1D,cAAc,IAAI,MAAM,CAEvB;IAED,6DAA6D;IACvD,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;IAED,wEAAwE;IAClE,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC,CAGvC;IAED,qFAAqF;IAC/E,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAGhD;IAED,yFAAyF;IACnF,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAI1D;CACF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { cloudflareRequest, decodeResponse } from "../client/errors.js";
|
|
2
|
+
import { CloudflareManager } from "../client/manager.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/user/userManager.ts
|
|
5
|
+
/**
|
|
6
|
+
* Out-of-Worker read access to the **user/token** identity behind a CF API token — `GET /user` and
|
|
7
|
+
* `GET /user/tokens/verify`/`/user/tokens/:id`. The audit CLI emitter uses it to attribute a
|
|
8
|
+
* control-plane action (`pithy migrate`, `pithy deploy`, …) to the right actor: a user token
|
|
9
|
+
* (`cfut_*`) resolves to the developer's email, an account token (`cfat_*`) to the token's name.
|
|
10
|
+
*
|
|
11
|
+
* These are *user-scoped* endpoints, so they need a user-bound token — CLAUDE.md prefers
|
|
12
|
+
* account-owned tokens for provisioning, but actor attribution is exactly the case where the token's
|
|
13
|
+
* own identity is the point. Reads only; never mints, never logs a token value.
|
|
14
|
+
*/
|
|
15
|
+
/** The CF user behind a user token. The SDK omits `email` from its type, but the API returns it. */
|
|
16
|
+
const CfUserIdentity = z.object({
|
|
17
|
+
id: z.string().optional().describe("The Cloudflare user id — stable cross-reference for the actor."),
|
|
18
|
+
email: z.string().optional().describe("The Cloudflare user's email — the human actor's identifier.")
|
|
19
|
+
}).describe("The identity behind a Cloudflare user (`cfut_*`) API token, from `GET /user`.");
|
|
20
|
+
/** The result of verifying the calling token: its id and lifecycle status. */
|
|
21
|
+
const CfTokenVerification = z.object({
|
|
22
|
+
id: z.string().describe("The token's id — addresses it for a follow-up name lookup."),
|
|
23
|
+
status: z.string().describe("The token's lifecycle status (`active` | `disabled` | `expired`).")
|
|
24
|
+
}).describe("The result of `GET /user/tokens/verify` for the calling Cloudflare API token.");
|
|
25
|
+
/** Just the token's name, the piece the account-token actor path needs. */
|
|
26
|
+
const TokenName = z.object({ name: z.string() });
|
|
27
|
+
var CloudflareUserManager = class extends CloudflareManager {
|
|
28
|
+
getServiceType() {
|
|
29
|
+
return "Cloudflare User";
|
|
30
|
+
}
|
|
31
|
+
/** Prove access by reading the user record. Never throws. */
|
|
32
|
+
async validateServiceAccess() {
|
|
33
|
+
try {
|
|
34
|
+
await this.getUser();
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** The Cloudflare user behind a user (`cfut_*`) token — `GET /user`. */
|
|
41
|
+
async getUser() {
|
|
42
|
+
const raw = await cloudflareRequest("get user", () => this.getClient().user.get());
|
|
43
|
+
return decodeResponse(CfUserIdentity, raw, "user get");
|
|
44
|
+
}
|
|
45
|
+
/** Verify the calling token and read its id + status — `GET /user/tokens/verify`. */
|
|
46
|
+
async verifyToken() {
|
|
47
|
+
const raw = await cloudflareRequest("verify token", () => this.getClient().user.tokens.verify());
|
|
48
|
+
return decodeResponse(CfTokenVerification, raw, "token verify");
|
|
49
|
+
}
|
|
50
|
+
/** The name of a token by id, or `null` if it can't be read — `GET /user/tokens/:id`. */
|
|
51
|
+
async getTokenName(tokenId) {
|
|
52
|
+
const raw = await cloudflareRequest(`get token ${tokenId}`, () => this.getClient().user.tokens.get(tokenId));
|
|
53
|
+
const parsed = TokenName.safeParse(raw);
|
|
54
|
+
return parsed.success ? parsed.data.name : null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
export { CfTokenVerification, CfUserIdentity, CloudflareUserManager };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { CloudflareManager } from "../client/manager";
|
|
2
|
+
import { CfBuild, CfBuildLog, CfBuildToken, CfBuildTrigger, CfRepoConnection, type CfTriggerEnvVar } from "./buildsTypes";
|
|
3
|
+
/** Arguments for `createRepoConnection`. */
|
|
4
|
+
export interface CreateRepoConnectionArgs {
|
|
5
|
+
/** The git provider hosting the repo. */
|
|
6
|
+
providerType: "gitlab" | "github";
|
|
7
|
+
/** The repository's id in the git provider. */
|
|
8
|
+
repoId: string;
|
|
9
|
+
/** The repo path relative to the provider account. */
|
|
10
|
+
repoName: string;
|
|
11
|
+
/** The provider account identifier (e.g. 'group:15366484'). */
|
|
12
|
+
providerAccountId: string;
|
|
13
|
+
/** The provider account display name. */
|
|
14
|
+
providerAccountName: string;
|
|
15
|
+
}
|
|
16
|
+
/** Arguments for `upsertTrigger`. */
|
|
17
|
+
export interface UpsertTriggerArgs {
|
|
18
|
+
/** The immutable Worker id (hex UUID) the trigger builds — not the worker name. */
|
|
19
|
+
scriptName: string;
|
|
20
|
+
/** The repo connection UUID to build from. */
|
|
21
|
+
repoConnectionId: string;
|
|
22
|
+
/** The build token UUID the trigger authenticates with. */
|
|
23
|
+
buildTokenUuid: string;
|
|
24
|
+
/** The trigger's human-readable name. */
|
|
25
|
+
triggerName: string;
|
|
26
|
+
/** Branch patterns whose pushes auto-fire the trigger (must be non-empty). */
|
|
27
|
+
branchIncludes: string[];
|
|
28
|
+
/** Branch patterns excluded from auto-firing. */
|
|
29
|
+
branchExcludes?: string[];
|
|
30
|
+
/** Path patterns whose changes auto-fire the trigger. */
|
|
31
|
+
pathIncludes?: string[];
|
|
32
|
+
/** The shell command that builds the Worker. */
|
|
33
|
+
buildCommand: string;
|
|
34
|
+
/** The shell command CF Builds runs to deploy after a build. */
|
|
35
|
+
deployCommand: string;
|
|
36
|
+
/** The repo subdirectory builds run from. Defaults to '/'. */
|
|
37
|
+
rootDirectory?: string;
|
|
38
|
+
/** Whether build caching is enabled. Defaults to true. */
|
|
39
|
+
buildCachingEnabled?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** A subset of trigger fields that can be patched after creation. */
|
|
42
|
+
export interface UpdateTriggerArgs {
|
|
43
|
+
/** A branch filter to set on the trigger. */
|
|
44
|
+
branchFilter?: string;
|
|
45
|
+
/** The build command to set. */
|
|
46
|
+
buildCommand?: string;
|
|
47
|
+
/** The deploy command to set. */
|
|
48
|
+
deployCommand?: string;
|
|
49
|
+
/** The root directory to set. */
|
|
50
|
+
rootDirectory?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Out-of-Worker access to the Cloudflare Builds API: repo connections, build triggers, builds, and
|
|
54
|
+
* trigger env vars. CF Builds is the CI/CD layer for Workers — it clones a git repo, runs a build
|
|
55
|
+
* command, and reports outcomes.
|
|
56
|
+
*
|
|
57
|
+
* As of the SDK version this package targets, the Builds endpoints are NOT exposed as typed SDK
|
|
58
|
+
* resources, so this manager uses the documented escape hatch (`getApiToken()` + raw `fetch`) against
|
|
59
|
+
* explicit `/accounts/{id}/builds/...` paths. Every request goes through `cloudflareRequest` so
|
|
60
|
+
* failures wrap as `cloudflare/request_failed`, and every response is validated with Zod
|
|
61
|
+
* (`cloudflare/invalid_response` on mismatch).
|
|
62
|
+
*/
|
|
63
|
+
export declare class CloudflareBuildsManager extends CloudflareManager {
|
|
64
|
+
/**
|
|
65
|
+
* Issue a raw CF REST call and unwrap the `{ result, success, errors }` envelope. A failure —
|
|
66
|
+
* either a non-2xx status **or** a 200 carrying `success: false` — throws `cloudflare/request_failed`
|
|
67
|
+
* with the structured CF error codes pinned to the front of `detail` (so idempotency checks read a
|
|
68
|
+
* stable marker, never a substring a verbose body could push past truncation). An unparseable body
|
|
69
|
+
* throws `cloudflare/invalid_response`.
|
|
70
|
+
*/
|
|
71
|
+
private call;
|
|
72
|
+
/**
|
|
73
|
+
* Register a git repository with CF Builds. `PUT /builds/repos/connections` is CF's documented
|
|
74
|
+
* upsert — calling with the same repo id returns the existing connection.
|
|
75
|
+
*/
|
|
76
|
+
createRepoConnection(args: CreateRepoConnectionArgs): Promise<CfRepoConnection>;
|
|
77
|
+
/**
|
|
78
|
+
* Upsert a build trigger linking a Worker script to a repo. Tries `POST /builds/triggers`; on the
|
|
79
|
+
* 409 "trigger already exists" conflict, falls back to listing the script's triggers and returning
|
|
80
|
+
* the matching one. Idempotent.
|
|
81
|
+
*/
|
|
82
|
+
upsertTrigger(args: UpsertTriggerArgs): Promise<CfBuildTrigger>;
|
|
83
|
+
/** List the build tokens available on the account. */
|
|
84
|
+
listBuildTokens(): Promise<CfBuildToken[]>;
|
|
85
|
+
/** Patch an existing build trigger's configuration. */
|
|
86
|
+
updateTrigger(triggerId: string, args: UpdateTriggerArgs): Promise<CfBuildTrigger>;
|
|
87
|
+
/** Delete a build trigger. */
|
|
88
|
+
deleteTrigger(triggerId: string): Promise<void>;
|
|
89
|
+
/** Manually start a build for a trigger at a specific branch + commit. */
|
|
90
|
+
triggerManualBuild(triggerId: string, branch: string, commitHash: string): Promise<CfBuild>;
|
|
91
|
+
/** Get a build's current status and metadata by id. */
|
|
92
|
+
getBuild(buildId: string): Promise<CfBuild>;
|
|
93
|
+
/** Cancel a running build. */
|
|
94
|
+
cancelBuild(buildId: string): Promise<void>;
|
|
95
|
+
/** Get the log output for a build. */
|
|
96
|
+
getBuildLogs(buildId: string): Promise<CfBuildLog[]>;
|
|
97
|
+
/** List all builds for a Worker script. */
|
|
98
|
+
listBuildsByScript(scriptName: string): Promise<CfBuild[]>;
|
|
99
|
+
/**
|
|
100
|
+
* List the build triggers for a Worker script. `externalScriptId` must be the immutable Worker id
|
|
101
|
+
* (hex UUID), resolved via `CloudflareWorkersManager.getWorkerInternalId`. Returns an empty array
|
|
102
|
+
* when the script has no triggers (CF 404 code 12000).
|
|
103
|
+
*/
|
|
104
|
+
listTriggersByScript(externalScriptId: string): Promise<CfBuildTrigger[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Bulk-upsert environment variables on a build trigger. The wire body is a map keyed by variable
|
|
107
|
+
* name (`{ NAME: { is_secret, value }, ... }`), not an array.
|
|
108
|
+
*/
|
|
109
|
+
upsertTriggerEnvVars(triggerId: string, envVars: CfTriggerEnvVar[]): Promise<void>;
|
|
110
|
+
getServiceType(): string;
|
|
111
|
+
/** Prove access by listing build triggers. Never throws. */
|
|
112
|
+
validateServiceAccess(): Promise<boolean>;
|
|
113
|
+
/** Validate a raw CF result against a Zod schema, throwing `cloudflare/invalid_response` on mismatch. */
|
|
114
|
+
private validate;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=buildsManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildsManager.d.ts","sourceRoot":"","sources":["../../src/workers/buildsManager.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EACL,OAAO,EACP,UAAU,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAkBvB,4CAA4C;AAC5C,MAAM,WAAW,wBAAwB;IACvC,yCAAyC;IACzC,YAAY,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAClC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB;IAC5D;;;;;;OAMG;YACW,IAAI;IA6ClB;;;OAGG;IACG,oBAAoB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAWpF;IAED;;;;OAIG;IACG,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CA4BpE;IAED,sDAAsD;IAChD,eAAe,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAK/C;IAED,uDAAuD;IACjD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAUvF;IAED,8BAA8B;IACxB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpD;IAED,0EAA0E;IACpE,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQhG;IAED,uDAAuD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKhD;IAED,8BAA8B;IACxB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhD;IAED,sCAAsC;IAChC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAKzD;IAED,2CAA2C;IACrC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAQ/D;IAED;;;;OAIG;IACG,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAa9E;IAED;;;OAGG;IACG,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvF;IAED,cAAc,IAAI,MAAM,CAEvB;IAED,4DAA4D;IACtD,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;IAED,yGAAyG;IACzG,OAAO,CAAC,QAAQ;CAGjB"}
|