alchemy 0.49.1 → 0.51.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 (92) hide show
  1. package/lib/apply.js +10 -9
  2. package/lib/apply.js.map +1 -1
  3. package/lib/aws/control/types.d.ts +4 -4
  4. package/lib/cloudflare/api-gateway-operation.d.ts +141 -0
  5. package/lib/cloudflare/api-gateway-operation.d.ts.map +1 -0
  6. package/lib/cloudflare/api-gateway-operation.js +153 -0
  7. package/lib/cloudflare/api-gateway-operation.js.map +1 -0
  8. package/lib/cloudflare/api-mitigation.d.ts +19 -0
  9. package/lib/cloudflare/api-mitigation.d.ts.map +1 -0
  10. package/lib/cloudflare/api-mitigation.js +1 -0
  11. package/lib/cloudflare/api-mitigation.js.map +1 -0
  12. package/lib/cloudflare/api-response.d.ts +2 -2
  13. package/lib/cloudflare/api-response.d.ts.map +1 -1
  14. package/lib/cloudflare/api-response.js +2 -1
  15. package/lib/cloudflare/api-response.js.map +1 -1
  16. package/lib/cloudflare/api-schema.d.ts +137 -0
  17. package/lib/cloudflare/api-schema.d.ts.map +1 -0
  18. package/lib/cloudflare/api-schema.js +197 -0
  19. package/lib/cloudflare/api-schema.js.map +1 -0
  20. package/lib/cloudflare/api-shield.d.ts +366 -0
  21. package/lib/cloudflare/api-shield.d.ts.map +1 -0
  22. package/lib/cloudflare/api-shield.js +427 -0
  23. package/lib/cloudflare/api-shield.js.map +1 -0
  24. package/lib/cloudflare/bucket.d.ts +70 -105
  25. package/lib/cloudflare/bucket.d.ts.map +1 -1
  26. package/lib/cloudflare/bucket.js +127 -244
  27. package/lib/cloudflare/bucket.js.map +1 -1
  28. package/lib/cloudflare/certificate-pack.d.ts +1 -1
  29. package/lib/cloudflare/certificate-pack.d.ts.map +1 -1
  30. package/lib/cloudflare/certificate-pack.js +18 -88
  31. package/lib/cloudflare/certificate-pack.js.map +1 -1
  32. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  33. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  34. package/lib/cloudflare/index.d.ts +3 -0
  35. package/lib/cloudflare/index.d.ts.map +1 -1
  36. package/lib/cloudflare/index.js +3 -0
  37. package/lib/cloudflare/index.js.map +1 -1
  38. package/lib/cloudflare/nuxt.js +1 -1
  39. package/lib/cloudflare/nuxt.js.map +1 -1
  40. package/lib/cloudflare/orange.d.ts.map +1 -1
  41. package/lib/cloudflare/orange.js +4 -1
  42. package/lib/cloudflare/orange.js.map +1 -1
  43. package/lib/cloudflare/redwood.d.ts.map +1 -1
  44. package/lib/cloudflare/redwood.js +5 -1
  45. package/lib/cloudflare/redwood.js.map +1 -1
  46. package/lib/cloudflare/vite.d.ts.map +1 -1
  47. package/lib/cloudflare/vite.js +2 -10
  48. package/lib/cloudflare/vite.js.map +1 -1
  49. package/lib/cloudflare/website.d.ts.map +1 -1
  50. package/lib/cloudflare/website.js +1 -0
  51. package/lib/cloudflare/website.js.map +1 -1
  52. package/lib/cloudflare/worker.d.ts.map +1 -1
  53. package/lib/cloudflare/worker.js +1 -0
  54. package/lib/cloudflare/worker.js.map +1 -1
  55. package/lib/cloudflare/zone.d.ts +12 -0
  56. package/lib/cloudflare/zone.d.ts.map +1 -1
  57. package/lib/cloudflare/zone.js +35 -0
  58. package/lib/cloudflare/zone.js.map +1 -1
  59. package/lib/destroy.d.ts.map +1 -1
  60. package/lib/destroy.js +26 -18
  61. package/lib/destroy.js.map +1 -1
  62. package/lib/os/exec.d.ts +1 -1
  63. package/lib/os/exec.d.ts.map +1 -1
  64. package/lib/os/exec.js +1 -1
  65. package/lib/os/exec.js.map +1 -1
  66. package/lib/scope.d.ts.map +1 -1
  67. package/lib/scope.js +2 -2
  68. package/lib/scope.js.map +1 -1
  69. package/package.json +2 -1
  70. package/src/apply.ts +11 -12
  71. package/src/aws/control/types.ts +4 -4
  72. package/src/cloudflare/api-gateway-operation.ts +340 -0
  73. package/src/cloudflare/api-mitigation.ts +22 -0
  74. package/src/cloudflare/api-response.ts +7 -3
  75. package/src/cloudflare/api-schema.ts +364 -0
  76. package/src/cloudflare/api-shield.ts +676 -0
  77. package/src/cloudflare/bucket.ts +276 -468
  78. package/src/cloudflare/certificate-pack.ts +28 -152
  79. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  80. package/src/cloudflare/index.ts +3 -0
  81. package/src/cloudflare/nuxt.ts +1 -1
  82. package/src/cloudflare/orange.ts +4 -1
  83. package/src/cloudflare/redwood.ts +6 -1
  84. package/src/cloudflare/vite.ts +2 -10
  85. package/src/cloudflare/website.ts +1 -0
  86. package/src/cloudflare/worker.ts +2 -1
  87. package/src/cloudflare/zone.ts +52 -0
  88. package/src/destroy.ts +26 -20
  89. package/src/os/exec.ts +2 -2
  90. package/src/scope.ts +3 -3
  91. package/templates/tanstack-start/package.json +3 -3
  92. package/templates/tanstack-start/vite.config.ts +2 -6
