alchemy 0.80.1 → 0.81.1

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.
Files changed (53) hide show
  1. package/bin/alchemy.js +1660 -1382
  2. package/bin/services/execute-alchemy.ts +18 -6
  3. package/lib/alchemy.js +1 -1
  4. package/lib/alchemy.js.map +1 -1
  5. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  6. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  7. package/lib/cloudflare/hyperdrive.d.ts +23 -5
  8. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  9. package/lib/cloudflare/hyperdrive.js.map +1 -1
  10. package/lib/cloudflare/index.d.ts +3 -0
  11. package/lib/cloudflare/index.d.ts.map +1 -1
  12. package/lib/cloudflare/index.js +3 -0
  13. package/lib/cloudflare/index.js.map +1 -1
  14. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  15. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +7 -0
  16. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  17. package/lib/cloudflare/queue.d.ts +0 -3
  18. package/lib/cloudflare/queue.d.ts.map +1 -1
  19. package/lib/cloudflare/queue.js +53 -31
  20. package/lib/cloudflare/queue.js.map +1 -1
  21. package/lib/cloudflare/redirect-rule.d.ts +12 -2
  22. package/lib/cloudflare/redirect-rule.d.ts.map +1 -1
  23. package/lib/cloudflare/redirect-rule.js +73 -98
  24. package/lib/cloudflare/redirect-rule.js.map +1 -1
  25. package/lib/cloudflare/tunnel-route.d.ts +172 -0
  26. package/lib/cloudflare/tunnel-route.d.ts.map +1 -0
  27. package/lib/cloudflare/tunnel-route.js +251 -0
  28. package/lib/cloudflare/tunnel-route.js.map +1 -0
  29. package/lib/cloudflare/warp-default-profile.d.ts +133 -0
  30. package/lib/cloudflare/warp-default-profile.d.ts.map +1 -0
  31. package/lib/cloudflare/warp-default-profile.js +138 -0
  32. package/lib/cloudflare/warp-default-profile.js.map +1 -0
  33. package/lib/cloudflare/warp-device-profile.d.ts +272 -0
  34. package/lib/cloudflare/warp-device-profile.d.ts.map +1 -0
  35. package/lib/cloudflare/warp-device-profile.js +291 -0
  36. package/lib/cloudflare/warp-device-profile.js.map +1 -0
  37. package/lib/scope.d.ts +1 -1
  38. package/lib/scope.d.ts.map +1 -1
  39. package/lib/scope.js +1 -1
  40. package/lib/scope.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/alchemy.ts +1 -1
  43. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  44. package/src/cloudflare/hyperdrive.ts +27 -5
  45. package/src/cloudflare/index.ts +3 -0
  46. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +11 -0
  47. package/src/cloudflare/queue.ts +97 -56
  48. package/src/cloudflare/redirect-rule.ts +129 -150
  49. package/src/cloudflare/tunnel-route.ts +495 -0
  50. package/src/cloudflare/warp-default-profile.ts +320 -0
  51. package/src/cloudflare/warp-device-profile.ts +616 -0
  52. package/src/scope.ts +2 -2
  53. package/workers/tunnel-proxy.js +1 -1
