alchemy 0.43.5 → 0.44.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 (144) hide show
  1. package/bin/alchemy.mjs +430 -343
  2. package/bin/alchemy.ts +7 -21
  3. package/bin/commands/create.ts +79 -23
  4. package/bin/services/template-manager.ts +9 -4
  5. package/bin/services/vibe-rules.ts +47 -0
  6. package/bin/types.ts +25 -5
  7. package/lib/alchemy.d.ts.map +1 -1
  8. package/lib/alchemy.js +8 -11
  9. package/lib/alchemy.js.map +1 -1
  10. package/lib/apply.js +2 -0
  11. package/lib/apply.js.map +1 -1
  12. package/lib/build-date.d.ts +1 -1
  13. package/lib/build-date.js +1 -1
  14. package/lib/cloudflare/assets.d.ts +1 -1
  15. package/lib/cloudflare/assets.d.ts.map +1 -1
  16. package/lib/cloudflare/bucket.d.ts.map +1 -1
  17. package/lib/cloudflare/bucket.js +9 -6
  18. package/lib/cloudflare/bucket.js.map +1 -1
  19. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
  20. package/lib/cloudflare/bundle/bundle-worker-dev.js +4 -1
  21. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
  22. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  23. package/lib/cloudflare/bundle/bundle-worker.js +4 -1
  24. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  25. package/lib/cloudflare/bundle/external.d.ts +1 -0
  26. package/lib/cloudflare/bundle/external.d.ts.map +1 -1
  27. package/lib/cloudflare/bundle/external.js +1 -1
  28. package/lib/cloudflare/bundle/external.js.map +1 -1
  29. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts +10 -0
  30. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +1 -0
  31. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js +67 -0
  32. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +1 -0
  33. package/lib/cloudflare/container.d.ts +27 -2
  34. package/lib/cloudflare/container.d.ts.map +1 -1
  35. package/lib/cloudflare/container.js +82 -24
  36. package/lib/cloudflare/container.js.map +1 -1
  37. package/lib/cloudflare/do-state-store/store.d.ts.map +1 -1
  38. package/lib/cloudflare/do-state-store/store.js +5 -1
  39. package/lib/cloudflare/do-state-store/store.js.map +1 -1
  40. package/lib/cloudflare/redirect-rule.js +1 -1
  41. package/lib/cloudflare/redirect-rule.js.map +1 -1
  42. package/lib/cloudflare/website.d.ts +17 -0
  43. package/lib/cloudflare/website.d.ts.map +1 -1
  44. package/lib/cloudflare/website.js +1 -0
  45. package/lib/cloudflare/website.js.map +1 -1
  46. package/lib/cloudflare/worker/miniflare.d.ts +4 -1
  47. package/lib/cloudflare/worker/miniflare.d.ts.map +1 -1
  48. package/lib/cloudflare/worker/miniflare.js +22 -2
  49. package/lib/cloudflare/worker/miniflare.js.map +1 -1
  50. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  51. package/lib/cloudflare/worker-metadata.js +12 -0
  52. package/lib/cloudflare/worker-metadata.js.map +1 -1
  53. package/lib/cloudflare/worker.d.ts +1 -1
  54. package/lib/cloudflare/wrangler.json.d.ts +24 -0
  55. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  56. package/lib/cloudflare/wrangler.json.js +20 -2
  57. package/lib/cloudflare/wrangler.json.js.map +1 -1
  58. package/lib/context.d.ts +6 -1
  59. package/lib/context.d.ts.map +1 -1
  60. package/lib/context.js +2 -1
  61. package/lib/context.js.map +1 -1
  62. package/lib/destroy.d.ts.map +1 -1
  63. package/lib/destroy.js +1 -0
  64. package/lib/destroy.js.map +1 -1
  65. package/lib/docker/api.d.ts +11 -0
  66. package/lib/docker/api.d.ts.map +1 -1
  67. package/lib/docker/api.js +41 -1
  68. package/lib/docker/api.js.map +1 -1
  69. package/lib/docker/image.d.ts +1 -1
  70. package/lib/docker/image.d.ts.map +1 -1
  71. package/lib/docker/image.js +11 -5
  72. package/lib/docker/image.js.map +1 -1
  73. package/lib/esbuild/bundle.d.ts +2 -2
  74. package/lib/index.d.ts.map +1 -1
  75. package/lib/index.js.map +1 -1
  76. package/lib/llms.cloudflare.txt +379 -0
  77. package/lib/llms.d.ts +10 -0
  78. package/lib/llms.d.ts.map +1 -0
  79. package/lib/llms.js +12 -0
  80. package/lib/llms.js.map +1 -0
  81. package/lib/planetscale/index.d.ts +3 -2
  82. package/lib/planetscale/index.d.ts.map +1 -1
  83. package/lib/planetscale/index.js +3 -2
  84. package/lib/planetscale/index.js.map +1 -1
  85. package/lib/planetscale/password.d.ts +233 -0
  86. package/lib/planetscale/password.d.ts.map +1 -0
  87. package/lib/planetscale/password.js +238 -0
  88. package/lib/planetscale/password.js.map +1 -0
  89. package/lib/scope.d.ts +6 -5
  90. package/lib/scope.d.ts.map +1 -1
  91. package/lib/scope.js +18 -7
  92. package/lib/scope.js.map +1 -1
  93. package/lib/test/bun.d.ts.map +1 -1
  94. package/lib/test/bun.js +1 -0
  95. package/lib/test/bun.js.map +1 -1
  96. package/lib/test/vitest.d.ts.map +1 -1
  97. package/lib/test/vitest.js +1 -0
  98. package/lib/test/vitest.js.map +1 -1
  99. package/lib/util/nanoid.d.ts +3 -0
  100. package/lib/util/nanoid.d.ts.map +1 -0
  101. package/lib/util/nanoid.js +13 -0
  102. package/lib/util/nanoid.js.map +1 -0
  103. package/lib/util/safe-fetch.d.ts.map +1 -1
  104. package/lib/util/safe-fetch.js +0 -1
  105. package/lib/util/safe-fetch.js.map +1 -1
  106. package/lib/util/telemetry/client.d.ts.map +1 -1
  107. package/lib/util/telemetry/client.js +13 -3
  108. package/lib/util/telemetry/client.js.map +1 -1
  109. package/lib/util/telemetry/constants.d.ts +2 -1
  110. package/lib/util/telemetry/constants.d.ts.map +1 -1
  111. package/lib/util/telemetry/constants.js +3 -2
  112. package/lib/util/telemetry/constants.js.map +1 -1
  113. package/package.json +10 -2
  114. package/src/alchemy.ts +8 -13
  115. package/src/apply.ts +2 -0
  116. package/src/build-date.ts +1 -1
  117. package/src/cloudflare/bucket.ts +22 -14
  118. package/src/cloudflare/bundle/bundle-worker-dev.ts +4 -1
  119. package/src/cloudflare/bundle/bundle-worker.ts +4 -1
  120. package/src/cloudflare/bundle/external.ts +1 -1
  121. package/src/cloudflare/bundle/nodejs-import-warning-plugin.ts +94 -0
  122. package/src/cloudflare/container.ts +113 -28
  123. package/src/cloudflare/do-state-store/store.ts +5 -1
  124. package/src/cloudflare/redirect-rule.ts +1 -1
  125. package/src/cloudflare/website.ts +21 -1
  126. package/src/cloudflare/worker/miniflare.ts +33 -8
  127. package/src/cloudflare/worker-metadata.ts +15 -0
  128. package/src/cloudflare/wrangler.json.ts +61 -5
  129. package/src/context.ts +7 -0
  130. package/src/destroy.ts +1 -0
  131. package/src/docker/api.ts +57 -1
  132. package/src/docker/image.ts +15 -6
  133. package/src/index.ts +1 -0
  134. package/src/llms.cloudflare.txt +379 -0
  135. package/src/llms.ts +25 -0
  136. package/src/planetscale/index.ts +3 -2
  137. package/src/planetscale/password.ts +366 -0
  138. package/src/scope.ts +25 -11
  139. package/src/test/bun.ts +1 -0
  140. package/src/test/vitest.ts +1 -0
  141. package/src/util/nanoid.ts +13 -0
  142. package/src/util/safe-fetch.ts +0 -1
  143. package/src/util/telemetry/client.ts +17 -3
  144. package/src/util/telemetry/constants.ts +5 -3