@@ -7,7 +7,7 @@ import {
7
7
  type CloudflareApi,
8
8
  type CloudflareApiOptions,
9
9
  } from "./api.ts";
10
- import type { Zone } from "./zone.ts";
10
+ import { findZoneForHostname, type Zone } from "./zone.ts";
11
11
 
12
12
  /**
13
13
  * Certificate Authority options for Advanced Certificate Packs
@@ -298,8 +298,18 @@ export const CertificatePack = Resource(
298
298
  const deleteResponse = await api.delete(
299
299
  `/zones/${zoneId}/ssl/certificate_packs/${this.output.id}`,
300
300
  );
301
-
302
- if (!deleteResponse.ok && deleteResponse.status !== 404) {
301
+ const deleteResponseJson = (await deleteResponse.json()) as {
302
+ errors: Array<{ code: number }>;
303
+ };
304
+ const isCurrentlyDeleting =
305
+ deleteResponse.status === 400 &&
306
+ deleteResponseJson.errors[0].code === 1406;
307
+
308
+ if (
309
+ !deleteResponse.ok &&
310
+ deleteResponse.status !== 404 &&
311
+ !isCurrentlyDeleting
312
+ ) {
303
313
  await handleApiError(
304
314
  deleteResponse,
305
315
  "delete",
@@ -314,96 +324,7 @@ export const CertificatePack = Resource(
314
324
  }
315
325
 
316
326
  if (this.phase === "update" && this.output?.id) {
317
- // Validate immutable properties
318
- const currentPack = this.output;
319
-
320
- if (props.certificateAuthority !== currentPack.certificateAuthority) {
321
- throw new Error(
322
- `Cannot change certificateAuthority from '${currentPack.certificateAuthority}' to '${props.certificateAuthority}'. Certificate Authority is immutable after creation. You must delete and recreate the certificate pack.`,
323
- );
324
- }
325
-
326
- if (
327
- JSON.stringify(props.hosts.sort()) !==
328
- JSON.stringify(currentPack.hosts.sort())
329
- ) {
330
- throw new Error(
331
- `Cannot change hosts from [${currentPack.hosts.join(", ")}] to [${props.hosts.join(", ")}]. Hosts are immutable after creation. You must delete and recreate the certificate pack.`,
332
- );
333
- }
334
-
335
- if (props.validationMethod !== currentPack.validationMethod) {
336
- throw new Error(
337
- `Cannot change validationMethod from '${currentPack.validationMethod}' to '${props.validationMethod}'. Validation method is immutable after creation. You must delete and recreate the certificate pack.`,
338
- );
339
- }
340
-
341
- if (props.validityDays !== currentPack.validityDays) {
342
- throw new Error(
343
- `Cannot change validityDays from ${currentPack.validityDays} to ${props.validityDays}. Validity period is immutable after creation. You must delete and recreate the certificate pack.`,
344
- );
345
- }
346
-
347
- const type = props.type || "advanced";
348
- if (type !== currentPack.type) {
349
- throw new Error(
350
- `Cannot change type from '${currentPack.type}' to '${type}'. Type is immutable after creation. You must delete and recreate the certificate pack.`,
351
- );
352
- }
353
-
354
- // Only cloudflareBranding can be updated
355
- if (props.cloudflareBranding !== currentPack.cloudflareBranding) {
356
- logger.log(
357
- `Updating certificate pack cloudflare branding from ${currentPack.cloudflareBranding} to ${props.cloudflareBranding}`,
358
- );
359
-
360
- const updateResponse = await api.patch(
361
- `/zones/${zoneId}/ssl/certificate_packs/${this.output.id}`,
362
- {
363
- cloudflare_branding: props.cloudflareBranding || false,
364
- },
365
- );
366
-
367
- if (!updateResponse.ok) {
368
- await handleApiError(
369
- updateResponse,
370
- "update",
371
- "certificate pack",
372
- this.output.id,
373
- );
374
- }
375
- }
376
-
377
- // Get updated certificate pack details
378
- const response = await api.get(
379
- `/zones/${zoneId}/ssl/certificate_packs/${this.output.id}`,
380
- );
381
-
382
- if (!response.ok) {
383
- await handleApiError(
384
- response,
385
- "get",
386
- "certificate pack",
387
- this.output.id,
388
- );
389
- }
390
-
391
- const updatedPack = (
392
- (await response.json()) as { result: CloudflareCertificatePack }
393
- ).result;
394
-
395
- return this({
396
- id: updatedPack.id,
397
- certificateAuthority: updatedPack.certificate_authority,
398
- cloudflareBranding: updatedPack.cloudflare_branding,
399
- hosts: updatedPack.hosts,
400
- status: updatedPack.status,
401
- type: updatedPack.type,
402
- validationMethod: updatedPack.validation_method,
403
- validityDays: updatedPack.validity_days,
404
- zoneId: updatedPack.zone_id,
405
- zoneName: zoneName,
406
- });
327
+ this.replace();
407
328
  }
408
329
 
409
330
  // Create new certificate pack
@@ -438,13 +359,13 @@ export const CertificatePack = Resource(
438
359
  return this({
439
360
  id: existingPack.id,
440
361
  certificateAuthority: existingPack.certificate_authority,
441
- cloudflareBranding: existingPack.cloudflare_branding,
362
+ cloudflareBranding: props.cloudflareBranding ?? false,
442
363
  hosts: existingPack.hosts,
443
364
  status: existingPack.status,
444
365
  type: existingPack.type,
445
366
  validationMethod: existingPack.validation_method,
446
367
  validityDays: existingPack.validity_days,
447
- zoneId: existingPack.zone_id,
368
+ zoneId,
448
369
  zoneName: zoneName,
449
370
  });
450
371
  }
@@ -498,13 +419,13 @@ export const CertificatePack = Resource(
498
419
  return this({
499
420
  id: createdPack.id,
500
421
  certificateAuthority: createdPack.certificate_authority,
501
- cloudflareBranding: createdPack.cloudflare_branding,
422
+ cloudflareBranding: props.cloudflareBranding ?? false,
502
423
  hosts: createdPack.hosts,
503
424
  status: createdPack.status,
504
425
  type: createdPack.type,
505
426
  validationMethod: createdPack.validation_method,
506
427
  validityDays: createdPack.validity_days,
507
- zoneId: createdPack.zone_id,
428
+ zoneId,
508
429
  zoneName: zoneName,
509
430
  });
510
431
  },
@@ -516,13 +437,11 @@ export const CertificatePack = Resource(
516
437
  interface CloudflareCertificatePack {
517
438
  id: string;
518
439
  certificate_authority: CertificateAuthority;
519
- cloudflare_branding: boolean;
520
440
  hosts: string[];
521
441
  status: CertificatePackStatus;
522
442
  type: "advanced";
523
443
  validation_method: ValidationMethod;
524
444
  validity_days: ValidityDays;
525
- zone_id: string;
526
445
  }
527
446
 
528
447
  /**
@@ -591,58 +510,6 @@ export async function waitForCertificatePackActive(
591
510
  );
592
511
  }
593
512
 
594
- /**
595
- * Helper function to find zone ID from a hostname
596
- * Searches for the zone that matches the hostname or its parent domains
597
- *
598
- * @param api CloudflareApi instance
599
- * @param hostname The hostname to find the zone for
600
- * @returns Promise resolving to the zone ID and zone name
601
- */
602
- async function findZoneForHostname(
603
- api: CloudflareApi,
604
- hostname: string,
605
- ): Promise<{ zoneId: string; zoneName: string }> {
606
- // Remove wildcard prefix if present
607
- const cleanHostname = hostname.replace(/^\*\./, "");
608
-
609
- // Get all zones and find the best match
610
- const response = await api.get("/zones");
611
-
612
- if (!response.ok) {
613
- throw new Error(`Failed to list zones: ${response.statusText}`);
614
- }
615
-
616
- const zonesData = (await response.json()) as {
617
- result: Array<{ id: string; name: string }>;
618
- };
619
-
620
- // Find the zone that best matches the hostname
621
- // We look for the longest matching zone name (most specific)
622
- let bestMatch: { zoneId: string; zoneName: string } | null = null;
623
- let longestMatch = 0;
624
-
625
- for (const zone of zonesData.result) {
626
- if (
627
- cleanHostname === zone.name ||
628
- cleanHostname.endsWith(`.${zone.name}`)
629
- ) {
630
- if (zone.name.length > longestMatch) {
631
- longestMatch = zone.name.length;
632
- bestMatch = { zoneId: zone.id, zoneName: zone.name };
633
- }
634
- }
635
- }
636
-
637
- if (!bestMatch) {
638
- throw new Error(
639
- `Could not find zone for hostname '${hostname}'. Available zones: ${zonesData.result.map((z) => z.name).join(", ")}`,
640
- );
641
- }
642
-
643
- return bestMatch;
644
- }
645
-
646
513
  /**
647
514
  * Helper function to find existing certificate packs that match the given configuration
648
515
  * Used to adopt existing certificates instead of creating duplicates
@@ -657,7 +524,9 @@ async function findMatchingCertificatePack(
657
524
  zoneId: string,
658
525
  props: CertificatePackProps,
659
526
  ): Promise<CloudflareCertificatePack | null> {
660
- const response = await api.get(`/zones/${zoneId}/ssl/certificate_packs`);
527
+ const response = await api.get(
528
+ `/zones/${zoneId}/ssl/certificate_packs?status=all`,
529
+ );
661
530
 
662
531
  if (!response.ok) {
663
532
  throw new Error(`Failed to list certificate packs: ${response.statusText}`);
@@ -686,6 +555,13 @@ async function findMatchingCertificatePack(
686
555
  const allHostsCovered = props.hosts.every((host) => packHosts.has(host));
687
556
 
688
557
  if (allHostsCovered) {
558
+ // If Cloudflare branding is enabled, ensure that the cert pack is cloudflare branded`
559
+ if (
560
+ props.cloudflareBranding &&
561
+ !pack.hosts.some((host) => host.endsWith("sni.cloudflaressl.com"))
562
+ ) {
563
+ continue;
564
+ }
689
565
  logger.log(
690
566
  `Found existing certificate pack ${pack.id} that covers all requested hosts`,
691
567
  );
@@ -4,4 +4,4 @@
4
4
  /**
5
5
  * The default Cloudflare Workers compatibility date, set based on the latest workerd release at the time of build.
6
6
  */