@@ -0,0 +1,251 @@
1
+ import { Resource, ResourceKind } from "../resource.js";
2
+ import { logger } from "../util/logger.js";
3
+ import { handleApiError } from "./api-error.js";
4
+ import { createCloudflareApi, } from "./api.js";
5
+ export function isTunnelRoute(resource) {
6
+ return resource?.[ResourceKind] === "cloudflare::TunnelRoute";
7
+ }
8
+ /**
9
+ * Creates and manages a Cloudflare Tunnel Route, which routes private network traffic
10
+ * through a Cloudflare Tunnel. This resource handles the route lifecycle (create, update, delete).
11
+ *
12
+ * @remarks
13
+ * Tunnel Routes enable private network access by routing CIDR ranges through Cloudflare Tunnels.
14
+ * This is commonly used for Zero Trust network access scenarios where you need to connect
15
+ * private networks to Cloudflare's edge.
16
+ *
17
+ * @see https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/private-net/
18
+ * @see https://developers.cloudflare.com/api/operations/zero-trust-networks-routes-create-a-tunnel-route
19
+ *
20
+ * @example
21
+ * // Create a basic tunnel route for a private network
22
+ * const tunnel = await Tunnel("my-tunnel", {
23
+ * name: "my-tunnel"
24
+ * });
25
+ *
26
+ * const route = await TunnelRoute("private-network", {
27
+ * network: "172.16.0.0/16",
28
+ * tunnel: tunnel
29
+ * });
30
+ *
31
+ * @example
32
+ * // Create a route with a comment and virtual network
33
+ * const route = await TunnelRoute("vpc-route", {
34
+ * network: "10.0.0.0/8",
35
+ * tunnel: "f70ff985-a4ef-4643-bbbc-4a0ed4fc8415",
36
+ * comment: "Main VPC network route",
37
+ * virtualNetworkId: "f70ff985-a4ef-4643-bbbc-4a0ed4fc8415"
38
+ * });
39
+ *
40
+ * @example
41
+ * // Adopt an existing route if it already exists
42
+ * const route = await TunnelRoute("existing-route", {
43
+ * network: "192.168.1.0/24",
44
+ * tunnel: tunnel,
45
+ * adopt: true,
46
+ * comment: "Updated comment"
47
+ * });
48
+ *
49
+ * @example
50
+ * // Create a route without deleting it when removed from Alchemy
51
+ * const route = await TunnelRoute("persistent-route", {
52
+ * network: "10.1.0.0/16",
53
+ * tunnel: tunnel,
54
+ * delete: false
55
+ * });
56
+ */
57
+ export const TunnelRoute = Resource("cloudflare::TunnelRoute", async function (id, props) {
58
+ // Create Cloudflare API client with automatic account discovery
59
+ const api = await createCloudflareApi(props);
60
+ // Resolve tunnel ID from either string or Tunnel resource
61
+ const tunnelId = typeof props.tunnel === "string" ? props.tunnel : props.tunnel.tunnelId;
62
+ const routeId = props.routeId || this.output?.id;
63
+ const adopt = props.adopt ?? this.scope.adopt;
64
+ if (this.phase === "delete") {
65
+ // For delete operations, check if the route ID exists in the output
66
+ if (routeId && props.delete !== false) {
67
+ await deleteRoute(api, routeId);
68
+ }
69
+ // Return destroyed state
70
+ return this.destroy();
71
+ }
72
+ // Check if network is being changed - network is immutable
73
+ if (this.phase === "update" &&
74
+ this.output?.network &&
75
+ this.output.network !== props.network) {
76
+ logger.log(`Network changed from '${this.output.network}' to '${props.network}', replacing route`);
77
+ this.replace(true);
78
+ }
79
+ // Check if tunnel is being changed - tunnel is immutable
80
+ if (this.phase === "update" &&
81
+ this.output?.tunnelId &&
82
+ this.output.tunnelId !== tunnelId) {
83
+ logger.log(`Tunnel changed from '${this.output.tunnelId}' to '${tunnelId}', replacing route`);
84
+ this.replace(true);
85
+ }
86
+ let routeData;
87
+ if (this.phase === "update" && routeId) {
88
+ // Update existing route (only comment and virtualNetworkId can be updated)
89
+ routeData = await updateRoute(api, routeId, {
90
+ comment: props.comment,
91
+ virtualNetworkId: props.virtualNetworkId,
92
+ });
93
+ }
94
+ else {
95
+ // Create new route
96
+ try {
97
+ routeData = await createRoute(api, {
98
+ network: props.network,
99
+ tunnelId,
100
+ comment: props.comment,
101
+ virtualNetworkId: props.virtualNetworkId,
102
+ });
103
+ }
104
+ catch (error) {
105
+ // Check if this is a "route already exists" error and adopt is enabled
106
+ if (adopt &&
107
+ error instanceof Error &&
108
+ (error.message.includes("already exists") ||
109
+ error.message.includes("duplicate") ||
110
+ error.status === 409)) {
111
+ logger.log(`Route for network '${props.network}' and tunnel '${tunnelId}' already exists, adopting it`);
112
+ // Find the existing route by network and tunnel
113
+ const existingRoute = await findRouteByNetworkAndTunnel(api, props.network, tunnelId);
114
+ if (!existingRoute) {
115
+ throw new Error(`Failed to find existing route for network '${props.network}' and tunnel '${tunnelId}' for adoption`);
116
+ }
117
+ routeData = existingRoute;
118
+ // Update comment/virtualNetworkId if provided
119
+ if (props.comment !== undefined ||
120
+ props.virtualNetworkId !== undefined) {
121
+ routeData = await updateRoute(api, existingRoute.id, {
122
+ comment: props.comment,
123
+ virtualNetworkId: props.virtualNetworkId,
124
+ });
125
+ }
126
+ }
127
+ else {
128
+ // Re-throw the error if adopt is false or it's not an "already exists" error
129
+ throw error;
130
+ }
131
+ }
132
+ }
133
+ // Transform API response to our interface
134
+ return {
135
+ id: routeData.id,
136
+ network: routeData.network,
137
+ tunnelId: routeData.tunnel_id,
138
+ comment: routeData.comment,
139
+ virtualNetworkId: routeData.virtual_network_id,
140
+ createdAt: routeData.created_at,
141
+ deletedAt: routeData.deleted_at,
142
+ type: "cloudflare::TunnelRoute",
143
+ };
144
+ });
145
+ /**
146
+ * Get tunnel route details
147
+ * @internal
148
+ */
149
+ export async function getTunnelRoute(api, routeId) {
150
+ const response = await api.get(`/accounts/${api.accountId}/teamnet/routes/${routeId}`);
151
+ if (!response.ok) {
152
+ await handleApiError(response, "get", "route", routeId);
153
+ }
154
+ const data = (await response.json());
155
+ return data.result;
156
+ }
157
+ /**
158
+ * List all tunnel routes with pagination support
159
+ * @internal
160
+ */
161
+ export async function listTunnelRoutes(api, options) {
162
+ const routes = [];
163
+ let page = 1;
164
+ const perPage = 100; // Maximum allowed by API
165
+ let hasMorePages = true;
166
+ const limit = options?.limit;
167
+ while (hasMorePages) {
168
+ const params = new URLSearchParams({
169
+ page: page.toString(),
170
+ per_page: perPage.toString(),
171
+ });
172
+ const response = await api.get(`/accounts/${api.accountId}/teamnet/routes?${params.toString()}`);
173
+ if (!response.ok) {
174
+ await handleApiError(response, "list", "route", "all");
175
+ }
176
+ const data = (await response.json());
177
+ routes.push(...data.result);
178
+ const resultInfo = data.result_info;
179
+ // Check if we've reached the limit
180
+ if (limit && routes.length >= limit) {
181
+ return routes.slice(0, limit);
182
+ }
183
+ // Check if we've seen all pages
184
+ hasMorePages =
185
+ resultInfo.page * resultInfo.per_page < resultInfo.total_count;
186
+ page++;
187
+ }
188
+ return routes;
189
+ }
190
+ /**
191
+ * Find a route by network and tunnel ID
192
+ * @internal
193
+ */
194
+ export async function findRouteByNetworkAndTunnel(api, network, tunnelId) {
195
+ const routes = await listTunnelRoutes(api);
196
+ // Look for a route with matching network and tunnel_id
197
+ const match = routes.find((route) => route.network === network && route.tunnel_id === tunnelId);
198
+ return match || null;
199
+ }
200
+ /**
201
+ * Delete a route
202
+ * @internal
203
+ */
204
+ async function deleteRoute(api, routeId) {
205
+ const response = await api.delete(`/accounts/${api.accountId}/teamnet/routes/${routeId}`);
206
+ if (!response.ok && response.status !== 404) {
207
+ await handleApiError(response, "delete", "route", routeId);
208
+ }
209
+ }
210
+ /**
211
+ * Create a new route
212
+ * @internal
213
+ */
214
+ async function createRoute(api, props) {
215
+ const payload = {
216
+ network: props.network,
217
+ tunnel_id: props.tunnelId,
218
+ };
219
+ if (props.comment !== undefined) {
220
+ payload.comment = props.comment;
221
+ }
222
+ if (props.virtualNetworkId !== undefined) {
223
+ payload.virtual_network_id = props.virtualNetworkId;
224
+ }
225
+ const response = await api.post(`/accounts/${api.accountId}/teamnet/routes`, payload);
226
+ if (!response.ok) {
227
+ await handleApiError(response, "create", "route", `${props.network} -> ${props.tunnelId}`);
228
+ }
229
+ const data = (await response.json());
230
+ return data.result;
231
+ }
232
+ /**
233
+ * Update route configuration
234
+ * @internal
235
+ */
236
+ async function updateRoute(api, routeId, props) {
237
+ const payload = {};
238
+ if (props.comment !== undefined) {
239
+ payload.comment = props.comment;
240
+ }
241
+ if (props.virtualNetworkId !== undefined) {
242
+ payload.virtual_network_id = props.virtualNetworkId;
243
+ }
244
+ const response = await api.patch(`/accounts/${api.accountId}/teamnet/routes/${routeId}`, payload);
245
+ if (!response.ok) {
246
+ await handleApiError(response, "update", "route", routeId);
247
+ }
248
+ const data = (await response.json());
249
+ return data.result;
250
+ }
251
+ //# sourceMappingURL=tunnel-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-route.js","sourceRoot":"","sources":["../../src/cloudflare/tunnel-route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ import type { Context } from "../context.ts";
2
+ import { type CloudflareApiOptions } from "./api.ts";
3
+ import type { ServiceModeV2, SplitTunnelConfig } from "./warp-device-profile.ts";
4
+ /**
5
+ * Properties for updating the WARP Default Profile
6
+ */
7
+ export interface WarpDefaultProfileProps extends CloudflareApiOptions {
8
+ /**
9
+ * Service mode configuration for WARP client
10
+ */
11
+ serviceModeV2?: ServiceModeV2;
12
+ /**
13
+ * Disable automatic fallback to direct connection if tunnel fails
14
+ */
15
+ disableAutoFallback?: boolean;
16
+ /**
17
+ * Allow users to manually switch WARP modes
18
+ */
19
+ allowModeSwitch?: boolean;
20
+ /**
21
+ * Lock the WARP toggle switch (users cannot change it)
22
+ */
23
+ switchLocked?: boolean;
24
+ /**
25
+ * Tunnel protocol to use
26
+ */
27
+ tunnelProtocol?: "wireguard" | "masque";
28
+ /**
29
+ * Auto-connect timeout in seconds
30
+ * Set to 0 to disable auto-connect
31
+ */
32
+ autoConnect?: number;
33
+ /**
34
+ * Allow users to disconnect from WARP
35
+ */
36
+ allowedToLeave?: boolean;
37
+ /**
38
+ * Captive portal timeout in seconds
39
+ * Time before showing captive portal
40
+ */
41
+ captivePortal?: number;
42
+ /**
43
+ * Support URL for feedback button in WARP client
44
+ */
45
+ supportUrl?: string;
46
+ /**
47
+ * Exclude office IPs from WARP tunnel
48
+ */
49
+ excludeOfficeIps?: boolean;
50
+ /**
51
+ * LAN allow duration in minutes
52
+ */
53
+ lanAllowMinutes?: number;
54
+ /**
55
+ * LAN subnet size for local network access
56
+ */
57
+ lanAllowSubnetSize?: number;
58
+ /**
59
+ * Split tunnel configuration
60
+ * Controls which routes bypass or use the WARP tunnel
61
+ */
62
+ splitTunnel?: SplitTunnelConfig;
63
+ /**
64
+ * Whether to delete the default profile settings when removed from Alchemy
65
+ * Note: This does not actually delete the default profile (which always exists),
66
+ * but removes it from Alchemy state management
67
+ *
68
+ * @default false
69
+ */
70
+ delete?: boolean;
71
+ }
72
+ export declare function isWarpDefaultProfile(resource: any): resource is WarpDefaultProfile;
73
+ /**
74
+ * Output returned after WARP Default Profile update
75
+ */
76
+ export type WarpDefaultProfile = WarpDefaultProfileProps & {
77
+ /**
78
+ * Time at which the profile was last modified
79
+ */
80
+ modifiedAt: number;
81
+ };
82
+ /**
83
+ * Manages the Cloudflare WARP Default Device Profile, which defines the
84
+ * account-wide default WARP client settings that apply when no custom profile matches.
85
+ *
86
+ * The default profile always exists and cannot be deleted. This resource allows you
87
+ * to update its settings programmatically.
88
+ *
89
+ * @see https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/device-profiles/
90
+ *
91
+ * @example
92
+ * ## Update default WARP settings
93
+ *
94
+ * Configure the default behavior for all devices
95
+ *
96
+ * const defaultProfile = await WarpDefaultProfile("default", {
97
+ * serviceModeV2: { mode: "warp" },
98
+ * autoConnect: 0,
99
+ * captivePortal: 180,
100
+ * supportUrl: "https://support.example.com"
101
+ * });
102
+ *
103
+ * @example
104
+ * ## Default profile with split tunnel
105
+ *
106
+ * Configure default split tunnel behavior for all devices
107
+ *
108
+ * const defaultWithSplitTunnel = await WarpDefaultProfile("default", {
109
+ * serviceModeV2: { mode: "warp" },
110
+ * splitTunnel: {
111
+ * mode: "exclude",
112
+ * entries: [
113
+ * { address: "10.0.0.0/8", description: "Internal network" },
114
+ * { address: "192.168.0.0/16", description: "Local network" }
115
+ * ]
116
+ * }
117
+ * });
118
+ *
119
+ * @example
120
+ * ## Locked default profile
121
+ *
122
+ * Create a locked-down default configuration
123
+ *
124
+ * const lockedDefault = await WarpDefaultProfile("default", {
125
+ * serviceModeV2: { mode: "warp" },
126
+ * switchLocked: true,
127
+ * allowedToLeave: false,
128
+ * disableAutoFallback: true,
129
+ * tunnelProtocol: "wireguard"
130
+ * });
131
+ */
132
+ export declare const WarpDefaultProfile: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<WarpDefaultProfile>, id: string, props?: WarpDefaultProfileProps) => Promise<WarpDefaultProfile>);
133
+ //# sourceMappingURL=warp-default-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warp-default-profile.d.ts","sourceRoot":"","sources":["../../src/cloudflare/warp-default-profile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAElC;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE;;OAEG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,cAAc,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC;IAExC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,GAAG,GACZ,QAAQ,IAAI,kBAAkB,CAEhC;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,uBAAuB,GAAG;IACzD;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,eAAO,MAAM,kBAAkB,yFAGrB,OAAO,CAAC,kBAAkB,CAAC,MAC7B,MAAM,UACH,uBAAuB,KAC7B,OAAO,CAAC,kBAAkB,CAAC,CAgB/B,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { Resource, ResourceKind } from "../resource.js";
2
+ import { handleApiError } from "./api-error.js";
3
+ import { createCloudflareApi, } from "./api.js";
4
+ export function isWarpDefaultProfile(resource) {
5
+ return resource?.[ResourceKind] === "cloudflare::WarpDefaultProfile";
6
+ }
7
+ /**
8
+ * Manages the Cloudflare WARP Default Device Profile, which defines the
9
+ * account-wide default WARP client settings that apply when no custom profile matches.
10
+ *
11
+ * The default profile always exists and cannot be deleted. This resource allows you
12
+ * to update its settings programmatically.
13
+ *
14
+ * @see https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/device-profiles/
15
+ *
16
+ * @example
17
+ * ## Update default WARP settings
18
+ *
19
+ * Configure the default behavior for all devices
20
+ *
21
+ * const defaultProfile = await WarpDefaultProfile("default", {
22
+ * serviceModeV2: { mode: "warp" },
23
+ * autoConnect: 0,
24
+ * captivePortal: 180,
25
+ * supportUrl: "https://support.example.com"
26
+ * });
27
+ *
28
+ * @example
29
+ * ## Default profile with split tunnel
30
+ *
31
+ * Configure default split tunnel behavior for all devices
32
+ *
33
+ * const defaultWithSplitTunnel = await WarpDefaultProfile("default", {
34
+ * serviceModeV2: { mode: "warp" },
35
+ * splitTunnel: {
36
+ * mode: "exclude",
37
+ * entries: [
38
+ * { address: "10.0.0.0/8", description: "Internal network" },
39
+ * { address: "192.168.0.0/16", description: "Local network" }
40
+ * ]
41
+ * }
42
+ * });
43
+ *
44
+ * @example
45
+ * ## Locked default profile
46
+ *
47
+ * Create a locked-down default configuration
48
+ *
49
+ * const lockedDefault = await WarpDefaultProfile("default", {
50
+ * serviceModeV2: { mode: "warp" },
51
+ * switchLocked: true,
52
+ * allowedToLeave: false,
53
+ * disableAutoFallback: true,
54
+ * tunnelProtocol: "wireguard"
55
+ * });
56
+ */
57
+ export const WarpDefaultProfile = Resource("cloudflare::WarpDefaultProfile", async function (id, props = {}) {
58
+ const api = await createCloudflareApi(props);
59
+ if (this.phase === "delete") {
60
+ // Default profile cannot be deleted, just remove from state
61
+ return this.destroy();
62
+ }
63
+ // Update default profile (single PATCH endpoint)
64
+ await updateDefaultPolicy(api, props);
65
+ return {
66
+ ...props,
67
+ modifiedAt: Date.now(),
68
+ };
69
+ });
70
+ // Type-safe mapping - TypeScript errors if any SimpleDeviceSettingKey is missing
71
+ const DEVICE_SETTINGS_MAP = {
72
+ disableAutoFallback: "disable_auto_fallback",
73
+ allowModeSwitch: "allow_mode_switch",
74
+ switchLocked: "switch_locked",
75
+ tunnelProtocol: "tunnel_protocol",
76
+ autoConnect: "auto_connect",
77
+ allowedToLeave: "allowed_to_leave",
78
+ captivePortal: "captive_portal",
79
+ supportUrl: "support_url",
80
+ excludeOfficeIps: "exclude_office_ips",
81
+ lanAllowMinutes: "lan_allow_minutes",
82
+ lanAllowSubnetSize: "lan_allow_subnet_size",
83
+ };
84
+ async function updateDefaultPolicy(api, props) {
85
+ // Build device settings object
86
+ const deviceSettings = {};
87
+ if (props.serviceModeV2) {
88
+ deviceSettings.service_mode_v2 = {
89
+ mode: props.serviceModeV2.mode,
90
+ ...(props.serviceModeV2.port && { port: props.serviceModeV2.port }),
91
+ };
92
+ }
93
+ // Apply all simple field mappings
94
+ for (const propKey of Object.keys(DEVICE_SETTINGS_MAP)) {
95
+ deviceSettings[DEVICE_SETTINGS_MAP[propKey]] = props[propKey];
96
+ }
97
+ // Split tunnel config is part of the body for default policy
98
+ if (props.splitTunnel) {
99
+ if (props.splitTunnel.mode === "include") {
100
+ deviceSettings.include = props.splitTunnel.entries.map((entry) => ({
101
+ address: entry.address,
102
+ ...(entry.description && { description: entry.description }),
103
+ }));
104
+ }
105
+ else {
106
+ deviceSettings.exclude = props.splitTunnel.entries.map((entry) => ({
107
+ address: entry.address,
108
+ ...(entry.description && { description: entry.description }),
109
+ }));
110
+ }
111
+ }
112
+ const requestBody = {
113
+ ...(Object.keys(deviceSettings).length > 0 && { ...deviceSettings }),
114
+ };
115
+ const response = await api.patch(`/accounts/${api.accountId}/devices/policy`, requestBody);
116
+ if (!response.ok) {
117
+ await handleApiError(response, "update", "warp_default_profile", "default");
118
+ }
119
+ }
120
+ async function updateDefaultSplitTunnel(api, config) {
121
+ const routes = config.entries.map((entry) => ({
122
+ address: entry.address,
123
+ ...(entry.description && { description: entry.description }),
124
+ }));
125
+ if (config.mode === "include") {
126
+ const response = await api.put(`/accounts/${api.accountId}/devices/policies/default/includes`, routes);
127
+ if (!response.ok) {
128
+ await handleApiError(response, "update split tunnel includes", "warp_default_profile", "default");
129
+ }
130
+ }
131
+ else {
132
+ const response = await api.put(`/accounts/${api.accountId}/devices/policies/default/excludes`, routes);
133
+ if (!response.ok) {
134
+ await handleApiError(response, "update split tunnel excludes", "warp_default_profile", "default");
135
+ }
136
+ }
137
+ }
138
+ //# sourceMappingURL=warp-default-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"warp-default-profile.js","sourceRoot":"","sources":["../../src/cloudflare/warp-default-profile.ts"],"names":[],"mappings":"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"}