@@ -47,6 +47,25 @@ export interface WranglerJsonProps {
47
47
  binding: string;
48
48
  directory: string;
49
49
  };
50
+
51
+ /**
52
+ * Transform hooks to modify generated configuration files
53
+ */
54
+ transform?: {
55
+ /**
56
+ * Hook to modify the wrangler.json object before it's written
57
+ *
58
+ * This function receives the generated wrangler.json spec and should return
59
+ * a modified version. It's applied as the final transformation before the
60
+ * file is written to disk.
61
+ *
62
+ * @param spec - The generated wrangler.json specification
63
+ * @returns The modified wrangler.json specification
64
+ */
65
+ wrangler?: (
66
+ spec: WranglerJsonSpec,
67
+ ) => WranglerJsonSpec | Promise<WranglerJsonSpec>;
68
+ };
50
69
  }
51
70
 
52
71
  /**
@@ -156,14 +175,19 @@ export const WranglerJson = Resource(
156
175
  spec.assets.directory = path.relative(dirname, spec.assets.directory);
157
176
  }
158
177
 
178
+ // Apply the wrangler configuration hook as the final transformation
179
+ const finalSpec = props.transform?.wrangler
180
+ ? await props.transform.wrangler(spec)
181
+ : spec;
182
+
159
183
  await fs.mkdir(dirname, { recursive: true });
160
- await fs.writeFile(filePath, await formatJson(spec));
184
+ await fs.writeFile(filePath, await formatJson(finalSpec));
161
185
 
162
186
  // Return the resource
163
187
  return this({
164
188
  ...props,
165
189
  path: path.relative(cwd, filePath),
166
- spec,
190
+ spec: finalSpec,
167
191
  createdAt: Date.now(),
168
192
  updatedAt: Date.now(),
169
193
  });
@@ -216,6 +240,7 @@ export interface WranglerJsonSpec {
216
240
  */
