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
14 changes: 14 additions & 0 deletions .changeset/fluffy-doodles-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@cipherstash/drizzle": minor
---

Added `generate-eql-migration` CLI command to automate EQL migration generation.

This command consolidates the manual process of running `drizzle-kit generate --custom` and populating the SQL file into a single command. It uses the bundled EQL SQL from `@cipherstash/schema` for offline-friendly, version-locked installations.

Usage:
```bash
npx generate-eql-migration
npx generate-eql-migration --name setup-eql
npx generate-eql-migration --out migrations
```
5 changes: 5 additions & 0 deletions .changeset/silly-hounds-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cipherstash/schema": major
---

Include EQL 2.1.8 in package distribution
135 changes: 135 additions & 0 deletions packages/drizzle/GENERATE_EQL_MIGRATION_CLI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# generate-eql-migration CLI Command

A command-line tool for easily generating Drizzle migrations that install CipherStash EQL (Encrypt Query Language) in your PostgreSQL database.

## Purpose

This CLI automates the process of:
1. Creating a custom Drizzle migration file
2. Populating it with the EQL SQL schema (bundled from `@cipherstash/schema`)
3. Making it ready to apply to your database

## Installation

The command is automatically available when you install `@cipherstash/drizzle`:

```bash
pnpm add @cipherstash/drizzle
```

## Usage

### Basic Usage

From your project root (where your Drizzle config is located):

```bash
npx generate-eql-migration
```

This will:
- Generate a migration named `install-eql` in the `drizzle/` directory
- Fill it with the EQL SQL schema
- Show you next steps

### Options

```bash
Usage: generate-eql-migration [options]

Options:
-n, --name <name> Migration name (default: "install-eql")
-o, --out <dir> Output directory (default: "drizzle")
-h, --help Display this help message
```

### Examples

```bash
# Default: creates drizzle/XXXX_install-eql.sql
npx generate-eql-migration

# Custom name
npx generate-eql-migration --name setup-eql

# Custom output directory
npx generate-eql-migration --out migrations

# Both custom name and directory
npx generate-eql-migration --name init-eql --out db/migrations
```

## How It Works

1. **Calls drizzle-kit**: Executes `pnpm drizzle-kit generate --custom --name=<name>` to create an empty migration file
2. **Locates EQL SQL**: Finds `cipherstash-encrypt-2-1-8.sql` from the installed `@cipherstash/schema` package
3. **Populates migration**: Writes the EQL SQL content to the generated migration file
4. **Reports success**: Shows the path to the migration and next steps

## Implementation Details

- **Location**: `packages/drizzle/bin/generate-eql-migration.js`
- **Package.json entry**: `"bin": { "generate-eql-migration": "./bin/generate-eql-migration.js" }`
- **Dependencies**:
- Requires `drizzle-kit` (peer dependency, optional)
- Reads SQL from `@cipherstash/schema` package
- Uses Node.js built-in modules (fs, path, child_process)

## Error Handling

The CLI will exit with an error if:
- `drizzle-kit` is not installed or fails to generate the migration
- The EQL SQL file cannot be found in `@cipherstash/schema`
- The Drizzle output directory doesn't exist
- The generated migration file cannot be found

## After Running

Once the migration is created, apply it with:

```bash
npx drizzle-kit migrate
```

Or use your custom migration workflow.

## Comparison to Manual Process

### Before (manual):
```bash
npx drizzle-kit generate --custom --name=install-eql
curl -sL https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql > drizzle/0001_install-eql.sql
npx drizzle-kit migrate
```

### After (automated):
```bash
npx generate-eql-migration
npx drizzle-kit migrate
```

## Benefits

1. **Offline-friendly**: Uses the bundled SQL file from `@cipherstash/schema` instead of downloading from GitHub
2. **Version-locked**: Always installs the EQL version that matches your installed `@cipherstash/schema` package
3. **Simplified workflow**: Single command instead of multiple steps
4. **Error-resistant**: Validates each step and provides clear error messages
5. **Flexible**: Supports custom names and output directories

## Troubleshooting

### "Failed to generate custom migration"
- Ensure `drizzle-kit` is installed: `pnpm add -D drizzle-kit`
- Check that you're running from the project root with a valid Drizzle config

### "Could not find EQL SQL file"
- Ensure `@cipherstash/schema` is installed (peer dependency)
- The CLI looks for `cipherstash-encrypt-2-1-8.sql` in the schema package

### "Drizzle directory not found"
- Run the command from your project root
- Or specify the correct path with `--out`

### "Could not find migration file"
- The CLI looks for files matching the pattern `*<name>.sql` in the output directory
- Check that `drizzle-kit` successfully created the migration
51 changes: 51 additions & 0 deletions packages/drizzle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,57 @@ Seamlessly integrate Protect.js with Drizzle ORM and PostgreSQL to encrypt your
npm install @cipherstash/protect @cipherstash/drizzle drizzle-orm
```

## Database Setup

Before using encrypted columns, you need to install the CipherStash EQL (Encrypt Query Language) functions in your PostgreSQL database.

### Install EQL via migration

The easiest way is to use the built-in CLI command:

```bash
npx generate-eql-migration
# or: pnpm/yarn/bun generate-eql-migration
```

This will:
1. Generate a custom Drizzle migration (default name: `install-eql`)
2. Populate it with the EQL SQL schema from `@cipherstash/schema`
3. Place it in your `drizzle/` directory

Then run your migrations:

```bash
npx drizzle-kit migrate
# or: pnpm/yarn/bun drizzle-kit migrate
```

#### CLI Options

```bash
Usage: generate-eql-migration [options]

