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
56 changes: 56 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Docs

on:
push:
branches: [main]
paths:
- 'packages/docs/**'
- '.github/workflows/docs.yml'
- 'pnpm-lock.yaml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build docs
run: pnpm --filter @branchly/docs build

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: packages/docs/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules/

# Build output
dist/
.astro/

# Test/coverage output
coverage/
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
dist
coverage
pnpm-lock.yaml
.astro
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ When your branches carry different migrations, a single shared development datab

It's local-first and plugin-based, so it isn't tied to a single stack. Out of the box it speaks **Git**, **Prisma**, **PostgreSQL**, and your **`.env`** file, with MySQL, SQLite, Drizzle, Knex, and direnv adapters alongside, and room for more.

📚 **Full documentation:** [konlanx.github.io/branchly](https://konlanx.github.io/branchly/) — guides, adapter references, and the adapter-authoring guide.

## Installation

Install one package — use whatever package manager your project already uses:
Expand Down
9 changes: 8 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { config, configs } from 'typescript-eslint';

export default config(
{
ignores: ['**/dist/**', '**/coverage/**', '**/node_modules/**'],
ignores: ['**/dist/**', '**/coverage/**', '**/node_modules/**', 'packages/docs/.astro/**'],
},
js.configs.recommended,
...configs.strictTypeChecked,
Expand Down Expand Up @@ -65,5 +65,12 @@ export default config(
files: ['**/*.{js,mjs,cjs}'],
extends: [configs.disableTypeChecked],
},
{
files: ['packages/docs/**/*.ts'],
extends: [configs.disableTypeChecked],
settings: {
'import-x/core-modules': ['astro:content'],
},
},
eslintPluginPrettierRecommended,
);
69 changes: 69 additions & 0 deletions packages/docs/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';

export default defineConfig({
site: 'https://konlanx.github.io',
base: '/branchly',
integrations: [
starlight({
title: 'branchly',
description: 'Give every Git branch its own database. Switch branches, and your data follows.',
logo: {
src: './src/assets/logo.svg',
alt: 'branchly',
},
favicon: '/favicon.svg',
social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/konlanx/branchly' }],
editLink: {
baseUrl: 'https://github.com/konlanx/branchly/edit/main/packages/docs/',
},
customCss: ['./src/styles/custom.css'],
sidebar: [
{
label: 'Start here',
items: [{ slug: 'start/why' }, { slug: 'start/installation' }, { slug: 'start/quickstart' }],
},
{
label: 'Guides',
items: [
{ slug: 'guides/how-it-works' },
{ slug: 'guides/configuration' },
{ slug: 'guides/cli' },
{ slug: 'guides/cache' },
{ slug: 'guides/cleanup' },
{ slug: 'guides/injected-envs' },
],
},
{
label: 'Adapters',
items: [
{ slug: 'adapters/overview' },
{
label: 'Datasources',
items: [{ slug: 'adapters/postgres' }, { slug: 'adapters/mysql' }, { slug: 'adapters/sqlite' }],
},
{
label: 'Migrators',
items: [{ slug: 'adapters/prisma' }, { slug: 'adapters/drizzle' }, { slug: 'adapters/knex' }],
},
{
label: 'Resolvers',
items: [{ slug: 'adapters/env-file' }, { slug: 'adapters/direnv' }],
},
],
},
{
label: 'Authoring adapters',
items: [
{ slug: 'authoring/overview' },
{ slug: 'authoring/datasource' },
{ slug: 'authoring/migrator' },
{ slug: 'authoring/resolver' },
{ slug: 'authoring/test-kit' },
{ slug: 'authoring/publishing' },
],
},
],
}),
],
});
28 changes: 28 additions & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@branchly/docs",
"version": "0.0.0",
"private": true,
"description": "Documentation site for branchly.",
"license": "MIT",
"type": "module",
"scripts": {
"build": "astro build",
"dev": "astro dev",
"preview": "astro preview",
"typecheck": "astro check"
},
"devDependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/starlight": "^0.36.0",
"astro": "^5.0.0",
"sharp": "^0.34.0",
"typescript": "5.9.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/konlanx/branchly.git",
"directory": "packages/docs"
},
"homepage": "https://konlanx.github.io/branchly",
"bugs": "https://github.com/konlanx/branchly/issues"
}
7 changes: 7 additions & 0 deletions packages/docs/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/docs/src/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions packages/docs/src/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineCollection } from 'astro:content';

