1/**
2 * Inline Dockerfile content for container platforms.
3 *
4 * Container platform props accept `dockerfile: string | { content: Input<string> }`:
5 * a plain string is always a **path** (relative to `context`); an object with a
6 * `content` key is **inline Dockerfile content**. `Dockerfile.inline` is the
7 * ergonomic way to produce the latter — a tagged template whose interpolations
8 * ride the normal {@link Output} machinery, so referencing another resource's
9 * attributes (e.g. an {@link ECR.Image}'s `imageUri`) creates a real dependency
10 * edge and re-resolves on change.
11 *
12 * The returned value is a plain JSON-serializable object (no symbols or
13 * brands) so it round-trips through persisted state unchanged; the structural
14 * `content` key is the discriminant.
15 *
16 * @example Base plus system packages, still bundling your Effect program
17 * ```typescript
18 * import * as Dockerfile from "alchemy/Docker/Dockerfile";
19 *
20 * const worker = yield* AWS.ECS.Task(
21 * "Transcoder",
22 * {
23 * cluster,
24 * main: import.meta.url,
25 * dockerfile: Dockerfile.inline`
26 * FROM oven/bun:1
27 * RUN apt-get update && apt-get install -y ffmpeg
28 * `,
29 * },
30 * impl,
31 * );
32 * ```
33 *
34 * @example Deriving from another resource's image (dependency edge for free)
35 * ```typescript
36 * const base = yield* AWS.ECR.Image("MlBase", { context: "./ml-base" });
37 *
38 * const trainer = yield* AWS.ECS.Task(
39 * "Trainer",
40 * {
41 * cluster,
42 * main: import.meta.url,
43 * dockerfile: Dockerfile.inline`
44 * FROM ${base.imageUri}
45 * RUN pip install -r /opt/requirements.txt
46 * `,
47 * },
48 * impl,
49 * );
50 * ```
51 */
52import type { Input } from "../Input.ts";
53import * as Output from "../Output.ts";
54
55/**
56 * Inline Dockerfile content. A plain, state-serializable object; the
57 * structural `content` key distinguishes it from a path string wherever
58 * `dockerfile` props accept `string | InlineDockerfile`.
59 *
60 * Never interpolate secrets: anything in Dockerfile content is baked into
61 * image layers. `Input<string>` excludes `Redacted` values at the type level.
62 */
63export interface InlineDockerfile {
64 readonly content: Input<string>;
65}
66
67/** Structural guard for {@link InlineDockerfile} vs a path string. */
68export const isInlineDockerfile = (value: unknown): value is InlineDockerfile =>
69 typeof value === "object" && value !== null && "content" in value;
70
71/**
72 * Tagged template producing {@link InlineDockerfile}. Interpolations may be
73 * plain strings or `Output<string>`s (resolved at deploy time via
74 * `Output.interpolate`); with no interpolations the content is a plain string.
75 */
76export const inline = (
77 template: TemplateStringsArray,
78 ...args: Array<Input<string>>
79): InlineDockerfile => ({
80 content:
81 args.length === 0
82 ? template.raw.join("")
83 : Output.interpolate(template, ...args),
84});