alchemy 0.3.1 → 0.4.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.
Files changed (104) hide show
  1. package/README.md +8 -3
  2. package/lib/ai/document.d.ts +12 -1
  3. package/lib/ai/document.js +10 -1
  4. package/lib/ai/index.d.ts +0 -2
  5. package/lib/ai/index.js +0 -2
  6. package/lib/alchemy.d.ts +5 -0
  7. package/lib/alchemy.js +44 -1
  8. package/lib/apply.js +17 -10
  9. package/lib/aws/account-id.d.ts +4 -1
  10. package/lib/aws/account-id.js +1 -1
  11. package/lib/aws/credentials.d.ts +5 -0
  12. package/lib/aws/credentials.js +0 -0
  13. package/lib/aws/oidc/oidc-provider.js +2 -2
  14. package/lib/aws/role.js +35 -23
  15. package/lib/cloudflare/account-api-token.d.ts +168 -0
  16. package/lib/cloudflare/account-api-token.js +179 -0
  17. package/lib/cloudflare/account-id.d.ts +1 -0
  18. package/lib/cloudflare/account-id.js +0 -0
  19. package/lib/cloudflare/api.d.ts +38 -1
  20. package/lib/cloudflare/api.js +55 -4
  21. package/lib/cloudflare/bucket.d.ts +118 -3
  22. package/lib/cloudflare/bucket.js +269 -89
  23. package/lib/cloudflare/custom-domain.d.ts +75 -0
  24. package/lib/cloudflare/custom-domain.js +154 -0
  25. package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
  26. package/lib/cloudflare/index.d.ts +5 -1
  27. package/lib/cloudflare/index.js +5 -1
  28. package/lib/cloudflare/permission-groups.d.ts +78 -0
  29. package/lib/cloudflare/permission-groups.js +48 -0
  30. package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
  31. package/lib/cloudflare/r2-rest-state-store.js +3 -2
  32. package/lib/cloudflare/static-site.d.ts +17 -18
  33. package/lib/cloudflare/static-site.js +19 -10
  34. package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
  35. package/lib/{util/encrypt.js → encrypt.js} +1 -1
  36. package/lib/fs/file-system-state-store.js +1 -1
  37. package/lib/fs/file.d.ts +18 -0
  38. package/lib/fs/file.js +6 -0
  39. package/lib/internal/{providers.d.ts → docs/providers.d.ts} +10 -4
  40. package/lib/internal/docs/providers.js +196 -0
  41. package/lib/secret.d.ts +3 -0
  42. package/lib/secret.js +13 -0
  43. package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
  44. package/lib/{util/serde.js → serde.js} +12 -4
  45. package/lib/test/bun.js +3 -2
  46. package/lib/util/sha256.d.ts +1 -0
  47. package/lib/util/sha256.js +4 -0
  48. package/lib/web/vitepress/config.d.ts +2 -1
  49. package/lib/web/vitepress/config.js +1 -0
  50. package/lib/web/vitepress/index.d.ts +1 -0
  51. package/lib/web/vitepress/index.js +1 -0
  52. package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
  53. package/lib/web/vitepress/process-front-matter-files.js +68 -0
  54. package/lib/web/vitepress/vitepress.js +3 -2
  55. package/package.json +8 -4
  56. package/src/ai/document.ts +26 -2
  57. package/src/ai/index.ts +0 -2
  58. package/src/alchemy.ts +56 -2
  59. package/src/apply.ts +21 -20
  60. package/src/aws/account-id.ts +6 -2
  61. package/src/aws/credentials.ts +6 -0
  62. package/src/aws/oidc/oidc-provider.ts +17 -17
  63. package/src/aws/role.ts +68 -54
  64. package/src/cloudflare/account-api-token.ts +365 -0
  65. package/src/cloudflare/account-id.ts +0 -0
  66. package/src/cloudflare/api.ts +88 -17
  67. package/src/cloudflare/bucket.ts +493 -133
  68. package/src/cloudflare/custom-domain.ts +318 -0
  69. package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
  70. package/src/cloudflare/index.ts +5 -1
  71. package/src/cloudflare/permission-groups.ts +137 -0
  72. package/src/cloudflare/r2-rest-state-store.ts +14 -13
  73. package/src/cloudflare/static-site.ts +20 -32
  74. package/src/dns/import-dns.ts +4 -4
  75. package/src/{util/encrypt.ts → encrypt.ts} +7 -10
  76. package/src/fs/file-system-state-store.ts +5 -5
  77. package/src/fs/file.ts +29 -0
  78. package/src/internal/docs/providers.ts +281 -0
  79. package/src/secret.ts +16 -0
  80. package/src/{util/serde.ts → serde.ts} +16 -10
  81. package/src/test/bun.ts +8 -7
  82. package/src/util/sha256.ts +5 -0
  83. package/src/web/vitepress/config.ts +3 -1
  84. package/src/web/vitepress/index.ts +1 -0
  85. package/src/web/vitepress/process-front-matter-files.ts +98 -0
  86. package/src/web/vitepress/vitepress.ts +3 -2
  87. package/lib/ai/approve.d.ts +0 -99
  88. package/lib/ai/approve.js +0 -76
  89. package/lib/ai/review.d.ts +0 -122
  90. package/lib/ai/review.js +0 -101
  91. package/lib/internal/getting-started.d.ts +0 -21
  92. package/lib/internal/getting-started.js +0 -87
  93. package/lib/internal/index.d.ts +0 -3
  94. package/lib/internal/index.js +0 -3
  95. package/lib/internal/providers.js +0 -172
  96. package/lib/internal/tutorial.d.ts +0 -104
  97. package/lib/internal/tutorial.js +0 -251
  98. package/src/ai/approve.ts +0 -163
  99. package/src/ai/review.ts +0 -213
  100. package/src/internal/getting-started.ts +0 -115
  101. package/src/internal/index.ts +0 -3
  102. package/src/internal/providers.ts +0 -241
  103. package/src/internal/tutorial.ts +0 -392
  104. /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
