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 .changeset/eager-cougars-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cipherstash/basic-example": minor
"@cipherstash/stack": minor
---

Improve CLI user experience for developer onboarding.
11 changes: 6 additions & 5 deletions examples/basic/src/encryption/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema'
import { Encryption } from '@cipherstash/stack'

export const helloTable = encryptedTable('hello', {
world: encryptedColumn('world').equality().orderAndRange(),
name: encryptedColumn('name').equality().freeTextSearch(),
age: encryptedColumn('age').dataType('number').equality().orderAndRange(),
export const usersTable = encryptedTable('users', {
email: encryptedColumn('email')
.equality()
.orderAndRange()
.freeTextSearch(),
})

export const encryptionClient = await Encryption({
schemas: [helloTable],
schemas: [usersTable],
})
56 changes: 34 additions & 22 deletions packages/stack-forge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,27 @@ bun add -D @cipherstash/stack-forge

## Quick Start

The fastest way to get started is with the interactive `init` command:
First, initialize your project with the `stash` CLI (from `@cipherstash/stack`):

```bash
npx stash-forge init
npx stash init
```

This will:
1. Check if `@cipherstash/stack` is installed and offer to install it
2. Ask for your database URL
3. Ask which integration you're using (Drizzle, Supabase, or plain PostgreSQL)
4. Let you build an encryption schema interactively or use a placeholder
5. Generate `stash.config.ts` and your encryption client file
This generates your encryption schema and installs `@cipherstash/stack-forge` as a dev dependency.

Then install EQL in your database:
Then set up your database and install EQL:

```bash
npx stash-forge install
npx stash-forge setup
```

This will:
1. Auto-detect your encryption client file (or ask for the path)
2. Ask for your database URL
3. Generate `stash.config.ts`
4. Ask which Postgres provider you're using (Supabase, Neon, AWS RDS, etc.) to determine the right install flags
5. Install EQL extensions in your database

That's it. EQL is now installed and your encryption schema is ready.

### Manual setup
Expand All @@ -64,6 +66,7 @@ import { defineConfig } from '@cipherstash/stack-forge'

export default defineConfig({
databaseUrl: process.env.DATABASE_URL!,
client: './src/encryption/index.ts',
})
```

Expand Down Expand Up @@ -117,25 +120,34 @@ The config file is resolved by walking up from the current working directory, si
stash-forge <command> [options]
```

### `init`
### `setup`

Initialize CipherStash Forge in your project with an interactive wizard.
Configure your database and install EQL extensions. Run this after `stash init` has set up your encryption schema.

```bash
npx stash-forge init
npx stash-forge setup [options]
```

The wizard will:
- Check if `@cipherstash/stack` is installed and prompt to install it (detects your package manager automatically)
- Auto-detect your encryption client file by scanning common locations (`./src/encryption/index.ts`, etc.), then confirm with you or ask for the path if not found
- Ask for your database URL (pre-fills from `DATABASE_URL` env var)
- Ask which integration you're using (Drizzle ORM, Supabase, or plain PostgreSQL)
- Ask where to create the encryption client file
- If the client file already exists, ask whether to keep it or overwrite
- Let you choose between building a schema interactively or using a placeholder:
- **Build a schema:** asks for table name, column names, data types, and search operations for each column
- **Placeholder:** generates an example `users` table with `email` and `name` columns
- Generate `stash.config.ts` and the encryption client file
- Print next steps with links to the [CipherStash dashboard](https://dashboard.cipherstash.com/sign-in) for credentials
- Generate `stash.config.ts` with the database URL and client path
- Ask which Postgres provider you're using to determine the right install flags:
- **Supabase** — uses `--supabase` (no operator families + Supabase role grants)
- **Neon, Vercel Postgres, PlanetScale, Prisma Postgres** — uses `--exclude-operator-family`
- **AWS RDS, Other / Self-hosted** — standard install
- Install EQL extensions in your database

If `--supabase` is passed as a flag, the provider selection is skipped.

| Option | Description |
|--------|-------------|
| `--force` | Overwrite existing `stash.config.ts` and reinstall EQL |
| `--dry-run` | Show what would happen without making changes |
| `--supabase` | Skip provider selection and use Supabase-compatible install |
| `--drizzle` | Generate a Drizzle migration instead of direct install |
| `--exclude-operator-family` | Skip operator family creation |
| `--latest` | Fetch the latest EQL from GitHub instead of using the bundled version |

### `install`

Expand Down
29 changes: 19 additions & 10 deletions packages/stack-forge/src/bin/stash-forge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ config()

import * as p from '@clack/prompts'
import {
initCommand,
installCommand,
pushCommand,
setupCommand,
statusCommand,
testConnectionCommand,
upgradeCommand,
Expand All @@ -19,7 +19,7 @@ Usage: stash-forge <command> [options]
Commands:
install Install EQL extensions into your database
upgrade Upgrade EQL extensions to the latest version
init Initialize CipherStash Forge in your project
setup Configure database and install EQL extensions
push Push encryption schema to database (CipherStash Proxy only)
validate Validate encryption schema for common misconfigurations
migrate Run pending encrypt config migrations
Expand All @@ -29,12 +29,12 @@ Commands:
Options:
--help, -h Show help
--version, -v Show version
--force (install) Reinstall even if already installed
--dry-run (install, push, upgrade) Show what would happen without making changes
--supabase (install, upgrade, validate) Use Supabase-compatible install and grant role permissions
--drizzle (install) Generate a Drizzle migration instead of direct install
--exclude-operator-family (install, upgrade, validate) Skip operator family creation (for non-superuser roles)
--latest (install, upgrade) Fetch the latest EQL from GitHub instead of using the bundled version
--force (setup, install) Reinstall even if already installed
--dry-run (setup, install, push, upgrade) Show what would happen without making changes
--supabase (setup, install, upgrade, validate) Use Supabase-compatible install and grant role permissions
--drizzle (setup, install) Generate a Drizzle migration instead of direct install
--exclude-operator-family (setup, install, upgrade, validate) Skip operator family creation (for non-superuser roles)
--latest (setup, install, upgrade) Fetch the latest EQL from GitHub instead of using the bundled version
`.trim()

interface ParsedArgs {
Expand Down Expand Up @@ -115,8 +115,17 @@ async function main() {
case 'status':
await statusCommand()
break
case 'init':
await initCommand()
case 'setup':
await setupCommand({
force: flags.force,
dryRun: flags['dry-run'],
supabase: flags.supabase,
excludeOperatorFamily: flags['exclude-operator-family'],
drizzle: flags.drizzle,
latest: flags.latest,
name: values.name,
out: values.out,
})
break
case 'test-connection':
await testConnectionCommand()
Expand Down
2 changes: 1 addition & 1 deletion packages/stack-forge/src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { initCommand } from './init.js'
export { setupCommand } from './init.js'
export { installCommand } from './install.js'
export { pushCommand } from './push.js'
export { statusCommand } from './status.js'
Expand Down
Loading
Loading