@alchemy.run/docker 2.0.0-beta.85 → 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 +9 -1
- package/lib/BuildHash.d.ts.map +1 -1
- package/lib/BuildHash.js +51 -19
- 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 +24 -1
- package/lib/Container.d.ts.map +1 -1
- package/lib/Container.js +14 -0
- 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 +33 -0
- package/lib/Docker.d.ts.map +1 -1
- package/lib/Docker.js +23 -2
- 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 +40 -12
- package/lib/Image.d.ts.map +1 -1
- package/lib/Image.js +41 -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 +100 -11
- package/lib/ImageSource.d.ts.map +1 -1
- package/lib/ImageSource.js +219 -73
- 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.map +1 -1
- package/lib/Service.d.ts.map +1 -1
- 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 +73 -24
- package/src/BuildPlan.ts +758 -0
- package/src/Container.ts +31 -1
- package/src/Context.ts +27 -1
- package/src/Docker.ts +56 -2
- package/src/Dockerfile.ts +0 -2
- package/src/DockerfileParser.ts +1152 -0
- package/src/Image.ts +45 -14
- package/src/ImageArchive.ts +77 -0
- package/src/ImageSource.ts +322 -103
- package/src/Network.ts +432 -10
- 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,11 +15,11 @@ import {
|
|
|
14
15
|
parseIgnoreRules,
|
|
15
16
|
walkIgnoring,
|
|
16
17
|
} from "alchemy/Util/Ignore";
|
|
18
|
+
import { initialCwd } from "alchemy/Util/Node";
|
|
17
19
|
import * as Data from "effect/Data";
|
|
18
20
|
import * as Effect from "effect/Effect";
|
|
19
21
|
import * as FileSystem from "effect/FileSystem";
|
|
20
22
|
import * as Path from "effect/Path";
|
|
21
|
-
import * as Result from "effect/Result";
|
|
22
23
|
import * as Stream from "effect/Stream";
|
|
23
24
|
|
|
24
25
|
export class BuildHashInvalidProps extends Data.TaggedError("BuildHashInvalidProps")<{
|
|
@@ -149,6 +150,25 @@ export const selectDockerBuildContext = Effect.fn(function* (
|
|
|
149
150
|
} satisfies DockerBuildContextSelection;
|
|
150
151
|
});
|
|
151
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
|
+
|
|
152
172
|
/**
|
|
153
173
|
* Hash a Docker build context together with the Dockerfile, platform, and
|
|
154
174
|
* build arguments. In `effective` mode, the selected `.dockerignore` is
|
|
@@ -158,6 +178,15 @@ export const selectDockerBuildContext = Effect.fn(function* (
|
|
|
158
178
|
export const hashDockerBuildInputs = Effect.fn(function* (
|
|
159
179
|
source: DockerBuildSource,
|
|
160
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
|
+
} = {},
|
|
161
190
|
) {
|
|
162
191
|
const fs = yield* FileSystem.FileSystem;
|
|
163
192
|
const path = yield* Path.Path;
|
|
@@ -197,15 +226,35 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
197
226
|
(entry): entry is string =>
|
|
198
227
|
entry !== undefined && entry !== ".." && !entry.startsWith("../") && !path.isAbsolute(entry),
|
|
199
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);
|
|
200
236
|
// Walk one level at a time so an excluded directory is discarded before
|
|
201
237
|
// its contents are read; entries come back sorted, preserving hash order.
|
|
202
238
|
const entries = yield* walkIgnoring({
|
|
203
|
-
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
|
+
),
|
|
204
253
|
rules: dockerignore?.rules,
|
|
205
254
|
force: forcedPaths,
|
|
206
255
|
});
|
|
207
256
|
|
|
208
|
-
for (const { path: entry } of entries) {
|
|
257
|
+
for (const { path: entry, type } of entries) {
|
|
209
258
|
// The ignore file and Dockerfile contents are hashed above; excluded
|
|
210
259
|
// entries were only walked so re-included descendants could be found.
|
|
211
260
|
if (
|
|
@@ -216,20 +265,14 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
216
265
|
}
|
|
217
266
|
|
|
218
267
|
const fullPath = path.join(context, entry);
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
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);
|
|
225
274
|
yield* Effect.sync(() =>
|
|
226
|
-
hasher.update(
|
|
227
|
-
`${JSON.stringify({
|
|
228
|
-
path: hashedEntry,
|
|
229
|
-
type: "SymbolicLink",
|
|
230
|
-
target: link.success,
|
|
231
|
-
})}\0`,
|
|
232
|
-
),
|
|
275
|
+
hasher.update(`${JSON.stringify({ path: entry, type: "SymbolicLink", target })}\0`),
|
|
233
276
|
);
|
|
234
277
|
continue;
|
|
235
278
|
}
|
|
@@ -242,7 +285,7 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
242
285
|
yield* Effect.sync(() =>
|
|
243
286
|
hasher.update(
|
|
244
287
|
`${JSON.stringify({
|
|
245
|
-
path:
|
|
288
|
+
path: entry,
|
|
246
289
|
type: info.type,
|
|
247
290
|
mode: info.mode & 0o7777,
|
|
248
291
|
size: info.type === "File" ? String(info.size) : undefined,
|
|
@@ -251,13 +294,19 @@ export const hashDockerBuildInputs = Effect.fn(function* (
|
|
|
251
294
|
);
|
|
252
295
|
|
|
253
296
|
if (info.type === "File") {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
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
|
+
}
|
|
261
310
|
}
|
|
262
311
|
}
|
|
263
312
|
|