package/src/aws/role.ts CHANGED
@@ -219,58 +219,72 @@ export const Role = Resource(
219
219
  async function (
220
220
  this: Context<Role>,
221
221
  id: string,
222
- props: RoleProps,
222
+ props: RoleProps
223
223
  ): Promise<Role> {
224
224
  const client = new IAMClient({});
225
225
 
226
226
  if (this.phase === "delete") {
227
- // Delete any inline policies first
228
- if (props.policies) {
229
- for (const policy of props.policies) {
230
- await ignore(NoSuchEntityException.name, () =>
231
- client.send(
232
- new DeleteRolePolicyCommand({
233
- RoleName: props.roleName,
234
- PolicyName: policy.policyName,
235
- }),
236
- ),
237
- );
238
- }
239
- }
240
-
241
- // We need to detach managed policies before deleting the role
242
- // First, get all attached policies
243
227
  try {
244
- const attachedPoliciesResponse = await client.send(
245
- new ListAttachedRolePoliciesCommand({
246
- RoleName: props.roleName,
247
- }),
248
- );
228
+ // Delete any inline policies first
229
+ if (props.policies) {
230
+ for (const policy of props.policies) {
231
+ await ignore(NoSuchEntityException.name, () =>
232
+ client.send(
233
+ new DeleteRolePolicyCommand({
234
+ RoleName: props.roleName,
235
+ PolicyName: policy.policyName,
236
+ })
237
+ )
238
+ );
239
+ }
240
+ }
249
241
 
250
- // Detach all managed policies
251
- const attachedPolicies =
252
- attachedPoliciesResponse.AttachedPolicies || [];
253
- for (const policy of attachedPolicies) {
254
- await client.send(
255
- new DetachRolePolicyCommand({
242
+ // We need to detach managed policies before deleting the role
243
+ // First, get all attached policies
244
+ try {
245
+ const attachedPoliciesResponse = await client.send(
246
+ new ListAttachedRolePoliciesCommand({
256
247
  RoleName: props.roleName,
257
- PolicyArn: policy.PolicyArn!,
258
- }),
248
+ })
259
249
  );
250
+
251
+ // Detach all managed policies
252
+ const attachedPolicies =
253
+ attachedPoliciesResponse.AttachedPolicies || [];
254
+ for (const policy of attachedPolicies) {
255
+ await ignore(NoSuchEntityException.name, () =>
256
+ client.send(
257
+ new DetachRolePolicyCommand({
258
+ RoleName: props.roleName,
259
+ PolicyArn: policy.PolicyArn!,
260
+ })
261
+ )
262
+ );
263
+ }
264
+ } catch (error: any) {
265
+ if (error.name !== NoSuchEntityException.name) {
266
+ throw error;
267
+ }
268
+ // Role doesn't exist, no need to continue with detaching policies
260
269
  }
270
+
271
+ // Try to delete the role, ignoring if it doesn't exist
272
+ await ignore(NoSuchEntityException.name, () =>
273
+ client.send(
274
+ new DeleteRoleCommand({
275
+ RoleName: props.roleName,
276
+ })
277
+ )
278
+ );
261
279
  } catch (error: any) {
280
+ // If we get any other error besides NoSuchEntityException, log it but don't fail
281
+ // This ensures the resource is still marked as destroyed
262
282
  if (error.name !== NoSuchEntityException.name) {
263
- throw error;
283
+ console.error(`Error deleting role ${props.roleName}:`, error);
264
284
  }
265
285
  }
266
286
 
267
- await ignore(NoSuchEntityException.name, () =>
268
- client.send(
269
- new DeleteRoleCommand({
270
- RoleName: props.roleName,
271
- }),
272
- ),
273
- );
287
+ // Always return destroyed state regardless of any errors
274
288
  return this.destroy();
275
289
  }
276
290
 
@@ -302,7 +316,7 @@ export const Role = Resource(
302
316
  Value: this.id,
303
317
  },
304
318
  ],
305
- }),
319
+ })
306
320
  );