217
241
  ai?: {
218
242
  binding: string;
243
+ experimental_remote?: boolean;
219
244
  };
220
245
 
221
246
  /**
@@ -223,6 +248,7 @@ export interface WranglerJsonSpec {
223
248
  */
224
249
  browser?: {
225
250
  binding: string;
251
+ experimental_remote?: boolean;
226
252
  };
227
253
 
228
254
  /**
@@ -230,6 +256,7 @@ export interface WranglerJsonSpec {
230
256
  */
231
257
  images?: {
232
258
  binding: string;
259
+ experimental_remote?: boolean;
233
260
  };
234
261
 
235
262
  /**
@@ -242,6 +269,7 @@ export interface WranglerJsonSpec {
242
269
  * The ID of the KV namespace used during `wrangler dev`
243
270
  */
244
271
  preview_id?: string;
272
+ experimental_remote?: boolean;
245
273
  }[];
246
274
 
247
275
  /**
@@ -266,6 +294,7 @@ export interface WranglerJsonSpec {
266
294
  * The preview name of this R2 bucket at the edge.
267
295
  */
268
296
  preview_bucket_name?: string;
297
+ experimental_remote?: boolean;
269
298
  }[];
270
299
 
271
300
  /**
@@ -308,6 +337,7 @@ export interface WranglerJsonSpec {
308
337
  vectorize?: {
309
338
  binding: string;
310
339
  index_name: string;
340
+ experimental_remote?: boolean;
311
341
  }[];
312
342
 
313
343
  /**
@@ -327,6 +357,7 @@ export interface WranglerJsonSpec {
327
357
  * The ID of the D1 database used during `wrangler dev`
328
358
  */
329
359
  preview_database_id?: string;
360
+ experimental_remote?: boolean;
330
361
  }[];
331
362
 