Options:
-n, --name <name> Migration name (default: "install-eql")
-o, --out <dir> Output directory (default: "drizzle")
-h, --help Display this help message

Examples:
npx generate-eql-migration
npx generate-eql-migration --name setup-eql
npx generate-eql-migration --out migrations
```

### Manual installation (alternative)

If you prefer to install EQL manually:

```bash
npx drizzle-kit generate --custom --name=install-eql
curl -sL https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql > drizzle/0001_install-eql.sql
npx drizzle-kit migrate
```

## Quick Start

### 1. Define your schema with encrypted columns
Expand Down
128 changes: 128 additions & 0 deletions packages/drizzle/bin/generate-eql-migration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env node

import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
import { resolve, dirname, join } from 'node:path';
import { execSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));

async function main() {
let migrationPath = null;
const args = process.argv.slice(2);

// Parse arguments
let migrationName = 'install-eql';
let drizzleDir = 'drizzle';
let showHelp = false;

for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--help' || arg === '-h') {
showHelp = true;
} else if (arg === '--name' || arg === '-n') {
migrationName = args[++i];
} else if (arg === '--out' || arg === '-o') {
drizzleDir = args[++i];
}
}

if (showHelp) {
console.log(`
Usage: generate-eql-migration [options]

Generate a Drizzle migration that installs CipherStash EQL

Options:
-n, --name <name> Migration name (default: "install-eql")
-o, --out <dir> Output directory (default: "drizzle")
-h, --help Display this help message

Examples:
npx generate-eql-migration
npx generate-eql-migration --name setup-eql
npx generate-eql-migration --out migrations

# Or with your package manager:
pnpm generate-eql-migration
yarn generate-eql-migration
bun generate-eql-migration
`);
process.exit(0);
}

console.log('🔐 Generating EQL migration for Drizzle...\n');

// Step 1: Generate custom migration with drizzle-kit
try {
console.log(`📝 Generating custom migration: ${migrationName}`);
execSync(`npx drizzle-kit generate --custom --name=${migrationName}`, {
stdio: 'inherit'
});
} catch (error) {
console.error('❌ Failed to generate custom migration');
console.error('Make sure drizzle-kit is installed in your project.');
process.exit(1);
}

try {
// Step 2: Find the SQL file from @cipherstash/schema package
const schemaPackagePath = resolve(__dirname, '../../schema');
const sqlFileName = 'cipherstash-encrypt-2-1-8.sql';
const sqlSourcePath = join(schemaPackagePath, sqlFileName);

if (!existsSync(sqlSourcePath)) {
throw new Error(`Could not find EQL SQL file at: ${sqlSourcePath}`);
}

// Step 3: Read the EQL SQL content
const eqlSql = readFileSync(sqlSourcePath, 'utf-8');

// Step 4: Find the generated migration file and write EQL SQL to it
// Drizzle generates migrations in format: 0001_migration_name.sql
const drizzlePath = resolve(process.cwd(), drizzleDir);

if (!existsSync(drizzlePath)) {
throw new Error(`Drizzle directory not found: ${drizzlePath}\nMake sure to run this command from your project root.`);
}

// Find the latest migration file with the specified name
const fs = await import('node:fs/promises');
const files = await fs.readdir(drizzlePath);
const migrationFile = files
.filter(f => f.endsWith('.sql') && f.includes(migrationName))
.sort()
.pop();

if (!migrationFile) {
throw new Error(`Could not find migration file for: ${migrationName}\nLooked in: ${drizzlePath}`);
}

migrationPath = join(drizzlePath, migrationFile);
console.log(`\n📄 Writing EQL SQL to: ${migrationFile}`);

writeFileSync(migrationPath, eqlSql, 'utf-8');

console.log(`\n✅ Successfully created EQL migration!`);
console.log(`\nNext steps:`);
console.log(` 1. Review the migration: ${migrationPath}`);
console.log(` 2. Run migrations: npx drizzle-kit migrate`);
console.log(` (or use your package manager: pnpm/yarn/bun drizzle-kit migrate)`);
} catch (error) {
// Cleanup: remove the migration file if it was created
if (migrationPath && existsSync(migrationPath)) {
try {
unlinkSync(migrationPath);
console.error(`\n🗑️ Cleaned up migration file: ${migrationPath}`);
} catch (cleanupError) {
console.error(`\n⚠️ Failed to cleanup migration file: ${migrationPath}`);
}
}
throw error;
}
}

main().catch(error => {
console.error('❌ Error:', error.message);
process.exit(1);
});
13 changes: 13 additions & 0 deletions packages/drizzle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@
"license": "MIT",
"author": "CipherStash <hello@cipherstash.com>",
"type": "module",
"bin": {
"generate-eql-migration": "./bin/generate-eql-migration.js"
},
"exports": {
"./pg": {
"types": "./dist/pg/index.d.ts",
"import": "./dist/pg/index.js",
"require": "./dist/pg/index.cjs"
}
},
"files": [
"dist",
"bin",
"README.md"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
Expand All @@ -36,7 +44,9 @@
},
"peerDependencies": {
"@cipherstash/protect": ">=10",
"@cipherstash/schema": ">=1.1",
"@types/pg": "*",
"drizzle-kit": ">=0.20",
"drizzle-orm": ">=0.33",
"pg": ">=8",
"postgres": ">=3"
Expand All @@ -45,6 +55,9 @@
"@types/pg": {
"optional": true
},
"drizzle-kit": {
"optional": true
},
"pg": {
"optional": true
},
Expand Down
Loading