alchemy 0.85.2 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/docker/api.ts CHANGED
@@ -106,9 +106,21 @@ export type ContainerInfo = {
106
106
  Aliases: string[] | null;
107
107
  }
108
108
  > | null;
109
+ Ports?: Record<
110
+ string,
111
+ Array<{ HostIp: string; HostPort: string }> | null
112
+ > | null;
109
113
  };
110
114
  };
111
115
 
116
+ export type ContainerRuntimeInfo = {
117
+ /**
118
+ * Map of internal container ports to their bound host ports.
119
+ * Format: "internalPort/protocol" -> hostPort (number)
120
+ */
121
+ ports: Record<string, number>;
122
+ };
123
+
112
124
  /**
113
125
  * Docker API client that wraps Docker CLI commands
114
126
  */
@@ -1,6 +1,12 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { Resource } from "../resource.ts";
3
- import { DockerApi, normalizeDuration, type ContainerInfo } from "./api.ts";
3
+ import { Secret } from "../secret.ts";
4
+ import {
5
+ DockerApi,
6
+ normalizeDuration,
7
+ type ContainerInfo,
8
+ type ContainerRuntimeInfo,
9
+ } from "./api.ts";
4
10
  import type { Image } from "./image.ts";
5
11
  import type { RemoteImage } from "./remote-image.ts";
6
12
 
@@ -140,7 +146,7 @@ export interface ContainerProps {
140
146
  /**
141
147
  * Environment variables
142
148
  */
143
- environment?: Record<string, string>;
149
+ environment?: Record<string, string | Secret>;
144
150
 
145
151
  /**
146
152
  * Port mappings
@@ -207,6 +213,11 @@ export interface Container extends ContainerProps {
207
213
  * Time when the container was created
208
214
  */
209
215
  createdAt: number;
216
+
217
+ /**
218
+ * Inspect the container to get detailed information
219
+ */
220
+ inspect(): Promise<ContainerRuntimeInfo>;
210
221
  }
211
222
 
212
223
  /**
@@ -370,6 +381,13 @@ export const Container = Resource(
370
381
  name: containerName,
371
382
  state: containerState,
372
383
  createdAt: new Date(containerInfo.Created).getTime(),
384
+ inspect: async () => {
385
+ const [info] = await api.inspectContainer(containerName);
386
+ if (!info) {
387
+ throw new Error(`Container ${containerName} not found`);
388
+ }
389
+ return toRuntimeInfo(info);
390
+ },
373
391
  };
374
392
  }
375
393
  }
@@ -396,7 +414,7 @@ export const Container = Resource(
396
414
  // Create new container
397
415
  const containerId = await api.createContainer(imageRef, containerName, {
398
416
  ports: portMappings,
399
- env: props.environment,
417
+ env: normalizeEnvironment(props.environment),
400
418
  volumes: volumeMappings,
401
419
  cmd: props.command,
402
420
  healthcheck: props.healthcheck,
@@ -424,10 +442,46 @@ export const Container = Resource(
424
442
  name: containerName,
425
443
  state: containerState,
426
444
  createdAt: Date.now(),
445
+ inspect: async () => {
446
+ const [info] = await api.inspectContainer(containerName);
447
+ if (!info) {
448
+ throw new Error(`Container ${containerName} not found`);
449
+ }
450
+ return toRuntimeInfo(info);
451
+ },
427
452
  };
428
453
  },
429
454
  );
430
455
 
456
+ function toRuntimeInfo(info: ContainerInfo): ContainerRuntimeInfo {
457
+ const ports: Record<string, number> = {};
458
+ const networkSettings = info.NetworkSettings;
459
+
460
+ if (networkSettings?.Ports) {
461
+ for (const [internal, bindings] of Object.entries(networkSettings.Ports)) {
462
+ if (bindings && bindings.length > 0) {
463
+ ports[internal] = parseInt(bindings[0].HostPort, 10);
464
+ }
465
+ }
466
+ }
467
+
468
+ // Also check HostConfig.PortBindings as a fallback or additional source
469
+ // though NetworkSettings.Ports is usually the source of truth for running containers
470
+ if (info.HostConfig.PortBindings) {
471
+ for (const [internal, bindings] of Object.entries(
472
+ info.HostConfig.PortBindings,
473
+ )) {
474
+ if (bindings && bindings.length > 0 && !(internal in ports)) {
475
+ ports[internal] = parseInt(bindings[0].HostPort, 10);
476
+ }
477
+ }
478
+ }
479
+
480
+ return {
481
+ ports,
482
+ };
483
+ }
484
+
431
485
  function getNetworkChanges(
432
486
  props: ContainerProps,
433
487
  containerInfo: ContainerInfo,
@@ -477,7 +531,12 @@ function shouldReplace(
477
531
  }
478
532
 
479
533
  // Environment variables
480
- if (!compareEnv(props.environment, containerInfo.Config.Env)) {
534
+ if (
535
+ !compareEnv(
536
+ normalizeEnvironment(props.environment),
537
+ containerInfo.Config.Env,
538
+ )
539
+ ) {
481
540
  return true;
482
541
  }
483
542
 
@@ -513,6 +572,21 @@ function shouldReplace(
513
572
  return false;
514
573
  }
515
574
 
575
+ /**
576
+ * Unwrap secrets in given environment variables
577
+ * @internal
578
+ */
579
+ function normalizeEnvironment(
580
+ environment: Record<string, string | Secret> | undefined,
581
+ ): Record<string, string> {
582
+ return Object.fromEntries(
583
+ Object.entries(environment ?? {}).map(([key, value]) => [
584
+ key,
585
+ Secret.unwrap(value),
586
+ ]),
587
+ );
588
+ }
589
+
516
590
  /**
517
591
  * Normalize port mappings to a comparable format
518
592
  * @internal
@@ -83,7 +83,7 @@ const renderErrorHtml = (props) => `
83
83
  Alchemy</a>.</p>
84
84
  </div>
85
85
  <div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
86
- <p class="text-sm text-slate-500">Alchemy 0.85.2</p>
86
+ <p class="text-sm text-slate-500">Alchemy 0.87.0</p>
87
87
  </div>
88
88
  </div>
89
89
  </body>