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
95 changes: 95 additions & 0 deletions .claude/skills/final-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Final Review Skill

When performing a final review before merge, include the following checks:

## Template Parity Check

Ensure all supported templates have parity across:

1. **Template files** (`templates/` directory):
- Each template should have a file (e.g., `bash.sh`, `typescript.ts`, `python.py`, `node.js`, `ruby.rb`)
- All templates should include the same example operations (copy file, update JSON, replace directory)

2. **Template registration** (`src/templates.rs`):
- Each template file should be registered in the `TEMPLATES` array
- Verify correct extension mapping

3. **CLI help text** (`src/main.rs`):
- The `--template` flag help text should list all supported templates

4. **Documentation** (`CLAUDE.md`):
- The templates section should list all template files

5. **Integration tests** (`tests/integration.rs`):
- The `test_list_templates` test should assert all templates are present

6. **Fixture tests** (`tests/fixture_operations.rs`):
- Each template should have a dedicated runtime test (e.g., `test_bash_runtime_migration`, `test_ruby_runtime_migration`)

## CLI Command and Option Coverage

Verify each CLI command and option is documented and tested:

### Commands

| Command | Documentation | Integration Test | Fixture Test |
|---------|---------------|------------------|--------------|
| `status` | CLAUDE.md | `test_status_no_migrations_dir`, `test_status_empty_migrations_dir`, `test_status_shows_applied_and_pending` | - |
| `up` | CLAUDE.md | `test_up_applies_migrations`, `test_failed_migration_stops_execution` | All `test_migration_*` tests |
| `create` | CLAUDE.md | `test_create_bash_migration`, `test_create_typescript_migration`, `test_create_increments_prefix` | - |

### Global Options

| Option | Documentation | Integration Test |
|--------|---------------|------------------|
| `-r, --root` | CLAUDE.md | Used in all integration tests |
| `-m, --migrations` | CLAUDE.md | Default used in tests |

### `up` Options

| Option | Documentation | Integration Test | Fixture Test |
|--------|---------------|------------------|--------------|
| `--dry-run` | CLAUDE.md | `test_up_dry_run` | `test_dry_run_preserves_fixture` |

### `create` Options

| Option | Documentation | Integration Test |
|--------|---------------|------------------|
| `--template` | CLAUDE.md | `test_create_typescript_migration` |
| `--description` | CLAUDE.md | (implicit in template content) |
| `--list-templates` | CLAUDE.md | `test_list_templates` |

## How to verify template parity

Run this command to list all templates from the CLI:
```bash
cargo run -- create dummy --list-templates
```

Then verify each template appears in:
- `templates/` directory (one file per template)
- `src/templates.rs` TEMPLATES array
- `src/main.rs` help text for `--template`
- `CLAUDE.md` templates section
- `tests/integration.rs` test_list_templates assertions
- `tests/fixture_operations.rs` runtime test functions

## How to verify CLI coverage

1. Run `cargo run -- -h` and `cargo run -- <command> -h` for each command
2. Verify each option shown in help output has:
- A corresponding entry in CLAUDE.md
- At least one integration test that exercises it
- A fixture test for options that affect migration execution

## Checklist