307
321
  }
308
322
  } catch (error: any) {
@@ -314,7 +328,7 @@ export const Role = Resource(
314
328
  const existingRole = await client.send(
315
329
  new GetRoleCommand({
316
330
  RoleName: props.roleName,
317
- }),
331
+ })
318
332
  );
319
333
  const roleTags =
320
334
  existingRole.Role?.Tags?.reduce(
@@ -322,7 +336,7 @@ export const Role = Resource(
322
336
  acc[tag.Key!] = tag.Value!;
323
337
  return acc;
324
338
  },
325
- {} as Record<string, string>,
339
+ {} as Record<string, string>
326
340
  ) || {};
327
341
 
328
342
  if (
@@ -340,7 +354,7 @@ export const Role = Resource(
340
354
  role = await client.send(
341
355
  new GetRoleCommand({
342
356
  RoleName: props.roleName,
343
- }),
357
+ })
344
358
  );
345
359
 
346
360
  // Update assume role policy if it changed
@@ -349,7 +363,7 @@ export const Role = Resource(
349
363
  new UpdateAssumeRolePolicyCommand({
350
364
  RoleName: props.roleName,
351
365
  PolicyDocument: assumeRolePolicyDocument,
352
- }),
366
+ })
353
367
  );
354
368
  }
355
369
 
@@ -363,7 +377,7 @@ export const Role = Resource(
363
377
  RoleName: props.roleName,
364
378
  Description: props.description,
365
379
  MaxSessionDuration: props.maxSessionDuration,
366
- }),
380
+ })
367
381
  );
368
382
  }
369
383
 
@@ -381,7 +395,7 @@ export const Role = Resource(
381
395
  new TagRoleCommand({
382
396
  RoleName: props.roleName,
383
397
  Tags: tags,
384
- }),
398
+ })
385
399
  );
386
400
 
387
401
  // Handle policy changes
@@ -393,7 +407,7 @@ export const Role = Resource(
393
407
  for (const oldPolicy of previousPolicies) {
394
408
  if (
395
409
  !currentPolicies.some(
396
- (p: { policyName: string }) => p.policyName === oldPolicy.policyName,
410
+ (p: { policyName: string }) => p.policyName === oldPolicy.policyName
397
411
  )
398
412
  ) {
399
413
  await ignore(NoSuchEntityException.name, () =>
@@ -401,8 +415,8 @@ export const Role = Resource(
401
415
  new DeleteRolePolicyCommand({
402
416
  RoleName: props.roleName,
403
417
  PolicyName: oldPolicy.policyName,
404
- }),
405
- ),
418
+ })
419
+ )
406
420
  );
407
421
  }
408
422
  }
@@ -410,7 +424,7 @@ export const Role = Resource(
410
424
  // Update or create policies
411
425
  for (const policy of currentPolicies) {
412
426
  const oldPolicy = previousPolicies.find(
413
- (p) => p.policyName === policy.policyName,
427
+ (p) => p.policyName === policy.policyName
414
428
  );
415
429
  if (
416
430
  !oldPolicy ||
@@ -422,7 +436,7 @@ export const Role = Resource(
422
436
  RoleName: props.roleName,
423
437
  PolicyName: policy.policyName,
424
438
  PolicyDocument: JSON.stringify(policy.policyDocument),
425
- }),
439
+ })
426
440
  );
