alchemy 0.80.1 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alchemy.js +11 -5
- package/bin/services/execute-alchemy.ts +18 -6
- package/lib/alchemy.js +1 -1
- package/lib/alchemy.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/hyperdrive.d.ts +23 -5
- package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
- package/lib/cloudflare/hyperdrive.js.map +1 -1
- package/lib/cloudflare/index.d.ts +3 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +3 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +7 -0
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
- package/lib/cloudflare/queue.d.ts +0 -3
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +53 -31
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/tunnel-route.d.ts +172 -0
- package/lib/cloudflare/tunnel-route.d.ts.map +1 -0
- package/lib/cloudflare/tunnel-route.js +251 -0
- package/lib/cloudflare/tunnel-route.js.map +1 -0
- package/lib/cloudflare/warp-default-profile.d.ts +133 -0
- package/lib/cloudflare/warp-default-profile.d.ts.map +1 -0
- package/lib/cloudflare/warp-default-profile.js +138 -0
- package/lib/cloudflare/warp-default-profile.js.map +1 -0
- package/lib/cloudflare/warp-device-profile.d.ts +272 -0
- package/lib/cloudflare/warp-device-profile.d.ts.map +1 -0
- package/lib/cloudflare/warp-device-profile.js +291 -0
- package/lib/cloudflare/warp-device-profile.js.map +1 -0
- package/lib/scope.d.ts +1 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +1 -1
- package/lib/scope.js.map +1 -1
- package/package.json +1 -1
- package/src/alchemy.ts +1 -1
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/hyperdrive.ts +27 -5
- package/src/cloudflare/index.ts +3 -0
- package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +11 -0
- package/src/cloudflare/queue.ts +97 -56
- package/src/cloudflare/tunnel-route.ts +495 -0
- package/src/cloudflare/warp-default-profile.ts +320 -0
- package/src/cloudflare/warp-device-profile.ts +616 -0
- package/src/scope.ts +2 -2
- package/workers/tunnel-proxy.js +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import type { Context } from "../context.ts";
|
|
2
|
+
import { Resource, ResourceKind } from "../resource.ts";
|
|
3
|
+
import { handleApiError } from "./api-error.ts";
|
|
4
|
+
import {
|
|
5
|
+
createCloudflareApi,
|
|
6
|
+
type CloudflareApi,
|
|
7
|
+
type CloudflareApiOptions,
|
|
8
|
+
} from "./api.ts";
|
|
9
|
+
import type {
|
|
10
|
+
ServiceModeV2,
|
|
11
|
+
SplitTunnelConfig,
|
|
12
|
+
} from "./warp-device-profile.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Properties for updating the WARP Default Profile
|
|
16
|
+
*/
|
|
17
|
+
export interface WarpDefaultProfileProps extends CloudflareApiOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Service mode configuration for WARP client
|
|
20
|
+
*/
|
|
21
|
+
serviceModeV2?: ServiceModeV2;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Disable automatic fallback to direct connection if tunnel fails
|
|
25
|
+
*/
|
|
26
|
+
disableAutoFallback?: boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Allow users to manually switch WARP modes
|
|
30
|
+
*/
|
|
31
|
+
allowModeSwitch?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Lock the WARP toggle switch (users cannot change it)
|
|
35
|
+
*/
|
|
36
|
+
switchLocked?: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Tunnel protocol to use
|
|
40
|
+
*/
|
|
41
|
+
tunnelProtocol?: "wireguard" | "masque";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Auto-connect timeout in seconds
|
|
45
|
+
* Set to 0 to disable auto-connect
|
|
46
|
+
*/
|
|
47
|
+
autoConnect?: number;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Allow users to disconnect from WARP
|
|
51
|
+
*/
|
|
52
|
+
allowedToLeave?: boolean;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Captive portal timeout in seconds
|
|
56
|
+
* Time before showing captive portal
|
|
57
|
+
*/
|
|
58
|
+
captivePortal?: number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Support URL for feedback button in WARP client
|
|
62
|
+
*/
|
|
63
|
+
supportUrl?: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Exclude office IPs from WARP tunnel
|
|
67
|
+
*/
|
|
68
|
+
excludeOfficeIps?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* LAN allow duration in minutes
|
|
72
|
+
*/
|
|
73
|
+
lanAllowMinutes?: number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* LAN subnet size for local network access
|
|
77
|
+
*/
|
|
78
|
+
lanAllowSubnetSize?: number;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Split tunnel configuration
|
|
82
|
+
* Controls which routes bypass or use the WARP tunnel
|
|
83
|
+
*/
|
|
84
|
+
splitTunnel?: SplitTunnelConfig;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether to delete the default profile settings when removed from Alchemy
|
|
88
|
+
* Note: This does not actually delete the default profile (which always exists),
|
|
89
|
+
* but removes it from Alchemy state management
|
|
90
|
+
*
|
|
91
|
+
* @default false
|
|
92
|
+
*/
|
|
93
|
+
delete?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isWarpDefaultProfile(
|
|
97
|
+
resource: any,
|
|
98
|
+
): resource is WarpDefaultProfile {
|
|
99
|
+
return resource?.[ResourceKind] === "cloudflare::WarpDefaultProfile";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Output returned after WARP Default Profile update
|
|
104
|
+
*/
|
|
105
|
+
export type WarpDefaultProfile = WarpDefaultProfileProps & {
|
|
106
|
+
/**
|
|
107
|
+
* Time at which the profile was last modified
|
|
108
|
+
*/
|
|
109
|
+
modifiedAt: number;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Manages the Cloudflare WARP Default Device Profile, which defines the
|
|
114
|
+
* account-wide default WARP client settings that apply when no custom profile matches.
|
|
115
|
+
*
|
|
116
|
+
* The default profile always exists and cannot be deleted. This resource allows you
|
|
117
|
+
* to update its settings programmatically.
|
|
118
|
+
*
|
|
119
|
+
* @see https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/device-profiles/
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ## Update default WARP settings
|
|
123
|
+
*
|
|
124
|
+
* Configure the default behavior for all devices
|
|
125
|
+
*
|
|
126
|
+
* const defaultProfile = await WarpDefaultProfile("default", {
|
|
127
|
+
* serviceModeV2: { mode: "warp" },
|
|
128
|
+
* autoConnect: 0,
|
|
129
|
+
* captivePortal: 180,
|
|
130
|
+
* supportUrl: "https://support.example.com"
|
|
131
|
+
* });
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ## Default profile with split tunnel
|
|
135
|
+
*
|
|
136
|
+
* Configure default split tunnel behavior for all devices
|
|
137
|
+
*
|
|
138
|
+
* const defaultWithSplitTunnel = await WarpDefaultProfile("default", {
|
|
139
|
+
* serviceModeV2: { mode: "warp" },
|
|
140
|
+
* splitTunnel: {
|
|
141
|
+
* mode: "exclude",
|
|
142
|
+
* entries: [
|
|
143
|
+
* { address: "10.0.0.0/8", description: "Internal network" },
|
|
144
|
+
* { address: "192.168.0.0/16", description: "Local network" }
|
|
145
|
+
* ]
|
|
146
|
+
* }
|
|
147
|
+
* });
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ## Locked default profile
|
|
151
|
+
*
|
|
152
|
+
* Create a locked-down default configuration
|
|
153
|
+
*
|
|
154
|
+
* const lockedDefault = await WarpDefaultProfile("default", {
|
|
155
|
+
* serviceModeV2: { mode: "warp" },
|
|
156
|
+
* switchLocked: true,
|
|
157
|
+
* allowedToLeave: false,
|
|
158
|
+
* disableAutoFallback: true,
|
|
159
|
+
* tunnelProtocol: "wireguard"
|
|
160
|
+
* });
|
|
161
|
+
*/
|
|
162
|
+
export const WarpDefaultProfile = Resource(
|
|
163
|
+
"cloudflare::WarpDefaultProfile",
|
|
164
|
+
async function (
|
|
165
|
+
this: Context<WarpDefaultProfile>,
|
|
166
|
+
id: string,
|
|
167
|
+
props: WarpDefaultProfileProps = {},
|
|
168
|
+
): Promise<WarpDefaultProfile> {
|
|
169
|
+
const api = await createCloudflareApi(props);
|
|
170
|
+
|
|
171
|
+
if (this.phase === "delete") {
|
|
172
|
+
// Default profile cannot be deleted, just remove from state
|
|
173
|
+
return this.destroy();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Update default profile (single PATCH endpoint)
|
|
177
|
+
await updateDefaultPolicy(api, props);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
...props,
|
|
181
|
+
modifiedAt: Date.now(),
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Internal API response type for default policy
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
interface CloudflareDefaultPolicyResponse {
|
|
191
|
+
device_settings?: {
|
|
192
|
+
service_mode_v2?: {
|
|
193
|
+
mode: string;
|
|
194
|
+
port?: number;
|
|
195
|
+
};
|
|
196
|
+
disable_auto_fallback?: boolean;
|
|
197
|
+
allow_mode_switch?: boolean;
|
|
198
|
+
switch_locked?: boolean;
|
|
199
|
+
tunnel_protocol?: string;
|
|
200
|
+
auto_connect?: number;
|
|
201
|
+
allowed_to_leave?: boolean;
|
|
202
|
+
captive_portal?: number;
|
|
203
|
+
support_url?: string;
|
|
204
|
+
exclude_office_ips?: boolean;
|
|
205
|
+
lan_allow_minutes?: number;
|
|
206
|
+
lan_allow_subnet_size?: number;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Keys with special handling (not simple 1:1 mapping)
|
|
211
|
+
type SpecialKeys = "serviceModeV2" | "splitTunnel" | "delete";
|
|
212
|
+
|
|
213
|
+
// Simple device setting keys that map directly to snake_case API fields
|
|
214
|
+
type SimpleDeviceSettingKey = Exclude<
|
|
215
|
+
keyof WarpDefaultProfileProps,
|
|
216
|
+
keyof CloudflareApiOptions | SpecialKeys
|
|
217
|
+
>;
|
|
218
|
+
|
|
219
|
+
// Type-safe mapping - TypeScript errors if any SimpleDeviceSettingKey is missing
|
|
220
|
+
const DEVICE_SETTINGS_MAP: Record<SimpleDeviceSettingKey, string> = {
|
|
221
|
+
disableAutoFallback: "disable_auto_fallback",
|
|
222
|
+
allowModeSwitch: "allow_mode_switch",
|
|
223
|
+
switchLocked: "switch_locked",
|
|
224
|
+
tunnelProtocol: "tunnel_protocol",
|
|
225
|
+
autoConnect: "auto_connect",
|
|
226
|
+
allowedToLeave: "allowed_to_leave",
|
|
227
|
+
captivePortal: "captive_portal",
|
|
228
|
+
supportUrl: "support_url",
|
|
229
|
+
excludeOfficeIps: "exclude_office_ips",
|
|
230
|
+
lanAllowMinutes: "lan_allow_minutes",
|
|
231
|
+
lanAllowSubnetSize: "lan_allow_subnet_size",
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
async function updateDefaultPolicy(
|
|
235
|
+
api: CloudflareApi,
|
|
236
|
+
props: WarpDefaultProfileProps,
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
// Build device settings object
|
|
239
|
+
const deviceSettings: Record<string, unknown> = {};
|
|
240
|
+
|
|
241
|
+
if (props.serviceModeV2) {
|
|
242
|
+
deviceSettings.service_mode_v2 = {
|
|
243
|
+
mode: props.serviceModeV2.mode,
|
|
244
|
+
...(props.serviceModeV2.port && { port: props.serviceModeV2.port }),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Apply all simple field mappings
|
|
249
|
+
for (const propKey of Object.keys(
|
|
250
|
+
DEVICE_SETTINGS_MAP,
|
|
251
|
+
) as SimpleDeviceSettingKey[]) {
|
|
252
|
+
deviceSettings[DEVICE_SETTINGS_MAP[propKey]] = props[propKey];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Split tunnel config is part of the body for default policy
|
|
256
|
+
if (props.splitTunnel) {
|
|
257
|
+
if (props.splitTunnel.mode === "include") {
|
|
258
|
+
deviceSettings.include = props.splitTunnel.entries.map((entry) => ({
|
|
259
|
+
address: entry.address,
|
|
260
|
+
...(entry.description && { description: entry.description }),
|
|
261
|
+
}));
|
|
262
|
+
} else {
|
|
263
|
+
deviceSettings.exclude = props.splitTunnel.entries.map((entry) => ({
|
|
264
|
+
address: entry.address,
|
|
265
|
+
...(entry.description && { description: entry.description }),
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const requestBody: any = {
|
|
271
|
+
...(Object.keys(deviceSettings).length > 0 && { ...deviceSettings }),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const response = await api.patch(
|
|
275
|
+
`/accounts/${api.accountId}/devices/policy`,
|
|
276
|
+
requestBody,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
await handleApiError(response, "update", "warp_default_profile", "default");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function updateDefaultSplitTunnel(
|
|
285
|
+
api: CloudflareApi,
|
|
286
|
+
config: SplitTunnelConfig,
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
const routes = config.entries.map((entry) => ({
|
|
289
|
+
address: entry.address,
|
|
290
|
+
...(entry.description && { description: entry.description }),
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
if (config.mode === "include") {
|
|
294
|
+
const response = await api.put(
|
|
295
|
+
`/accounts/${api.accountId}/devices/policies/default/includes`,
|
|
296
|
+
routes,
|
|
297
|
+
);
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
await handleApiError(
|
|
300
|
+
response,
|
|
301
|
+
"update split tunnel includes",
|
|
302
|
+
"warp_default_profile",
|
|
303
|
+
"default",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
const response = await api.put(
|
|
308
|
+
`/accounts/${api.accountId}/devices/policies/default/excludes`,
|
|
309
|
+
routes,
|
|
310
|
+
);
|
|
311
|
+
if (!response.ok) {
|
|
312
|
+
await handleApiError(
|
|
313
|
+
response,
|
|
314
|
+
"update split tunnel excludes",
|
|
315
|
+
"warp_default_profile",
|
|
316
|
+
"default",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|