Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ development history from before the open-sourcing is not carried over.

### Added

- Service-scoped tool checks: a `requires` tool may list the `services:` that
need it. Untagged tools stay baseline (always checked); a tagged tool is
checked only when one of its services is in the run's selection, so a partial
`tarjan up <service>` skips toolchains it won't use. `tarjan doctor
<service...>` scopes the same way, and the Starlark `tool()` builtin gains a
`services` argument.
- Initial public release of tarjan: spin up a complete local development
environment for a whole product from a single config file
(`tarjan.yaml` / `tarjan.star`).
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Other commands:
tarjan upgrade # update tarjan to the latest release (--check to only look)
tarjan doctor # check required tools are installed & up to date
tarjan doctor --install # ...and install the missing ones (opt-in)
tarjan doctor api # ...checking only the toolchain the api service needs
tarjan validate # parse the config and print the service start order
tarjan pull # git pull every cloned repo in the current workspace
tarjan pull 0.1.0 # ...in the named "<name>-0.1.0" workspace
Expand Down Expand Up @@ -120,6 +121,14 @@ By default each `tarjan up` materialises a **fresh, timestamped workspace** unde

Set a top-level `version:` (or pass `tarjan up --version <label>`) to instead use a **named, reusable workspace** at `<workspaceRoot>/<name>-<version>` (e.g. `~/tarjan/myproduct/myproduct-0.1.0/`). Repeated `up` runs of the same version reuse that directory — already-cloned repos and finished `setup` steps are skipped — so it comes back up fast. Use different versions (`--version pr-42`) to keep parallel environments side by side. `--workspace ./dir` still overrides with an explicit path.

Parallel workspaces **coexist on disk, but only one environment runs at a time**: services publish fixed host ports (`5432:5432`, `http://localhost:8080`, …) that every workspace shares, so bringing up a second environment collides with the first ("address already in use" — the already-running check only guards re-`up` of the *same* workspace). Switch by stopping one and starting the other; because clones and setup are reused, the swap is quick:

```bash
tarjan up --version feat-a # work on feature A
# ...later: Ctrl+C (or `tarjan down`), then
tarjan up --version feat-b # feature B — its workspace comes back fast
```

---

## Configuration (`tarjan.yaml`)
Expand Down Expand Up @@ -191,6 +200,7 @@ workspace:
| `install` | Bespoke install command (escape hatch) — a single string, or a per-OS map (`darwin`/`linux`/`windows`). |
| `installHint` | Free-text pointer shown when the tool is missing and no provider above is set. |
| `optional` | Missing → warning instead of error. |
| `services` | Scope the tool to the services that need it: it is checked only when one of them is in the run's selection. A tool with no `services` is a **baseline** — always checked. |

**Declare *what*, not *how-per-OS*.** `mise` handles versioned language runtimes
(dotnet, node, python, go, java, flutter…) and `package` handles OS client tools
Expand All @@ -200,6 +210,13 @@ commands. When several are set on one tool, the most explicit wins:
**`install` > `mise` > `package`** (use `install` to override a provider for one
awkward tool).

**Scoped to the selection.** A partial run only demands the toolchains it will
actually use: a tool tagged `services: [postgres]` is skipped when no selected
service (dependencies included) is `postgres`. So `tarjan up web` doesn't fail
on a missing backend toolchain, and `tarjan doctor <service>` answers "what does
*this* service need on my machine?" before you start it. Untagged tools are the
baseline every run checks.

