@pithy-sh/cloudflare 0.1.2 → 0.1.4
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 +101 -0
- package/dist/ai/aiManager.d.ts.map +1 -0
- package/dist/ai/aiManager.js +150 -0
- package/dist/ai/vectorizeManager.d.ts +69 -0
- package/dist/ai/vectorizeManager.d.ts.map +1 -0
- package/dist/ai/vectorizeManager.js +103 -0
- package/dist/ai/vectorizeProvisioner.d.ts +125 -0
- package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
- package/dist/ai/vectorizeProvisioner.js +197 -0
- package/dist/client/accounts.d.ts +50 -0
- package/dist/client/accounts.d.ts.map +1 -0
- package/dist/client/accounts.js +57 -0
- package/dist/client/clients.d.ts +124 -0
- package/dist/client/clients.d.ts.map +1 -0
- package/dist/client/clients.js +218 -0
- package/dist/client/errors.d.ts +75 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +100 -0
- package/dist/client/manager.d.ts +47 -0
- package/dist/client/manager.d.ts.map +1 -0
- package/dist/client/manager.js +53 -0
- package/dist/d1/d1Manager.d.ts +76 -0
- package/dist/d1/d1Manager.d.ts.map +1 -0
- package/dist/d1/d1Manager.js +130 -0
- package/dist/d1/d1PreparedStatement.d.ts +26 -0
- package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
- package/dist/d1/d1PreparedStatement.js +93 -0
- package/dist/d1/d1Provisioner.d.ts +30 -0
- package/dist/d1/d1Provisioner.d.ts.map +1 -0
- package/dist/d1/d1Provisioner.js +69 -0
- package/dist/email/emailRoutingManager.d.ts +38 -0
- package/dist/email/emailRoutingManager.d.ts.map +1 -0
- package/dist/email/emailRoutingManager.js +126 -0
- package/dist/email/emailSendManager.d.ts +47 -0
- package/dist/email/emailSendManager.d.ts.map +1 -0
- package/dist/email/emailSendManager.js +54 -0
- package/dist/env/devVars.d.ts +67 -0
- package/dist/env/devVars.d.ts.map +1 -0
- package/dist/env/devVars.js +86 -0
- package/dist/hostnames/customHostnamesManager.d.ts +53 -0
- package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
- package/dist/hostnames/customHostnamesManager.js +86 -0
- package/dist/kv/kvManager.d.ts +78 -0
- package/dist/kv/kvManager.d.ts.map +1 -0
- package/dist/kv/kvManager.js +137 -0
- package/dist/kv/kvProvisioner.d.ts +30 -0
- package/dist/kv/kvProvisioner.d.ts.map +1 -0
- package/dist/kv/kvProvisioner.js +73 -0
- package/dist/media/assetSeeder.d.ts +33 -0
- package/dist/media/assetSeeder.d.ts.map +1 -0
- package/dist/media/assetSeeder.js +72 -0
- package/dist/media/imageManager.d.ts +36 -0
- package/dist/media/imageManager.d.ts.map +1 -0
- package/dist/media/imageManager.js +105 -0
- package/dist/media/ownership.d.ts +42 -0
- package/dist/media/ownership.d.ts.map +1 -0
- package/dist/media/ownership.js +48 -0
- package/dist/media/streamManager.d.ts +85 -0
- package/dist/media/streamManager.d.ts.map +1 -0
- package/dist/media/streamManager.js +147 -0
- package/dist/queue/queueManager.d.ts +86 -0
- package/dist/queue/queueManager.d.ts.map +1 -0
- package/dist/queue/queueManager.js +116 -0
- package/dist/r2/r2Credentials.d.ts +14 -0
- package/dist/r2/r2Credentials.d.ts.map +1 -0
- package/dist/r2/r2Credentials.js +15 -0
- package/dist/r2/r2Manager.d.ts +228 -0
- package/dist/r2/r2Manager.d.ts.map +1 -0
- package/dist/r2/r2Manager.js +407 -0
- package/dist/r2/r2Provisioner.d.ts +41 -0
- package/dist/r2/r2Provisioner.d.ts.map +1 -0
- package/dist/r2/r2Provisioner.js +88 -0
- package/dist/secrets/secretsStoreManager.d.ts +84 -0
- package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
- package/dist/secrets/secretsStoreManager.js +149 -0
- package/dist/secrets/secretsStores.d.ts +49 -0
- package/dist/secrets/secretsStores.d.ts.map +1 -0
- package/dist/secrets/secretsStores.js +67 -0
- package/dist/test-utils/emailRoutingRules.d.ts +41 -0
- package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
- package/dist/test-utils/emailRoutingRules.js +91 -0
- package/dist/test-utils/fixtureReportSetup.d.ts +27 -0
- package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
- package/dist/test-utils/fixtureReportSetup.js +32 -0
- package/dist/test-utils/fixtures.d.ts +226 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/fixtures.js +279 -0
- package/dist/test-utils/harness.d.ts +194 -0
- package/dist/test-utils/harness.d.ts.map +1 -0
- package/dist/test-utils/harness.js +353 -0
- package/dist/test-utils/inboundRecorder.d.ts +93 -0
- package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
- package/dist/test-utils/inboundRecorder.js +143 -0
- package/dist/test-utils/integrationSetup.d.ts +24 -0
- package/dist/test-utils/integrationSetup.d.ts.map +1 -0
- package/dist/test-utils/integrationSetup.js +39 -0
- package/dist/test-utils/reap.d.ts +114 -0
- package/dist/test-utils/reap.d.ts.map +1 -0
- package/dist/test-utils/reap.js +187 -0
- package/dist/tokens/accountTokensManager.d.ts +159 -0
- package/dist/tokens/accountTokensManager.d.ts.map +1 -0
- package/dist/tokens/accountTokensManager.js +282 -0
- package/dist/tokens/permissions.d.ts +38 -0
- package/dist/tokens/permissions.d.ts.map +1 -0
- package/dist/tokens/permissions.js +52 -0
- package/dist/tokens/profiles.d.ts +104 -0
- package/dist/tokens/profiles.d.ts.map +1 -0
- package/dist/tokens/profiles.js +139 -0
- package/dist/turnstile/turnstileManager.d.ts +77 -0
- package/dist/turnstile/turnstileManager.d.ts.map +1 -0
- package/dist/turnstile/turnstileManager.js +140 -0
- package/dist/user/userManager.d.ts +38 -0
- package/dist/user/userManager.d.ts.map +1 -0
- package/dist/user/userManager.js +60 -0
- package/dist/workers/buildsManager.d.ts +118 -0
- package/dist/workers/buildsManager.d.ts.map +1 -0
- package/dist/workers/buildsManager.js +239 -0
- package/dist/workers/buildsTypes.d.ts +104 -0
- package/dist/workers/buildsTypes.d.ts.map +1 -0
- package/dist/workers/buildsTypes.js +89 -0
- package/dist/workers/workersBuildEvent.d.ts +66 -0
- package/dist/workers/workersBuildEvent.d.ts.map +1 -0
- package/dist/workers/workersBuildEvent.js +49 -0
- package/dist/workers/workersManager.d.ts +138 -0
- package/dist/workers/workersManager.d.ts.map +1 -0
- package/dist/workers/workersManager.js +314 -0
- package/dist/workers/workersProvisioner.d.ts +82 -0
- package/dist/workers/workersProvisioner.d.ts.map +1 -0
- package/dist/workers/workersProvisioner.js +108 -0
- package/dist/workflows/stepFailure.d.ts +182 -0
- package/dist/workflows/stepFailure.d.ts.map +1 -0
- package/dist/workflows/stepFailure.js +229 -0
- package/dist/workflows/workflowsClient.d.ts +89 -0
- package/dist/workflows/workflowsClient.d.ts.map +1 -0
- package/dist/workflows/workflowsClient.js +158 -0
- package/dist/zones/zonesManager.d.ts +52 -0
- package/dist/zones/zonesManager.d.ts.map +1 -0
- package/dist/zones/zonesManager.js +76 -0
- package/package.json +18 -9
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { CloudflareNotConfiguredError } from "../client/errors.js";
|
|
4
|
+
import { accountResource } from "./accountTokensManager.js";
|
|
5
|
+
import { isPermissionKey, resolvePermissionKeys } from "./permissions.js";
|
|
6
|
+
//#region src/tokens/profiles.ts
|
|
7
|
+
/** The valid stores, for validating a `--store` flag or a profile's `defaultStore`. */
|
|
8
|
+
const TOKEN_STORES = [
|
|
9
|
+
"secrets-store",
|
|
10
|
+
"dev-vars",
|
|
11
|
+
"ephemeral"
|
|
12
|
+
];
|
|
13
|
+
/** The name of the one CI system token — the single least-privilege credential a CI pipeline runs under. */
|
|
14
|
+
const CI_SYSTEM_PROFILE = "ci-system";
|
|
15
|
+
/**
|
|
16
|
+
* The base permissions the `ci-system` token always carries: deploy Workers, run migrations against
|
|
17
|
+
* remote D1, and read/write the Secrets Store (a deployed Worker binding CFSS secrets needs Secrets
|
|
18
|
+
* Store access at deploy). Every composed capability's {@link Capability.ciPermissions} unions on top.
|
|
19
|
+
*/
|
|
20
|
+
const CI_SYSTEM_BASE = [
|
|
21
|
+
"workers:write",
|
|
22
|
+
"d1:read",
|
|
23
|
+
"d1:write",
|
|
24
|
+
"secrets:read",
|
|
25
|
+
"secrets:write"
|
|
26
|
+
];
|
|
27
|
+
/** The valid secret scopes, for validating a capability's declaration. */
|
|
28
|
+
const TOKEN_SECRET_SCOPES = ["environment", "global"];
|
|
29
|
+
/** The env-var / secret name a profile's token is stored under: `CF_TOKEN_<PROFILE>`. */
|
|
30
|
+
function tokenSecretName(profile) {
|
|
31
|
+
return `CF_TOKEN_${profile.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Author a token profile: validates its permission keys against the catalog at author time, so a typo
|
|
35
|
+
* fails where the profile is declared, not deep in a mint call. Returns the profile unchanged, typed.
|
|
36
|
+
*/
|
|
37
|
+
function defineTokenProfile(profile) {
|
|
38
|
+
resolvePermissionKeys(profile.permissions);
|
|
39
|
+
return profile;
|
|
40
|
+
}
|
|
41
|
+
/** Validate a capability's declared permission keys, throwing an actionable error on any unknown one. */
|
|
42
|
+
function assertPermissionKeys(keys, where) {
|
|
43
|
+
for (const key of keys) if (!isPermissionKey(key)) throw new CloudflareNotConfiguredError({
|
|
44
|
+
message: `${where} declares an unknown permission key: ${key}.`,
|
|
45
|
+
action: "Use a known permission key (d1:read, d1:write, workers:write, secrets:read, secrets:write, email:routing, kv:write)."
|
|
46
|
+
});
|
|
47
|
+
return keys;
|
|
48
|
+
}
|
|
49
|
+
/** Build the aggregating `ci-system` profile: the base permissions ∪ every capability's `ciPermissions`. */
|
|
50
|
+
function ciSystemProfile(capabilities) {
|
|
51
|
+
const permissions = [...CI_SYSTEM_BASE];
|
|
52
|
+
for (const capability of capabilities) for (const key of assertPermissionKeys(capability.ciPermissions ?? [], `Capability "${capability.name}"`)) if (!permissions.includes(key)) permissions.push(key);
|
|
53
|
+
return {
|
|
54
|
+
name: CI_SYSTEM_PROFILE,
|
|
55
|
+
permissions,
|
|
56
|
+
secret: tokenSecretName(CI_SYSTEM_PROFILE),
|
|
57
|
+
defaultStore: "dev-vars",
|
|
58
|
+
description: "The one CI pipeline credential: deploy Workers, migrate, and every composed capability's CI needs. Set it as CI's CLOUDFLARE_API_TOKEN."
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Build a concrete, validated worker-consumer {@link TokenProfile} from a capability's structural seam entry. */
|
|
62
|
+
function profileFromSeam(name, seam, capability) {
|
|
63
|
+
const store = seam.defaultStore;
|
|
64
|
+
if (store !== void 0 && !TOKEN_STORES.includes(store)) throw new CloudflareNotConfiguredError({
|
|
65
|
+
message: `Token profile "${name}" (capability "${capability}") declares an unknown store: ${store}.`,
|
|
66
|
+
action: `Use one of: ${TOKEN_STORES.join(", ")}.`
|
|
67
|
+
});
|
|
68
|
+
const scope = seam.secretScope;
|
|
69
|
+
if (scope !== void 0 && !TOKEN_SECRET_SCOPES.includes(scope)) throw new CloudflareNotConfiguredError({
|
|
70
|
+
message: `Token profile "${name}" (capability "${capability}") declares an unknown secret scope: ${scope}.`,
|
|
71
|
+
action: `Use one of: ${TOKEN_SECRET_SCOPES.join(", ")}.`
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
permissions: assertPermissionKeys(seam.permissions, `Token profile "${name}"`),
|
|
76
|
+
resources: seam.resources,
|
|
77
|
+
secret: seam.secret ?? tokenSecretName(name),
|
|
78
|
+
secretScope: scope,
|
|
79
|
+
defaultStore: store,
|
|
80
|
+
description: seam.description ?? name
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The project's token-profile registry: the aggregating `ci-system` profile plus every composed
|
|
85
|
+
* capability's worker-consumer {@link Capability.tokenProfiles} slice. A capability profile that
|
|
86
|
+
* clashes with `ci-system` (or another capability's name) fails loudly at assembly. This is the one
|
|
87
|
+
* registry `pithy token` reads.
|
|
88
|
+
*/
|
|
89
|
+
function resolveTokenProfiles(capabilities) {
|
|
90
|
+
const profiles = { [CI_SYSTEM_PROFILE]: ciSystemProfile(capabilities) };
|
|
91
|
+
for (const capability of capabilities) for (const [name, seam] of Object.entries(capability.tokenProfiles ?? {})) {
|
|
92
|
+
if (profiles[name]) throw new CloudflareNotConfiguredError({
|
|
93
|
+
message: `Token profile "${name}" is declared more than once (capability "${capability.name}").`,
|
|
94
|
+
action: "Give each token profile a unique name."
|
|
95
|
+
});
|
|
96
|
+
profiles[name] = profileFromSeam(name, seam, capability.name);
|
|
97
|
+
}
|
|
98
|
+
return profiles;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a profile by name from the aggregated registry, applying an adopter override. An unknown
|
|
102
|
+
* name fails with an actionable error listing the known profiles.
|
|
103
|
+
*/
|
|
104
|
+
function resolveProfile(profiles, name, override) {
|
|
105
|
+
const base = profiles[name];
|
|
106
|
+
if (!base) throw new CloudflareNotConfiguredError({
|
|
107
|
+
message: `Unknown token profile: ${name}.`,
|
|
108
|
+
action: `Use one of: ${Object.keys(profiles).join(", ")}.`,
|
|
109
|
+
detail: `resolve token profile: ${name} is not in the registry`
|
|
110
|
+
});
|
|
111
|
+
if (!override) return base;
|
|
112
|
+
return {
|
|
113
|
+
...base,
|
|
114
|
+
permissions: override.permissions ?? base.permissions,
|
|
115
|
+
resources: override.resources ?? base.resources,
|
|
116
|
+
defaultStore: override.store ?? base.defaultStore
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Build the account-scoped {@link TokenPermission}s for a set of permission keys — the mint input. */
|
|
120
|
+
function permissionsForKeys(keys, accountId) {
|
|
121
|
+
return [{
|
|
122
|
+
permissionGroupNames: resolvePermissionKeys(keys),
|
|
123
|
+
resources: accountResource(accountId)
|
|
124
|
+
}];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Turn a resolved profile into the {@link TokenPermission}s the account-tokens manager mints from: the
|
|
128
|
+
* profile's permission keys resolved to CF group names, scoped to the account (or the profile's
|
|
129
|
+
* explicit resources). One policy per profile — a token carries exactly the access its profile declares.
|
|
130
|
+
*/
|
|
131
|
+
function profilePermissions(profile, accountId) {
|
|
132
|
+
const resources = profile.resources && profile.resources !== "account" ? profile.resources : accountResource(accountId);
|
|
133
|
+
return [{
|
|
134
|
+
permissionGroupNames: resolvePermissionKeys(profile.permissions),
|
|
135
|
+
resources
|
|
136
|
+
}];
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { CI_SYSTEM_PROFILE, TOKEN_STORES, defineTokenProfile, permissionsForKeys, profilePermissions, resolveProfile, resolveTokenProfiles, tokenSecretName };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import type { Widget, WidgetListResponse } from "cloudflare/resources/turnstile/widgets";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { CloudflareManager } from "../client/manager";
|
|
6
|
+
/**
|
|
7
|
+
* The two widget modes Pithy provisions. `managed` is Cloudflare's *visible* managed widget (CF decides
|
|
8
|
+
* whether to show interaction) — used where a challenge should be seen (a login page). `invisible` runs
|
|
9
|
+
* silently — used where a form should not interrupt (a lead capture). One of each per domain, max.
|
|
10
|
+
*/
|
|
11
|
+
export type TurnstileWidgetMode = "managed" | "invisible";
|
|
12
|
+
/**
|
|
13
|
+
* The server-side response from Turnstile's `/siteverify` endpoint. Pithy validates the raw JSON
|
|
14
|
+
* through this object before trusting it — a humanity check is a security boundary (CLAUDE.md §HTTP:
|
|
15
|
+
* Turnstile is composable middleware). `challenge_ts` is an ISO-8601 string on the wire, decoded to
|
|
16
|
+
* a `Date` via the `JsonDate` codec.
|
|
17
|
+
*/
|
|
18
|
+
export declare const TurnstileVerification: z.ZodObject<{
|
|
19
|
+
success: z.ZodBoolean;
|
|
20
|
+
"error-codes": z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
21
|
+
messages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
challenge_ts: z.ZodOptional<z.ZodCodec<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodDate]>, z.ZodDate>>;
|
|
23
|
+
hostname: z.ZodOptional<z.ZodString>;
|
|
24
|
+
action: z.ZodOptional<z.ZodString>;
|
|
25
|
+
cdata: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export type TurnstileVerification = z.output<typeof TurnstileVerification>;
|
|
28
|
+
/** Options for a server-side `verify` call. */
|
|
29
|
+
export interface TurnstileVerifyOptions {
|
|
30
|
+
/** The end-user IP the token was issued to, for an extra integrity check. */
|
|
31
|
+
remoteIp?: string;
|
|
32
|
+
/** The expected action label, asserted by Cloudflare against the token. */
|
|
33
|
+
action?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Out-of-Worker Turnstile access over the REST API: server-side token verification against
|
|
37
|
+
* `/siteverify`, plus widget management (create/list/update/rotate/delete) via the SDK. Inside a
|
|
38
|
+
* Worker the same siteverify call works over `fetch`; this manager is the CLI/CI/provisioning
|
|
39
|
+
* counterpart, addressed by account.
|
|
40
|
+
*/
|
|
41
|
+
export declare class CloudflareTurnstileManager extends CloudflareManager {
|
|
42
|
+
/**
|
|
43
|
+
* Verify a Turnstile token server-side against `/siteverify`. The secret is the widget's secret
|
|
44
|
+
* key (never the sitekey). The raw response is validated through `TurnstileVerification`; a
|
|
45
|
+
* malformed body throws `cloudflare/invalid_response`, a network failure is wrapped as
|
|
46
|
+
* `cloudflare/request_failed`.
|
|
47
|
+
*/
|
|
48
|
+
verify(token: string, secret: string, options?: TurnstileVerifyOptions): Promise<TurnstileVerification>;
|
|
49
|
+
/** Find a widget by name. Returns null when no widget matches; never throws on a missing match. */
|
|
50
|
+
getTurnstile(name: string): Promise<WidgetListResponse | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Every widget claiming `domain`, matched case-insensitively (hostnames are). The sibling of
|
|
53
|
+
* {@link getTurnstile}, keyed on the domain instead of the name — and **plural**, deliberately:
|
|
54
|
+
* Cloudflare's widget names are not unique and a domain may legitimately appear on several widgets,
|
|
55
|
+
* so returning one would hide the rest from a caller deciding whether the domain is free.
|
|
56
|
+
*
|
|
57
|
+
* Exact hostname equality only. Cloudflare treats a widget's domain as covering its subdomains, but
|
|
58
|
+
* this is the input to a *refusal*, and a refusal inferred from a subdomain relationship would block
|
|
59
|
+
* `app.example.com` because someone once made a widget for `example.com`.
|
|
60
|
+
*/
|
|
61
|
+
listTurnstilesByDomain(domain: string): Promise<WidgetListResponse[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Create a widget for the given domains in the requested mode — `managed` (visible) or `invisible`
|
|
64
|
+
* (silent). Defaults to `invisible` so existing callers keep their behavior.
|
|
65
|
+
*/
|
|
66
|
+
addTurnstile(name: string, domains: string[], mode?: TurnstileWidgetMode): Promise<Widget>;
|
|
67
|
+
/** Delete a widget by its sitekey. */
|
|
68
|
+
deleteTurnstile(siteKey: string): Promise<void>;
|
|
69
|
+
/** Replace the allowed domains on an existing widget (kept in invisible mode). */
|
|
70
|
+
updateTurnstileDomains(siteKey: string, domains: string[], name: string): Promise<Widget>;
|
|
71
|
+
/** Rotate a widget's secret key, invalidating the old secret immediately. */
|
|
72
|
+
rotateTurnstile(siteKey: string): Promise<Widget>;
|
|
73
|
+
getServiceType(): string;
|
|
74
|
+
/** Prove access by listing widgets. Never throws. */
|
|
75
|
+
validateServiceAccess(): Promise<boolean>;
|
|
76
|
+
}
|
|
77
|
+
//# 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,140 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { CloudflareInvalidResponseError, cloudflareRequest } from "../client/errors.js";
|
|
4
|
+
import { CloudflareManager } from "../client/manager.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { JsonDate } from "@pithy-sh/core/src/data/codecs";
|
|
7
|
+
//#region src/turnstile/turnstileManager.ts
|
|
8
|
+
/** The siteverify endpoint Turnstile tokens are validated against (server-side, not the SDK). */
|
|
9
|
+
const SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
10
|
+
/** Per-call SDK timeout + retry budget for widget management operations. */
|
|
11
|
+
const requestOptions = {
|
|
12
|
+
timeout: 1e4,
|
|
13
|
+
maxRetries: 3
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The server-side response from Turnstile's `/siteverify` endpoint. Pithy validates the raw JSON
|
|
17
|
+
* through this object before trusting it — a humanity check is a security boundary (CLAUDE.md §HTTP:
|
|
18
|
+
* Turnstile is composable middleware). `challenge_ts` is an ISO-8601 string on the wire, decoded to
|
|
19
|
+
* a `Date` via the `JsonDate` codec.
|
|
20
|
+
*/
|
|
21
|
+
const TurnstileVerification = z.object({
|
|
22
|
+
success: z.boolean().describe("Whether the token passed the humanity challenge."),
|
|
23
|
+
"error-codes": z.array(z.string()).default([]).describe("Machine-readable failure reasons; empty on success."),
|
|
24
|
+
messages: z.array(z.string()).optional().describe("Optional human-readable notices from Cloudflare."),
|
|
25
|
+
challenge_ts: JsonDate.optional().describe("When the challenge was solved (ISO-8601 on the wire, Date in JS)."),
|
|
26
|
+
hostname: z.string().optional().describe("The hostname the challenge was solved on."),
|
|
27
|
+
action: z.string().optional().describe("The customer-supplied action label bound to the token."),
|
|
28
|
+
cdata: z.string().optional().describe("The customer-supplied context data bound to the token.")
|
|
29
|
+
}).describe("Server-side Turnstile siteverify response from challenges.cloudflare.com.");
|
|
30
|
+
/**
|
|
31
|
+
* Out-of-Worker Turnstile access over the REST API: server-side token verification against
|
|
32
|
+
* `/siteverify`, plus widget management (create/list/update/rotate/delete) via the SDK. Inside a
|
|
33
|
+
* Worker the same siteverify call works over `fetch`; this manager is the CLI/CI/provisioning
|
|
34
|
+
* counterpart, addressed by account.
|
|
35
|
+
*/
|
|
36
|
+
var CloudflareTurnstileManager = class extends CloudflareManager {
|
|
37
|
+
/**
|
|
38
|
+
* Verify a Turnstile token server-side against `/siteverify`. The secret is the widget's secret
|
|
39
|
+
* key (never the sitekey). The raw response is validated through `TurnstileVerification`; a
|
|
40
|
+
* malformed body throws `cloudflare/invalid_response`, a network failure is wrapped as
|
|
41
|
+
* `cloudflare/request_failed`.
|
|
42
|
+
*/
|
|
43
|
+
async verify(token, secret, options) {
|
|
44
|
+
const body = new URLSearchParams({
|
|
45
|
+
secret,
|
|
46
|
+
response: token
|
|
47
|
+
});
|
|
48
|
+
if (options?.remoteIp) body.set("remoteip", options.remoteIp);
|
|
49
|
+
if (options?.action) body.set("action", options.action);
|
|
50
|
+
const raw = await cloudflareRequest("Turnstile siteverify", async () => {
|
|
51
|
+
const response = await fetch(SITEVERIFY_URL, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
54
|
+
body
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) throw new CloudflareInvalidResponseError({
|
|
57
|
+
message: "Turnstile siteverify returned a non-OK status.",
|
|
58
|
+
detail: `siteverify responded ${response.status} ${response.statusText}.`
|
|
59
|
+
});
|
|
60
|
+
return await response.json();
|
|
61
|
+
});
|
|
62
|
+
const parsed = TurnstileVerification.safeParse(raw);
|
|
63
|
+
if (!parsed.success) throw new CloudflareInvalidResponseError({
|
|
64
|
+
message: "Turnstile siteverify response had an unexpected shape.",
|
|
65
|
+
detail: parsed.error.message
|
|
66
|
+
});
|
|
67
|
+
return parsed.data;
|
|
68
|
+
}
|
|
69
|
+
/** Find a widget by name. Returns null when no widget matches; never throws on a missing match. */
|
|
70
|
+
async getTurnstile(name) {
|
|
71
|
+
return cloudflareRequest(`Turnstile get widget '${name}'`, async () => {
|
|
72
|
+
for await (const widget of this.getClient().turnstile.widgets.list({ account_id: this.accountId })) if (widget.name === name) return widget;
|
|
73
|
+
return null;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Every widget claiming `domain`, matched case-insensitively (hostnames are). The sibling of
|
|
78
|
+
* {@link getTurnstile}, keyed on the domain instead of the name — and **plural**, deliberately:
|
|
79
|
+
* Cloudflare's widget names are not unique and a domain may legitimately appear on several widgets,
|
|
80
|
+
* so returning one would hide the rest from a caller deciding whether the domain is free.
|
|
81
|
+
*
|
|
82
|
+
* Exact hostname equality only. Cloudflare treats a widget's domain as covering its subdomains, but
|
|
83
|
+
* this is the input to a *refusal*, and a refusal inferred from a subdomain relationship would block
|
|
84
|
+
* `app.example.com` because someone once made a widget for `example.com`.
|
|
85
|
+
*/
|
|
86
|
+
async listTurnstilesByDomain(domain) {
|
|
87
|
+
const wanted = domain.toLowerCase();
|
|
88
|
+
return cloudflareRequest(`Turnstile list widgets for '${domain}'`, async () => {
|
|
89
|
+
const matches = [];
|
|
90
|
+
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);
|
|
91
|
+
return matches;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a widget for the given domains in the requested mode — `managed` (visible) or `invisible`
|
|
96
|
+
* (silent). Defaults to `invisible` so existing callers keep their behavior.
|
|
97
|
+
*/
|
|
98
|
+
async addTurnstile(name, domains, mode = "invisible") {
|
|
99
|
+
return cloudflareRequest(`Turnstile create widget '${name}'`, () => this.getClient().turnstile.widgets.create({
|
|
100
|
+
account_id: this.accountId,
|
|
101
|
+
domains,
|
|
102
|
+
mode,
|
|
103
|
+
name
|
|
104
|
+
}, requestOptions));
|
|
105
|
+
}
|
|
106
|
+
/** Delete a widget by its sitekey. */
|
|
107
|
+
async deleteTurnstile(siteKey) {
|
|
108
|
+
await cloudflareRequest(`Turnstile delete widget '${siteKey}'`, () => this.getClient().turnstile.widgets.delete(siteKey, { account_id: this.accountId }, requestOptions));
|
|
109
|
+
}
|
|
110
|
+
/** Replace the allowed domains on an existing widget (kept in invisible mode). */
|
|
111
|
+
async updateTurnstileDomains(siteKey, domains, name) {
|
|
112
|
+
return cloudflareRequest(`Turnstile update domains for '${siteKey}'`, () => this.getClient().turnstile.widgets.update(siteKey, {
|
|
113
|
+
account_id: this.accountId,
|
|
114
|
+
domains,
|
|
115
|
+
mode: "invisible",
|
|
116
|
+
name
|
|
117
|
+
}, requestOptions));
|
|
118
|
+
}
|
|
119
|
+
/** Rotate a widget's secret key, invalidating the old secret immediately. */
|
|
120
|
+
async rotateTurnstile(siteKey) {
|
|
121
|
+
return cloudflareRequest(`Turnstile rotate secret for '${siteKey}'`, () => this.getClient().turnstile.widgets.rotateSecret(siteKey, {
|
|
122
|
+
account_id: this.accountId,
|
|
123
|
+
invalidate_immediately: true
|
|
124
|
+
}, requestOptions));
|
|
125
|
+
}
|
|
126
|
+
getServiceType() {
|
|
127
|
+
return "Cloudflare Turnstile";
|
|
128
|
+
}
|
|
129
|
+
/** Prove access by listing widgets. Never throws. */
|
|
130
|
+
async validateServiceAccess() {
|
|
131
|
+
try {
|
|
132
|
+
await this.getClient().turnstile.widgets.list({ account_id: this.accountId });
|
|
133
|
+
return true;
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
140
|
+
export { CloudflareTurnstileManager, TurnstileVerification };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { CloudflareManager } from "../client/manager";
|
|
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
|
+
export declare const CfUserIdentity: z.ZodObject<{
|
|
17
|
+
id: z.ZodOptional<z.ZodString>;
|
|
18
|
+
email: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export type CfUserIdentity = z.output<typeof CfUserIdentity>;
|
|
21
|
+
/** The result of verifying the calling token: its id and lifecycle status. */
|
|
22
|
+
export declare const CfTokenVerification: z.ZodObject<{
|
|
23
|
+
id: z.ZodString;
|
|
24
|
+
status: z.ZodString;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export type CfTokenVerification = z.output<typeof CfTokenVerification>;
|
|
27
|
+
export declare class CloudflareUserManager extends CloudflareManager {
|
|
28
|
+
getServiceType(): string;
|
|
29
|
+
/** Prove access by reading the user record. Never throws. */
|
|
30
|
+
validateServiceAccess(): Promise<boolean>;
|
|
31
|
+
/** The Cloudflare user behind a user (`cfut_*`) token — `GET /user`. */
|
|
32
|
+
getUser(): Promise<CfUserIdentity>;
|
|
33
|
+
/** Verify the calling token and read its id + status — `GET /user/tokens/verify`. */
|
|
34
|
+
verifyToken(): Promise<CfTokenVerification>;
|
|
35
|
+
/** The name of a token by id, or `null` if it can't be read — `GET /user/tokens/:id`. */
|
|
36
|
+
getTokenName(tokenId: string): Promise<string | null>;
|
|
37
|
+
}
|
|
38
|
+
//# 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,60 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { cloudflareRequest, decodeResponse } from "../client/errors.js";
|
|
4
|
+
import { CloudflareManager } from "../client/manager.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/user/userManager.ts
|
|
7
|
+
/**
|
|
8
|
+
* Out-of-Worker read access to the **user/token** identity behind a CF API token — `GET /user` and
|
|
9
|
+
* `GET /user/tokens/verify`/`/user/tokens/:id`. The audit CLI emitter uses it to attribute a
|
|
10
|
+
* control-plane action (`pithy migrate`, `pithy deploy`, …) to the right actor: a user token
|
|
11
|
+
* (`cfut_*`) resolves to the developer's email, an account token (`cfat_*`) to the token's name.
|
|
12
|
+
*
|
|
13
|
+
* These are *user-scoped* endpoints, so they need a user-bound token — CLAUDE.md prefers
|
|
14
|
+
* account-owned tokens for provisioning, but actor attribution is exactly the case where the token's
|
|
15
|
+
* own identity is the point. Reads only; never mints, never logs a token value.
|
|
16
|
+
*/
|
|
17
|
+
/** The CF user behind a user token. The SDK omits `email` from its type, but the API returns it. */
|
|
18
|
+
const CfUserIdentity = z.object({
|
|
19
|
+
id: z.string().optional().describe("The Cloudflare user id — stable cross-reference for the actor."),
|
|
20
|
+
email: z.string().optional().describe("The Cloudflare user's email — the human actor's identifier.")
|
|
21
|
+
}).describe("The identity behind a Cloudflare user (`cfut_*`) API token, from `GET /user`.");
|
|
22
|
+
/** The result of verifying the calling token: its id and lifecycle status. */
|
|
23
|
+
const CfTokenVerification = z.object({
|
|
24
|
+
id: z.string().describe("The token's id — addresses it for a follow-up name lookup."),
|
|
25
|
+
status: z.string().describe("The token's lifecycle status (`active` | `disabled` | `expired`).")
|
|
26
|
+
}).describe("The result of `GET /user/tokens/verify` for the calling Cloudflare API token.");
|
|
27
|
+
/** Just the token's name, the piece the account-token actor path needs. */
|
|
28
|
+
const TokenName = z.object({ name: z.string() });
|
|
29
|
+
var CloudflareUserManager = class extends CloudflareManager {
|
|
30
|
+
getServiceType() {
|
|
31
|
+
return "Cloudflare User";
|
|
32
|
+
}
|
|
33
|
+
/** Prove access by reading the user record. Never throws. */
|
|
34
|
+
async validateServiceAccess() {
|
|
35
|
+
try {
|
|
36
|
+
await this.getUser();
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** The Cloudflare user behind a user (`cfut_*`) token — `GET /user`. */
|
|
43
|
+
async getUser() {
|
|
44
|
+
const raw = await cloudflareRequest("get user", () => this.getClient().user.get());
|
|
45
|
+
return decodeResponse(CfUserIdentity, raw, "user get");
|
|
46
|
+
}
|
|
47
|
+
/** Verify the calling token and read its id + status — `GET /user/tokens/verify`. */
|
|
48
|
+
async verifyToken() {
|
|
49
|
+
const raw = await cloudflareRequest("verify token", () => this.getClient().user.tokens.verify());
|
|
50
|
+
return decodeResponse(CfTokenVerification, raw, "token verify");
|
|
51
|
+
}
|
|
52
|
+
/** The name of a token by id, or `null` if it can't be read — `GET /user/tokens/:id`. */
|
|
53
|
+
async getTokenName(tokenId) {
|
|
54
|
+
const raw = await cloudflareRequest(`get token ${tokenId}`, () => this.getClient().user.tokens.get(tokenId));
|
|
55
|
+
const parsed = TokenName.safeParse(raw);
|
|
56
|
+
return parsed.success ? parsed.data.name : null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { CfTokenVerification, CfUserIdentity, CloudflareUserManager };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { CloudflareManager } from "../client/manager";
|
|
4
|
+
import { CfBuild, CfBuildLog, CfBuildToken, CfBuildTrigger, CfRepoConnection, type CfTriggerEnvVar } from "./buildsTypes";
|
|
5
|
+
/** Arguments for `createRepoConnection`. */
|
|
6
|
+
export interface CreateRepoConnectionArgs {
|
|
7
|
+
/** The git provider hosting the repo. */
|
|
8
|
+
providerType: "gitlab" | "github";
|
|
9
|
+
/** The repository's id in the git provider. */
|
|
10
|
+
repoId: string;
|
|
11
|
+
/** The repo path relative to the provider account. */
|
|
12
|
+
repoName: string;
|
|
13
|
+
/** The provider account identifier (e.g. 'group:15366484'). */
|
|
14
|
+
providerAccountId: string;
|
|
15
|
+
/** The provider account display name. */
|
|
16
|
+
providerAccountName: string;
|
|
17
|
+
}
|
|
18
|
+
/** Arguments for `upsertTrigger`. */
|
|
19
|
+
export interface UpsertTriggerArgs {
|
|
20
|
+
/** The immutable Worker id (hex UUID) the trigger builds — not the worker name. */
|
|
21
|
+
scriptName: string;
|
|
22
|
+
/** The repo connection UUID to build from. */
|
|
23
|
+
repoConnectionId: string;
|
|
24
|
+
/** The build token UUID the trigger authenticates with. */
|
|
25
|
+
buildTokenUuid: string;
|
|
26
|
+
/** The trigger's human-readable name. */
|
|
27
|
+
triggerName: string;
|
|
28
|
+
/** Branch patterns whose pushes auto-fire the trigger (must be non-empty). */
|
|
29
|
+
branchIncludes: string[];
|
|
30
|
+
/** Branch patterns excluded from auto-firing. */
|
|
31
|
+
branchExcludes?: string[];
|
|
32
|
+
/** Path patterns whose changes auto-fire the trigger. */
|
|
33
|
+
pathIncludes?: string[];
|
|
34
|
+
/** The shell command that builds the Worker. */
|
|
35
|
+
buildCommand: string;
|
|
36
|
+
/** The shell command CF Builds runs to deploy after a build. */
|
|
37
|
+
deployCommand: string;
|
|
38
|
+
/** The repo subdirectory builds run from. Defaults to '/'. */
|
|
39
|
+
rootDirectory?: string;
|
|
40
|
+
/** Whether build caching is enabled. Defaults to true. */
|
|
41
|
+
buildCachingEnabled?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** A subset of trigger fields that can be patched after creation. */
|
|
44
|
+
export interface UpdateTriggerArgs {
|
|
45
|
+
/** A branch filter to set on the trigger. */
|
|
46
|
+
branchFilter?: string;
|
|
47
|
+
/** The build command to set. */
|
|
48
|
+
buildCommand?: string;
|
|
49
|
+
/** The deploy command to set. */
|
|
50
|
+
deployCommand?: string;
|
|
51
|
+
/** The root directory to set. */
|
|
52
|
+
rootDirectory?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Out-of-Worker access to the Cloudflare Builds API: repo connections, build triggers, builds, and
|
|
56
|
+
* trigger env vars. CF Builds is the CI/CD layer for Workers — it clones a git repo, runs a build
|
|
57
|
+
* command, and reports outcomes.
|
|
58
|
+
*
|
|
59
|
+
* As of the SDK version this package targets, the Builds endpoints are NOT exposed as typed SDK
|
|
60
|
+
* resources, so this manager uses the documented escape hatch (`getApiToken()` + raw `fetch`) against
|
|
61
|
+
* explicit `/accounts/{id}/builds/...` paths. Every request goes through `cloudflareRequest` so
|
|
62
|
+
* failures wrap as `cloudflare/request_failed`, and every response is validated with Zod
|
|
63
|
+
* (`cloudflare/invalid_response` on mismatch).
|
|
64
|
+
*/
|
|
65
|
+
export declare class CloudflareBuildsManager extends CloudflareManager {
|
|
66
|
+
/**
|
|
67
|
+
* Issue a raw CF REST call and unwrap the `{ result, success, errors }` envelope. A failure —
|
|
68
|
+
* either a non-2xx status **or** a 200 carrying `success: false` — throws `cloudflare/request_failed`
|
|
69
|
+
* with the structured CF error codes pinned to the front of `detail` (so idempotency checks read a
|
|
70
|
+
* stable marker, never a substring a verbose body could push past truncation). An unparseable body
|
|
71
|
+
* throws `cloudflare/invalid_response`.
|
|
72
|
+
*/
|
|
73
|
+
private call;
|
|
74
|
+
/**
|
|
75
|
+
* Register a git repository with CF Builds. `PUT /builds/repos/connections` is CF's documented
|
|
76
|
+
* upsert — calling with the same repo id returns the existing connection.
|
|
77
|
+
*/
|
|
78
|
+
createRepoConnection(args: CreateRepoConnectionArgs): Promise<CfRepoConnection>;
|
|
79
|
+
/**
|
|
80
|
+
* Upsert a build trigger linking a Worker script to a repo. Tries `POST /builds/triggers`; on the
|
|
81
|
+
* 409 "trigger already exists" conflict, falls back to listing the script's triggers and returning
|
|
82
|
+
* the matching one. Idempotent.
|
|
83
|
+
*/
|
|
84
|
+
upsertTrigger(args: UpsertTriggerArgs): Promise<CfBuildTrigger>;
|
|
85
|
+
/** List the build tokens available on the account. */
|
|
86
|
+
listBuildTokens(): Promise<CfBuildToken[]>;
|
|
87
|
+
/** Patch an existing build trigger's configuration. */
|
|
88
|
+
updateTrigger(triggerId: string, args: UpdateTriggerArgs): Promise<CfBuildTrigger>;
|
|
89
|
+
/** Delete a build trigger. */
|
|
90
|
+
deleteTrigger(triggerId: string): Promise<void>;
|
|
91
|
+
/** Manually start a build for a trigger at a specific branch + commit. */
|
|
92
|
+
triggerManualBuild(triggerId: string, branch: string, commitHash: string): Promise<CfBuild>;
|
|
93
|
+
/** Get a build's current status and metadata by id. */
|
|
94
|
+
getBuild(buildId: string): Promise<CfBuild>;
|
|
95
|
+
/** Cancel a running build. */
|
|
96
|
+
cancelBuild(buildId: string): Promise<void>;
|
|
97
|
+
/** Get the log output for a build. */
|
|
98
|
+
getBuildLogs(buildId: string): Promise<CfBuildLog[]>;
|
|
99
|
+
/** List all builds for a Worker script. */
|
|
100
|
+
listBuildsByScript(scriptName: string): Promise<CfBuild[]>;
|
|
101
|
+
/**
|
|
102
|
+
* List the build triggers for a Worker script. `externalScriptId` must be the immutable Worker id
|
|
103
|
+
* (hex UUID), resolved via `CloudflareWorkersManager.getWorkerInternalId`. Returns an empty array
|
|
104
|
+
* when the script has no triggers (CF 404 code 12000).
|
|
105
|
+
*/
|
|
106
|
+
listTriggersByScript(externalScriptId: string): Promise<CfBuildTrigger[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Bulk-upsert environment variables on a build trigger. The wire body is a map keyed by variable
|
|
109
|
+
* name (`{ NAME: { is_secret, value }, ... }`), not an array.
|
|
110
|
+
*/
|
|
111
|
+
upsertTriggerEnvVars(triggerId: string, envVars: CfTriggerEnvVar[]): Promise<void>;
|
|
112
|
+
getServiceType(): string;
|
|
113
|
+
/** Prove access by listing build triggers. Never throws. */
|
|
114
|
+
validateServiceAccess(): Promise<boolean>;
|
|
115
|
+
/** Validate a raw CF result against a Zod schema, throwing `cloudflare/invalid_response` on mismatch. */
|
|
116
|
+
private validate;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=buildsManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildsManager.d.ts","sourceRoot":"","sources":["../../src/workers/buildsManager.ts"],"names":[],"mappings":"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"}
|