DeployKit

Artifacts

Describe the files you want to deploy.

An artifact is the set of files to deploy, keyed by destination path. Paths are checked as you add them, so a bad path fails straight away rather than halfway through an upload.

You don't always need one: deployDirectory and deployManifest build it for you. Build one yourself when you want to add, generate or combine files.

From a directory

Every file under the directory, recursively, including dotfiles. Paths are relative to the directory.

import { Artifact } from "@deploykit/core"

const artifact = Artifact.fromDirectory("./build") // Effect<Artifact>, needs a FileSystem

File contents are read when they're uploaded, not now. Nothing is filtered out, so remove anything you don't want public before deploying.

From individual files

Mix in-memory content with files from disk.

import { Effect } from "effect"
import { Artifact, Entry } from "@deploykit/core"

const artifact = Effect.gen(function* () {
  return yield* Artifact.make([
    yield* Entry.text("index.html", "<h1>Hello</h1>"),
    yield* Entry.file("logo.png", "./assets/logo.png"),
    yield* Entry.bytes("data.bin", new Uint8Array([1, 2, 3]))
  ])
})
EntryContentRead when
text / Entry.textA stringAlready in memory
bytes / Entry.bytesA Uint8ArrayAlready in memory
file / Entry.fileA path on disk; size is read nowAt upload

Two files at the same path fail with DuplicatePathError. An unsafe path, such as one with .., fails with InvalidArtifactPathError.

A template plus generated files

A common pattern is one prepared template plus a generated file per customer. Artifact.add returns a new artifact and leaves the original alone, so you can reuse the template.

import { Effect } from "effect"
import { Artifact, Entry } from "@deploykit/core"

const forCustomer = (template: Artifact.Artifact, config: object) =>
  Effect.gen(function* () {
    const file = yield* Entry.text("config.json", JSON.stringify(config))
    return yield* Artifact.add(template, file)
  })

Inspecting an artifact

Artifact.fileCount(artifact) // number of files
Artifact.list(artifact) // entries, in insertion order
Artifact.has(artifact, "index.html") // Effect<boolean>
Artifact.get(artifact, "index.html") // Effect<Option<Entry>>

Layers that override each other

When files come from several places, such as a template, generated config and user uploads, Artifact.layered stacks them with later layers winning. Unlike a plain merge, it records every override so you can see what replaced what.

import { Artifact } from "@deploykit/core"

const result = Artifact.layered([
  { name: "template", entries: templateEntries },
  { name: "generated", entries: generatedEntries },
  { name: "uploads", entries: uploadEntries }
])

result.artifact // what to deploy
result.overrides // [{ path, winner: "uploads", replaced: "template" }, ...]
result.layerOf(path) // which layer the surviving file came from

// Two files in the same layer claimed one path. Almost always a bug.
Artifact.collisionsWithinLayers(result)

// Per layer: how many files it contributed, and how many survived.
Artifact.summarise(layers, result)

Files stored elsewhere

If the files are in object storage rather than on local disk, describe them with a manifest and deploykit streams them in at upload time.

Last updated on

On this page