7
- export const DEFAULT_COMPATIBILITY_DATE = "2025-07-19";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-07-22";
@@ -4,6 +4,9 @@ export * from "./ai-gateway.ts";
4
4
  export * from "./ai.ts";
5
5
  export * from "./analytics-engine.ts";
6
6
  export * from "./api-error.ts";
7
+ export * from "./api-gateway-operation.ts";
8
+ export * from "./api-schema.ts";
9
+ export * from "./api-shield.ts";
7
10
  export * from "./api.ts";
8
11
  export * from "./assets.ts";
9
12
  export * from "./astro.ts";
@@ -55,7 +55,7 @@ export async function Nuxt<B extends Bindings>(
55
55
  ...props,
56
56
  noBundle: props?.noBundle ?? true,
57
57
  // Default build command, can be overridden by props.command
58
- command: props?.command ?? "bun run build",
58
+ command: props?.command ?? "nuxt build",
59
59
  // Default entry point for cloudflare-module preset
60
60
  main: props?.main ?? "./.output/server/index.mjs",
61
61
  // Default static assets directory for cloudflare-module preset
@@ -40,7 +40,10 @@ export async function Orange<B extends Bindings>(
40
40
  ): Promise<Orange<B>> {
41
41
  return Website(id, {
42
42
  ...props,
43
- command: props?.command ?? "bun run build",
43
+ command: props?.command ?? "vite build",
44
+ dev: props?.dev ?? {
45
+ command: "vite dev",
46
+ },
44
47
  // The entrypoint and the Wrangler main must differ for the Cloudflare vite plugin
45
48
  // to bundle the application correctly, and alchemy must run our bundler on the Vite
46
49
  // output since we don't resolve vite imports.
@@ -43,7 +43,12 @@ export async function Redwood<B extends Bindings>(
43
43
  ): Promise<Redwood<B>> {
44
44
  return Website(id, {
45
45
  ...props,
46
- command: props?.command ?? "bun run clean && RWSDK_DEPLOY=1 bun run build",
46
+ command:
47
+ props?.command ??
48
+ "rm -rf ./node_modules/.vite && RWSDK_DEPLOY=1 vite build",
49
+ dev: props?.dev ?? {
50
+ command: "vite dev",
51
+ },
47
52
  noBundle: props?.noBundle ?? true,
48
53
  wrangler:
49
54
  props?.wrangler === false
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { detectPackageManager } from "../util/detect-package-manager.ts";
3
2
  import type { Assets } from "./assets.ts";
4
3
  import type { Bindings } from "./bindings.ts";
5
4
  import { Website, type WebsiteProps } from "./website.ts";
@@ -18,14 +17,6 @@ export async function Vite<B extends Bindings>(
18
17
  props: ViteProps<B>,
19
18
  ): Promise<Vite<B>> {
20
19
  const defaultAssets = path.join("dist", "client");
21
- const packageManager = await detectPackageManager();
22
- const devCommand = {
23
- npm: "npx vite dev",
24
- bun: "bun vite dev",
25
- pnpm: "pnpm vite dev",
26
- yarn: "yarn vite dev",
27
- deno: "deno vite dev",
28
- }[packageManager];
29
20
  return Website(id, {
30
21
  ...props,
31
22
  spa: true,
@@ -36,8 +27,9 @@ export async function Vite<B extends Bindings>(
36
27
  dist: props.assets.dist ?? defaultAssets,
37
28
  }
38
29
  : (props.assets ?? defaultAssets),
30
+ command: props.command ?? "vite build",
39
31
  dev: props.dev ?? {
40
- command: devCommand,
32
+ command: "vite dev",
41
33
  },
42
34
  });
43
35
  }
@@ -231,6 +231,7 @@ export default {
231
231
  cwd,
232
232
  command: props.command,
233
233
  env: {
234
+ ...(process.env ?? {}),
234
235
  ...(props.env ?? {}),
235
236
  ...(props.commandEnv ?? {}),
236
237
  ...Object.fromEntries(
@@ -1088,6 +1088,7 @@ export const _Worker = Resource(
1088
1088
  command: props.dev.command,
1089
1089
  cwd: props.dev.cwd || props.cwd || process.cwd(),
1090
1090
  env: {
1091
+ ...(process.env ?? {}),
1091
1092
  ...props.env,
1092
1093
  ...Object.fromEntries(
1093
1094
  Object.entries(props.bindings ?? {}).flatMap(([key, value]) =>
@@ -1834,7 +1835,7 @@ async function createDevCommand(props: {
1834
1835
  id: string;
1835
1836
  command: string;
1836
1837
  cwd: string;
1837
- env: Record<string, string>;
1838
+ env: Record<string, string | undefined>;
1838
1839
  }): Promise<{ url: string }> {
1839
1840
  const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
1840
1841
  if (await exists(persistFile)) {
@@ -610,3 +610,55 @@ export interface CloudflareZone {
610
610
  modified_on: string;
611
611
  activated_on: string | null;
612
612
  }
613
+
614
+ /**
615
+ * Helper function to find zone ID from a hostname
616
+ * Searches for the zone that matches the hostname or its parent domains
617
+ *
618
+ * @param api CloudflareApi instance
619
+ * @param hostname The hostname to find the zone for
620
+ * @returns Promise resolving to the zone ID and zone name
621
+ */
622
+ export async function findZoneForHostname(
623
+ api: CloudflareApi,
624
+ hostname: string,
625
+ ): Promise<{ zoneId: string; zoneName: string }> {
626
+ // Remove wildcard prefix if present
627
+ const cleanHostname = hostname.replace(/^\*\./, "");
628
+
629
+ // Get all zones and find the best match
630
+ const response = await api.get("/zones");
631
+
632
+ if (!response.ok) {
633
+ throw new Error(`Failed to list zones: ${response.statusText}`);
634
+ }
635
+
636
+ const zonesData = (await response.json()) as {
637
+ result: Array<{ id: string; name: string }>;
638
+ };
639
+
640
+ // Find the zone that best matches the hostname
641
+ // We look for the longest matching zone name (most specific)
642
+ let bestMatch: { zoneId: string; zoneName: string } | null = null;
643
+ let longestMatch = 0;
644
+
645
+ for (const zone of zonesData.result) {
646
+ if (
647
+ cleanHostname === zone.name ||
648
+ cleanHostname.endsWith(`.${zone.name}`)
649
+ ) {
650
+ if (zone.name.length > longestMatch) {
651
+ longestMatch = zone.name.length;
652
+ bestMatch = { zoneId: zone.id, zoneName: zone.name };
653
+ }
654
+ }
655
+ }
656
+
657
+ if (!bestMatch) {
658
+ throw new Error(
659
+ `Could not find zone for hostname '${hostname}'. Available zones: ${zonesData.result.map((z) => z.name).join(", ")}`,
660
+ );
661
+ }
662
+
663
+ return bestMatch;
664
+ }
package/src/destroy.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  ResourceSeq,
12
12
  } from "./resource.ts";
13
13
  import { isScope, type PendingDeletions, Scope } from "./scope.ts";
14
+ import type { State } from "./state.ts";
14
15
  import { formatFQN } from "./util/cli.ts";
15
16
  import { logger } from "./util/logger.ts";
16
17
 
@@ -108,12 +109,29 @@ export async function destroy<Type extends string>(
108
109
  });
109
110
  }
110
111
 
111
- const state = await scope.state.get(instance[ResourceID]);
112
-
113
- if (state === undefined) {
114
- return;
112
+ let state: State;
113
+ let props: ResourceProps | undefined;
114
+ if (options?.replace) {
115
+ props = options.replace.props;
116
+ state = {
117
+ output: options.replace.output!,
118
+ status: "deleting",
119
+ oldProps: options.replace.props,
120
+ data: {},
121
+ kind: instance[ResourceKind],
122
+ id: instance[ResourceID],
123
+ fqn: instance[ResourceFQN],
124
+ seq: instance[ResourceSeq],
125
+ props,
126
+ };
127
+ } else {
128
+ const _state = await scope.state.get(instance[ResourceID]);
129
+ if (_state === undefined) {
130
+ return;
131
+ }
132
+ state = _state;
133
+ props = state.props;
115
134
  }
116
-
117
135
  const ctx = context({
118
136
  scope,
119
137
  phase: "delete",
@@ -121,20 +139,8 @@ export async function destroy<Type extends string>(
121
139
  id: instance[ResourceID],
122
140
  fqn: instance[ResourceFQN],
123
141
  seq: instance[ResourceSeq],
124
- props: options?.replace?.props ?? state.props,
125
- state: options?.replace?.output
126
- ? {
127
- output: options.replace.output,
128
- status: "deleting",
129
- oldProps: options.replace.props,
130
- data: {},
131
- kind: instance[ResourceKind],
132
- id: instance[ResourceID],
133
- fqn: instance[ResourceFQN],
134
- seq: instance[ResourceSeq],
135
- props: options.replace.props,
136
- }
137
- : state,
142
+ props,
143
+ state,
138
144
  // TODO(sam|michael): should this always be false or !!options?.replace
139
145
  isReplacement: false,
140
146
  replace: () => {
@@ -157,7 +163,7 @@ export async function destroy<Type extends string>(
157
163
  nestedScope = options?.replace?.props == null ? scope : undefined;
158
164
  return await Provider.handler.bind(ctx)(
159
165
  instance[ResourceID],
160
- options?.replace?.props ?? state.props!,
166
+ ctx.props,
161
167
  );
162
168
  },
163
169
  );
package/src/os/exec.ts CHANGED
@@ -48,7 +48,7 @@ export interface ExecProps {
48
48
  /**
49
49
  * Environment variables to set
50
50
  */
51
- env?: Record<string, string | Secret<string>>;
51
+ env?: Record<string, string | Secret<string> | undefined>;
52
52
 
53
53
  /**
54
54
  * Whether to inherit stdio from parent process
@@ -218,7 +218,7 @@ export const Exec = Resource(
218
218
  for (const [key, value] of Object.entries(props.env)) {
219
219
  if (typeof value === "string") {
220
220
  processedEnv[key] = value;
221
- } else {
221
+ } else if (value) {
222
222
  // Handle Secret objects
223
223
  processedEnv[key] = value.unencrypted;
224
224
  }
package/src/scope.ts CHANGED
@@ -142,7 +142,6 @@ export class Scope {
142
142
  private isSkipped = false;
143
143
  private finalized = false;
144
144
  private startedAt = performance.now();
145
-
146
145
  private deferred: (() => Promise<any>)[] = [];
147
146
 
148
147
  public get appName(): string {
@@ -345,13 +344,13 @@ export class Scope {
345
344
  }
346
345
 
347
346
  public async set<T>(key: string, value: T): Promise<void> {
348
- return this.withScopeState<void>(async (state, persist) => {
347
+ await this.withScopeState<void>(async (state, persist) => {
349
348
  state.data[key] = value;
350
349
  await persist(state); // only one line to save!
351
350
  });
352
351
  }
353
352
 
354
- public async get<T>(key: string): Promise<T> {
353
+ public get<T>(key: string): Promise<T> {
355
354
  return this.withScopeState<T>(async (state) => state.data[key]);
356
355
  }
357
356
 
@@ -475,6 +474,7 @@ export class Scope {
475
474
  }
476
475
  throw e;
477
476
  })) ?? [];
477
+
478
478
  //todo(michael): remove once we deprecate doss; see: https://github.com/sam-goodwin/alchemy/issues/585
479
479
  let hasCorruptedResources = false;
480
480
  if (pendingDeletions) {
@@ -11,9 +11,9 @@
11
11
  "start": "node .output/server/index.mjs"
12
12
  },
13
13
  "dependencies": {
14
- "@tanstack/react-router": "^1.128.3",
15
- "@tanstack/react-router-devtools": "^1.128.3",
16
- "@tanstack/react-start": "^1.128.3",
14
+ "@tanstack/react-router": "^1.129.2",
15
+ "@tanstack/react-router-devtools": "^1.129.2",
16
+ "@tanstack/react-start": "^1.129.2",
17
17
  "react": "^19.0.0",
18
18
  "react-dom": "^19.0.0",
19
19
  "tailwind-merge": "3",
@@ -1,5 +1,6 @@
1
1
  import tailwindcss from "@tailwindcss/vite";
2
2
  import { tanstackStart } from "@tanstack/react-start/plugin/vite";
3
+ import viteReact from "@vitejs/plugin-react";
3
4
  import { cloudflareWorkersDevEnvironmentShim } from "alchemy/cloudflare";
4
5
  import { defineConfig, PluginOption } from "vite";
5
6
  import tsConfigPaths from "vite-tsconfig-paths";
@@ -23,12 +24,7 @@ export default defineConfig({
23
24
  tanstackStart({
24
25
  target: "cloudflare-module",
25
26
  customViteReactPlugin: true,
26
- tsr: {
27
- routeTreeFileHeader: [
28
- "/** biome-ignore-all lint/suspicious/noExplicitAny: code generated by @tanstack/react-start */",
29
- ],
30
- quoteStyle: "double",
31
- },
32
27
  }),
28
+ viteReact(),
33
29
  ],
34
30
  });