alchemy 0.35.0 → 0.36.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/bin/alchemy.mjs +45102 -45100
- package/bin/alchemy.ts +1 -1
- package/bin/create-alchemy.ts +10 -4
- package/lib/cloudflare/bundle/bundle-worker.js +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/docker/api.d.ts +185 -0
- package/lib/docker/api.d.ts.map +1 -0
- package/lib/docker/api.js +288 -0
- package/lib/docker/api.js.map +1 -0
- package/lib/docker/container.d.ts +147 -0
- package/lib/docker/container.d.ts.map +1 -0
- package/lib/docker/container.js +109 -0
- package/lib/docker/container.js.map +1 -0
- package/lib/docker/image.d.ts +92 -0
- package/lib/docker/image.d.ts.map +1 -0
- package/lib/docker/image.js +92 -0
- package/lib/docker/image.js.map +1 -0
- package/lib/docker/index.d.ts +7 -0
- package/lib/docker/index.d.ts.map +1 -0
- package/lib/docker/index.js +7 -0
- package/lib/docker/index.js.map +1 -0
- package/lib/docker/network.d.ts +62 -0
- package/lib/docker/network.d.ts.map +1 -0
- package/lib/docker/network.js +49 -0
- package/lib/docker/network.js.map +1 -0
- package/lib/docker/remote-image.d.ts +45 -0
- package/lib/docker/remote-image.d.ts.map +1 -0
- package/lib/docker/remote-image.js +36 -0
- package/lib/docker/remote-image.js.map +1 -0
- package/lib/docker/volume.d.ts +83 -0
- package/lib/docker/volume.d.ts.map +1 -0
- package/lib/docker/volume.js +76 -0
- package/lib/docker/volume.js.map +1 -0
- package/lib/fs/file-system-state-store.d.ts.map +1 -1
- package/lib/fs/file-system-state-store.js +5 -1
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/fs/file.d.ts.map +1 -1
- package/lib/fs/file.js +6 -3
- package/lib/fs/file.js.map +1 -1
- package/lib/os/exec.d.ts +19 -1
- package/lib/os/exec.d.ts.map +1 -1
- package/lib/os/exec.js +28 -4
- package/lib/os/exec.js.map +1 -1
- package/lib/util/cli.d.ts.map +1 -1
- package/lib/util/cli.js +1 -1
- package/lib/util/cli.js.map +1 -1
- package/package.json +5 -1
- package/src/cloudflare/bundle/bundle-worker.ts +1 -1
- package/src/docker/api.ts +365 -0
- package/src/docker/container.ts +267 -0
- package/src/docker/image.ts +200 -0
- package/src/docker/index.ts +6 -0
- package/src/docker/network.ts +101 -0
- package/src/docker/remote-image.ts +83 -0
- package/src/docker/volume.ts +154 -0
- package/src/fs/file-system-state-store.ts +5 -1
- package/src/fs/file.ts +6 -3
- package/src/os/exec.ts +48 -6
- package/src/util/cli.ts +1 -3
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { Context } from "../context.ts";
|
|
4
|
+
import { Resource } from "../resource.ts";
|
|
5
|
+
import { DockerApi } from "./api.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for building a Docker image
|
|
9
|
+
*/
|
|
10
|
+
export interface DockerBuildOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Path to the build context directory
|
|
13
|
+
*/
|
|
14
|
+
context: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Path to the Dockerfile, relative to context
|
|
18
|
+
*/
|
|
19
|
+
dockerfile?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Target build platform (e.g., linux/amd64)
|
|
23
|
+
*/
|
|
24
|
+
platform?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build arguments as key-value pairs
|
|
28
|
+
*/
|
|
29
|
+
buildArgs?: Record<string, string>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Target build stage in multi-stage builds
|
|
33
|
+
*/
|
|
34
|
+
target?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List of images to use for cache
|
|
38
|
+
*/
|
|
39
|
+
cacheFrom?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Properties for creating a Docker image
|
|
44
|
+
*/
|
|
45
|
+
export interface ImageProps {
|
|
46
|
+
/**
|
|
47
|
+
* Repository name for the image (e.g., "username/image")
|
|
48
|
+
*/
|
|
49
|
+
name: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Tag for the image (e.g., "latest")
|
|
53
|
+
*/
|
|
54
|
+
tag?: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build configuration
|
|
58
|
+
*/
|
|
59
|
+
build: DockerBuildOptions;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether to skip pushing the image to registry
|
|
63
|
+
*/
|
|
64
|
+
skipPush?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Docker Image resource
|
|
69
|
+
*/
|
|
70
|
+
export interface Image extends Resource<"docker::Image">, ImageProps {
|
|
71
|
+
/**
|
|
72
|
+
* Full image reference (name:tag)
|
|
73
|
+
*/
|
|
74
|
+
imageRef: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Image ID
|
|
78
|
+
*/
|
|
79
|
+
imageId?: string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Repository digest if pushed
|
|
83
|
+
*/
|
|
84
|
+
repoDigest?: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Time when the image was built
|
|
88
|
+
*/
|
|
89
|
+
builtAt: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build and manage a Docker image from a Dockerfile
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* // Build a Docker image from a Dockerfile
|
|
97
|
+
* const appImage = await Image("app-image", {
|
|
98
|
+
* name: "myapp",
|
|
99
|
+
* tag: "latest",
|
|
100
|
+
* build: {
|
|
101
|
+
* context: "./app",
|
|
102
|
+
* dockerfile: "Dockerfile",
|
|
103
|
+
* buildArgs: {
|
|
104
|
+
* NODE_ENV: "production"
|
|
105
|
+
* }
|
|
106
|
+
* }
|
|
107
|
+
* });
|
|
108
|
+
*/
|
|
109
|
+
export const Image = Resource(
|
|
110
|
+
"docker::Image",
|
|
111
|
+
async function (
|
|
112
|
+
this: Context<Image>,
|
|
113
|
+
_id: string,
|
|
114
|
+
props: ImageProps,
|
|
115
|
+
): Promise<Image> {
|
|
116
|
+
// Initialize Docker API client
|
|
117
|
+
const api = new DockerApi();
|
|
118
|
+
|
|
119
|
+
if (this.phase === "delete") {
|
|
120
|
+
// No action needed for delete as Docker images aren't automatically removed
|
|
121
|
+
// This is intentional as other resources might depend on the same image
|
|
122
|
+
return this.destroy();
|
|
123
|
+
} else {
|
|
124
|
+
// Normalize properties
|
|
125
|
+
const tag = props.tag || "latest";
|
|
126
|
+
const imageRef = `${props.name}:${tag}`;
|
|
127
|
+
|
|
128
|
+
// Validate build context
|
|
129
|
+
const { context } = props.build;
|
|
130
|
+
await fs.access(context);
|
|
131
|
+
|
|
132
|
+
// Determine Dockerfile path
|
|
133
|
+
const dockerfile = props.build.dockerfile || "Dockerfile";
|
|
134
|
+
const dockerfilePath = path.join(context, dockerfile);
|
|
135
|
+
await fs.access(dockerfilePath);
|
|
136
|
+
|
|
137
|
+
// Prepare build options
|
|
138
|
+
const buildOptions: Record<string, string> = props.build.buildArgs || {};
|
|
139
|
+
|
|
140
|
+
// Add platform if specified
|
|
141
|
+
let buildArgs = ["build", "-t", imageRef];
|
|
142
|
+
|
|
143
|
+
if (props.build.platform) {
|
|
144
|
+
buildArgs.push("--platform", props.build.platform);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Add target if specified
|
|
148
|
+
if (props.build.target) {
|
|
149
|
+
buildArgs.push("--target", props.build.target);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Add cache sources if specified
|
|
153
|
+
if (props.build.cacheFrom && props.build.cacheFrom.length > 0) {
|
|
154
|
+
for (const cacheSource of props.build.cacheFrom) {
|
|
155
|
+
buildArgs.push("--cache-from", cacheSource);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add build arguments
|
|
160
|
+
for (const [key, value] of Object.entries(buildOptions)) {
|
|
161
|
+
buildArgs.push("--build-arg", `${key}="${value}"`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Add dockerfile if not the default
|
|
165
|
+
if (props.build.dockerfile && props.build.dockerfile !== "Dockerfile") {
|
|
166
|
+
buildArgs.push("-f", props.build.dockerfile);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Add context path
|
|
170
|
+
buildArgs.push(props.build.context);
|
|
171
|
+
|
|
172
|
+
// Execute build command
|
|
173
|
+
console.log(`Building Docker image: ${imageRef}`);
|
|
174
|
+
const { stdout } = await api.exec(buildArgs);
|
|
175
|
+
|
|
176
|
+
// Extract image ID from build output if available
|
|
177
|
+
const imageIdMatch = /Successfully built ([a-f0-9]+)/.exec(stdout);
|
|
178
|
+
const imageId = imageIdMatch ? imageIdMatch[1] : undefined;
|
|
179
|
+
|
|
180
|
+
console.log(`Successfully built Docker image: ${imageRef}`);
|
|
181
|
+
|
|
182
|
+
// Handle push if required
|
|
183
|
+
let repoDigest: string | undefined;
|
|
184
|
+
if (!props.skipPush) {
|
|
185
|
+
console.log(`Pushing Docker image: ${imageRef}`);
|
|
186
|
+
// TODO: Implement push once API supports it
|
|
187
|
+
console.warn("Image pushing is not yet implemented");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Return the resource using this() to construct output
|
|
191
|
+
return this({
|
|
192
|
+
...props,
|
|
193
|
+
imageRef,
|
|
194
|
+
imageId,
|
|
195
|
+
repoDigest,
|
|
196
|
+
builtAt: Date.now(),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Context } from "../context.ts";
|
|
2
|
+
import { Resource } from "../resource.ts";
|
|
3
|
+
import { DockerApi } from "./api.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties for creating a Docker network
|
|
7
|
+
*/
|
|
8
|
+
export interface NetworkProps {
|
|
9
|
+
/**
|
|
10
|
+
* Network name
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Network driver to use
|
|
16
|
+
* @default "bridge"
|
|
17
|
+
*/
|
|
18
|
+
driver?: "bridge" | "host" | "none" | "overlay" | "macvlan" | (string & {});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Enable IPv6 on the network
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
enableIPv6?: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Network-scoped alias for containers
|
|
28
|
+
*/
|
|
29
|
+
labels?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Docker Network resource
|
|
34
|
+
*/
|
|
35
|
+
export interface Network extends Resource<"docker::Network">, NetworkProps {
|
|
36
|
+
/**
|
|
37
|
+
* Network ID
|
|
38
|
+
*/
|
|
39
|
+
id: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Time when the network was created
|
|
43
|
+
*/
|
|
44
|
+
createdAt: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create and manage a Docker Network
|
|
49
|
+
*
|
|
50
|
+
* @see https://docs.docker.com/engine/network/
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Create a simple bridge network
|
|
54
|
+
* const appNetwork = await Network("app-network", {
|
|
55
|
+
* name: "app-network"
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* // Create a custom network with driver
|
|
60
|
+
* const overlayNetwork = await Network("overlay-network", {
|
|
61
|
+
* name: "overlay-network",
|
|
62
|
+
* driver: "overlay",
|
|
63
|
+
* enableIPv6: true,
|
|
64
|
+
* labels: {
|
|
65
|
+
* "com.example.description": "Network for application services"
|
|
66
|
+
* }
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
export const Network = Resource(
|
|
70
|
+
"docker::Network",
|
|
71
|
+
async function (
|
|
72
|
+
this: Context<Network>,
|
|
73
|
+
_id: string,
|
|
74
|
+
props: NetworkProps,
|
|
75
|
+
): Promise<Network> {
|
|
76
|
+
// Initialize Docker API client
|
|
77
|
+
const api = new DockerApi();
|
|
78
|
+
|
|
79
|
+
// Handle delete phase
|
|
80
|
+
if (this.phase === "delete") {
|
|
81
|
+
if (this.output?.id) {
|
|
82
|
+
// Remove network
|
|
83
|
+
await api.removeNetwork(this.output.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Return destroyed state
|
|
87
|
+
return this.destroy();
|
|
88
|
+
} else {
|
|
89
|
+
// Create the network
|
|
90
|
+
props.driver = props.driver || "bridge";
|
|
91
|
+
const networkId = await api.createNetwork(props.name, props.driver);
|
|
92
|
+
|
|
93
|
+
// Return the resource using this() to construct output
|
|
94
|
+
return this({
|
|
95
|
+
...props,
|
|
96
|
+
id: networkId,
|
|
97
|
+
createdAt: Date.now(),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Context } from "../context.ts";
|
|
2
|
+
import { Resource } from "../resource.ts";
|
|
3
|
+
import { DockerApi } from "./api.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties for creating a Docker image
|
|
7
|
+
*/
|
|
8
|
+
export interface RemoteImageProps {
|
|
9
|
+
/**
|
|
10
|
+
* Docker image name (e.g., "nginx")
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tag for the image (e.g., "latest" or "1.19-alpine")
|
|
16
|
+
*/
|
|
17
|
+
tag?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Always attempt to pull the image, even if it exists locally
|
|
21
|
+
*/
|
|
22
|
+
alwaysPull?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Docker Remote Image resource
|
|
27
|
+
*/
|
|
28
|
+
export interface RemoteImage
|
|
29
|
+
extends Resource<"docker::RemoteImage">,
|
|
30
|
+
RemoteImageProps {
|
|
31
|
+
/**
|
|
32
|
+
* Full image reference (name:tag)
|
|
33
|
+
*/
|
|
34
|
+
imageRef: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Time when the image was created or pulled
|
|
38
|
+
*/
|
|
39
|
+
createdAt: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create or reference a Docker Remote Image
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Pull the nginx image
|
|
47
|
+
* const nginxImage = await RemoteImage("nginx", {
|
|
48
|
+
* name: "nginx",
|
|
49
|
+
* tag: "latest"
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
*/
|
|
53
|
+
export const RemoteImage = Resource(
|
|
54
|
+
"docker::RemoteImage",
|
|
55
|
+
async function (
|
|
56
|
+
this: Context<RemoteImage>,
|
|
57
|
+
_id: string,
|
|
58
|
+
props: RemoteImageProps,
|
|
59
|
+
): Promise<RemoteImage> {
|
|
60
|
+
// Initialize Docker API client
|
|
61
|
+
const api = new DockerApi();
|
|
62
|
+
|
|
63
|
+
if (this.phase === "delete") {
|
|
64
|
+
// No action needed for delete as Docker images aren't automatically removed
|
|
65
|
+
// This is intentional as other resources might depend on the same image
|
|
66
|
+
return this.destroy();
|
|
67
|
+
} else {
|
|
68
|
+
// Normalize properties
|
|
69
|
+
const tag = props.tag || "latest";
|
|
70
|
+
const imageRef = `${props.name}:${tag}`;
|
|
71
|
+
|
|
72
|
+
// Pull image
|
|
73
|
+
await api.pullImage(imageRef);
|
|
74
|
+
|
|
75
|
+
// Return the resource using this() to construct output
|
|
76
|
+
return this({
|
|
77
|
+
...props,
|
|
78
|
+
imageRef,
|
|
79
|
+
createdAt: Date.now(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Context } from "../context.ts";
|
|
2
|
+
import { Resource } from "../resource.ts";
|
|
3
|
+
import { DockerApi } from "./api.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface for volume label
|
|
7
|
+
*/
|
|
8
|
+
export interface VolumeLabel {
|
|
9
|
+
/**
|
|
10
|
+
* Label name
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Label value
|
|
16
|
+
*/
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Properties for creating a Docker volume
|
|
22
|
+
*/
|
|
23
|
+
export interface VolumeProps {
|
|
24
|
+
/**
|
|
25
|
+
* Volume name
|
|
26
|
+
*/
|
|
27
|
+
name: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Volume driver to use
|
|
31
|
+
* @default "local"
|
|
32
|
+
*/
|
|
33
|
+
driver?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Driver-specific options
|
|
37
|
+
*/
|
|
38
|
+
driverOpts?: Record<string, string>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Custom metadata labels for the volume
|
|
42
|
+
*/
|
|
43
|
+
labels?: VolumeLabel[] | Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Docker Volume resource
|
|
48
|
+
*/
|
|
49
|
+
export interface Volume extends Resource<"docker::Volume">, VolumeProps {
|
|
50
|
+
/**
|
|
51
|
+
* Volume ID (same as name for Docker volumes)
|
|
52
|
+
*/
|
|
53
|
+
id: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Volume mountpoint path on the host
|
|
57
|
+
*/
|
|
58
|
+
mountpoint?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Time when the volume was created
|
|
62
|
+
*/
|
|
63
|
+
createdAt: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create and manage a Docker Volume
|
|
68
|
+
*
|
|
69
|
+
* @see https://docs.docker.com/engine/reference/commandline/volume/
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Create a simple Docker volume
|
|
73
|
+
* const dataVolume = await Volume("data-volume", {
|
|
74
|
+
* name: "data-volume"
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Create a Docker volume with custom driver and options
|
|
79
|
+
* const dbVolume = await Volume("db-data", {
|
|
80
|
+
* name: "db-data",
|
|
81
|
+
* driver: "local",
|
|
82
|
+
* driverOpts: {
|
|
83
|
+
* "type": "nfs",
|
|
84
|
+
* "o": "addr=10.0.0.1,rw",
|
|
85
|
+
* "device": ":/path/to/dir"
|
|
86
|
+
* },
|
|
87
|
+
* labels: [
|
|
88
|
+
* { name: "com.example.usage", value: "database-storage" },
|
|
89
|
+
* { name: "com.example.backup", value: "weekly" }
|
|
90
|
+
* ]
|
|
91
|
+
* });
|
|
92
|
+
*/
|
|
93
|
+
export const Volume = Resource(
|
|
94
|
+
"docker::Volume",
|
|
95
|
+
async function (
|
|
96
|
+
this: Context<Volume>,
|
|
97
|
+
_id: string,
|
|
98
|
+
props: VolumeProps,
|
|
99
|
+
): Promise<Volume> {
|
|
100
|
+
// Initialize Docker API client
|
|
101
|
+
const api = new DockerApi();
|
|
102
|
+
|
|
103
|
+
// Process labels to ensure consistent format
|
|
104
|
+
const processedLabels: Record<string, string> = {};
|
|
105
|
+
if (props.labels) {
|
|
106
|
+
if (Array.isArray(props.labels)) {
|
|
107
|
+
// Convert array of label objects to Record
|
|
108
|
+
for (const label of props.labels) {
|
|
109
|
+
processedLabels[label.name] = label.value;
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// Use Record directly
|
|
113
|
+
Object.assign(processedLabels, props.labels);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle delete phase
|
|
118
|
+
if (this.phase === "delete") {
|
|
119
|
+
if (this.output?.name) {
|
|
120
|
+
// Remove volume
|
|
121
|
+
await api.removeVolume(this.output.name);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Return destroyed state
|
|
125
|
+
return this.destroy();
|
|
126
|
+
} else {
|
|
127
|
+
// Set default driver if not provided
|
|
128
|
+
props.driver = props.driver || "local";
|
|
129
|
+
const driverOpts = props.driverOpts || {};
|
|
130
|
+
|
|
131
|
+
// Create the volume
|
|
132
|
+
const volumeName = await api.createVolume(
|
|
133
|
+
props.name,
|
|
134
|
+
props.driver,
|
|
135
|
+
driverOpts,
|
|
136
|
+
processedLabels,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Get volume details to retrieve mountpoint
|
|
140
|
+
const volumeInfos = await api.inspectVolume(volumeName);
|
|
141
|
+
const mountpoint = volumeInfos[0].Mountpoint;
|
|
142
|
+
|
|
143
|
+
// Return the resource using this() to construct output
|
|
144
|
+
return this({
|
|
145
|
+
...props,
|
|
146
|
+
id: volumeName,
|
|
147
|
+
mountpoint,
|
|
148
|
+
createdAt: Date.now(),
|
|
149
|
+
labels: Array.isArray(props.labels) ? props.labels : undefined,
|
|
150
|
+
driverOpts: props.driverOpts,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
);
|
|
@@ -7,6 +7,7 @@ import { deserializeState, type State, type StateStore } from "../state.ts";
|
|
|
7
7
|
import { ignore } from "../util/ignore.ts";
|
|
8
8
|
|
|
9
9
|
const stateRootDir = path.join(process.cwd(), ".alchemy");
|
|
10
|
+
const ALCHEMY_SEPERATOR_CHAR = process.platform === "win32" ? "-" : ":";
|
|
10
11
|
|
|
11
12
|
export class FileSystemStateStore implements StateStore {
|
|
12
13
|
public readonly dir: string;
|
|
@@ -128,7 +129,10 @@ export class FileSystemStateStore implements StateStore {
|
|
|
128
129
|
throw new Error(`ID cannot include colons: ${key}`);
|
|
129
130
|
}
|
|
130
131
|
if (key.includes("/")) {
|
|
131
|
-
|
|
132
|
+
//todo(michael): remove this next time we do a breaking change
|
|
133
|
+
//* windows doesn't support ":" in file paths, but we already use ":"
|
|
134
|
+
//* so now we use both to prevent breaking changes`
|
|
135
|
+
key = key.replaceAll("/", ALCHEMY_SEPERATOR_CHAR);
|
|
132
136
|
}
|
|
133
137
|
return path.join(this.dir, `${key}.json`);
|
|
134
138
|
}
|
package/src/fs/file.ts
CHANGED
|
@@ -184,9 +184,12 @@ export const File = Resource(
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// Create directory and write file
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
const dirName = path.dirname(filePath);
|
|
188
|
+
if (dirName !== ".") {
|
|
189
|
+
await fs.promises.mkdir(dirName, {
|
|
190
|
+
recursive: true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
190
193
|
|
|
191
194
|
await fs.promises.writeFile(filePath, props.content);
|
|
192
195
|
|
package/src/os/exec.ts
CHANGED
|
@@ -266,30 +266,72 @@ const defaultOptions: SpawnOptions = {
|
|
|
266
266
|
shell: true,
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Options for exec function
|
|
271
|
+
*/
|
|
272
|
+
export interface ExecOptions extends Partial<SpawnOptions> {
|
|
273
|
+
/**
|
|
274
|
+
* Whether to capture stdout and stderr
|
|
275
|
+
* @default false
|
|
276
|
+
*/
|
|
277
|
+
captureOutput?: boolean;
|
|
278
|
+
}
|
|
279
|
+
|
|
269
280
|
/**
|
|
270
281
|
* Execute a shell command.
|
|
282
|
+
*
|
|
283
|
+
* @param command The command to execute
|
|
284
|
+
* @param options Options for the command execution
|
|
285
|
+
* @returns Promise that resolves when the command completes.
|
|
286
|
+
* If captureOutput is true, resolves with { stdout, stderr } strings.
|
|
271
287
|
*/
|
|
272
288
|
export async function exec(
|
|
273
289
|
command: string,
|
|
274
|
-
options?:
|
|
275
|
-
): Promise<
|
|
290
|
+
options?: ExecOptions,
|
|
291
|
+
): Promise<{ stdout: string; stderr: string } | undefined> {
|
|
276
292
|
const [cmd, ...args] = command.split(/\s+/);
|
|
293
|
+
const captureOutput = options?.captureOutput === true;
|
|
277
294
|
|
|
278
295
|
return new Promise((resolve, reject) => {
|
|
279
|
-
|
|
296
|
+
// Set stdio to pipe only if we're capturing output
|
|
297
|
+
const spawnOptions = {
|
|
280
298
|
...defaultOptions,
|
|
281
299
|
...options,
|
|
282
300
|
env: {
|
|
283
301
|
...defaultOptions.env,
|
|
284
302
|
...options?.env,
|
|
285
303
|
},
|
|
286
|
-
|
|
304
|
+
stdio: captureOutput ? "pipe" : defaultOptions.stdio,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const child = spawn(cmd, args, spawnOptions);
|
|
308
|
+
|
|
309
|
+
let stdout = "";
|
|
310
|
+
let stderr = "";
|
|
311
|
+
|
|
312
|
+
if (captureOutput) {
|
|
313
|
+
child.stdout?.on("data", (data) => {
|
|
314
|
+
stdout += data.toString();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
child.stderr?.on("data", (data) => {
|
|
318
|
+
stderr += data.toString();
|
|
319
|
+
});
|
|
320
|
+
}
|
|
287
321
|
|
|
288
322
|
child.on("close", (code) => {
|
|
289
323
|
if (code === 0) {
|
|
290
|
-
|
|
324
|
+
if (captureOutput) {
|
|
325
|
+
resolve({ stdout, stderr });
|
|
326
|
+
} else {
|
|
327
|
+
resolve(undefined);
|
|
328
|
+
}
|
|
291
329
|
} else {
|
|
292
|
-
reject(
|
|
330
|
+
reject(
|
|
331
|
+
new Error(
|
|
332
|
+
`Command failed with exit code ${code}${stderr ? `: ${stderr}` : ""}`,
|
|
333
|
+
),
|
|
334
|
+
);
|
|
293
335
|
}
|
|
294
336
|
});
|
|
295
337
|
|
package/src/util/cli.ts
CHANGED
|
@@ -17,9 +17,7 @@ type ColorName = keyof typeof colors;
|
|
|
17
17
|
|
|
18
18
|
// Check if colors should be disabled
|
|
19
19
|
const shouldDisableColors = (): boolean => {
|
|
20
|
-
return Boolean(
|
|
21
|
-
process.env.CI || process.env.NO_COLOR || !process.stdout.isTTY,
|
|
22
|
-
);
|
|
20
|
+
return Boolean(process.env.NO_COLOR);
|
|
23
21
|
};
|
|
24
22
|
|
|
25
23
|
// Apply color if colors are enabled
|