backend/migrations/
m20250805230408_fix_automerge_storage.rs

1use sqlx::{PgConnection, Postgres};
2use sqlx_migrator::Migration;
3use sqlx_migrator::Operation;
4use sqlx_migrator::error::Error;
5use sqlx_migrator::vec_box;
6use std::env;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use std::process::ExitStatus;
10
11pub(crate) struct FixAutomergeStorage;
12#[async_trait::async_trait]
13impl Migration<Postgres> for FixAutomergeStorage {
14    fn app(&self) -> &str {
15        "backend"
16    }
17    fn name(&self) -> &str {
18        "20250805230408_fix_automerge_storage"
19    }
20    fn parents(&self) -> Vec<Box<dyn Migration<Postgres>>> {
21        vec![]
22    }
23    fn operations(&self) -> Vec<Box<dyn Operation<Postgres>>> {
24        vec_box![MigrationOperation]
25    }
26
27    fn is_atomic(&self) -> bool {
28        false
29    }
30}
31
32struct MigrationOperation;
33#[async_trait::async_trait]
34impl Operation<Postgres> for MigrationOperation {
35    async fn up(&self, _: &mut PgConnection) -> Result<(), Error> {
36        if env::var_os("INVOCATION_ID").is_some() {
37            run_automerge_migration_during_deployment()?;
38        } else {
39            run_automerge_migration_during_development()?;
40        }
41
42        Ok(())
43    }
44
45    async fn down(&self, _: &mut PgConnection) -> Result<(), Error> {
46        Ok(())
47    }
48}
49
50fn run_automerge_migration_during_development()
51-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
52    let cwd = env::current_dir().map_err(|e| format!("Failed to get current directory: {e}"))?;
53
54    let git_root = find_git_root(&cwd).ok_or("No .git root found")?;
55
56    let automerge_server_dir = git_root.join("packages").join("automerge-doc-server");
57
58    let status = Command::new("npm")
59        .args(["run", "main", "--", "--migrate", "fix_automerge_storage"])
60        .current_dir(&automerge_server_dir)
61        .status()?;
62
63    check_status(status, "`npm run migrate-storage`")?;
64
65    Ok(())
66}
67
68fn run_automerge_migration_during_deployment()
69-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
70    let status = Command::new("automerge-doc-server")
71        .args(["--migrate", "fix_automerge_storage"])
72        .status()
73        .map_err(|e| format!("Failed to run `automerge-doc-server`: {e}"))?;
74
75    check_status(status, "`automerge-doc-server --migrate automerge_storage`")?;
76
77    Ok(())
78}
79
80fn check_status(
81    status: ExitStatus,
82    command: &str,
83) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
84    if status.success() {
85        println!("{command} succeeded");
86        Ok(())
87    } else {
88        Err(format!("{command} failed with exit code {:?}", status.code()).into())
89    }
90}
91
92fn find_git_root(start: impl AsRef<Path>) -> Option<PathBuf> {
93    let mut dir = start.as_ref().canonicalize().ok()?;
94
95    loop {
96        if dir.join(".git").is_dir() {
97            return Some(dir);
98        }
99
100        if !dir.pop() {
101            break;
102        }
103    }
104
105    None
106}