KeiSeiKit-1.0/_primitives/_rust/kei-migrate/src/cmd_create.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

44 lines
1.6 KiB
Rust

//! `kei-migrate create <name>` — scaffold a new timestamped migration pair.
use anyhow::{bail, Context, Result};
use chrono::Utc;
use std::fs;
use std::path::{Path, PathBuf};
const UP_TEMPLATE: &str = "-- up migration\n-- Write forward-direction SQL below.\n\n";
const DOWN_TEMPLATE: &str =
"-- down migration\n-- Write reverse SQL below, or add `-- IRREVERSIBLE` to block reversion.\n\n";
/// Create `<dir>/<utc-timestamp>_<sanitized-name>.sql` + `.down.sql`. Returns paths written.
pub fn run(dir: &Path, name: &str) -> Result<(PathBuf, PathBuf)> {
validate_name(name)?;
fs::create_dir_all(dir).with_context(|| format!("mkdir -p {}", dir.display()))?;
let ts = Utc::now().format("%Y%m%d%H%M%S").to_string();
let sanitized = sanitize(name);
let up = dir.join(format!("{}_{}.sql", ts, sanitized));
let down = dir.join(format!("{}_{}.down.sql", ts, sanitized));
if up.exists() || down.exists() {
bail!("collision: {} or {} already exists", up.display(), down.display());
}
fs::write(&up, UP_TEMPLATE)?;
fs::write(&down, DOWN_TEMPLATE)?;
println!("[create] {}", up.display());
println!("[create] {}", down.display());
Ok((up, down))
}
fn validate_name(name: &str) -> Result<()> {
if name.is_empty() {
bail!("migration name must not be empty");
}
if name.len() > 80 {
bail!("migration name too long ({} chars, max 80)", name.len());
}
Ok(())
}
fn sanitize(name: &str) -> String {
name.chars()
.map(|c| if c.is_ascii_alphanumeric() { c.to_ascii_lowercase() } else { '_' })
.collect()
}