Prefer normal TypeScript files for repo scripts. The script should expose a small command surface, keep business logic importable, and avoid hiding real work inside YAML or shell strings.
scripts/preview/preview.tsis run from the rootpreviewscript withtrpc-cli scripts/preview/preview.ts.apps/os/scripts/cli.tsandapps/semaphore/scripts/cli.tsusecreateCli({ ...import.meta, jsonInput: "auto" })for richer app CLIs.packages/iterate/pubme.jsis a compact oRPC router wired intotrpc-cli.scripts/ci/*.tsare direct CI command modules. They are intentionally plain TypeScript because Depot YAML should call scripts, not embed large code strings.
For small scripts, export functions and let trpc-cli expose them:
trpc-cli scripts/preview/preview.ts --helpFor app-style CLIs, use the existing pattern:
import { createBuiltInPrompts, createCli, isAgent, yamlTableConsoleLogger } from "trpc-cli";
import { isMainModule } from "@iterate-com/shared/dev/is-main-module";
export async function doThing(options: { name: string }) {
return { ok: true, name: options.name };
}
if (isMainModule(import.meta.url)) {
void createCli({
...import.meta,
name: "my-script",
jsonInput: "auto",
}).run({
logger: yamlTableConsoleLogger,
prompts: isAgent() ? undefined : createBuiltInPrompts(),
});
}For validated command routers, use oRPC plus Zod as in packages/iterate/pubme.js:
import { os } from "@orpc/server";
import { createCli } from "trpc-cli";
import { z } from "zod";
const router = {
publish: os
.input(
z.object({
version: z.string().describe("Version to publish"),
dryRun: z.boolean().default(false).describe("Print without changing state"),
}),
)
.handler(async ({ input }) => input),
};
createCli({ router }).run();trpc-cli also supports TypeBox schemas. This repo currently standardizes on
Zod/oRPC in its examples; use TypeBox only when the script is already
JSON-schema-first or the surrounding package uses TypeBox.
Depot workflows should call scripts directly:
- name: Update PR dashboard
run: pnpm tsx scripts/ci/pr-dashboard.tsKeep CI scripts deterministic:
- read secrets from environment variables or Doppler;
- fail loudly when required env vars are missing;
- keep Slack/GitHub helpers shared in
scripts/ci; - make the real function exportable so it can be imported by tests later;
- do not use interactive prompts in CI.
When a command needs human prompts locally, use isAgent() to disable prompts
for agents and CI.