**Per-repo versions.** A repo that pins its own tool versions — a `mise.toml` or
`.tool-versions` in its root — owns them: before a service's setup/command runs,
tarjan runs `mise install` in its workdir, so each repo gets exactly the runtime
Expand Down
2 changes: 1 addition & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func toolNames(tools []Tool) []string {

func TestRequiredToolsScopesToSelection(t *testing.T) {
c := &Config{Requires: []Tool{
{Name: "git"}, // baseline: always required
{Name: "git"}, // baseline: always required
{Name: "docker", Services: []string{"postgres"}},
{Name: "dotnet", Services: []string{"service"}},
{Name: "node", Services: []string{"studio", "studio-cloud"}},
Expand Down
6 changes: 5 additions & 1 deletion website/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
/* Let Tailwind v4 see the classes used inside Fumadocs UI. */
@source "../node_modules/fumadocs-ui/dist/**/*.js";

/* Drive `dark:` utilities from the theme class next-themes sets on <html>,
not from prefers-color-scheme — so the landing page follows the docs'
theme toggle. */
@custom-variant dark (&:where(.dark, .dark *));

html {
scroll-behavior: smooth;
}

/* Landing page (outside the docs) uses an explicit dark canvas. */
body {
font-family:
ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica,
Expand Down
2 changes: 1 addition & 1 deletion website/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className="dark h-full antialiased" suppressHydrationWarning>
<html lang="en" className="h-full antialiased" suppressHydrationWarning>
<body className="min-h-full">
<RootProvider
theme={{ defaultTheme: "dark", enableSystem: false }}
Expand Down
79 changes: 46 additions & 33 deletions website/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,21 @@ function Nav() {
return (
<header className="mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
<div className="flex items-center gap-2 font-mono text-lg font-bold tracking-tight">
<span className="text-indigo-400">›</span> tarjan
<span className="text-indigo-500 dark:text-indigo-400">›</span> tarjan
</div>
<nav className="flex items-center gap-6 text-sm text-white/70">
<a href="#features" className="hover:text-white">
<nav className="flex items-center gap-6 text-sm text-neutral-600 dark:text-white/70">
<a
href="#features"
className="hover:text-neutral-950 dark:hover:text-white"
>
Features
</a>
<Link href="/docs" className="hover:text-white">
<Link href="/docs" className="hover:text-neutral-950 dark:hover:text-white">
Docs
</Link>
<a
href={GITHUB}
className="rounded-lg border border-white/15 px-3 py-1.5 font-medium text-white hover:bg-white/5"
className="rounded-lg border border-neutral-950/15 px-3 py-1.5 font-medium text-neutral-950 hover:bg-neutral-950/5 dark:border-white/15 dark:text-white dark:hover:bg-white/5"
>
GitHub ↗
</a>
Expand All @@ -55,28 +58,29 @@ function Nav() {
function Hero() {
return (
<section className="mx-auto max-w-6xl px-6 pt-16 pb-12 text-center sm:pt-24">
<span className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1 text-xs text-white/70">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
<span className="inline-flex items-center gap-2 rounded-full border border-neutral-950/10 bg-neutral-950/5 px-3 py-1 text-xs text-neutral-600 dark:border-white/10 dark:bg-white/5 dark:text-white/70">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400" />
Terraform / Aspire — but for your{" "}
<em className="not-italic text-white">local</em> dev environment
<em className="not-italic text-neutral-950 dark:text-white">local</em>{" "}
dev environment
</span>
<h1 className="mx-auto mt-6 max-w-4xl text-balance text-4xl font-bold leading-tight tracking-tight sm:text-6xl">
Spin up a whole product&apos;s local dev environment in{" "}
<span className="bg-gradient-to-r from-indigo-400 to-emerald-300 bg-clip-text text-transparent">
<span className="bg-gradient-to-r from-indigo-600 to-emerald-500 bg-clip-text text-transparent dark:from-indigo-400 dark:to-emerald-300">
one command
</span>
</h1>
<p className="mx-auto mt-6 max-w-2xl text-pretty text-lg text-white/65">
<p className="mx-auto mt-6 max-w-2xl text-pretty text-lg text-neutral-600 dark:text-white/65">
Clone five repos, start Postgres, install deps, run the API, boot the web
app and the mobile app, wire up the cloud bits. tarjan turns that checklist
into{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 font-mono text-sm">
<code className="rounded bg-neutral-950/10 px-1.5 py-0.5 font-mono text-sm dark:bg-white/10">
tarjan up
</code>
.
</p>

<div className="mx-auto mt-8 flex max-w-md items-center justify-between rounded-xl border border-white/10 bg-black/40 px-4 py-3 font-mono text-sm">
<div className="mx-auto mt-8 flex max-w-md items-center justify-between rounded-xl border border-neutral-950/10 bg-neutral-950 px-4 py-3 font-mono text-sm text-white dark:border-white/10 dark:bg-black/40">
<span>
<span className="text-white/40">$</span> tarjan up
</span>
Expand All @@ -86,13 +90,13 @@ function Hero() {
<div className="mt-8 flex items-center justify-center gap-3">
<Link
href="/docs"
className="rounded-lg bg-white px-5 py-2.5 text-sm font-semibold text-black hover:bg-white/90"
className="rounded-lg bg-neutral-950 px-5 py-2.5 text-sm font-semibold text-white hover:bg-neutral-800 dark:bg-white dark:text-black dark:hover:bg-white/90"
>
Get started
</Link>
<a
href={GITHUB}
className="rounded-lg border border-white/15 px-5 py-2.5 text-sm font-semibold hover:bg-white/5"
className="rounded-lg border border-neutral-950/15 px-5 py-2.5 text-sm font-semibold hover:bg-neutral-950/5 dark:border-white/15 dark:hover:bg-white/5"
>
Star on GitHub
</a>
Expand All @@ -104,12 +108,12 @@ function Hero() {
function Problem() {
return (
<section className="mx-auto max-w-5xl px-6 py-12">
<div className="grid gap-6 rounded-2xl border border-white/10 bg-white/[0.02] p-8 md:grid-cols-2">
<div className="grid gap-6 rounded-2xl border border-neutral-950/10 bg-neutral-950/[0.02] p-8 md:grid-cols-2 dark:border-white/10 dark:bg-white/[0.02]">
<div>
<p className="text-sm font-semibold uppercase tracking-wider text-rose-300/80">
<p className="text-sm font-semibold uppercase tracking-wider text-rose-600/90 dark:text-rose-300/80">
Without tarjan
</p>
<ul className="mt-4 space-y-2 text-sm text-white/60">
<ul className="mt-4 space-y-2 text-sm text-neutral-600 dark:text-white/60">
<li>· clone repo A, B, C, D…</li>
<li>· install .NET / Node / Docker by hand</li>
<li>· start the database, run migrations</li>
Expand All @@ -119,10 +123,10 @@ function Problem() {
</ul>
</div>
<div>
<p className="text-sm font-semibold uppercase tracking-wider text-emerald-300/80">
<p className="text-sm font-semibold uppercase tracking-wider text-emerald-600/90 dark:text-emerald-300/80">
With tarjan
</p>
<pre className="mt-4 overflow-x-auto rounded-lg bg-black/50 p-4 font-mono text-sm text-white/80">
<pre className="mt-4 overflow-x-auto rounded-lg bg-neutral-950 p-4 font-mono text-sm text-white/80 dark:bg-black/50">
{`$ tarjan up
✓ tools checked
✓ repos cloned → fresh workspace
Expand Down Expand Up @@ -190,9 +194,11 @@ function Features() {

function Card({ title, children }: { title: string; children: ReactNode }) {
return (
<div className="rounded-xl border border-white/10 bg-white/[0.02] p-5 transition hover:border-white/20 hover:bg-white/[0.04]">
<div className="rounded-xl border border-neutral-950/10 bg-neutral-950/[0.02] p-5 transition hover:border-neutral-950/20 hover:bg-neutral-950/[0.04] dark:border-white/10 dark:bg-white/[0.02] dark:hover:border-white/20 dark:hover:bg-white/[0.04]">
<h3 className="font-semibold">{title}</h3>
<p className="mt-2 text-sm leading-relaxed text-white/60">{children}</p>
<p className="mt-2 text-sm leading-relaxed text-neutral-600 dark:text-white/60">
{children}
</p>
</div>
);
}
Expand All @@ -203,11 +209,11 @@ function ConfigShowcase() {
<h2 className="text-center text-3xl font-bold tracking-tight">
One declarative file
</h2>
<p className="mx-auto mt-3 max-w-2xl text-center text-white/60">
<p className="mx-auto mt-3 max-w-2xl text-center text-neutral-600 dark:text-white/60">
Repos to clone, tools to require, services to run — with dependencies,
health checks and restart policies.
</p>
<pre className="mx-auto mt-8 max-w-3xl overflow-x-auto rounded-2xl border border-white/10 bg-black/50 p-6 font-mono text-[13px] leading-relaxed text-white/80">
<pre className="mx-auto mt-8 max-w-3xl overflow-x-auto rounded-2xl border border-neutral-950/10 bg-neutral-950 p-6 font-mono text-[13px] leading-relaxed text-white/80 dark:border-white/10 dark:bg-black/50">
{`name: acme
repos:
- { name: api, url: https://github.com/acme/api.git }
Expand Down Expand Up @@ -259,14 +265,18 @@ function Commands() {
<h2 className="text-center text-3xl font-bold tracking-tight">
A command for every step of the loop
</h2>
<div className="mt-8 divide-y divide-white/5 overflow-hidden rounded-2xl border border-white/10">
<div className="mt-8 divide-y divide-neutral-950/5 overflow-hidden rounded-2xl border border-neutral-950/10 dark:divide-white/5 dark:border-white/10">
{COMMANDS.map(([cmd, desc]) => (
<div
key={cmd}
className="flex flex-col gap-1 px-5 py-3.5 sm:flex-row sm:items-center sm:justify-between"
>
<code className="font-mono text-sm text-emerald-300">{cmd}</code>
<span className="text-sm text-white/55">{desc}</span>
<code className="font-mono text-sm text-emerald-700 dark:text-emerald-300">
{cmd}
</code>
<span className="text-sm text-neutral-500 dark:text-white/55">
{desc}
</span>
</div>
))}
</div>
Expand All @@ -280,23 +290,23 @@ function CallToAction() {
<h2 className="text-3xl font-bold tracking-tight">
Bring the whole thing up.
</h2>
<p className="mx-auto mt-3 max-w-xl text-white/60">
<p className="mx-auto mt-3 max-w-xl text-neutral-600 dark:text-white/60">
Download a prebuilt binary, point a config at your repos, and run{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 font-mono text-sm">
<code className="rounded bg-neutral-950/10 px-1.5 py-0.5 font-mono text-sm dark:bg-white/10">
tarjan up
</code>
.
</p>
<div className="mt-7 flex items-center justify-center gap-3">
<Link
href="/docs"
className="rounded-lg bg-white px-5 py-2.5 text-sm font-semibold text-black hover:bg-white/90"
className="rounded-lg bg-neutral-950 px-5 py-2.5 text-sm font-semibold text-white hover:bg-neutral-800 dark:bg-white dark:text-black dark:hover:bg-white/90"
>
Read the docs
</Link>
<a
href={`${GITHUB}/releases/latest`}
className="rounded-lg border border-white/15 px-5 py-2.5 text-sm font-semibold hover:bg-white/5"
className="rounded-lg border border-neutral-950/15 px-5 py-2.5 text-sm font-semibold hover:bg-neutral-950/5 dark:border-white/15 dark:hover:bg-white/5"
>
Download
</a>
Expand All @@ -307,11 +317,14 @@ function CallToAction() {

function Footer() {
return (
<footer className="border-t border-white/10">
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-3 px-6 py-8 text-sm text-white/45 sm:flex-row">
<footer className="border-t border-neutral-950/10 dark:border-white/10">
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-3 px-6 py-8 text-sm text-neutral-500 dark:text-white/45 sm:flex-row">
<span className="font-mono">› tarjan</span>
<span>MIT licensed · built with Go</span>
<a href={GITHUB} className="hover:text-white/70">
<a
href={GITHUB}
className="hover:text-neutral-950 dark:hover:text-white/70"
>
github.com/stevenzg/tarjan
</a>
</div>
Expand Down
15 changes: 10 additions & 5 deletions website/content/docs/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,30 @@ description: The tarjan CLI reference.
| Command | What it does |
| --- | --- |
| `tarjan init [--star]` | Write a starter `tarjan.yaml` (or `tarjan.star`). |
| `tarjan up` | Materialise a workspace and start the environment. |
| `tarjan up [service...]` | Materialise a workspace and start the environment (or just the named services + their dependencies). |
| `tarjan down` | Stop an environment started elsewhere. |
| `tarjan status [--watch]` | Show live service status; `--watch` refreshes it. |
| `tarjan ui` | Full-screen dashboard: logs + restart/reload keys. |
| `tarjan logs <svc> [-f]` | Print (or follow) a service's captured logs. |
| `tarjan restart <svc>` | Restart one service in place. |
| `tarjan reload` | Reconcile a running environment to the edited config. |
| `tarjan exec <svc> -- cmd` | Run a command in a service's dir + environment. |
| `tarjan validate` | Parse the config and print the start order. |
| `tarjan doctor [--install]` | Check required tools; `--install` installs them. |
| `tarjan pull [version]` | Fast-forward every cloned repo in a workspace (the most recent one, or the named `<name>-<version>`). |
| `tarjan validate [service...]` | Parse the config and print the start order (optionally for a selection). |
| `tarjan doctor [service...]` | Check required tools — all of them, or just what the named services need; `--install` installs them. |
| `tarjan workspace [--open]` | (Re)generate the VS Code workspace. |
| `tarjan upgrade [--check]` | Self-update tarjan to the latest release (`--check` only reports). |
| `tarjan version` | Print version information. |

## `tarjan up` flags

- `[service...]` — positional service names, same as `--only` (the two combine).
- `--only a,b` — start a subset (dependencies pulled in unless `--no-deps`).
- `--profile p` — activate profile groups.
- `--install` — install missing/outdated required tools.
- `--workspace <dir>` — reuse a workspace instead of a fresh one.
- `--install` — install missing/outdated required tools via their provider (`install`/`mise`/`package`).
- `--ai` — with `--install`, let an agent CLI install what the providers can't.
- `--version <label>` — use the named, reusable workspace `<name>-<label>` (defaults to the config's `version:`; see [Workspaces & versions](/docs/configuration#workspaces--versions)).
- `--workspace <dir>` — reuse an explicit workspace directory.
- `--no-start` — prepare the workspace (clone, deps, IDE) but don't start.

## Controlling a running environment
Expand Down
Loading