- [ ] All templates have parity (same example operations)
- [ ] All CLI commands documented in CLAUDE.md
- [ ] All CLI options documented in CLAUDE.md
- [ ] Each command has integration tests
- [ ] Each option has at least one test
- [ ] Migration execution options have fixture tests
- [ ] `cargo nextest run` passes
- [ ] `cargo clippy` passes
- [ ] `cargo fmt --check` passes
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ migrate status # Show applied/pending migrations
migrate up # Apply all pending migrations
migrate up --dry-run # Preview without applying
migrate create <name> # Create new bash migration
migrate create <name> --runtime ts # Create TypeScript migration
migrate create <name> --list-runtimes # List available runtimes
migrate create <name> --template ts # Create TypeScript migration
migrate create <name> --list-templates # List available templates
```

## Options
Expand Down Expand Up @@ -66,7 +66,7 @@ await fs.writeFile(`${projectRoot}/config.json`, '{}');
- `status.rs` - Status command
- `up.rs` - Up command
- `create.rs` - Create command
- `templates/` - Template source files (bash.sh, typescript.ts, python.py, node.js)
- `templates/` - Template source files (bash.sh, typescript.ts, python.py, node.js, ruby.rb)

## Development

Expand Down
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ glob = "0.3"

[dev-dependencies]
tempfile = "3"
serde_json = "1"
12 changes: 5 additions & 7 deletions conductor.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"scripts": {
"setup": "./scripts/setup",
"test": "./scripts/test",
"build": "cargo build --release",
"lint": "cargo fmt --check && cargo clippy -- -D warnings"
}
}
"scripts": {
"setup": "./scripts/setup",
"run": "./scripts/test"
}
}
20 changes: 10 additions & 10 deletions src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,32 @@ pub fn run(
project_root: &Path,
migrations_dir: &Path,
name: Option<&str>,
runtime: &str,
template_name: &str,
description: Option<&str>,
list_runtimes: bool,
should_list_templates: bool,
) -> Result<()> {
// Handle --list-runtimes flag
if list_runtimes {
println!("Available runtimes:");
// Handle --list-templates flag
if should_list_templates {
println!("Available templates:");
for template in list_templates() {
println!(" {}", template);
}
return Ok(());
}

// Name is required when not listing runtimes
// Name is required when not listing templates
let name = match name {
Some(n) => n,
None => bail!("Migration name is required. Usage: migrate create <name>"),
};

// Validate runtime
let template = match get_template(runtime) {
// Validate template
let template = match get_template(template_name) {
Some(t) => t,
None => {
bail!(
"Unknown runtime '{}'. Available: {}",
runtime,
"Unknown template '{}'. Available: {}",
template_name,
list_templates().collect::<Vec<_>>().join(", ")
);
}
Expand Down
16 changes: 8 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@ enum Commands {
/// Migration name (e.g., "add-config")
name: Option<String>,

/// Runtime template (bash, ts, python, node)
/// Template to use (bash, ts, python, node, ruby)
#[arg(short = 't', long, default_value = "bash")]
runtime: String,
template: String,

/// Migration description
#[arg(short = 'd', long)]
description: Option<String>,

/// List available runtimes
/// List available templates
#[arg(long)]
list_runtimes: bool,
list_templates: bool,
},
}

Expand All @@ -62,17 +62,17 @@ fn main() -> Result<()> {
}
Commands::Create {
name,
runtime,
template,
description,
list_runtimes,
list_templates,
} => {
commands::create::run(
&cli.root,
&cli.migrations,
name.as_deref(),
&runtime,
&template,
description.as_deref(),
list_runtimes,
list_templates,
)?;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ pub static TEMPLATES: &[Template] = &[
extension: ".js",
content: include_str!("../templates/node.js"),
},
Template {
name: "ruby",
extension: ".rb",
content: include_str!("../templates/ruby.rb"),
},
];

/// Get a template by name
Expand Down
14 changes: 13 additions & 1 deletion templates/bash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,17 @@ set -euo pipefail

cd "$MIGRATE_PROJECT_ROOT"

# Your migration code here
echo "Running migration: $MIGRATE_ID"

# Example operations (remove or modify as needed):

# 1. Copy file from migration sub-dir to target location
# cp "$MIGRATE_MIGRATIONS_DIR/$MIGRATE_ID/config.example.json" ./config/config.json

# 2. Update a JSON file: remove one element and set another value
# jq 'del(.oldField) | .settings.newValue = "updated"' config.json > config.json.tmp
# mv config.json.tmp config.json

# 3. Delete one directory and replace it with another
# rm -rf ./old-directory
# cp -r "$MIGRATE_MIGRATIONS_DIR/$MIGRATE_ID/new-directory" ./new-directory
45 changes: 43 additions & 2 deletions templates/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,50 @@ const fs = require('fs').promises;
const path = require('path');

const projectRoot = process.env.MIGRATE_PROJECT_ROOT;
const migrationsDir = process.env.MIGRATE_MIGRATIONS_DIR;
const migrationId = process.env.MIGRATE_ID;
const dryRun = process.env.MIGRATE_DRY_RUN === 'true';

console.log(`Running migration: ${migrationId}`);
async function main() {
console.log(`Running migration: ${migrationId}`);

// Your migration code here
// Example operations (remove or modify as needed):

// 1. Copy file from migration sub-dir to target location
// const sourceFile = path.join(migrationsDir, migrationId, 'config.example.json');
// const targetFile = path.join(projectRoot, 'config', 'config.json');
// await fs.mkdir(path.dirname(targetFile), { recursive: true });
// await fs.copyFile(sourceFile, targetFile);

// 2. Update a JSON file: remove one element and set another value
// const configPath = path.join(projectRoot, 'config.json');
// const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
// delete config.oldField;
// config.settings = config.settings || {};
// config.settings.newValue = 'updated';
// await fs.writeFile(configPath, JSON.stringify(config, null, 2));

// 3. Delete one directory and replace it with another
// const oldDir = path.join(projectRoot, 'old-directory');
// const newDirSource = path.join(migrationsDir, migrationId, 'new-directory');
// const newDirTarget = path.join(projectRoot, 'new-directory');
// await fs.rm(oldDir, { recursive: true, force: true });
// await copyDir(newDirSource, newDirTarget);
}

// Helper: recursively copy a directory
async function copyDir(src, dest) {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await copyDir(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
}
}

main();
Loading