warden

warden.config.ts

Optional per-repo config for app builds, saved batches and affected e2e suites.

warden.config.ts declares projects, app builds, saved batches and e2e suites for a repo; add one when you outgrow the defaults. Optional, at the repo root. Without it, warden detects a single Expo project from app.json / app.config.*.

Setup

Add @delacour/warden as a dev dependency, then wrap the config in defineConfig. Your editor gets autocompletion, the defaults and docs on hover, and type errors for typos:

bun add -d @delacour/warden

warden runs the file itself (Bun transpiles it), so it needs no build step. It looks for warden.config.ts, .mts, .js, .mjs and then warden.config.json, walking up from the cwd. Keep only one of them per directory. JSON configs still work but have no autocompletion.

Instead of an object, the default export can be a sync function. It receives { configDir, env }:

warden.config.ts
import { defineConfig } from "@delacour/warden/config";

export default defineConfig(({ env }) => ({
  projects: [{ name: "app", bundleId: { ios: env.IOS_BUNDLE_ID ?? "com.example.app" } }],
}));

If you'd rather not install the package, use a type-only import. It is erased before the file runs:

warden.config.ts
import type { WardenConfig } from "@delacour/warden/config";

export default { batches: { smoke: { platform: "ios", cmd: ["true"] } } } satisfies WardenConfig;

The section types are exported too: WardenConfig, WardenProject, WardenBatchPreset, WardenE2eSuite, WardenE2eSpecEntry, WardenE2eMetro, WardenE2eSpec, WardenE2eDeviceLeg, WardenAppOption.

Top level

Besides projects, batches and e2e, the top level takes:

Prop

Type

Projects

warden.config.ts
import { defineConfig } from "@delacour/warden/config";

export default defineConfig({
  projects: [{
    name: "salient",
    root: "apps/salient/app",
    bundleId: { ios: "com.example.salient", android: "com.example.salient" },
    fingerprint: { command: "bun run --silent fingerprint:{platform}" },
    eas: { profile: "development-simulator", workflow: ".eas/workflows/dev-build.yml", trigger: false },
    build: { ios: "bunx expo run:ios --no-install --no-bundler" }
  }]
});

Prop

Type

The file needs at least one of projects, batches and e2e. Without projects, app builds fall back to detecting app.json / app.config.*. With several projects, the one whose root contains the cwd is picked (deepest wins), else pass --project.

Batches

batches maps a name to a saved warden batch invocation, run with warden batch <name>. Each field mirrors the flag of the same name. Flags you pass on the command line override the preset, and -- <cmd> overrides cmd. ios and android can't be used as names.

warden.config.ts
import { defineConfig } from "@delacour/warden/config";

export default defineConfig({
  projects: [{ name: "salient", root: "apps/salient/app", bundleId: { ios: "com.example.salient" } }],
  batches: {
    "salient-e2e": {
      project: "salient",
      platform: "ios", count: 5, max: 5, profile: "iphone-17",
      ports: ["8091:20"], app: true, retry: 1,
      env: { E2E_SESSION_FILE: "e2e-artifacts/batch/session.json" },
      serve: "bun scripts/e2e/run-ios.ts --session",
      serveReady: "file:e2e-artifacts/batch/session.json",
      serveTimeout: "20m",
      jobsFrom: { command: "bun scripts/e2e/select-specs.ts --list --offline" },
      cmd: ["bun", "scripts/e2e/run-ios.ts", "--attach", "{job}", "--device", "{udid}"]
    }
  }
});

The preset's cwd is its project's root, or the config file's directory when there is no project. Serve, the jobs and jobsFrom.command run there, and relative paths in the preset resolve against it.

Prop

Type

e2e

e2e maps a suite name to the specs warden affected selects and warden e2e runs. See Affected e2e for how selection works. Every path and glob is relative to the config file.

warden.config.ts
import { defineConfig } from "@delacour/warden/config";

export default defineConfig({
  e2e: {
    mobile: {
      specsDir: "specs",
      spec: { bundleId: "com.example.mobile" },
      platform: "ios",
      routerRoot: "apps/mobile/src/app",
      maxDepth: 3,
      runAll: ["bun.lock", "apps/mobile/ios/**"],
      specs: {
        "qa-login": { entries: ["apps/mobile/src/app/(auth)/login.tsx"] },
        "store-*": { required: false }
      }
    }
  }
});

Prop

Type

metro (a dev-client build needs a bundler; warden owns it for the run):

Prop

Type

Metro runs bunx expo start --dev-client --port <p> in the project root with CI=1, logging to <batch dir>/metro.log. It counts as ready only once /status on that port is proven to serve this checkout (the X-React-Native-Project-Root header, else the listening process's cwd); another worktree's Metro on the port fails the run with port <p> is served by <dir>, not this worktree. Spec processes get WARDEN_METRO_URL; serve gets {metroUrl}.

specs.<id|glob>:

Prop

Type

On this page