import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';

export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};
27 changes: 27 additions & 0 deletions packages/docs/src/content/docs/adapters/direnv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: direnv
description: The @branchly/resolver-direnv adapter — pointing your shell at the branch database via .envrc.
---

`@branchly/resolver-direnv` writes the branch connection as an `export` line in your [direnv](https://direnv.net/) `.envrc`, so every shell in the project directory sees the current branch's database.

## Configuration

```ts
resolver: {
use: 'direnv',
file: '.envrc',
key: 'DATABASE_URL',
}
```

| Option | Default | Meaning |
| ------ | ---------------- | ------------------------------ |
| `file` | `'.envrc'` | The direnv file to write into. |
| `key` | `'DATABASE_URL'` | The variable to export. |

## Behavior

Like the [env-file resolver](/branchly/adapters/env-file/), the write is an upsert: an existing `export DATABASE_URL=…` line is replaced, everything else in your `.envrc` is preserved, and the line is appended if missing.

One direnv-ism to know: direnv re-evaluates `.envrc` when it changes, but an already-running process keeps the environment it started with. For long-running processes that should follow branch switches, [`branchly run`](/branchly/guides/injected-envs/) is the more direct tool.
29 changes: 29 additions & 0 deletions packages/docs/src/content/docs/adapters/drizzle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Drizzle
description: The @branchly/migrator-drizzle adapter — fingerprinting and applying Drizzle migrations.
---

`@branchly/migrator-drizzle` drives [Drizzle Kit](https://orm.drizzle.team/docs/kit-overview): it fingerprints your generated SQL migrations, applies them with `drizzle-kit migrate`, and runs your seed command on fresh databases.

## Configuration

```ts
migrator: {
use: 'drizzle',
}
```

| Option | Default | Meaning |
| --------------- | --------------------------- | ----------------------------------------------------------------- |
| `migrationsDir` | `'drizzle'` | Where your generated `.sql` migrations live. |
| `seed` | _(none)_ | Seed command (e.g. `'tsx src/seed.ts'`). Skipped when unset. |
| `applyCommand` | `'npx drizzle-kit migrate'` | The command used to apply pending migrations. |
| `urlEnv` | `'DATABASE_URL'` | The env var the apply/seed commands receive the connection under. |

## Behavior

- **Fingerprint** — a hash of the `.sql` file names in `migrationsDir`. Branches with identical migration sets share snapshots.
- **Apply** — `drizzle-kit migrate` applies only pending migrations, satisfying the kernel's idempotency requirement.
- **Seed** — your configured command, run with the branch connection injected as `urlEnv`, on freshly built databases only.

Works with any of the datasource adapters — PostgreSQL, MySQL, or SQLite.
29 changes: 29 additions & 0 deletions packages/docs/src/content/docs/adapters/env-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: env file
description: The @branchly/resolver-env-file adapter — pointing your app at the branch database via .env.
---

`@branchly/resolver-env-file` is the default resolver: after provisioning, it upserts the branch's connection string into your `.env` file, so anything that reads `.env` — your app, Prisma, scripts — picks up the right database automatically.

## Configuration

```ts
resolver: {
use: 'env-file',
file: '.env',
key: 'DATABASE_URL',
}
```

| Option | Default | Meaning |
| ------ | ---------------- | --------------------------- |
| `file` | `'.env'` | The env file to write into. |
| `key` | `'DATABASE_URL'` | The variable to set. |

## Behavior

The write is an **upsert**: an existing `DATABASE_URL=` line is replaced in place, every other line is left exactly as it was, and the key is appended if absent. Your comments and other variables survive every branch switch.

`branchly init` makes sure the file is gitignored — the value changes per branch and per machine, so it should never be committed.

Using Doppler or CI-injected environments where a file write would be overridden at launch? Pair this with [`branchly run`](/branchly/guides/injected-envs/), or use the [direnv resolver](/branchly/adapters/direnv/).
29 changes: 29 additions & 0 deletions packages/docs/src/content/docs/adapters/knex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Knex
description: The @branchly/migrator-knex adapter — fingerprinting and applying Knex migrations.
---

`@branchly/migrator-knex` drives [Knex migrations](https://knexjs.org/guide/migrations.html): it fingerprints your migration files, applies them with `knex migrate:latest`, and runs your seed command on fresh databases.

## Configuration

```ts
migrator: {
use: 'knex',
}
```

| Option | Default | Meaning |
| --------------- | --------------------------- | ------------------------------------------------------------------------------- |
| `migrationsDir` | `'migrations'` | Where your migration files live (`.js`, `.cjs`, `.mjs`, `.ts`, `.cts`, `.mts`). |
| `seed` | _(none)_ | Seed command (e.g. `'npx knex seed:run'`). Skipped when unset. |
| `applyCommand` | `'npx knex migrate:latest'` | The command used to apply pending migrations. |
| `urlEnv` | `'DATABASE_URL'` | The env var the apply/seed commands receive the connection under. |

## Behavior

- **Fingerprint** — a hash of the migration file names in `migrationsDir`. Branches with identical migration sets share snapshots.
- **Apply** — `knex migrate:latest` runs only pending migrations, satisfying the kernel's idempotency requirement. Your `knexfile` should read its connection from `urlEnv` (the common `process.env.DATABASE_URL` pattern).
- **Seed** — your configured command (typically `npx knex seed:run`), run with the branch connection injected as `urlEnv`, on freshly built databases only.

`branchly init` recognizes every `knexfile` flavor — `.js`, `.cjs`, `.mjs`, `.ts`, `.cts`, `.mts` — for both migrator and datasource detection. Works with any of the datasource adapters.
41 changes: 41 additions & 0 deletions packages/docs/src/content/docs/adapters/mysql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: MySQL
description: The @branchly/datasource-mysql adapter — per-branch databases with SQL-level cloning.
---

`@branchly/datasource-mysql` gives each branch its own database (schema) on your MySQL server, named `<prefix>_<key>`.

| Capability | Value | Via |
| ------------------- | :---: | -------------------------------- |
| `instantClone` | ✅ | SQL-level replay (see below) |
| `snapshot` | ✅ | Snapshots are ordinary databases |
| `isolatedPerBranch` | ✅ | One database per branch |

## Configuration

```ts
datasource: {
use: 'mysql',
url: env('DATABASE_URL'),
prefix: 'app',
}
```

| Option | Default | Meaning |
| -------- | ------------ | --------------------------------------------------------------------------------------- |
| `url` | _(required)_ | Your existing connection string. branchly keeps host + credentials, swaps the database. |
| `prefix` | `'app'` | Database name prefix; everything branchly creates starts with `<prefix>_`. |

## How cloning works

MySQL has no template mechanism, so this adapter clones at the SQL level:

1. Replays each table's `SHOW CREATE TABLE` definition into the target database.
2. Copies data with `INSERT … SELECT`, with foreign-key checks disabled during the copy so table order doesn't matter.
3. Recreates views against the target schema, retrying in dependency order so views-on-views resolve.

For development-sized databases this is fast — not Postgres-template fast, but easily quick enough that branch switches stay pleasant. The result is a fully independent database: writes to the clone never touch the source (the conformance kit verifies exactly this).

## Requirements

The connection's user must be able to `CREATE DATABASE`, `DROP DATABASE`, and read `information_schema` — standard for a development root or admin user.
Loading
Loading