427
441
  }
428
442
  }
@@ -432,7 +446,7 @@ export const Role = Resource(
432
446
  const attachedPoliciesResponse = await client.send(
433
447
  new ListAttachedRolePoliciesCommand({
434
448
  RoleName: props.roleName,
435
- }),
449
+ })
436
450
  );
437
451
 
438
452
  const currentAttachedPolicies =
@@ -450,7 +464,7 @@ export const Role = Resource(
450
464
  new DetachRolePolicyCommand({
451
465
  RoleName: props.roleName,
452
466
  PolicyArn: policyArn,
453
- }),
467
+ })
454
468
  );
455
469
  }
456
470
  }
@@ -462,7 +476,7 @@ export const Role = Resource(
462
476
  new AttachRolePolicyCommand({
463
477
  RoleName: props.roleName,
464
478
  PolicyArn: policyArn,
465
- }),
479
+ })
466
480
  );
467
481
  }
468
482
  }
@@ -479,5 +493,5 @@ export const Role = Resource(
479
493
  roleName: role.Role.RoleName ?? props.roleName,
480
494
  createDate: role.Role.CreateDate!,
481
495
  });
482
- },
496
+ }
483
497
  );
@@ -0,0 +1,365 @@
1
+ import { alchemy } from "../alchemy";
2
+ import type { Context } from "../context";
3
+ import { Resource } from "../resource";
4
+ import { Secret } from "../secret";
5
+ import { sha256 } from "../util/sha256";
6
+ import { createCloudflareApi } from "./api";
7
+
8
+ /**
9
+ * Permission group for a token policy
10
+ */
11
+ export interface TokenPolicyPermissionGroup {
12
+ /**
13
+ * ID of the permission group
14
+ */
15
+ id: string;
16
+
17
+ /**
18
+ * Optional metadata for the permission group
19
+ */
20
+ meta?: Record<string, any>;
21
+ }
22
+
23
+ /**
24
+ * Policy that defines what the token can access
25
+ */
26
+ export interface TokenPolicy {
27
+ /**
28
+ * Effect of the policy
29
+ */
30
+ effect: "allow" | "deny";
31
+
32
+ /**
33
+ * Permission groups to include in the policy
34
+ */
35
+ permissionGroups: TokenPolicyPermissionGroup[];
36
+
37
+ /**
38
+ * Resources the policy applies to
39
+ */
40
+ resources: Record<string, string>;
41
+ }
42
+
43
+ /**
44
+ * Condition for token usage (e.g., IP restrictions)
45
+ */
46
+ export interface TokenCondition {
47
+ /**
48
+ * IP address conditions
49
+ */
50
+ requestIp?: {
51
+ /**
52
+ * IP ranges to allow
53
+ */
54
+ in?: string[];
55
+
56
+ /**
57
+ * IP ranges to deny
58
+ */
59
+ notIn?: string[];
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Properties for creating or updating an Account API Token
65
+ */
66
+ export interface AccountApiTokenProps {
67
+ /**
68
+ * Name of the token
69
+ */
70
+ name: string;
71
+
72
+ /**
73
+ * Policies that define what the token can access
74
+ */
75
+ policies: TokenPolicy[];
76
+
77
+ /**
78
+ * Optional expiration date for the token (ISO format)
79
+ */
80
+ expiresOn?: string;
81
+
82
+ /**
83
+ * Optional "not before" date (token is not valid before this date) (ISO format)
84
+ */
85
+ notBefore?: string;
86
+
87
+ /**
88
+ * Optional conditions for token use (like IP restrictions)
89
+ */
90
+ condition?: TokenCondition;
91
+ }
92
+
93
+ /**
94
+ * Cloudflare API token format as returned by the API
95
+ */
96
+ interface CloudflareApiToken {
97
+ id: string;
98
+ name: string;
99
+ status: string;
100
+ policies: {
101
+ effect: "allow" | "deny";
102
+ permission_groups: {
103
+ id: string;
104
+ meta: Record<string, any>;
105
+ }[];
106
+ resources: Record<string, string>;
107
+ }[];
108
+ expires_on?: string;
109
+ not_before?: string;
110
+ condition?: {
111
+ request_ip?: {
112
+ in?: string[];
113
+ not_in?: string[];
114
+ };
115
+ };
116
+ value?: string;
117
+ }
118
+
119
+ /**
120
+ * Output returned after Account API Token creation/update
121
+ */
122
+ export interface AccountApiToken
123
+ extends Resource<"cloudflare::AccountApiToken">,
124
+ AccountApiTokenProps {
125
+ /**
126
+ * The ID of the token
127
+ *
128
+ * Equiv. to ACCESS_KEY_ID
129
+ */
130
+ id: string;
131
+
132
+ /**
133
+ * Status of the token
134
+ */
135
+ status: string;
136
+
137
+ /**
138
+ * Actual token value (only available on creation)
139
+ * Stored as a Secret for security
140
+ *
141
+ * Equiv. to SECRET_ACCESS_KEY
142
+ */
143
+ value?: Secret;
144
+
145
+ /**
146
+ * Access key ID for the token
147
+ *
148
+ * An alias of {@link id}
149
+ */
150
+ accessKeyId: string;
151
+
152
+ /**
153
+ * Secret access key for the token
154
+ *
155
+ * The SHA-256 hash of the token {@link value}
156
+ *
157
+ * @see https://developers.cloudflare.com/r2/api/tokens/#get-s3-api-credentials-from-an-api-token
158
+ */
159
+ secretAccessKey: string;
160
+ }
161
+
162
+ /**
163
+ * Creates a Cloudflare Account API Token with specified permissions.
164
+ *
165
+ * @see https://developers.cloudflare.com/api/resources/accounts/subresources/tokens/methods/create/
166
+ *
167
+ * @example
168
+ * // First, fetch all permission groups
169
+ * const permissions = await PermissionGroups("cloudflare-permissions", {
170
+ * accountId: cfAccountId,
171
+ * });
172
+ *
173
+ * // Create a token with read-only permissions for specific zones
174
+ * const readOnlyToken = await AccountApiToken("readonly-token", {
175
+ * name: "Readonly Zone Token",
176
+ * policies: [
177
+ * {
178
+ * effect: "allow",
179
+ * permissionGroups: [
180
+ * { id: permissions["Zone Read"].id },
181
+ * { id: permissions["Analytics Read"].id }
182
+ * ],
183
+ * resources: {
184
+ * "com.cloudflare.api.account.zone.22b1de5f1c0e4b3ea97bb1e963b06a43": "*",
185
+ * "com.cloudflare.api.account.zone.eb78d65290b24279ba6f44721b3ea3c4": "*"
186
+ * }
187
+ * }
188
+ * ],
189
+ * expiresOn: "2024-12-31T23:59:59Z"
190
+ * });
191
+ *
192
+ * @example
193
+ * // Create a token with time and IP restrictions
194
+ * const restrictedToken = await AccountApiToken("restricted-token", {
195
+ * name: "Restricted Access Token",
196
+ * policies: [
197
+ * {
198
+ * effect: "allow",
199
+ * permissionGroups: [
200
+ * { id: permissions["Worker Routes Edit"].id }
201
+ * ],
202
+ * resources: {
203
+ * "com.cloudflare.api.account.worker.route.*": "*"
204
+ * }
205
+ * }
206
+ * ],
207
+ * notBefore: "2023-01-01T00:00:00Z",
208
+ * expiresOn: "2023-12-31T23:59:59Z",
209
+ * condition: {
210
+ * requestIp: {
211
+ * in: ["192.168.1.0/24", "10.0.0.0/8"],
212
+ * notIn: ["192.168.1.100/32"]
213
+ * }
214
+ * }
215
+ * });
216
+ */
217
+ export const AccountApiToken = Resource(
218
+ "cloudflare::AccountApiToken",
219
+ async function (
220
+ this: Context<AccountApiToken>,
221
+ id: string,
222
+ props: AccountApiTokenProps
223
+ ): Promise<AccountApiToken> {
224
+ // Create Cloudflare API client with automatic account discovery
225
+ const api = await createCloudflareApi();
226
+
227
+ if (this.phase === "delete") {
228
+ // Delete token if we have an ID
229
+ if (this.output?.id) {
230
+ try {
231
+ const deleteResponse = await api.delete(
232
+ `/accounts/${api.accountId}/tokens/${this.output.id}`
233
+ );
234
+
235
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
236
+ const errorData: any = await deleteResponse.json().catch(() => ({
237
+ errors: [{ message: deleteResponse.statusText }],
238
+ }));
239
+ console.error(`Error deleting token '${props.name}':`, errorData);
240
+ }
241
+ } catch (error) {
242
+ console.error(`Error deleting token '${props.name}':`, error);
243
+ }
244
+ }
245
+
246
+ // Return destroyed state
247
+ return this.destroy();
248
+ }
249
+
250
+ // Transform our properties to API format
251
+ const apiPayload = {
252
+ name: props.name,
253
+ policies: props.policies.map((policy) => ({
254
+ effect: policy.effect,
255
+ permission_groups: policy.permissionGroups.map((pg) => ({
256
+ id: pg.id,
257
+ meta: pg.meta || {},
258
+ })),
259
+ resources: policy.resources,
260
+ })),
261
+ // Format dates for Cloudflare API (removing milliseconds)
262
+ ...(props.expiresOn
263
+ ? { expires_on: formatCloudflareDate(props.expiresOn) }
264
+ : {}),
265
+ ...(props.notBefore
266
+ ? { not_before: formatCloudflareDate(props.notBefore) }
267
+ : {}),
268
+ ...(props.condition
269
+ ? {
270
+ condition: {
271
+ request_ip: props.condition.requestIp
272
+ ? {
273
+ in: props.condition.requestIp.in || [],
274
+ not_in: props.condition.requestIp.notIn || [],
275
+ }
276
+ : undefined,
277
+ },
278
+ }
279
+ : {}),
280
+ };
281
+
282
+ /**
283
+ * Formats a date string for Cloudflare API by removing milliseconds
284
+ * Converts from "2023-01-01T00:00:00.000Z" to "2023-01-01T00:00:00Z"
285
+ */
286
+ function formatCloudflareDate(dateStr: string): string {
287
+ return dateStr.replace(/\.\d{3}Z$/, "Z");
288
+ }
289
+
290
+ let response;
291
+ let tokenValue;
292
+
293
+ if (this.phase === "update" && this.output?.id) {
294
+ // Update existing token
295
+ response = await api.put(
296
+ `/accounts/${api.accountId}/tokens/${this.output.id}`,
297
+ apiPayload
298
+ );
299
+ } else {
300
+ // Create new token
301
+ response = await api.post(
302
+ `/accounts/${api.accountId}/tokens`,
303
+ apiPayload
304
+ );
305
+ }
306
+
307
+ if (!response.ok) {
308
+ const errorData: any = await response.json().catch(() => ({
309
+ errors: [{ message: response.statusText }],
310
+ }));
311
+
312
+ throw new Error(
313
+ `Error ${this.phase === "update" ? "updating" : "creating"} token '${props.name}': ${
314
+ errorData.errors?.[0]?.message || response.statusText
315
+ }`
316
+ );
317
+ }
318
+
319
+ const result: { result: CloudflareApiToken } = await response.json();
320
+ const tokenData = result.result;
321
+
322
+ if (tokenData.value) {
323
+ tokenValue = alchemy.secret(tokenData.value);
324
+ } else {
325
+ if (!this.output?.value) {
326
+ throw new Error(
327
+ `Token '${props.name}' was created but we have no record of its value. Try deleting and recreating the token.`
328
+ );
329
+ }
330
+ tokenValue = this.output?.value;
331
+ }
332
+
333
+ // Transform API response to our format
334
+ return this({
335
+ id: tokenData.id,
336
+ name: tokenData.name,
337
+ status: tokenData.status,
338
+ policies: tokenData.policies.map((policy) => ({
339
+ effect: policy.effect,
340
+ permissionGroups: policy.permission_groups.map((pg) => ({
341
+ id: pg.id,
342
+ meta: pg.meta,
343
+ })),
344
+ resources: policy.resources,
345
+ })),
346
+ ...(tokenData.expires_on ? { expiresOn: tokenData.expires_on } : {}),
347
+ ...(tokenData.not_before ? { notBefore: tokenData.not_before } : {}),
348
+ ...(tokenData.condition
349
+ ? {
350
+ condition: {
351
+ requestIp: tokenData.condition.request_ip
352
+ ? {
353
+ in: tokenData.condition.request_ip.in || [],
354
+ notIn: tokenData.condition.request_ip.not_in || [],
355
+ }
356
+ : undefined,
357
+ },
358
+ }
359
+ : {}),
360
+ value: tokenValue,
361
+ accessKeyId: tokenData.id,
362
+ secretAccessKey: sha256(tokenValue.unencrypted),
363
+ });
364
+ }
365
+ );
File without changes