332
363
  /**
@@ -398,6 +429,7 @@ export interface WranglerJsonSpec {
398
429
  dispatch_namespaces?: {
399
430
  binding: string;
400
431
  namespace: string;
432
+ experimental_remote?: boolean;
401
433
  }[];
402
434
  }
403
435
 
@@ -412,8 +444,12 @@ function processBindings(
412
444
  workerCwd: string,
413
445
  ): void {
414
446
  // Arrays to collect different binding types
415
- const kvNamespaces: { binding: string; id: string; preview_id: string }[] =
416
- [];
447
+ const kvNamespaces: {
448
+ binding: string;
449
+ id: string;
450
+ preview_id: string;
451
+ experimental_remote?: boolean;
452
+ }[] = [];
417
453
  const durableObjects: {
418
454
  name: string;
419
455
  class_name: string;
@@ -440,6 +476,7 @@ function processBindings(
440
476
  database_name: string;
441
477
  migrations_dir?: string;
442
478
  preview_database_id: string;
479
+ experimental_remote?: boolean;
443
480
  }[] = [];
444
481
  const queues: {
445
482
  producers: { queue: string; binding: string }[];
@@ -459,7 +496,11 @@ function processBindings(
459
496
  const new_sqlite_classes: string[] = [];
460
497
  const new_classes: string[] = [];
461
498
 
462
- const vectorizeIndexes: { binding: string; index_name: string }[] = [];
499
+ const vectorizeIndexes: {
500
+ binding: string;
501
+ index_name: string;
502
+ experimental_remote?: boolean;
503
+ }[] = [];
463
504
  const analyticsEngineDatasets: { binding: string; dataset: string }[] = [];
464
505
  const hyperdrive: {
465
506
  binding: string;
@@ -475,6 +516,7 @@ function processBindings(
475
516
  const dispatchNamespaces: {
476
517
  binding: string;
477
518
  namespace: string;
519
+ experimental_remote?: boolean;
478
520
  }[] = [];
479
521
  const containers: {
480
522
  class_name: string;
@@ -527,6 +569,9 @@ function processBindings(
527
569
  binding: bindingName,
528
570
  id: id,
529
571
  preview_id: id,
572
+ ...("dev" in binding && binding.dev?.remote
573
+ ? { experimental_remote: true }
574
+ : {}),
530
575
  });
531
576
  } else if (
532
577
  typeof binding === "object" &&
@@ -550,6 +595,7 @@ function processBindings(
550
595
  binding: bindingName,
551
596
  bucket_name: binding.name,
552
597
  preview_bucket_name: binding.name,
598
+ ...(binding.dev?.remote ? { experimental_remote: true } : {}),
553
599
  });
554
600
  } else if (binding.type === "secret") {
555
601
  // Secret binding
@@ -573,6 +619,7 @@ function processBindings(
573
619
  database_name: binding.name,
574
620
  migrations_dir: binding.migrationsDir,
575
621
  preview_database_id: binding.id,
622
+ ...(binding.dev?.remote ? { experimental_remote: true } : {}),
576
623
  });
577
624
  } else if (binding.type === "queue") {
578
625
  queues.producers.push({
@@ -583,6 +630,8 @@ function processBindings(
583
630
  vectorizeIndexes.push({
584
631
  binding: bindingName,
585
632
  index_name: binding.name,
633
+ // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
634
+ experimental_remote: true,
586
635
  });
587
636
  } else if (binding.type === "browser") {
588
637
  if (spec.browser) {
@@ -590,6 +639,8 @@ function processBindings(
590
639
  }
591
640
  spec.browser = {
592
641
  binding: bindingName,
642
+ // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
643
+ experimental_remote: true,
593
644
  };
594
645
  } else if (binding.type === "ai") {
595
646
  if (spec.ai) {
@@ -597,6 +648,8 @@ function processBindings(
597
648
  }
598
649
  spec.ai = {
599
650
  binding: bindingName,
651
+ // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
652
+ experimental_remote: true,
600
653
  };
601
654
  } else if (binding.type === "images") {
602
655
  if (spec.images) {
@@ -604,6 +657,8 @@ function processBindings(
604
657
  }
605
658
  spec.images = {
606
659
  binding: bindingName,
660
+ // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
661
+ experimental_remote: true,
607
662
  };
608
663
  } else if (binding.type === "analytics_engine") {
609
664
  analyticsEngineDatasets.push({
@@ -648,6 +703,7 @@ function processBindings(
648
703
  dispatchNamespaces.push({
649
704
  binding: bindingName,
650
705
  namespace: binding.namespaceName,
706
+ experimental_remote: true,
651
707
  });
652
708
  } else if (binding.type === "secret_key") {
653
709
  // no-op
package/src/context.ts CHANGED
@@ -46,6 +46,10 @@ export interface BaseContext<Out extends Resource> {
46
46
  id: ResourceID;
47
47
  fqn: ResourceFQN;
48
48
  scope: Scope;
49
+ /**
50
+ * Indicates whether this resource is being created as a replacement for another resource
51
+ */
52
+ isReplacement: boolean;
49
53
  get<T>(key: string): Promise<T | undefined>;
