-module(m25@internal@cli). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -define(FILEPATH, "src/m25/internal/cli.gleam"). -export([migrate_for_tests/1, run_cli/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -file("src/m25/internal/cli.gleam", 23). ?DOC(false). -spec add_error_context(binary(), binary()) -> binary(). add_error_context(Error, Context) -> <<<>/binary, Error/binary>>. -file("src/m25/internal/cli.gleam", 27). ?DOC(false). -spec print(binary(), boolean()) -> nil. print(Message, Silent) -> case Silent of false -> gleam_stdlib:print_error(Message); true -> nil end. -file("src/m25/internal/cli.gleam", 34). ?DOC(false). -spec println(binary(), boolean()) -> nil. println(Message, Silent) -> print(<>, Silent). -file("src/m25/internal/cli.gleam", 38). ?DOC(false). -spec check_m25_schema_exists(pog:connection()) -> {ok, boolean()} | {error, binary()}. check_m25_schema_exists(Tx) -> Query_result = begin _pipe = pog:'query'( <<"select schema_name from information_schema.schemata where schema_name = 'm25'"/utf8>> ), _pipe@1 = pog:returning( _pipe, begin gleam@dynamic@decode:field( 0, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Value) -> gleam@dynamic@decode:success(Value) end ) end ), _pipe@2 = pog:execute(_pipe@1, Tx), gleam@result:map_error(_pipe@2, fun gleam@string:inspect/1) end, gleam@result:'try'( Query_result, fun(Response) -> case erlang:element(3, Response) of [_] -> {ok, true}; [] -> {ok, false}; _ -> {error, <<"Too many rows"/utf8>>} end end ). -file("src/m25/internal/cli.gleam", 58). ?DOC(false). -spec get_current_version(pog:connection()) -> {ok, gleam@option:option(gleam@time@timestamp:timestamp())} | {error, binary()}. get_current_version(Tx) -> Query_result = begin _pipe = pog:'query'( <<"select version::timestamp from m25.version order by version desc limit 1"/utf8>> ), _pipe@1 = pog:returning( _pipe, begin gleam@dynamic@decode:field( 0, pog:timestamp_decoder(), fun(Timestamp) -> gleam@dynamic@decode:success(Timestamp) end ) end ), _pipe@2 = pog:execute(_pipe@1, Tx), gleam@result:map_error(_pipe@2, fun gleam@string:inspect/1) end, gleam@result:'try'( Query_result, fun(Response) -> case erlang:element(3, Response) of [] -> {ok, none}; [Version] -> {ok, {some, Version}}; _ -> {error, <<"Too many rows"/utf8>>} end end ). -file("src/m25/internal/cli.gleam", 80). ?DOC(false). -spec get_migrations() -> {ok, list(binary())} | {error, binary()}. get_migrations() -> gleam@result:'try'( begin _pipe = gleam_erlang_ffi:priv_directory(<<"m25"/utf8>>), gleam@result:replace_error( _pipe, <<"Couldn't get priv directory"/utf8>> ) end, fun(Priv) -> Directory@1 = case begin _pipe@1 = [Priv, <<"migrations"/utf8>>], gleam@list:reduce(_pipe@1, fun filepath:join/2) end of {ok, Directory} -> Directory; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"m25/internal/cli"/utf8>>, function => <<"get_migrations"/utf8>>, line => 86, value => _assert_fail, start => 1993, 'end' => 2078, pattern_start => 2004, pattern_end => 2017}) end, _pipe@2 = simplifile:get_files(Directory@1), gleam@result:map_error(_pipe@2, fun(Err) -> _pipe@3 = Err, _pipe@4 = gleam@string:inspect(_pipe@3), add_error_context( _pipe@4, <<"Unable to get files in priv directory"/utf8>> ) end) end ). -file("src/m25/internal/cli.gleam", 98). ?DOC(false). -spec version_from_filename(binary()) -> gleam@time@timestamp:timestamp(). version_from_filename(Filename) -> Filename@2 = case begin _pipe = Filename, _pipe@1 = filepath:split(_pipe), gleam@list:last(_pipe@1) end of {ok, Filename@1} -> Filename@1; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"m25/internal/cli"/utf8>>, function => <<"version_from_filename"/utf8>>, line => 99, value => _assert_fail, start => 2320, 'end' => 2385, pattern_start => 2331, pattern_end => 2343}) end, Version_string@1 = case begin _pipe@2 = Filename@2, _pipe@3 = gleam@string:split(_pipe@2, <<"_"/utf8>>), gleam@list:first(_pipe@3) end of {ok, Version_string} -> Version_string; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"m25/internal/cli"/utf8>>, function => <<"version_from_filename"/utf8>>, line => 100, value => _assert_fail@1, start => 2388, 'end' => 2463, pattern_start => 2399, pattern_end => 2417}) end, Version@1 = case tempo@datetime:parse( <>, {custom, <<"YYYYMMDDHHmmssZ"/utf8>>} ) of {ok, Version} -> Version; _assert_fail@2 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"m25/internal/cli"/utf8>>, function => <<"version_from_filename"/utf8>>, line => 102, value => _assert_fail@2, start => 2534, 'end' => 2633, pattern_start => 2545, pattern_end => 2556}) end, tempo@datetime:to_timestamp(Version@1). -file("src/m25/internal/cli.gleam", 107). ?DOC(false). -spec get_migrations_to_apply( gleam@option:option(gleam@time@timestamp:timestamp()) ) -> {ok, gleam@option:option(binary())} | {error, binary()}. get_migrations_to_apply(Current_version) -> gleam@result:'try'( begin _pipe = get_migrations(), gleam@result:map_error( _pipe, fun(_capture) -> add_error_context( _capture, <<"Failed to get migrations for m25 tables"/utf8>> ) end ) end, fun(Files) -> Files_to_apply = case Current_version of none -> Files; {some, Current_version@1} -> _pipe@1 = Files, gleam@list:filter( _pipe@1, fun(File) -> gleam@time@timestamp:compare( version_from_filename(File), Current_version@1 ) =:= gt end ) end, gleam@bool:guard(case Files_to_apply of [] -> true; _ -> false end, {ok, none}, fun() -> gleam@result:'try'( begin _pipe@2 = Files_to_apply, _pipe@3 = gleam@list:try_map( _pipe@2, fun simplifile:read/1 ), _pipe@4 = gleam@result:map( _pipe@3, fun(_capture@1) -> gleam@string:join(_capture@1, <<"\n"/utf8>>) end ), gleam@result:map_error( _pipe@4, fun(Err) -> add_error_context( gleam@string:inspect(Err), <<"Failed to read migrations"/utf8>> ) end ) end, fun(Migration_sql) -> Latest_migration@1 = case gleam@list:last( Files_to_apply ) of {ok, Latest_migration} -> Latest_migration; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"m25/internal/cli"/utf8>>, function => <<"get_migrations_to_apply"/utf8>>, line => 146, value => _assert_fail, start => 3631, 'end' => 3690, pattern_start => 3642, pattern_end => 3662}) end, New_version = version_from_filename( Latest_migration@1 ), New_version_sql = <<<<"insert into m25.version (version) values (timestamptz '"/utf8, (begin _pipe@5 = New_version, gleam@time@timestamp:to_rfc3339( _pipe@5, {duration, 0, 0} ) end)/binary>>/binary, "');\n"/utf8>>, {ok, {some, <<<>/binary, New_version_sql/binary>>}} end ) end) end ). -file("src/m25/internal/cli.gleam", 157). ?DOC(false). -spec apply_migrations(pog:connection(), binary(), boolean()) -> {ok, nil} | {error, binary()}. apply_migrations(Tx, Migration_sql, Silent) -> print(<<"Applying migrations for m25... "/utf8>>, Silent), gleam@result:'try'( begin _pipe@1 = gleam@string:last( begin _pipe = Migration_sql, gleam@string:trim_end(_pipe) end ), gleam@result:replace_error(_pipe@1, <<"Empty migration file"/utf8>>) end, fun(Last_char) -> Sql = case Last_char of <<";"/utf8>> -> Migration_sql; _ -> <> end, Sql@1 = <<<<"do $m25migration$ begin "/utf8, Sql/binary>>/binary, " end;$m25migration$"/utf8>>, Query_result = begin _pipe@2 = pog:'query'(Sql@1), _pipe@3 = pog:execute(_pipe@2, Tx), gleam@result:map_error(_pipe@3, fun gleam@string:inspect/1) end, gleam@result:'try'( Query_result, fun(_) -> println(<<"ok."/utf8>>, Silent), {ok, nil} end ) end ). -file("src/m25/internal/cli.gleam", 187). ?DOC(false). -spec handle_migration(pog:connection(), boolean(), boolean()) -> {ok, nil} | {error, binary()}. handle_migration(Tx, Apply, Silent) -> print(<<"Checking schema... "/utf8>>, Silent), gleam@result:'try'( begin _pipe = check_m25_schema_exists(Tx), gleam@result:map_error( _pipe, fun(_capture) -> add_error_context( _capture, <<"Unable to check if 'm25' schema exists"/utf8>> ) end ) end, fun(Schema_exists) -> println(<<"ok."/utf8>>, Silent), print(<<"Checking current version of m25 tables... "/utf8>>, Silent), Version_result = case Schema_exists of false -> {ok, none}; true -> _pipe@1 = get_current_version(Tx), gleam@result:map_error( _pipe@1, fun(_capture@1) -> add_error_context( _capture@1, <<"Unable to check m25 table version"/utf8>> ) end ) end, gleam@result:'try'( Version_result, fun(Current_version) -> println( <<"ok. Current version: "/utf8, (begin _pipe@2 = Current_version, _pipe@3 = gleam@option:map( _pipe@2, fun(_capture@2) -> gleam@time@timestamp:to_rfc3339( _capture@2, {duration, 0, 0} ) end ), gleam@option:unwrap(_pipe@3, <<"none"/utf8>>) end)/binary>>, Silent ), print( <<"Getting migrations for m25 tables... "/utf8>>, Silent ), gleam@result:'try'( begin _pipe@4 = get_migrations_to_apply(Current_version), gleam@result:map_error( _pipe@4, fun(_capture@3) -> add_error_context( _capture@3, <<"Failed to get migrations to apply for m25 tables"/utf8>> ) end ) end, fun(Migration_sql) -> println(<<"ok."/utf8>>, Silent), case Migration_sql of none -> println( <<"No migrations to apply for m25 tables\n"/utf8>>, Silent ), {ok, nil}; {some, Migration_sql@1} -> case Apply of true -> _pipe@5 = apply_migrations( Tx, Migration_sql@1, Silent ), gleam@result:map_error( _pipe@5, fun(_capture@4) -> add_error_context( _capture@4, <<"Failed to apply migrations for m25 tables"/utf8>> ) end ); false -> gleam_stdlib:println( Migration_sql@1 ), {ok, nil} end end end ) end ) end ). -file("src/m25/internal/cli.gleam", 254). ?DOC(false). -spec migrate_for_tests(pog:connection()) -> {ok, nil} | {error, binary()}. migrate_for_tests(Tx) -> handle_migration(Tx, true, true). -file("src/m25/internal/cli.gleam", 258). ?DOC(false). -spec connection_string_flag() -> glint:flag(binary()). connection_string_flag() -> _pipe = glint:string_flag(<<"addr"/utf8>>), _pipe@1 = glint:flag_help( _pipe, <<"The connection string for your Postgres database"/utf8>> ), glint:flag_default( _pipe@1, <<"postgresql://postgres:postgres@localhost:5432/postgres"/utf8>> ). -file("src/m25/internal/cli.gleam", 266). ?DOC(false). -spec apply_flag() -> glint:flag(boolean()). apply_flag() -> _pipe = glint:bool_flag(<<"apply"/utf8>>), _pipe@1 = glint:flag_help( _pipe, <<"Apply migrations instead of just printing them"/utf8>> ), glint:flag_default(_pipe@1, false). -file("src/m25/internal/cli.gleam", 272). ?DOC(false). -spec silent_flag() -> glint:flag(boolean()). silent_flag() -> _pipe = glint:bool_flag(<<"silent"/utf8>>), _pipe@1 = glint:flag_help(_pipe, <<"Don't print messages to stderr"/utf8>>), glint:flag_default(_pipe@1, false). -file("src/m25/internal/cli.gleam", 278). ?DOC(false). -spec migrate_command() -> glint:command({ok, nil} | {error, snag:snag()}). migrate_command() -> glint:command_help( <<"Migrate M25 modules in your Postgres database"/utf8>>, fun() -> glint:flag( connection_string_flag(), fun(Connection_string_arg) -> glint:flag( apply_flag(), fun(Apply_flag) -> glint:flag( silent_flag(), fun(Silent_flag) -> glint:command( fun(_, _, Flags) -> gleam@result:'try'( Connection_string_arg(Flags), fun(Connection_string) -> gleam@result:'try'( Apply_flag(Flags), fun(Apply) -> gleam@result:'try'( Silent_flag( Flags ), fun(Silent) -> Pool_name = gleam_erlang_ffi:new_name( <<"m25_migrations"/utf8>> ), gleam@result:'try'( begin _pipe = pog:url_config( Pool_name, Connection_string ), gleam@result:replace_error( _pipe, {snag, <<"Invalid Postgres connection string"/utf8>>, [<<"invalid connection string"/utf8>>]} ) end, fun( Config ) -> Conn@1 = case pog:start( Config ) of {ok, {started, _, Conn}} -> Conn; _assert_fail -> erlang:error( #{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"m25/internal/cli"/utf8>>, function => <<"migrate_command"/utf8>>, line => 300, value => _assert_fail, start => 7776, 'end' => 7833, pattern_start => 7787, pattern_end => 7813} ) end, Transaction_result = pog:transaction( Conn@1, fun( Tx ) -> gleam@result:'try'( handle_migration( Tx, Apply, Silent ), fun( _ ) -> case Apply of true -> println( <<"\nSuccess! Applied migrations for m25"/utf8>>, Silent ); false -> nil end, {ok, nil} end ) end ), case Transaction_result of {ok, _} -> {ok, nil}; {error, {transaction_rolled_back, Msg}} -> {error, {snag, Msg, [<<"transaction rolled back"/utf8>>]}}; {error, {transaction_query_error, Err}} -> {error, {snag, <<"Query error: "/utf8, (gleam@string:inspect( Err ))/binary>>, [<<"query error"/utf8>>]}} end end ) end ) end ) end ) end ) end ) end ) end ) end ). -file("src/m25/internal/cli.gleam", 324). ?DOC(false). -spec run_cli() -> nil. run_cli() -> Cli = begin _pipe = glint:new(), _pipe@1 = glint:with_name(_pipe, <<"m25"/utf8>>), _pipe@2 = glint:as_module(_pipe@1), _pipe@3 = glint:pretty_help(_pipe@2, glint:default_pretty_help()), glint:add(_pipe@3, [<<"migrate"/utf8>>], migrate_command()) end, glint:run_and_handle( Cli, erlang:element(4, argv:load()), fun(Cli_result) -> gleam@result:map_error( Cli_result, fun(Errors) -> println( <<"Command failed with an error: "/utf8, (erlang:element(2, Errors))/binary>>, false ) end ) end ).