@alchemy.run/docker 2.0.0-beta.84 → 2.0.0-beta.86
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/README.md +16 -0
- package/lib/Bootstrap.d.ts.map +1 -1
- package/lib/Bootstrap.js +2 -1
- package/lib/Bootstrap.js.map +1 -1
- package/lib/BuildCache.d.ts +87 -0
- package/lib/BuildCache.d.ts.map +1 -0
- package/lib/BuildCache.js +39 -0
- package/lib/BuildCache.js.map +1 -0
- package/lib/BuildHash.d.ts +20 -3
- package/lib/BuildHash.d.ts.map +1 -1
- package/lib/BuildHash.js +60 -21
- package/lib/BuildHash.js.map +1 -1
- package/lib/BuildPlan.d.ts +202 -0
- package/lib/BuildPlan.d.ts.map +1 -0
- package/lib/BuildPlan.js +514 -0
- package/lib/BuildPlan.js.map +1 -0
- package/lib/Container.d.ts +122 -1
- package/lib/Container.d.ts.map +1 -1
- package/lib/Container.js +232 -4
- package/lib/Container.js.map +1 -1
- package/lib/Context.d.ts +20 -1
- package/lib/Context.d.ts.map +1 -1
- package/lib/Context.js +15 -0
- package/lib/Context.js.map +1 -1
- package/lib/Docker.d.ts +113 -1
- package/lib/Docker.d.ts.map +1 -1
- package/lib/Docker.js +50 -8
- package/lib/Docker.js.map +1 -1
- package/lib/Dockerfile.d.ts +0 -2
- package/lib/Dockerfile.d.ts.map +1 -1
- package/lib/Dockerfile.js.map +1 -1
- package/lib/DockerfileParser.d.ts +125 -0
- package/lib/DockerfileParser.d.ts.map +1 -0
- package/lib/DockerfileParser.js +1040 -0
- package/lib/DockerfileParser.js.map +1 -0
- package/lib/HealthcheckCommand.d.ts.map +1 -1
- package/lib/HealthcheckCommand.js.map +1 -1
- package/lib/Image.d.ts +69 -13
- package/lib/Image.d.ts.map +1 -1
- package/lib/Image.js +59 -9
- package/lib/Image.js.map +1 -1
- package/lib/ImageArchive.d.ts +41 -0
- package/lib/ImageArchive.d.ts.map +1 -0
- package/lib/ImageArchive.js +54 -0
- package/lib/ImageArchive.js.map +1 -0
- package/lib/ImageSource.d.ts +151 -8
- package/lib/ImageSource.d.ts.map +1 -1
- package/lib/ImageSource.js +247 -72
- package/lib/ImageSource.js.map +1 -1
- package/lib/Network.d.ts +175 -3
- package/lib/Network.d.ts.map +1 -1
- package/lib/Network.js +296 -8
- package/lib/Network.js.map +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js.map +1 -1
- package/lib/Registry.d.ts.map +1 -1
- package/lib/Registry.js.map +1 -1
- package/lib/RegistryError.d.ts.map +1 -1
- package/lib/RegistryError.js.map +1 -1
- package/lib/RemoteImage.d.ts.map +1 -1
- package/lib/RemoteImage.js +1 -1
- package/lib/RemoteImage.js.map +1 -1
- package/lib/Service.d.ts +24 -3
- package/lib/Service.d.ts.map +1 -1
- package/lib/Service.js +154 -12
- package/lib/Service.js.map +1 -1
- package/lib/ServiceImage.d.ts.map +1 -1
- package/lib/ServiceImage.js.map +1 -1
- package/lib/ShellWord.d.ts +28 -0
- package/lib/ShellWord.d.ts.map +1 -0
- package/lib/ShellWord.js +301 -0
- package/lib/ShellWord.js.map +1 -0
- package/lib/Swarm.d.ts.map +1 -1
- package/lib/Swarm.js.map +1 -1
- package/lib/Volume.d.ts +24 -1
- package/lib/Volume.d.ts.map +1 -1
- package/lib/Volume.js +16 -0
- package/lib/Volume.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +9 -9
- package/src/Bootstrap.ts +2 -0
- package/src/BuildCache.ts +111 -0
- package/src/BuildHash.ts +85 -26
- package/src/BuildPlan.ts +758 -0
- package/src/Container.ts +361 -5
- package/src/Context.ts +27 -1
- package/src/Docker.ts +157 -9
- package/src/Dockerfile.ts +0 -2
- package/src/DockerfileParser.ts +1152 -0
- package/src/Image.ts +75 -15
- package/src/ImageArchive.ts +77 -0
- package/src/ImageSource.ts +391 -99
- package/src/Network.ts +432 -10
- package/src/RemoteImage.ts +1 -1
- package/src/Service.ts +246 -62
- package/src/ShellWord.ts +326 -0
- package/src/Volume.ts +33 -1
package/src/BuildHash.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as crypto from "node:crypto";
|
|
10
|
+
import { dotAlchemyDirectory } from "alchemy/AlchemyContext";
|
|
10
11
|
import {
|
|
11
12
|
type IgnoreRules,
|
|
12
13
|
listFileSystemDirectory,
|
|
@@ -14,12 +15,18 @@ import {
|
|
|
14
15
|
parseIgnoreRules,
|
|
15
16
|
walkIgnoring,
|
|
16
17
|
} from "alchemy/Util/Ignore";
|
|
18
|
+
import { initialCwd } from "alchemy/Util/Node";
|
|
19
|
+
import * as Data from "effect/Data";
|
|
17
20
|
import * as Effect from "effect/Effect";
|
|
18
21
|
import * as FileSystem from "effect/FileSystem";
|
|
19
22
|
import * as Path from "effect/Path";
|
|
20
|
-
import * as Result from "effect/Result";
|
|
21
23
|
import * as Stream from "effect/Stream";
|
|
22
24
|
|
|
25
|
+
export class BuildHashInvalidProps extends Data.TaggedError("BuildHashInvalidProps")<{
|
|
26
|
+
readonly message: string;
|
|
27
|
+
readonly cause?: unknown;
|
|
28
|
+
}> {}
|
|
29
|
+
|
|
23
30
|
export interface DockerBuildSource {
|
|
24
31
|
context: string;
|
|
25
32
|
dockerfile: string;
|
|
@@ -51,10 +58,14 @@ export const resolveDockerBuildPaths = Effect.fn(function* (
|
|
|
51
58
|
: path.resolve(context, source.dockerfile);
|
|
52
59
|
|
|
53
60
|
if (!(yield* fs.exists(context))) {
|
|
54
|
-
return yield*
|
|
61
|
+
return yield* new BuildHashInvalidProps({
|
|
62
|
+
message: `Docker build context does not exist: ${context}`,
|
|
63
|
+
});
|
|
55
64
|
}
|
|
56
65
|
if (!(yield* fs.exists(dockerfile))) {
|
|
57
|
-
return yield*
|
|
66
|
+
return yield* new BuildHashInvalidProps({
|
|
67
|
+
message: `Dockerfile does not exist: ${dockerfile}`,
|
|
68
|
+
});
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
return { context, dockerfile };
|
|
@@ -139,6 +150,25 @@ export const selectDockerBuildContext = Effect.fn(function* (
|
|
|
139
150
|
} satisfies DockerBuildContextSelection;
|
|
140
151
|
});
|
|
141
152
|
|
|
153
|
+
/**
|
|
154
|
+
* The context-relative path of Alchemy's `.alchemy` directory when it lies
|
|
155
|
+
* strictly inside `context`, otherwise `undefined`.
|
|
156
|
+
*/
|
|
157
|
+
const containedRuntimeDirectory = Effect.fn(function* (context: string) {
|
|
158
|
+
const path = yield* Path.Path;
|
|
159
|
+
const runtime = path.resolve(initialCwd, yield* dotAlchemyDirectory);
|
|
160
|
+
const relative = normalizeRelativePath(path.relative(context, runtime));
|
|
161
|
+
return relative === "" ||
|
|
162
|
+
relative === ".." ||
|
|
163
|
+
relative.startsWith("../") ||
|
|
164
|
+
path.isAbsolute(relative)
|
|
165
|
+
? undefined
|
|
166
|
+
: relative;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
/** Files below this size are read in one call instead of streamed. */
|
|
170
|
+
const SMALL_FILE_BYTES = 1024 * 1024;
|
|
171
|
+
|
|
142
172
|
/**
|
|
143
173
|
* Hash a Docker build context together with the Dockerfile, platform, and
|
|
144
174
|
* build arguments. In `effective` mode, the selected `.dockerignore` is
|
|
@@ -148,6 +178,15 @@ export const selectDockerBuildContext = Effect.fn(function* (
|
|
|
148
178
|
export const hashDockerBuildInputs = Effect.fn(function* (
|
|
149
179
|
source: DockerBuildSource,
|
|
150
180
|
mode: DockerBuildHashMode,
|
|
181
|
+
options: {
|
|
182
|
+
/**
|
|
183
|
+
* Directory names left out of the hash at any depth, on top of
|
|
184
|
+
* `.dockerignore` and `.alchemy` — e.g. `.git` and `node_modules` for a
|
|
185
|
+
* dev watcher that must not restart on git's index writes or re-hash
|
|
186
|
+
* installed dependencies on every edit.
|
|
187
|
+
*/
|
|
188
|
+
readonly excludeNames?: ReadonlyArray<string>;
|
|
189
|
+
} = {},
|
|
151
190
|
) {
|
|
152
191
|
const fs = yield* FileSystem.FileSystem;
|
|
153
192
|
const path = yield* Path.Path;
|
|
@@ -187,15 +226,35 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
187
226
|
(entry): entry is string =>
|
|
188
227
|
entry !== undefined && entry !== ".." && !entry.startsWith("../") && !path.isAbsolute(entry),
|
|
189
228
|
);
|
|
229
|
+
// Alchemy writes run logs and local state to `.alchemy` on every run. When a
|
|
230
|
+
// context contains it (e.g. `context: "."` at the project root), hashing it
|
|
231
|
+
// would give the image a new tag on every deploy. A context inside
|
|
232
|
+
// `.alchemy` (a generated one) is still hashed in full.
|
|
233
|
+
const runtimeDirectory = yield* containedRuntimeDirectory(context);
|
|
234
|
+
const excludedNames = new Set(options.excludeNames ?? []);
|
|
235
|
+
const listDirectory = yield* listFileSystemDirectory(context);
|
|
190
236
|
// Walk one level at a time so an excluded directory is discarded before
|
|
191
237
|
// its contents are read; entries come back sorted, preserving hash order.
|
|
192
238
|
const entries = yield* walkIgnoring({
|
|
193
|
-
list:
|
|
239
|
+
list:
|
|
240
|
+
runtimeDirectory === undefined && excludedNames.size === 0
|
|
241
|
+
? listDirectory
|
|
242
|
+
: (directory: string) =>
|
|
243
|
+
listDirectory(directory).pipe(
|
|
244
|
+
Effect.map((children) =>
|
|
245
|
+
children.filter(
|
|
246
|
+
(child) =>
|
|
247
|
+
!(child.type === "Directory" && excludedNames.has(child.name)) &&
|
|
248
|
+
(directory.length === 0 ? child.name : `${directory}/${child.name}`) !==
|
|
249
|
+
runtimeDirectory,
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
),
|
|
194
253
|
rules: dockerignore?.rules,
|
|
195
254
|
force: forcedPaths,
|
|
196
255
|
});
|
|
197
256
|
|
|
198
|
-
for (const { path: entry } of entries) {
|
|
257
|
+
for (const { path: entry, type } of entries) {
|
|
199
258
|
// The ignore file and Dockerfile contents are hashed above; excluded
|
|
200
259
|
// entries were only walked so re-included descendants could be found.
|
|
201
260
|
if (
|
|
@@ -206,20 +265,14 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
206
265
|
}
|
|
207
266
|
|
|
208
267
|
const fullPath = path.join(context, entry);
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
268
|
+
// Docker preserves a symbolic link in the build context; hashing the
|
|
269
|
+
// target file would miss retargets between files with identical
|
|
270
|
+
// contents and metadata. The walk reports the entry's own type, so only
|
|
271
|
+
// links are read as links.
|
|
272
|
+
if (type === "SymbolicLink") {
|
|
273
|
+
const target = yield* fs.readLink(fullPath);
|
|
215
274
|
yield* Effect.sync(() =>
|
|
216
|
-
hasher.update(
|
|
217
|
-
`${JSON.stringify({
|
|
218
|
-
path: hashedEntry,
|
|
219
|
-
type: "SymbolicLink",
|
|
220
|
-
target: link.success,
|
|
221
|
-
})}\0`,
|
|
222
|
-
),
|
|
275
|
+
hasher.update(`${JSON.stringify({ path: entry, type: "SymbolicLink", target })}\0`),
|
|
223
276
|
);
|
|
224
277
|
continue;
|
|
225
278
|
}
|
|
@@ -232,7 +285,7 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
232
285
|
yield* Effect.sync(() =>
|
|
233
286
|
hasher.update(
|
|
234
287
|
`${JSON.stringify({
|
|
235
|
-
path:
|
|
288
|
+
path: entry,
|
|
236
289
|
type: info.type,
|
|
237
290
|
mode: info.mode & 0o7777,
|
|
238
291
|
size: info.type === "File" ? String(info.size) : undefined,
|
|
@@ -241,13 +294,19 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
241
294
|
);
|
|
242
295
|
|
|
243
296
|
if (info.type === "File") {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)
|
|
250
|
-
|
|
297
|
+
// Read small files whole (one call); stream large ones so memory stays flat.
|
|
298
|
+
if (info.size < SMALL_FILE_BYTES) {
|
|
299
|
+
const bytes = yield* fs.readFile(fullPath);
|
|
300
|
+
yield* Effect.sync(() => hasher.update(bytes));
|
|
301
|
+
} else {
|
|
302
|
+
yield* fs.stream(fullPath).pipe(
|
|
303
|
+
Stream.runForEach((chunk) =>
|
|
304
|
+
Effect.sync(() => {
|
|
305
|
+
hasher.update(chunk);
|
|
306
|
+
}),
|
|
307
|
+
),
|
|
308
|
+
);
|
|
309
|
+
}
|
|
251
310
|
}
|
|
252
311
|
}
|
|
253
312
|
|