50
54
  set<T>(key: string, value: T): Promise<void>;
51
55
  delete<T>(key: string): Promise<T | undefined>;
@@ -88,6 +92,7 @@ export function context<
88
92
  state,
89
93
  replace,
90
94
  props,
95
+ isReplacement = false,
91
96
  }: {
92
97
  scope: Scope;
93
98
  phase: "create" | "update" | "delete";
@@ -98,6 +103,7 @@ export function context<
98
103
  props: Props;
99
104
  state: State<Kind, Props, Out>;
100
105
  replace: () => never;
106
+ isReplacement?: boolean;
101
107
  }): Context<Out> {
102
108
  type InternalSymbols =
103
109
  | typeof ResourceID
@@ -133,6 +139,7 @@ export function context<
133
139
  output: state.output,
134
140
  props,
135
141
  replace,
142
+ isReplacement,
136
143
  get: (key: string) => state.data[key],
137
144
  set: async (key: string, value: any) => {
138
145
  state.data[key] = value;
package/src/destroy.ts CHANGED
@@ -119,6 +119,7 @@ export async function destroy<Type extends string>(
119
119
  seq: instance[ResourceSeq],
120
120
  props: options?.replace?.props ?? state.props,
121
121
  state,
122
+ isReplacement: false,
122
123
  replace: () => {
123
124
  throw new Error("Cannot replace a resource that is being deleted");
124
125
  },
package/src/docker/api.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import { spawn } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
2
5
  import { exec } from "../os/exec.ts";
3
6
 
4
7
  /**
@@ -9,6 +12,16 @@ export interface DockerApiOptions {
9
12
  * Custom path to Docker binary
10
13
  */
11
14
  dockerPath?: string;
15
+
16
+ /**
17
+ * Optional directory that will be used as the Docker CLI configuration
18
+ * directory (equivalent to setting the DOCKER_CONFIG environment variable).
19
+ *
20
+ * This makes authentication actions like `docker login` operate on an
21
+ * isolated credentials store which avoids race-conditions when multiple
22
+ * processes manipulate the default global config simultaneously.
23
+ */
24
+ configDir?: string;
12
25
  }
13
26
 
14
27
  type VolumeInfo = {
@@ -27,6 +40,8 @@ type VolumeInfo = {
27
40
  export class DockerApi {
28
41
  /** Path to Docker CLI */
29
42
  readonly dockerPath: string;
43
+ /** Directory to use for Docker CLI config */
44
+ readonly configDir?: string;
30
45
 
31
46
  /**
32
47
  * Create a new Docker API client
@@ -35,6 +50,7 @@ export class DockerApi {
35
50
  */
36
51
  constructor(options: DockerApiOptions = {}) {
37
52
  this.dockerPath = options.dockerPath || "docker";
53
+ this.configDir = options.configDir;
38
54
  }
39
55
 
40
56
  /**
@@ -44,11 +60,17 @@ export class DockerApi {
44
60
  * @returns Result of the command
45
61
  */
46
62
  async exec(args: string[]): Promise<{ stdout: string; stderr: string }> {
63
+ // If a custom config directory is provided, ensure all commands use it by
64
+ // setting the DOCKER_CONFIG env variable for the spawned process.
65
+ const env = this.configDir
66
+ ? { ...process.env, DOCKER_CONFIG: this.configDir }
67
+ : process.env;
68
+
47
69
  const command = `${this.dockerPath} ${args.join(" ")}`;
48
70
  const result = (await exec(command, {
49
71
  captureOutput: true,
50
72
  shell: true,
51
- env: process.env,
73
+ env,
52
74
  })) as { stdout: string; stderr: string };
53
75
 
54
76
  return result;
@@ -377,6 +399,27 @@ export class DockerApi {
377
399
  username: string,
378
400
  password: string,
379
401
  ): Promise<void> {
402
+ // If we have a custom config directory, write credentials directly to
403
+ // config.json to avoid race conditions with the global credential store
404
+ if (this.configDir) {
405
+ const authConfigPath = path.join(this.configDir, "config.json");
406
+ const authToken = Buffer.from(`${username}:${password}`).toString(
407
+ "base64",
408
+ );
409
+
410
+ const configJson = {
411
+ auths: {
412
+ [registry]: {
413
+ auth: authToken,
414
+ },
415
+ },
416
+ };
417
+
418
+ await fs.writeFile(authConfigPath, JSON.stringify(configJson));
419
+ return;
420
+ }
421
+
422
+ // Fallback to original docker login behavior for backwards compatibility
380
423
  return new Promise((resolve, reject) => {
381
424
  const args = [
382
425
  "login",
@@ -429,6 +472,19 @@ export class DockerApi {
429
472
  * @param registry Registry URL
430
473
  */
431
474
  async logout(registry: string): Promise<void> {
475
+ // If we have a custom config directory, we can just remove the auth entry
476
+ // or delete the config file entirely since it's isolated
477
+ if (this.configDir) {
478
+ try {
479
+ const authConfigPath = path.join(this.configDir, "config.json");
480
+ await fs.unlink(authConfigPath);
481
+ } catch {
482
+ // Ignore errors - file might not exist or already be deleted
483
+ }
484
+ return;
485
+ }
486
+
487
+ // Fallback to original docker logout behavior
432
488
  try {
433
489
  await this.exec(["logout", registry]);
434
490
  } catch (error) {
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import type { Context } from "../context.ts";
4
5
  import { Resource } from "../resource.ts";
@@ -31,7 +32,7 @@ export interface DockerBuildOptions {
31
32
  /**
32
33
  * Build arguments as key-value pairs
33
34
  */
34
- buildArgs?: Record<string, string>;
35
+ args?: Record<string, string>;
35
36
 
36
37
  /**
37
38
  * Target build stage in multi-stage builds
@@ -134,8 +135,16 @@ export const Image = Resource(
134
135
  id: string,
135
136
  props: ImageProps,
136
137
  ): Promise<Image> {
137
- // Initialize Docker API client
138
- const api = new DockerApi();
138
+ // Create a temporary directory that will act as an isolated Docker config
139
+ // (credentials) directory. This prevents race-conditions when multiple
140
+ // concurrent tests perform `docker login` / `logout` by ensuring each
141
+ // Image operation has its own credential store.
142
+ const tempConfigDir = await fs.mkdtemp(
143
+ path.join(os.tmpdir(), "docker-config-"),
144
+ );
145
+
146
+ // Initialize Docker API client with the isolated config directory
147
+ const api = new DockerApi({ configDir: tempConfigDir });
139
148
 
140
149
  if (this.phase === "delete") {
141
150
  // No action needed for delete as Docker images aren't automatically removed
@@ -166,7 +175,7 @@ export const Image = Resource(
166
175
  await fs.access(dockerfile);
167
176
 
168
177
  // Prepare build options
169
- const buildOptions: Record<string, string> = props.build?.buildArgs || {};
178
+ const buildOptions: Record<string, string> = props.build?.args || {};
170
179
 
171
180
  // Add platform if specified
172
181
  let buildArgs = ["build", "-t", imageRef];
@@ -223,7 +232,7 @@ export const Image = Resource(
223
232
  : `${registryHost}/${imageRef}`;
224
233
 
225
234
  try {
226
- // Authenticate to registry
235
+ // Authenticate to registry using the isolated config directory
227
236
  await api.login(registryHost, username, password.unencrypted);
228
237
 
229
238
  // Tag local image with fully qualified name if necessary
@@ -251,7 +260,7 @@ export const Image = Resource(
251
260
  // Update the final image reference to point at the pushed image
252
261
  finalImageRef = targetImage;
253
262
  } finally {
254
- // Always try to logout failures are non-fatal
263
+ // Clean up credentials from the isolated config
255
264
  await api.logout(registryHost);
256
265
  }
257
266
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type { AlchemyOptions, Phase } from "./alchemy.ts";
2
2
  export type * from "./context.ts";
3
+
3
4
  export * from "./resource.ts";
4
5
  export * from "./scope.ts";
5
6
  export * from "./secret.ts";