Repository: TheDragonCode/laravel-migration-actions Branch: main Commit: 3ca0d9696909 Files: 199 Total size: 305.0 KB Directory structure: gitextract_mm59vhog/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ ├── preview-updater.yml │ └── workflows/ │ ├── code-style.yml │ ├── docs.yml │ ├── license.yml │ ├── preview.yml │ ├── release-drafter.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── biome.json ├── composer.json ├── config/ │ └── deploy-operations.php ├── database/ │ └── migrations/ │ ├── 2022_08_18_180137_change_migration_actions_table.php │ ├── 2023_01_21_172923_rename_migrations_actions_table_to_actions.php │ ├── 2024_05_21_112438_rename_actions_table_to_operations.php │ └── 2024_05_21_114318_rename_column_in_operations_table.php ├── docs/ │ ├── cfg/ │ │ └── buildprofiles.xml │ ├── do.tree │ ├── docs_libraries.tree │ ├── snippets/ │ │ ├── actual_file_names.sh │ │ ├── ask.sh │ │ ├── async.php │ │ ├── before_after.sh │ │ ├── deployer.php │ │ ├── di_invoke.php │ │ ├── di_up_down.php │ │ ├── empty.php │ │ ├── event_service_provider.php │ │ ├── events_list.php │ │ ├── example.php │ │ ├── example_artisan.php │ │ ├── except_environment.php │ │ ├── failed_status.php │ │ ├── invokable_status.php │ │ ├── invoke_and_down.php │ │ ├── listen_events.php │ │ ├── make_auto.sh │ │ ├── need_before.php │ │ ├── nested.sh │ │ ├── nested_example.sh │ │ ├── on_environment.php │ │ ├── on_environments.php │ │ ├── once_method.php │ │ ├── operation_helper_all.php │ │ ├── operation_helper_directory.php │ │ ├── operation_helper_file.php │ │ ├── operations_helper.sh │ │ ├── operations_status.sh │ │ ├── order_running_operations.sh │ │ ├── rollback.sh │ │ ├── some_listener.php │ │ ├── success_status.php │ │ ├── with_operation.php │ │ ├── with_operation_helper.php │ │ └── within_transactions.php │ ├── topics/ │ │ ├── artisan-commands.topic │ │ ├── creating-operations.topic │ │ ├── customize-stub.topic │ │ ├── database-data-dumper.topic │ │ ├── events.topic │ │ ├── execution-status.topic │ │ ├── installation.topic │ │ ├── introduction.topic │ │ ├── operation-helper.topic │ │ ├── operations-status.topic │ │ ├── rolling-back-operations.topic │ │ ├── running-operations.topic │ │ ├── snippets_composer.topic │ │ ├── upgrade-3.topic │ │ ├── upgrade-4.topic │ │ ├── upgrade-5.topic │ │ ├── upgrade-6.topic │ │ ├── upgrade-7.topic │ │ └── usage.topic │ ├── v.list │ ├── versions.json │ └── writerside.cfg ├── ide.json ├── phpunit.xml ├── pint.json ├── resources/ │ └── stubs/ │ └── deploy-operation.stub ├── src/ │ ├── Concerns/ │ │ ├── ConfirmableTrait.php │ │ ├── HasAbout.php │ │ ├── HasArtisan.php │ │ ├── HasIsolatable.php │ │ └── HasOptionable.php │ ├── Console/ │ │ ├── Command.php │ │ ├── FreshCommand.php │ │ ├── InstallCommand.php │ │ ├── MakeCommand.php │ │ ├── OperationsCommand.php │ │ ├── RollbackCommand.php │ │ └── StatusCommand.php │ ├── Constants/ │ │ ├── Names.php │ │ ├── Options.php │ │ └── Order.php │ ├── Data/ │ │ ├── Casts/ │ │ │ ├── BoolCast.php │ │ │ ├── Config/ │ │ │ │ ├── ExcludeCast.php │ │ │ │ └── PathCast.php │ │ │ ├── OperationNameCast.php │ │ │ └── PathCast.php │ │ ├── Config/ │ │ │ ├── ConfigData.php │ │ │ ├── QueueData.php │ │ │ ├── ShowData.php │ │ │ └── TransactionsData.php │ │ └── OptionsData.php │ ├── Enums/ │ │ ├── MethodEnum.php │ │ └── StatusEnum.php │ ├── Events/ │ │ ├── BaseEvent.php │ │ ├── DeployOperationEnded.php │ │ ├── DeployOperationFailed.php │ │ ├── DeployOperationStarted.php │ │ └── NoPendingDeployOperations.php │ ├── Helpers/ │ │ ├── GitHelper.php │ │ ├── OperationHelper.php │ │ └── SorterHelper.php │ ├── Jobs/ │ │ └── OperationJob.php │ ├── Listeners/ │ │ ├── Listener.php │ │ └── MigrationEndedListener.php │ ├── Notifications/ │ │ └── Notification.php │ ├── Operation.php │ ├── Processors/ │ │ ├── FreshProcessor.php │ │ ├── InstallProcessor.php │ │ ├── MakeProcessor.php │ │ ├── OperationsProcessor.php │ │ ├── Processor.php │ │ ├── RollbackProcessor.php │ │ └── StatusProcessor.php │ ├── Repositories/ │ │ └── OperationsRepository.php │ ├── ServiceProvider.php │ ├── Services/ │ │ ├── MigratorService.php │ │ └── MutexService.php │ └── helpers.php └── tests/ ├── Commands/ │ ├── EventsTest.php │ ├── FreshTest.php │ ├── InstallTest.php │ ├── MakeTest.php │ ├── MutexTest.php │ ├── OperationsTest.php │ ├── RollbackTest.php │ └── StatusTest.php ├── Concerns/ │ ├── AssertDatabase.php │ ├── Database.php │ ├── Files.php │ └── Some.php ├── Helpers/ │ ├── GitTest.php │ ├── OperationTest.php │ └── SorterTest.php ├── TestCase.php └── fixtures/ ├── app/ │ ├── async/ │ │ ├── 2021_04_06_212742_every_time.php │ │ └── 2023_04_06_212637_foo_bar.php │ ├── di/ │ │ ├── 2022_10_11_234251_invoke.php │ │ ├── 2022_10_11_234251_invoke_down.php │ │ └── 2022_10_11_234312_up_down.php │ ├── empty/ │ │ ├── .gitkeep │ │ └── some.txt │ ├── operations/ │ │ ├── 2020_12_07_153105_foo_bar.php │ │ ├── 2021_01_02_020947_every_time.php │ │ ├── 2021_05_24_120003_run_on_all.php │ │ ├── 2021_05_24_120003_run_on_many_environments.php │ │ ├── 2021_05_24_120003_run_on_production.php │ │ ├── 2021_05_24_120003_run_on_testing.php │ │ ├── 2021_06_07_132849_run_except_production.php │ │ ├── 2021_06_07_132917_run_except_testing.php │ │ ├── 2021_06_07_134045_run_except_many_environments.php │ │ ├── 2021_10_26_143247_run_allow.php │ │ ├── 2021_10_26_143304_run_disallow.php │ │ ├── 2021_12_23_165047_run_success.php │ │ ├── 2021_12_23_184029_run_failed.php │ │ ├── 2022_08_17_135147_test_before_enabled.php │ │ ├── 2022_08_17_135153_test_before_disabled.php │ │ └── sub_path/ │ │ ├── 2021_12_15_205804_baz.php │ │ └── 2022_10_27_230732_foo.php │ ├── operations_failed/ │ │ ├── 2021_12_23_165048_run_success_on_failed.php │ │ └── 2021_12_23_184029_run_failed_failure.php │ ├── stubs/ │ │ ├── 2021_02_15_124237_test_success_transactions.stub │ │ ├── 2021_02_15_124852_test_failed_transactions.stub │ │ ├── customized.stub │ │ └── make_example.stub │ └── via_migrations/ │ ├── 2025_03_31_213407_custom.php │ ├── 2025_03_31_234251_invoke.php │ └── 2025_03_31_234312_up_down.php ├── migrations/ │ ├── 2020_12_07_164624_create_test_table.php │ ├── 2021_01_02_022431_create_every_time_table.php │ ├── 2021_02_15_124419_create_transactions_table.php │ ├── 2021_05_24_122027_create_environment_table.php │ ├── 2021_12_23_165218_create_success_table.php │ ├── 2021_12_23_184434_create_failed_table.php │ └── 2022_08_17_150549_create_before_table.php └── migrations_with_operations/ ├── 2025_03_31_213847_call_invokable.php └── 2025_03_31_213921_call_up_down.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true max_line_length = 120 tab_width = 4 trim_trailing_whitespace = false ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = true ij_smart_tabs = false ij_visual_guides = none ij_wrap_on_typing = false [*.blade.php] ij_continuation_indent_size = 4 ij_blade_keep_indents_on_empty_lines = false [*.css] ij_css_align_closing_brace_with_properties = false ij_css_blank_lines_around_nested_selector = 1 ij_css_blank_lines_between_blocks = 1 ij_css_block_comment_add_space = true ij_css_brace_placement = end_of_line ij_css_enforce_quotes_on_format = true ij_css_hex_color_long_format = true ij_css_hex_color_lower_case = false ij_css_hex_color_short_format = false ij_css_hex_color_upper_case = true ij_css_keep_blank_lines_in_code = 1 ij_css_keep_indents_on_empty_lines = false ij_css_keep_single_line_blocks = false ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_css_space_after_colon = true ij_css_space_before_opening_brace = true ij_css_use_double_quotes = true ij_css_value_alignment = do_not_align [*.less] ij_less_align_closing_brace_with_properties = false ij_less_blank_lines_around_nested_selector = 1 ij_less_blank_lines_between_blocks = 1 ij_less_block_comment_add_space = false ij_less_brace_placement = 0 ij_less_enforce_quotes_on_format = false ij_less_hex_color_long_format = true ij_less_hex_color_lower_case = false ij_less_hex_color_short_format = false ij_less_hex_color_upper_case = true ij_less_keep_blank_lines_in_code = 2 ij_less_keep_indents_on_empty_lines = false ij_less_keep_single_line_blocks = false ij_less_line_comment_add_space = false ij_less_line_comment_at_first_column = false ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_less_space_after_colon = true ij_less_space_before_opening_brace = true ij_less_use_double_quotes = true ij_less_value_alignment = 0 [*.sass] ij_sass_align_closing_brace_with_properties = false ij_sass_blank_lines_around_nested_selector = 1 ij_sass_blank_lines_between_blocks = 1 ij_sass_brace_placement = 0 ij_sass_enforce_quotes_on_format = false ij_sass_hex_color_long_format = false ij_sass_hex_color_lower_case = false ij_sass_hex_color_short_format = false ij_sass_hex_color_upper_case = false ij_sass_keep_blank_lines_in_code = 2 ij_sass_keep_indents_on_empty_lines = false ij_sass_keep_single_line_blocks = false ij_sass_line_comment_add_space = false ij_sass_line_comment_at_first_column = false ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_sass_space_after_colon = true ij_sass_space_before_opening_brace = true ij_sass_use_double_quotes = true ij_sass_value_alignment = 0 [*.scss] ij_scss_align_closing_brace_with_properties = false ij_scss_blank_lines_around_nested_selector = 1 ij_scss_blank_lines_between_blocks = 1 ij_scss_block_comment_add_space = true ij_scss_brace_placement = 0 ij_scss_enforce_quotes_on_format = true ij_scss_hex_color_long_format = true ij_scss_hex_color_lower_case = false ij_scss_hex_color_short_format = false ij_scss_hex_color_upper_case = true ij_scss_keep_blank_lines_in_code = 1 ij_scss_keep_indents_on_empty_lines = false ij_scss_keep_single_line_blocks = false ij_scss_line_comment_add_space = false ij_scss_line_comment_at_first_column = false ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_scss_space_after_colon = true ij_scss_space_before_opening_brace = true ij_scss_use_double_quotes = true ij_scss_value_alignment = 0 [*.twig] ij_twig_keep_indents_on_empty_lines = false ij_twig_spaces_inside_comments_delimiters = true ij_twig_spaces_inside_delimiters = true ij_twig_spaces_inside_variable_delimiters = true [*.vue] ij_continuation_indent_size = 4 ij_vue_indent_children_of_top_level = template ij_vue_interpolation_new_line_after_start_delimiter = true ij_vue_interpolation_new_line_before_end_delimiter = true ij_vue_interpolation_wrap = off ij_vue_keep_indents_on_empty_lines = false ij_vue_spaces_within_interpolation_expressions = true [.editorconfig] ij_editorconfig_align_group_field_declarations = false ij_editorconfig_space_after_colon = false ij_editorconfig_space_after_comma = true ij_editorconfig_space_before_colon = false ij_editorconfig_space_before_comma = false ij_editorconfig_spaces_around_assignment_operators = true [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,phpunit.xml.dist}] ij_xml_align_attributes = true ij_xml_align_text = false ij_xml_attribute_wrap = normal ij_xml_block_comment_add_space = true ij_xml_block_comment_at_first_column = true ij_xml_keep_blank_lines = 2 ij_xml_keep_indents_on_empty_lines = false ij_xml_keep_line_breaks = true ij_xml_keep_line_breaks_in_text = true ij_xml_keep_whitespaces = false ij_xml_keep_whitespaces_around_cdata = preserve ij_xml_keep_whitespaces_inside_cdata = false ij_xml_line_comment_at_first_column = true ij_xml_space_after_tag_name = false ij_xml_space_around_equals_in_attribute = false ij_xml_space_inside_empty_tag = true ij_xml_text_wrap = normal [{*.ats,*.cts,*.mts,*.ts}] ij_continuation_indent_size = 4 ij_typescript_align_imports = false ij_typescript_align_multiline_array_initializer_expression = false ij_typescript_align_multiline_binary_operation = false ij_typescript_align_multiline_chained_methods = false ij_typescript_align_multiline_extends_list = false ij_typescript_align_multiline_for = true ij_typescript_align_multiline_parameters = true ij_typescript_align_multiline_parameters_in_calls = false ij_typescript_align_multiline_ternary_operation = false ij_typescript_align_object_properties = 0 ij_typescript_align_union_types = false ij_typescript_align_var_statements = 0 ij_typescript_array_initializer_new_line_after_left_brace = false ij_typescript_array_initializer_right_brace_on_new_line = false ij_typescript_array_initializer_wrap = off ij_typescript_assignment_wrap = off ij_typescript_binary_operation_sign_on_next_line = false ij_typescript_binary_operation_wrap = off ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_typescript_blank_lines_after_imports = 1 ij_typescript_blank_lines_around_class = 1 ij_typescript_blank_lines_around_field = 0 ij_typescript_blank_lines_around_field_in_interface = 0 ij_typescript_blank_lines_around_function = 1 ij_typescript_blank_lines_around_method = 1 ij_typescript_blank_lines_around_method_in_interface = 1 ij_typescript_block_brace_style = end_of_line ij_typescript_block_comment_add_space = false ij_typescript_block_comment_at_first_column = true ij_typescript_call_parameters_new_line_after_left_paren = false ij_typescript_call_parameters_right_paren_on_new_line = false ij_typescript_call_parameters_wrap = off ij_typescript_catch_on_new_line = false ij_typescript_chained_call_dot_on_new_line = true ij_typescript_class_brace_style = next_line ij_typescript_comma_on_new_line = false ij_typescript_do_while_brace_force = never ij_typescript_else_on_new_line = false ij_typescript_enforce_trailing_comma = remove ij_typescript_enum_constants_wrap = on_every_item ij_typescript_extends_keyword_wrap = off ij_typescript_extends_list_wrap = off ij_typescript_field_prefix = _ ij_typescript_file_name_style = relaxed ij_typescript_finally_on_new_line = false ij_typescript_for_brace_force = never ij_typescript_for_statement_new_line_after_left_paren = false ij_typescript_for_statement_right_paren_on_new_line = false ij_typescript_for_statement_wrap = off ij_typescript_force_quote_style = true ij_typescript_force_semicolon_style = true ij_typescript_function_expression_brace_style = end_of_line ij_typescript_if_brace_force = never ij_typescript_import_merge_members = global ij_typescript_import_prefer_absolute_path = global ij_typescript_import_sort_members = true ij_typescript_import_sort_module_name = false ij_typescript_import_use_node_resolution = true ij_typescript_imports_wrap = on_every_item ij_typescript_indent_case_from_switch = true ij_typescript_indent_chained_calls = true ij_typescript_indent_package_children = 0 ij_typescript_jsdoc_include_types = false ij_typescript_jsx_attribute_value = braces ij_typescript_keep_blank_lines_in_code = 1 ij_typescript_keep_first_column_comment = true ij_typescript_keep_indents_on_empty_lines = false ij_typescript_keep_line_breaks = true ij_typescript_keep_simple_blocks_in_one_line = false ij_typescript_keep_simple_methods_in_one_line = false ij_typescript_line_comment_add_space = true ij_typescript_line_comment_at_first_column = false ij_typescript_method_brace_style = next_line ij_typescript_method_call_chain_wrap = off ij_typescript_method_parameters_new_line_after_left_paren = false ij_typescript_method_parameters_right_paren_on_new_line = false ij_typescript_method_parameters_wrap = off ij_typescript_object_literal_wrap = on_every_item ij_typescript_object_types_wrap = on_every_item ij_typescript_parentheses_expression_new_line_after_left_paren = false ij_typescript_parentheses_expression_right_paren_on_new_line = false ij_typescript_place_assignment_sign_on_next_line = false ij_typescript_prefer_as_type_cast = false ij_typescript_prefer_explicit_types_function_expression_returns = false ij_typescript_prefer_explicit_types_function_returns = false ij_typescript_prefer_explicit_types_vars_fields = false ij_typescript_prefer_parameters_wrap = false ij_typescript_reformat_c_style_comments = false ij_typescript_space_after_colon = true ij_typescript_space_after_comma = true ij_typescript_space_after_dots_in_rest_parameter = false ij_typescript_space_after_generator_mult = true ij_typescript_space_after_property_colon = true ij_typescript_space_after_quest = true ij_typescript_space_after_type_colon = true ij_typescript_space_after_unary_not = true ij_typescript_space_before_async_arrow_lparen = true ij_typescript_space_before_catch_keyword = true ij_typescript_space_before_catch_left_brace = true ij_typescript_space_before_catch_parentheses = true ij_typescript_space_before_class_lbrace = true ij_typescript_space_before_class_left_brace = true ij_typescript_space_before_colon = true ij_typescript_space_before_comma = false ij_typescript_space_before_do_left_brace = true ij_typescript_space_before_else_keyword = true ij_typescript_space_before_else_left_brace = true ij_typescript_space_before_finally_keyword = true ij_typescript_space_before_finally_left_brace = true ij_typescript_space_before_for_left_brace = true ij_typescript_space_before_for_parentheses = true ij_typescript_space_before_for_semicolon = false ij_typescript_space_before_function_left_parenth = true ij_typescript_space_before_generator_mult = false ij_typescript_space_before_if_left_brace = true ij_typescript_space_before_if_parentheses = true ij_typescript_space_before_method_call_parentheses = false ij_typescript_space_before_method_left_brace = true ij_typescript_space_before_method_parentheses = false ij_typescript_space_before_property_colon = false ij_typescript_space_before_quest = true ij_typescript_space_before_switch_left_brace = true ij_typescript_space_before_switch_parentheses = true ij_typescript_space_before_try_left_brace = true ij_typescript_space_before_type_colon = false ij_typescript_space_before_unary_not = false ij_typescript_space_before_while_keyword = true ij_typescript_space_before_while_left_brace = true ij_typescript_space_before_while_parentheses = true ij_typescript_spaces_around_additive_operators = true ij_typescript_spaces_around_arrow_function_operator = true ij_typescript_spaces_around_assignment_operators = true ij_typescript_spaces_around_bitwise_operators = true ij_typescript_spaces_around_equality_operators = true ij_typescript_spaces_around_logical_operators = true ij_typescript_spaces_around_multiplicative_operators = true ij_typescript_spaces_around_relational_operators = true ij_typescript_spaces_around_shift_operators = true ij_typescript_spaces_around_unary_operator = false ij_typescript_spaces_within_array_initializer_brackets = false ij_typescript_spaces_within_brackets = false ij_typescript_spaces_within_catch_parentheses = false ij_typescript_spaces_within_for_parentheses = false ij_typescript_spaces_within_if_parentheses = false ij_typescript_spaces_within_imports = true ij_typescript_spaces_within_interpolation_expressions = true ij_typescript_spaces_within_method_call_parentheses = false ij_typescript_spaces_within_method_parentheses = false ij_typescript_spaces_within_object_literal_braces = true ij_typescript_spaces_within_object_type_braces = true ij_typescript_spaces_within_parentheses = false ij_typescript_spaces_within_switch_parentheses = false ij_typescript_spaces_within_type_assertion = false ij_typescript_spaces_within_union_types = true ij_typescript_spaces_within_while_parentheses = false ij_typescript_special_else_if_treatment = true ij_typescript_ternary_operation_signs_on_next_line = false ij_typescript_ternary_operation_wrap = off ij_typescript_union_types_wrap = on_every_item ij_typescript_use_chained_calls_group_indents = false ij_typescript_use_double_quotes = false ij_typescript_use_explicit_js_extension = auto ij_typescript_use_path_mapping = always ij_typescript_use_public_modifier = false ij_typescript_use_semicolon_after_statement = false ij_typescript_var_declaration_wrap = normal ij_typescript_while_brace_force = never ij_typescript_while_on_new_line = false ij_typescript_wrap_comments = false [{*.bash,*.sh,*.zsh}] ij_shell_binary_ops_start_line = false ij_shell_keep_column_alignment_padding = false ij_shell_minify_program = false ij_shell_redirect_followed_by_space = false ij_shell_switch_cases_indented = false ij_shell_use_unix_line_separator = true [{*.cjs,*.js}] ij_continuation_indent_size = 4 ij_javascript_align_imports = false ij_javascript_align_multiline_array_initializer_expression = false ij_javascript_align_multiline_binary_operation = false ij_javascript_align_multiline_chained_methods = false ij_javascript_align_multiline_extends_list = false ij_javascript_align_multiline_for = true ij_javascript_align_multiline_parameters = true ij_javascript_align_multiline_parameters_in_calls = false ij_javascript_align_multiline_ternary_operation = false ij_javascript_align_object_properties = 0 ij_javascript_align_union_types = false ij_javascript_align_var_statements = 1 ij_javascript_array_initializer_new_line_after_left_brace = true ij_javascript_array_initializer_right_brace_on_new_line = true ij_javascript_array_initializer_wrap = on_every_item ij_javascript_assignment_wrap = off ij_javascript_binary_operation_sign_on_next_line = false ij_javascript_binary_operation_wrap = off ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_javascript_blank_lines_after_imports = 1 ij_javascript_blank_lines_around_class = 1 ij_javascript_blank_lines_around_field = 0 ij_javascript_blank_lines_around_function = 1 ij_javascript_blank_lines_around_method = 1 ij_javascript_block_brace_style = end_of_line ij_javascript_block_comment_add_space = false ij_javascript_block_comment_at_first_column = true ij_javascript_call_parameters_new_line_after_left_paren = false ij_javascript_call_parameters_right_paren_on_new_line = false ij_javascript_call_parameters_wrap = off ij_javascript_catch_on_new_line = false ij_javascript_chained_call_dot_on_new_line = true ij_javascript_class_brace_style = next_line ij_javascript_comma_on_new_line = false ij_javascript_do_while_brace_force = never ij_javascript_else_on_new_line = false ij_javascript_enforce_trailing_comma = remove ij_javascript_extends_keyword_wrap = off ij_javascript_extends_list_wrap = off ij_javascript_field_prefix = _ ij_javascript_file_name_style = lisp_case ij_javascript_finally_on_new_line = false ij_javascript_for_brace_force = never ij_javascript_for_statement_new_line_after_left_paren = false ij_javascript_for_statement_right_paren_on_new_line = false ij_javascript_for_statement_wrap = off ij_javascript_force_quote_style = true ij_javascript_force_semicolon_style = true ij_javascript_function_expression_brace_style = end_of_line ij_javascript_if_brace_force = never ij_javascript_import_merge_members = global ij_javascript_import_prefer_absolute_path = true ij_javascript_import_sort_members = true ij_javascript_import_sort_module_name = false ij_javascript_import_use_node_resolution = true ij_javascript_imports_wrap = on_every_item ij_javascript_indent_case_from_switch = true ij_javascript_indent_chained_calls = true ij_javascript_indent_package_children = 0 ij_javascript_jsx_attribute_value = braces ij_javascript_keep_blank_lines_in_code = 1 ij_javascript_keep_first_column_comment = true ij_javascript_keep_indents_on_empty_lines = false ij_javascript_keep_line_breaks = true ij_javascript_keep_simple_blocks_in_one_line = false ij_javascript_keep_simple_methods_in_one_line = false ij_javascript_line_comment_add_space = false ij_javascript_line_comment_at_first_column = false ij_javascript_method_brace_style = end_of_line ij_javascript_method_call_chain_wrap = off ij_javascript_method_parameters_new_line_after_left_paren = false ij_javascript_method_parameters_right_paren_on_new_line = false ij_javascript_method_parameters_wrap = off ij_javascript_object_literal_wrap = on_every_item ij_javascript_object_types_wrap = on_every_item ij_javascript_parentheses_expression_new_line_after_left_paren = false ij_javascript_parentheses_expression_right_paren_on_new_line = false ij_javascript_place_assignment_sign_on_next_line = false ij_javascript_prefer_as_type_cast = false ij_javascript_prefer_explicit_types_function_expression_returns = false ij_javascript_prefer_explicit_types_function_returns = false ij_javascript_prefer_explicit_types_vars_fields = false ij_javascript_prefer_parameters_wrap = false ij_javascript_reformat_c_style_comments = false ij_javascript_space_after_colon = true ij_javascript_space_after_comma = true ij_javascript_space_after_dots_in_rest_parameter = false ij_javascript_space_after_generator_mult = true ij_javascript_space_after_property_colon = true ij_javascript_space_after_quest = true ij_javascript_space_after_type_colon = true ij_javascript_space_after_unary_not = true ij_javascript_space_before_async_arrow_lparen = true ij_javascript_space_before_catch_keyword = true ij_javascript_space_before_catch_left_brace = true ij_javascript_space_before_catch_parentheses = true ij_javascript_space_before_class_lbrace = true ij_javascript_space_before_class_left_brace = true ij_javascript_space_before_colon = true ij_javascript_space_before_comma = false ij_javascript_space_before_do_left_brace = true ij_javascript_space_before_else_keyword = true ij_javascript_space_before_else_left_brace = true ij_javascript_space_before_finally_keyword = true ij_javascript_space_before_finally_left_brace = true ij_javascript_space_before_for_left_brace = true ij_javascript_space_before_for_parentheses = true ij_javascript_space_before_for_semicolon = false ij_javascript_space_before_function_left_parenth = true ij_javascript_space_before_generator_mult = false ij_javascript_space_before_if_left_brace = true ij_javascript_space_before_if_parentheses = true ij_javascript_space_before_method_call_parentheses = false ij_javascript_space_before_method_left_brace = true ij_javascript_space_before_method_parentheses = false ij_javascript_space_before_property_colon = false ij_javascript_space_before_quest = true ij_javascript_space_before_switch_left_brace = true ij_javascript_space_before_switch_parentheses = true ij_javascript_space_before_try_left_brace = true ij_javascript_space_before_type_colon = false ij_javascript_space_before_unary_not = false ij_javascript_space_before_while_keyword = true ij_javascript_space_before_while_left_brace = true ij_javascript_space_before_while_parentheses = true ij_javascript_spaces_around_additive_operators = true ij_javascript_spaces_around_arrow_function_operator = true ij_javascript_spaces_around_assignment_operators = true ij_javascript_spaces_around_bitwise_operators = true ij_javascript_spaces_around_equality_operators = true ij_javascript_spaces_around_logical_operators = true ij_javascript_spaces_around_multiplicative_operators = true ij_javascript_spaces_around_relational_operators = true ij_javascript_spaces_around_shift_operators = true ij_javascript_spaces_around_unary_operator = false ij_javascript_spaces_within_array_initializer_brackets = false ij_javascript_spaces_within_brackets = false ij_javascript_spaces_within_catch_parentheses = false ij_javascript_spaces_within_for_parentheses = false ij_javascript_spaces_within_if_parentheses = false ij_javascript_spaces_within_imports = true ij_javascript_spaces_within_interpolation_expressions = true ij_javascript_spaces_within_method_call_parentheses = false ij_javascript_spaces_within_method_parentheses = false ij_javascript_spaces_within_object_literal_braces = true ij_javascript_spaces_within_object_type_braces = true ij_javascript_spaces_within_parentheses = false ij_javascript_spaces_within_switch_parentheses = false ij_javascript_spaces_within_type_assertion = false ij_javascript_spaces_within_union_types = true ij_javascript_spaces_within_while_parentheses = false ij_javascript_special_else_if_treatment = true ij_javascript_ternary_operation_signs_on_next_line = false ij_javascript_ternary_operation_wrap = off ij_javascript_union_types_wrap = on_every_item ij_javascript_use_chained_calls_group_indents = false ij_javascript_use_double_quotes = false ij_javascript_use_explicit_js_extension = auto ij_javascript_use_path_mapping = always ij_javascript_use_public_modifier = false ij_javascript_use_semicolon_after_statement = false ij_javascript_var_declaration_wrap = on_every_item ij_javascript_while_brace_force = never ij_javascript_while_on_new_line = false ij_javascript_wrap_comments = false [{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}] ij_continuation_indent_size = 4 ij_php_align_assignments = true ij_php_align_class_constants = true ij_php_align_enum_cases = true ij_php_align_group_field_declarations = false ij_php_align_inline_comments = false ij_php_align_key_value_pairs = true ij_php_align_match_arm_bodies = true ij_php_align_multiline_array_initializer_expression = true ij_php_align_multiline_binary_operation = false ij_php_align_multiline_chained_methods = false ij_php_align_multiline_extends_list = true ij_php_align_multiline_for = false ij_php_align_multiline_parameters = false ij_php_align_multiline_parameters_in_calls = false ij_php_align_multiline_ternary_operation = false ij_php_align_named_arguments = true ij_php_align_phpdoc_comments = false ij_php_align_phpdoc_param_names = false ij_php_anonymous_brace_style = end_of_line ij_php_api_weight = 28 ij_php_array_initializer_new_line_after_left_brace = true ij_php_array_initializer_right_brace_on_new_line = true ij_php_array_initializer_wrap = on_every_item ij_php_assignment_wrap = on_every_item ij_php_attributes_wrap = split_into_lines ij_php_author_weight = 3 ij_php_binary_operation_sign_on_next_line = true ij_php_binary_operation_wrap = on_every_item ij_php_blank_lines_after_class_header = 0 ij_php_blank_lines_after_function = 1 ij_php_blank_lines_after_imports = 1 ij_php_blank_lines_after_opening_tag = 1 ij_php_blank_lines_after_package = 1 ij_php_blank_lines_around_class = 1 ij_php_blank_lines_around_constants = 0 ij_php_blank_lines_around_enum_cases = 0 ij_php_blank_lines_around_field = 1 ij_php_blank_lines_around_method = 1 ij_php_blank_lines_before_class_end = 0 ij_php_blank_lines_before_imports = 1 ij_php_blank_lines_before_method_body = 0 ij_php_blank_lines_before_package = 1 ij_php_blank_lines_before_return_statement = 1 ij_php_blank_lines_between_imports = 1 ij_php_block_brace_style = end_of_line ij_php_call_parameters_new_line_after_left_paren = true ij_php_call_parameters_right_paren_on_new_line = true ij_php_call_parameters_wrap = on_every_item ij_php_catch_on_new_line = true ij_php_category_weight = 28 ij_php_class_brace_style = next_line ij_php_comma_after_last_argument = false ij_php_comma_after_last_array_element = true ij_php_comma_after_last_closure_use_var = false ij_php_comma_after_last_match_arm = true ij_php_comma_after_last_parameter = false ij_php_concat_spaces = true ij_php_copyright_weight = 4 ij_php_deprecated_weight = 0 ij_php_do_while_brace_force = always ij_php_else_if_style = combine ij_php_else_on_new_line = true ij_php_example_weight = 28 ij_php_extends_keyword_wrap = normal ij_php_extends_list_wrap = on_every_item ij_php_fields_default_visibility = protected ij_php_filesource_weight = 28 ij_php_finally_on_new_line = true ij_php_for_brace_force = always ij_php_for_statement_new_line_after_left_paren = false ij_php_for_statement_right_paren_on_new_line = false ij_php_for_statement_wrap = normal ij_php_force_empty_methods_in_one_line = true ij_php_force_short_declaration_array_style = true ij_php_getters_setters_naming_style = camel_case ij_php_getters_setters_order_style = getters_first ij_php_global_weight = 28 ij_php_group_use_wrap = on_every_item ij_php_if_brace_force = always ij_php_if_lparen_on_next_line = true ij_php_if_rparen_on_next_line = true ij_php_ignore_weight = 28 ij_php_import_sorting = alphabetic ij_php_indent_break_from_case = true ij_php_indent_case_from_switch = true ij_php_indent_code_in_php_tags = false ij_php_internal_weight = 1 ij_php_keep_blank_lines_after_lbrace = 1 ij_php_keep_blank_lines_before_right_brace = 0 ij_php_keep_blank_lines_in_code = 1 ij_php_keep_blank_lines_in_declarations = 0 ij_php_keep_control_statement_in_one_line = false ij_php_keep_first_column_comment = false ij_php_keep_indents_on_empty_lines = false ij_php_keep_line_breaks = true ij_php_keep_rparen_and_lbrace_on_one_line = true ij_php_keep_simple_classes_in_one_line = true ij_php_keep_simple_methods_in_one_line = true ij_php_lambda_brace_style = end_of_line ij_php_license_weight = 5 ij_php_line_comment_add_space = false ij_php_line_comment_at_first_column = false ij_php_link_weight = 7 ij_php_lower_case_boolean_const = true ij_php_lower_case_keywords = true ij_php_lower_case_null_const = true ij_php_method_brace_style = next_line ij_php_method_call_chain_wrap = on_every_item ij_php_method_parameters_new_line_after_left_paren = true ij_php_method_parameters_right_paren_on_new_line = true ij_php_method_parameters_wrap = on_every_item ij_php_method_weight = 13 ij_php_modifier_list_wrap = false ij_php_multiline_chained_calls_semicolon_on_new_line = false ij_php_namespace_brace_style = 2 ij_php_new_line_after_php_opening_tag = true ij_php_null_type_position = in_the_end ij_php_package_weight = 28 ij_php_param_weight = 9 ij_php_parameters_attributes_wrap = split_into_lines ij_php_parentheses_expression_new_line_after_left_paren = false ij_php_parentheses_expression_right_paren_on_new_line = false ij_php_phpdoc_blank_line_before_tags = true ij_php_phpdoc_blank_lines_around_parameters = true ij_php_phpdoc_keep_blank_lines = true ij_php_phpdoc_param_spaces_between_name_and_description = 2 ij_php_phpdoc_param_spaces_between_tag_and_type = 2 ij_php_phpdoc_param_spaces_between_type_and_name = 2 ij_php_phpdoc_use_fqcn = true ij_php_phpdoc_wrap_long_lines = true ij_php_place_assignment_sign_on_next_line = false ij_php_place_parens_for_constructor = 1 ij_php_property_read_weight = 11 ij_php_property_weight = 10 ij_php_property_write_weight = 12 ij_php_return_type_on_new_line = false ij_php_return_weight = 15 ij_php_see_weight = 6 ij_php_since_weight = 2 ij_php_sort_phpdoc_elements = true ij_php_space_after_colon = true ij_php_space_after_colon_in_enum_backed_type = true ij_php_space_after_colon_in_named_argument = true ij_php_space_after_colon_in_return_type = true ij_php_space_after_comma = true ij_php_space_after_for_semicolon = true ij_php_space_after_quest = true ij_php_space_after_type_cast = true ij_php_space_after_unary_not = true ij_php_space_before_array_initializer_left_brace = true ij_php_space_before_catch_keyword = true ij_php_space_before_catch_left_brace = true ij_php_space_before_catch_parentheses = true ij_php_space_before_class_left_brace = true ij_php_space_before_closure_left_parenthesis = true ij_php_space_before_colon = true ij_php_space_before_colon_in_enum_backed_type = false ij_php_space_before_colon_in_named_argument = false ij_php_space_before_colon_in_return_type = false ij_php_space_before_comma = false ij_php_space_before_do_left_brace = true ij_php_space_before_else_keyword = true ij_php_space_before_else_left_brace = true ij_php_space_before_finally_keyword = true ij_php_space_before_finally_left_brace = true ij_php_space_before_for_left_brace = true ij_php_space_before_for_parentheses = true ij_php_space_before_for_semicolon = false ij_php_space_before_if_left_brace = true ij_php_space_before_if_parentheses = true ij_php_space_before_method_call_parentheses = false ij_php_space_before_method_left_brace = true ij_php_space_before_method_parentheses = false ij_php_space_before_quest = true ij_php_space_before_short_closure_left_parenthesis = true ij_php_space_before_switch_left_brace = true ij_php_space_before_switch_parentheses = true ij_php_space_before_try_left_brace = true ij_php_space_before_unary_not = false ij_php_space_before_while_keyword = true ij_php_space_before_while_left_brace = true ij_php_space_before_while_parentheses = true ij_php_space_between_ternary_quest_and_colon = false ij_php_spaces_around_additive_operators = true ij_php_spaces_around_arrow = false ij_php_spaces_around_assignment_in_declare = false ij_php_spaces_around_assignment_operators = true ij_php_spaces_around_bitwise_operators = true ij_php_spaces_around_equality_operators = true ij_php_spaces_around_logical_operators = true ij_php_spaces_around_multiplicative_operators = true ij_php_spaces_around_null_coalesce_operator = true ij_php_spaces_around_pipe_in_union_type = false ij_php_spaces_around_relational_operators = true ij_php_spaces_around_shift_operators = true ij_php_spaces_around_unary_operator = false ij_php_spaces_around_var_within_brackets = false ij_php_spaces_within_array_initializer_braces = false ij_php_spaces_within_brackets = false ij_php_spaces_within_catch_parentheses = false ij_php_spaces_within_for_parentheses = false ij_php_spaces_within_if_parentheses = false ij_php_spaces_within_method_call_parentheses = false ij_php_spaces_within_method_parentheses = false ij_php_spaces_within_parentheses = false ij_php_spaces_within_short_echo_tags = true ij_php_spaces_within_switch_parentheses = false ij_php_spaces_within_while_parentheses = false ij_php_special_else_if_treatment = true ij_php_subpackage_weight = 28 ij_php_ternary_operation_signs_on_next_line = true ij_php_ternary_operation_wrap = on_every_item ij_php_throws_weight = 14 ij_php_todo_weight = 28 ij_php_treat_multiline_arrays_and_lambdas_multiline = false ij_php_unknown_tag_weight = 28 ij_php_upper_case_boolean_const = false ij_php_upper_case_null_const = false ij_php_uses_weight = 28 ij_php_var_weight = 28 ij_php_variable_naming_style = camel_case ij_php_version_weight = 8 ij_php_while_brace_force = always ij_php_while_on_new_line = true [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,composer.lock,jest.config}] ij_continuation_indent_size = 4 ij_json_array_wrapping = on_every_item ij_json_keep_blank_lines_in_code = 1 ij_json_keep_indents_on_empty_lines = false ij_json_keep_line_breaks = true ij_json_keep_trailing_comma = false ij_json_object_wrapping = on_every_item ij_json_property_alignment = do_not_align ij_json_space_after_colon = true ij_json_space_after_comma = true ij_json_space_before_colon = false ij_json_space_before_comma = false ij_json_spaces_within_braces = true ij_json_spaces_within_brackets = false ij_json_wrap_long_lines = false [{*.htm,*.html,*.sht,*.shtm,*.shtml}] ij_continuation_indent_size = 4 ij_visual_guides = 160 ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 ij_html_align_attributes = false ij_html_align_text = false ij_html_attribute_wrap = on_every_item ij_html_block_comment_add_space = false ij_html_block_comment_at_first_column = true ij_html_do_not_align_children_of_min_lines = 0 ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot ij_html_enforce_quotes = true ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var ij_html_keep_blank_lines = 2 ij_html_keep_indents_on_empty_lines = false ij_html_keep_line_breaks = true ij_html_keep_line_breaks_in_text = true ij_html_keep_whitespaces = false ij_html_keep_whitespaces_inside = span,pre,textarea ij_html_line_comment_at_first_column = true ij_html_new_line_after_last_attribute = never ij_html_new_line_before_first_attribute = when_multiline ij_html_quote_style = double ij_html_remove_new_line_before_tags = br ij_html_space_after_tag_name = false ij_html_space_around_equality_in_attribute = false ij_html_space_inside_empty_tag = true ij_html_text_wrap = off [{*.http,*.rest}] indent_size = 0 ij_continuation_indent_size = 4 ij_http request_call_parameters_wrap = on_every_item [{*.markdown,*.md}] ij_markdown_force_one_space_after_blockquote_symbol = true ij_markdown_force_one_space_after_header_symbol = true ij_markdown_force_one_space_after_list_bullet = true ij_markdown_force_one_space_between_words = true ij_markdown_format_tables = true ij_markdown_insert_quote_arrows_on_wrap = true ij_markdown_keep_indents_on_empty_lines = false ij_markdown_keep_line_breaks_inside_text_blocks = true ij_markdown_max_lines_around_block_elements = 1 ij_markdown_max_lines_around_header = 1 ij_markdown_max_lines_between_paragraphs = 1 ij_markdown_min_lines_around_block_elements = 1 ij_markdown_min_lines_around_header = 2 ij_markdown_min_lines_between_paragraphs = 1 ij_markdown_wrap_text_if_long = true ij_markdown_wrap_text_inside_blockquotes = true [{*.yaml,*.yml}] ij_yaml_align_values_properties = do_not_align ij_yaml_autoinsert_sequence_marker = true ij_yaml_block_mapping_on_new_line = false ij_yaml_indent_sequence_value = true ij_yaml_keep_indents_on_empty_lines = false ij_yaml_keep_line_breaks = true ij_yaml_sequence_on_new_line = true ij_yaml_space_before_colon = false ij_yaml_spaces_within_braces = true ij_yaml_spaces_within_brackets = true ================================================ FILE: .gitattributes ================================================ * text=auto .github/ export-ignore docs/ export-ignore tests/ export-ignore *.stub linguist-language=php .editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore phpunit.xml export-ignore ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Report description: Report a bug or other issue body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! ⚠️Review existing issues to see whether someone else has already reported your issue. - type: textarea id: environment attributes: label: Environment description: | Tip: Use the `composer info dragon-code/laravel-deploy-operations` command to get information for Laravel Lang. Tip: Use the `php artisan --version` command to get information for Laravel Framework. Tip: Use the `php -v` command to get information for PHP. value: | - PHP Version: - Database Driver & Version: - Deploy Operations Version: - Laravel Version: validations: required: true - type: textarea id: description attributes: label: Issue description description: | Be as specific and detailed as possible to help us triaging your issue. Screenshots and/or animations can be very useful in helping to understand the issue you're facing. Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. Tip: You can use https://www.screentogif.com to record animations and videos. validations: required: true - type: textarea id: steps attributes: label: Steps to reproduce description: Take some time to try and reproduce the issue, then explain how to do so here. validations: required: true - type: markdown attributes: value: | ❤️ The Dragon Code? Please consider supporting [`our collective`](https://boosty.to/dragon-code). ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Laravel issue url: https://github.com/laravel/framework/issues about: 'If you have a question about your Laravel implementation, ask it in your Laravel project.' ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature Proposal description: Propose a new feature body: - type: textarea id: description attributes: label: Feature description description: | Think through your proposal and describe it clearly. Note that features are only added to the most recent version of Laravel Lang. validations: required: true - type: markdown attributes: value: | ❤️ The Dragon Code? Please consider supporting [`our collective`](https://boosty.to/dragon-code). ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: daily labels: - dependabot ================================================ FILE: .github/preview-updater.yml ================================================ image: parameters: title: 'Deploy Operations' ================================================ FILE: .github/workflows/code-style.yml ================================================ name: Code Style on: [ push, pull_request ] permissions: write-all jobs: check: uses: TheDragonCode/.github/.github/workflows/code-style.yml@main ================================================ FILE: .github/workflows/docs.yml ================================================ name: Documentation on: push: workflow_dispatch: permissions: id-token: write pages: write env: COMPOSER_TOKEN: ${{ secrets.COMPOSER_TOKEN }} ARTIFACT_DOCS: webHelpDO2-all.zip INSTANCE: docs/do DOMAIN_NAME: deploy-operations.dragon-code.pro BUILDER_VERSION: 2025.04.8412 jobs: build: name: Build application runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Build documentation uses: JetBrains/writerside-github-action@v4 with: instance: ${{ env.INSTANCE }} artifact: ${{ env.ARTIFACT_DOCS }} docker-version: ${{ env.BUILDER_VERSION }} - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: docs path: | artifacts/${{ env.ARTIFACT_DOCS }} artifacts/report.json retention-days: 7 test: needs: build name: Testing runs-on: ubuntu-latest steps: - name: Download docs artifact uses: actions/download-artifact@v8 with: name: docs path: artifacts - name: Test documentation uses: JetBrains/writerside-checker-action@v1 with: instance: ${{ env.INSTANCE }} robots: needs: build name: Generate robots.txt runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Create robots.txt run: | touch robots.txt echo "User-Agent: *" >> robots.txt echo "Disallow: " >> robots.txt echo "Host: https://${{ env.DOMAIN_NAME }}" >> robots.txt echo "Sitemap: https://${{ env.DOMAIN_NAME }}/sitemap.xml" >> robots.txt - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: robots path: robots.txt retention-days: 7 deploy-pages: environment: name: deploy url: ${{ steps.deployment.outputs.page_url }} needs: - test - robots name: Deploy to pages runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Download docs artifact uses: actions/download-artifact@v8 with: name: docs - name: Download robots artifact uses: actions/download-artifact@v8 with: name: robots - name: Unzip artifact run: unzip -O UTF-8 -qq '${{ env.ARTIFACT_DOCS }}' -d dir - name: Move robots run: | sudo mv robots.txt dir/robots.txt - name: Setup Pages uses: actions/configure-pages@v6 - name: Upload artifact uses: actions/upload-pages-artifact@v5 with: path: dir - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 ================================================ FILE: .github/workflows/license.yml ================================================ name: Update license on: schedule: - cron: '0 3 1 1 *' workflow_dispatch: jobs: Update: uses: TheDragonCode/.github/.github/workflows/license.yml@main ================================================ FILE: .github/workflows/preview.yml ================================================ name: Preview Updater on: schedule: - cron: '20 2 * * *' workflow_dispatch: permissions: contents: write pull-requests: write jobs: preview: uses: TheDragonCode/.github/.github/workflows/preview.yml@main ================================================ FILE: .github/workflows/release-drafter.yml ================================================ name: Release Drafter on: push: branches: - main workflow_dispatch: jobs: Update: uses: TheDragonCode/.github/.github/workflows/release-drafter.yml@main ================================================ FILE: .github/workflows/tests.yml ================================================ name: Tests on: [ push, pull_request ] jobs: build: runs-on: ubuntu-latest strategy: fail-fast: true matrix: php: [ "8.2", "8.3", "8.4", "8.5" ] laravel: [ "11.0", "12.0", "13.0" ] exclude: - laravel: "13.0" php: "8.2" name: PHP ${{ matrix.php }}, Laravel ${{ matrix.laravel }} steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: curl, mbstring, zip, pcntl, pdo, pdo_sqlite, iconv coverage: xdebug - name: Install dependencies run: composer require --dev laravel/framework:^${{ matrix.laravel }} - name: Execute tests run: sudo vendor/bin/phpunit ================================================ FILE: .gitignore ================================================ .idea/ _site/ build/ node_modules/ tmp/ vendor/ .cache .DS_Store .env .php_cs.cache .phpintel .temp *.bak *.cache *.clover *.orig composer.lock package-lock.json ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020-2026 Andrey Helldar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # 🚀 Laravel Deploy Operations Laravel Deploy Operations [![Stable Version][badge_stable]][link_packagist] [![Total Downloads][badge_downloads]][link_packagist] [![Github Workflow Status][badge_build]][link_build] [![License][badge_license]][link_license] ⚡ **Performing any actions during the deployment process** Create specific classes for a one-time or more-time usage, that can be executed automatically after each deployment. Perfect for seeding or updating some data instantly after some database changes, feature updates, or perform any actions. This package is for you if... - you regularly need to update specific data after you deploy new code - you often perform jobs after deployment - you sometimes forget to execute that one specific job and stuff gets crazy - your code gets cluttered with jobs that are not being used anymore - your co-workers always need to be reminded to execute that one job after some database changes - you often seed or process data in a migration file (which is a big no-no!) ## Installation To get the latest version of **Deploy Operations**, simply require the project using [Composer](https://getcomposer.org): ```Bash composer require dragon-code/laravel-deploy-operations ``` ## Documentation 📚 [Check out the full documentation to learn everything that Laravel Deploy Operations has to offer.][link_website] ## Basic Usage Create your first operation using `php artisan make:operation` console command and define the actions it should perform. ```php use App\Models\Article; use DragonCode\LaravelDeployOperations\Operation; return new class extends Operation { public function __invoke(): void { Article::query() ->lazyById(chunkSize: 100, column: 'id') ->each->update(['is_active' => true]); // and/or any actions... } }; ``` Next, you can run the console command to start operations: ```Bash php artisan operations ``` ## Downloads Stats This project has gone the way of several names, and here are the number of downloads of each of them: - ![](https://img.shields.io/packagist/dt/dragon-code/laravel-deploy-operations?style=flat-square&label=dragon-code%2Flaravel-deploy-operations) - ![](https://img.shields.io/packagist/dt/dragon-code/laravel-actions?style=flat-square&label=dragon-code%2Flaravel-actions) - ![](https://img.shields.io/packagist/dt/dragon-code/laravel-migration-actions?style=flat-square&label=dragon-code%2Flaravel-migration-actions) - ![](https://img.shields.io/packagist/dt/andrey-helldar/laravel-actions?style=flat-square&label=andrey-helldar%2Flaravel-actions) ## License This package is licensed under the [MIT License](LICENSE). [badge_build]: https://img.shields.io/github/actions/workflow/status/TheDragonCode/laravel-deploy-operations/tests.yml?style=flat-square [badge_downloads]: https://img.shields.io/packagist/dt/dragon-code/laravel-deploy-operations.svg?style=flat-square [badge_license]: https://img.shields.io/packagist/l/dragon-code/laravel-deploy-operations.svg?style=flat-square [badge_stable]: https://img.shields.io/github/v/release/TheDragonCode/laravel-deploy-operations?label=packagist&style=flat-square [link_build]: https://github.com/TheDragonCode/laravel-deploy-operations/actions [link_license]: LICENSE [link_packagist]: https://packagist.org/packages/dragon-code/laravel-deploy-operations [link_website]: https://deploy-operations.dragon-code.pro ================================================ FILE: biome.json ================================================ { "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, "files": { "ignoreUnknown": false, "includes": [ "**", "!node_modules", "!vendor", "!composer.json", "!composer.lock", "!package.json", "!package-lock.json", "!**/analytics.*", "!**/metrics.*", "!coverage", "!dist", "!build", "!public" ] }, "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 4 }, "linter": { "enabled": true, "rules": { "recommended": true } }, "javascript": { "formatter": { "quoteStyle": "double" } }, "json": { "formatter": { "enabled": true, "bracketSpacing": true, "expand": "always" }, "parser": { "allowComments": true } }, "assist": { "enabled": true, "actions": { "source": { "organizeImports": "on" } } } } ================================================ FILE: composer.json ================================================ { "name": "dragon-code/laravel-deploy-operations", "description": "Performing any actions during the deployment process", "license": "MIT", "type": "library", "keywords": [ "laravel", "deploy", "deployment", "operations", "action", "actions", "migration", "migrations", "dragon-code", "dragon", "andrey-helldar" ], "authors": [ { "name": "Andrey Helldar", "email": "helldar@dragon-code.pro", "homepage": "https://dragon-code.pro" } ], "support": { "issues": "https://github.com/TheDragonCode/laravel-deploy-operations/issues", "source": "https://github.com/TheDragonCode/laravel-deploy-operations" }, "funding": [ { "type": "boosty", "url": "https://boosty.to/dragon-code" }, { "type": "yoomoney", "url": "https://yoomoney.ru/to/410012608840929" } ], "require": { "php": "^8.2", "composer-runtime-api": "^2.2", "dragon-code/support": "^6.6", "laravel/framework": "^11.0 || ^12.0 || ^13.0", "laravel/prompts": ">=0.1", "spatie/laravel-data": "^4.14" }, "require-dev": { "mockery/mockery": "^1.3.1", "nesbot/carbon": "^2.62.1 || ^3.0", "orchestra/testbench": "^9.0 || ^10.0 || ^11.0", "phpunit/phpunit": "^11.0 || ^12.0" }, "conflict": { "andrey-helldar/laravel-actions": "*", "dragon-code/laravel-actions": "*", "dragon-code/laravel-migration-actions": "*" }, "suggest": { "dragon-code/laravel-data-dumper": "Required if you want to save the execution state using the `schema:dump` console command" }, "minimum-stability": "stable", "prefer-stable": true, "autoload": { "psr-4": { "DragonCode\\LaravelDeployOperations\\": "src/" }, "files": [ "src/helpers.php" ] }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "config": { "allow-plugins": { "ergebnis/composer-normalize": true, "laravel/pint": true, "symfony/thanks": true }, "preferred-install": "dist", "sort-packages": true }, "extra": { "laravel": { "providers": [ "DragonCode\\LaravelDeployOperations\\ServiceProvider" ] } } } ================================================ FILE: config/deploy-operations.php ================================================ env('DB_CONNECTION'), /* |-------------------------------------------------------------------------- | Operations Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the operations that have already run for | your application. Using this information, we can determine which of | the operations on disk haven't actually been run in the database. | */ 'table' => 'operations', /* |-------------------------------------------------------------------------- | Database Transactions |-------------------------------------------------------------------------- | | This setting defines the rules for working with database transactions. | This specifies a common value for all operations, but you can override this | value directly in the class of the operation itself. */ 'transactions' => [ // Determines whether the use of database transactions is enabled. 'enabled' => false, // The number of attempts to execute a request within a transaction before throwing an error. 'attempts' => 1, ], /* |-------------------------------------------------------------------------- | Operations Path |-------------------------------------------------------------------------- | | This option defines the path to the operation directory. | */ 'path' => base_path('operations'), /* |-------------------------------------------------------------------------- | Path Exclusion |-------------------------------------------------------------------------- | | This option determines which directory and/or file paths should be | excluded when processing files. | | Valid values: array, string or null | | Specify `null` to disable. | | For example, | ['foo', 'bar'] | 'foo' | null | */ 'exclude' => null, /* |-------------------------------------------------------------------------- | Asynchronous settings |-------------------------------------------------------------------------- | | Defines whether the operation will run synchronously or asynchronously. | | When this option is activated, each operation will be performed through jobs. */ 'async' => false, /* |-------------------------------------------------------------------------- | Queue |-------------------------------------------------------------------------- | | This option specifies the queue settings that will process | asynchronous operations. | */ 'queue' => [ /* |-------------------------------------------------------------------------- | Queue Connection |-------------------------------------------------------------------------- | | This parameter defines the default connection. | */ 'connection' => env('DEPLOY_OPERATIONS_QUEUE_CONNECTION', env('QUEUE_CONNECTION', 'sync')), /* |-------------------------------------------------------------------------- | Queue Name |-------------------------------------------------------------------------- | | This parameter specifies the name of the queue to which asynchronous | jobs will be sent. | */ 'name' => env('DEPLOY_OPERATIONS_QUEUE_NAME'), ], /* |-------------------------------------------------------------------------- | Show |-------------------------------------------------------------------------- | | This option determines the display settings for various information messages. | */ 'show' => [ /* |-------------------------------------------------------------------------- | Full Path |-------------------------------------------------------------------------- | | This parameter determines how exactly the link to the created file should | be displayed - the full path to the file or a relative one. | */ 'full_path' => (bool) env('DEPLOY_OPERATIONS_SHOW_FULL_PATH', false), ], ]; ================================================ FILE: database/migrations/2022_08_18_180137_change_migration_actions_table.php ================================================ hasTable()) { Schema::table($this->table(), function (Blueprint $table) { if ($this->hasColumn('migration') && $this->doesntHaveColumn('action')) { $table->renameColumn('migration', 'action'); } $table->unsignedInteger('batch')->change(); }); } } public function down(): void { if ($this->hasTable()) { Schema::table($this->table(), function (Blueprint $table) { $table->renameColumn('action', 'migration'); $table->integer('batch')->change(); }); } } protected function hasTable(): bool { return Schema::hasTable($this->table()); } protected function hasColumn(string $column): bool { return Schema::hasColumn($this->table(), $column); } protected function doesntHaveColumn(string $column): bool { return ! $this->hasColumn($column); } protected function table(): string { return app(ConfigData::class)->table; } }; ================================================ FILE: database/migrations/2023_01_21_172923_rename_migrations_actions_table_to_actions.php ================================================ doesntSame('migration_actions', $this->table())) { $this->validateTable($this->table()); Schema::rename('migration_actions', $this->table()); } } public function down(): void { if (Schema::hasTable($this->table()) && $this->doesntSame('migration_actions', $this->table())) { $this->validateTable('migration_actions'); Schema::rename($this->table(), 'migration_actions'); } } protected function validateTable(string $name): void { if (Schema::hasTable($name)) { throw new RuntimeException(sprintf('A table named [%s] already exists. Change the table name settings in the [%s] configuration file.', $name, 'config/actions.php')); } } protected function doesntSame(string $first, string $second): bool { return $first !== $second; } protected function table(): string { return app(ConfigData::class)->table; } }; ================================================ FILE: database/migrations/2024_05_21_112438_rename_actions_table_to_operations.php ================================================ doesntSame('actions', $this->table())) { $this->validateTable($this->table()); Schema::rename('actions', $this->table()); } } public function down(): void { if (Schema::hasTable($this->table()) && $this->doesntSame('actions', $this->table())) { $this->validateTable('actions'); Schema::rename($this->table(), 'actions'); } } protected function validateTable(string $name): void { if (Schema::hasTable($name)) { throw new RuntimeException(sprintf('A table named [%s] already exists. Change the table name settings in the [%s] configuration file.', $name, 'config/deploy-operations.php')); } } protected function doesntSame(string $first, string $second): bool { return $first !== $second; } protected function table(): string { return app(ConfigData::class)->table; } }; ================================================ FILE: database/migrations/2024_05_21_114318_rename_column_in_operations_table.php ================================================ rename('action', 'operation'); } public function down(): void { $this->rename('operation', 'action'); } protected function rename(string $from, string $to): void { if (Schema::hasColumn($this->table(), $from)) { Schema::table($this->table(), fn (Blueprint $table) => $table->renameColumn($from, $to)); } } protected function table(): string { return app(ConfigData::class)->table; } }; ================================================ FILE: docs/cfg/buildprofiles.xml ================================================ true en_US logo.svg https://deploy-operations.dragon-code.pro true true https://github.com/TheDragonCode/laravel-deploy-operations/edit/main/docs/ logo.svg,logo.svg,logo.svg,logo.svg https://raw.githubusercontent.com/TheDragonCode/laravel-deploy-operations/refs/heads/main/docs/versions.json 1280 https://deploy-operations.dragon-code.pro/ false ================================================ FILE: docs/do.tree ================================================ ================================================ FILE: docs/docs_libraries.tree ================================================ ================================================ FILE: docs/snippets/actual_file_names.sh ================================================ # actual file names 2022_10_14_000001_test1 # 1 2022_10_14_000004_test4 # 4 bar/2022_10_14_000003_test3 # 3 foo/2022_10_14_000002_test2 # 2 ================================================ FILE: docs/snippets/ask.sh ================================================ php artisan make:operation Creating an operation ┌ What should the operation be named? ─────────────────────────┐ │ E.g. activate articles │ └──────────────────────────────────────────────────────────────┘ Press Enter to autodetect ================================================ FILE: docs/snippets/async.php ================================================ get('qwerty'); } }; ================================================ FILE: docs/snippets/di_up_down.php ================================================ get('qwerty'); } public function down(Some $some): void { $value = $some->get('qwerty'); } }; ================================================ FILE: docs/snippets/empty.php ================================================ [SomeOperationsListener::class], DeployOperationEnded::class => [SomeOperationsListener::class], DeployOperationFailed::class => [SomeOperationsListener::class], NoPendingDeployOperations::class => [SomeOperationsListener::class], ]; } ================================================ FILE: docs/snippets/events_list.php ================================================ lazyById(chunkSize: 100, column: 'id') ->each->update(['is_active' => true]); // and/or any actions... } }; ================================================ FILE: docs/snippets/example_artisan.php ================================================ artisan('some_command', [ // parameters ]); } }; ================================================ FILE: docs/snippets/except_environment.php ================================================ environment(), ['testing', 'staging'], true); } }; ================================================ FILE: docs/snippets/failed_status.php ================================================ isProduction(); } }; ================================================ FILE: docs/snippets/on_environments.php ================================================ environment(), ['testing', 'staging'], true); } }; ================================================ FILE: docs/snippets/once_method.php ================================================ method; // MethodEnum object value $isBefore = $event->before; // boolean } } ================================================ FILE: docs/snippets/success_status.php ================================================ Information on how to call Artisan commands from operations Information on how to call Artisan commands from operations Information on how to call Artisan commands from operations

Quite often, when working with operations, it becomes necessary to run one or another console command, and each time you have to write the following code:

================================================ FILE: docs/topics/creating-operations.topic ================================================ Information on how to create operations Information on how to create operations Information on how to create operations

To create an operation use the %command_make% artisan command:

%artisan% %command_make% some_name

The new operation's file will be placed in your %directory% directory in the base path of your app.

Each operation file name contains a timestamp, which allows Laravel to determine the order of the operations. For example,

2025_04_02_121627_some_name.php
The question will not be asked when calling a console command passing the --quiet parameter.

When calling the %command_run% console command without passing a name in the name parameter, you will be asked for a name for the file.

You can enter your own or simply press Enter to continue. In this case, automatic file name generation will be applied

If you do not specify the name attribute, then the file name will be generated automatically according to the rule:

The name for the file will be automatically obtained from the currently active git repository branch at the root of the project.

If the branch name cannot be determined, the word “auto” will be used.

You can use nested paths to create operations:

All of these commands will create a file called %directory%/foo/bar/Y_m_d_His_qwe_rty.php.

For example:

By default, the new operation class will contain the __invoke method, but you can easily replace it with public up name.

Note that the __invoke method has been added as a single call. This means that when the operation is running, it will be called, but not when it is rolled back.

You should also pay attention to the fact that if there is an __invoke method in the class, the down method will not be called.

You can also use the dependency injection with __invoke, up and down methods:

================================================ FILE: docs/topics/customize-stub.topic ================================================ Information on how to publish a template file for changes Information on how to publish a template file for changes Information on how to publish a template file for changes

You may publish the stub used by the make:operation command and/or Laravel Idea plugin for JetBrains PhpStorm if you want to modify it.

%vendor_publish%

This will create the file stubs/deploy-operation.stub, which you can modify to suit you.

================================================ FILE: docs/topics/database-data-dumper.topic ================================================ Adding data from certain tables when executing the `php artisan schema:dump` console command Adding data from certain tables when executing the `php artisan schema:dump` console command Adding data from certain tables when executing the `php artisan schema:dump` console command

As you build your application, you may accumulate more and more migrations over time. This can lead to your database/migrations directory becoming bloated with potentially hundreds of migrations. If you would like, you may "squash" your migrations into a single SQL file. To get started, execute the schema:dump command:

%artisan% schema:dump

You can read more about the operation of this console command in the Laravel documentation.

Here we mention this console command because operations tend to save the execution state in order to prevent re-runs where this is not explicitly allowed. But if you run sequentially the console commands %artisan% schema:dump and %artisan% migrate:fresh, you will see that all actions will be called again.

This is due to the fact that the dump mechanism saves the contents of just one table - migrations.

To solve this problem, there is a Database Data Dumper project that allows you to specify a list of tables required for export to a dump.

In addition to those that you can easily specify in its configuration file, we recommend that you also specify the operations database table from this project in order to save the state of the operations when performing a clean deployment of the database from a dump.

composer require dragon-code/laravel-data-dumper --dev
================================================ FILE: docs/topics/events.topic ================================================ Information about events sent when operations are running Information about events sent when operations are running Information about events sent when operations are running

You can also handle events when executing operations:

If there are no operation files to execute, the NoPendingDeployOperations event will be sent.

In other cases, the DeployOperationStarted event will be sent before processing starts, and the DeployOperationEnded event will be sent after processing.

For example:

It is also possible to subscribe to events manually:

================================================ FILE: docs/topics/execution-status.topic ================================================ Information about the execution of actions during successful and unsuccessful operation launches Information about the execution of actions during successful and unsuccessful operation launches Information about the execution of actions during successful and unsuccessful operation launches

You can also override the success and failed methods, which are called on success or failure processing.

Call the %artisan% %command_run% command.

The log file will contain one success record.

Call the %artisan% %command_run% command.

The log file will contain one failed record.

The methods will work in the same way in conjunction with the __invoke magic method. The only difference is that in this case the down method will not be executed.

================================================ FILE: docs/topics/installation.topic ================================================ Installation and configuration Installation and configuration Installation and configuration

To get the latest version of %product_short% , simply require the project using Composer:

composer require %package_name%

If necessary, you can publish the configuration file by calling the console command:

%vendor_publish%

Now you can create new operations.

PHP Laravel Package Documentation Status
^8.2 11, 12, 13 ^7.0 supported
^8.2 10, 11, 12 ^6.0 Documentation not supported
^8.2 10, 11 ^5.0 Documentation not supported
^8.0 7, 8, 9, 10, 11 ^4.0 Documentation not supported
^8.0 7, 8, 9 ^3.0 Documentation not supported
^7.3, ^8.0 6, 7, 8, 9 ^2.0 --- not supported
^7.3, ^8.0 6, 7, 8 ^1.0 --- not supported
================================================ FILE: docs/topics/introduction.topic ================================================ %product% ⚡ Performing any actions during the deployment process Guide Helpers Extras ================================================ FILE: docs/topics/operation-helper.topic ================================================ Information about launching operations with the help of the helper Information about launching operations with the help of the helper Information about launching operations with the help of the helper

To quickly call operations from other code, you can use the OperationHelper helper function.

This will execute any operations not performed earlier:

For example,

This will execute all previously unexecuted operations in the specified folder:

For example,

This will execute the defined operation file even if it was previously called. Be careful about calling it again.

For example,

If you are using JetBrains PhpStorm with the Laravel Idea plugin installed, you can use the operation function to autocomplete.

operation helper ================================================ FILE: docs/topics/operations-status.topic ================================================ Information on how to view the status of performed operations Information on how to view the status of performed operations Information on how to view the status of performed operations

The %command_status% command displays the execution status of operations. In it you can see which operations were executed and which were not:

%artisan% %command_status%

For example,

================================================ FILE: docs/topics/rolling-back-operations.topic ================================================ Information on how to roll back operations Information on how to roll back operations Information on how to roll back operations

To roll back the latest operation, you may use the %command_rollback% command. This command rolls back the last "batch" of operations, which may include multiple operation files:

%artisan% %command_rollback%

You may roll back a limited number of operations by providing the step option to the rollback command. For example, the following command will roll back the last five operations:

%artisan% %command_rollback% --step=5

For example:

The %command_fresh% command will drop all operation records from the operation table and then execute the operations command:

%artisan% %command_fresh%
================================================ FILE: docs/topics/running-operations.topic ================================================ Information on how to invoke operations during a deployment Information on how to invoke operations during a deployment Information on how to invoke operations during a deployment

To run all of your outstanding operations, execute the %command_run% artisan command:

%artisan% %command_run%

The order in which operations are called is checked by file name in alphabetical order, without taking into account directory names:

If you are deploying your application across multiple servers and running operations as part of your deployment process, you likely do not want two servers attempting to run the database at the same time. To avoid this, you may use the isolated option when invoking the %command_run% command.

%artisan% %command_run% --isolated

Sometimes it becomes necessary to launch operations separately, for example, to notify about the successful deployment of a project.

There is a before option for this when calling operations:

%artisan% %command_run% --before

When you call the %command_run% command with the before parameter, the script will only perform operations for which the needBefore method is true.

For backwards compatibility, the needBefore method returns true by default, but operations will only be executed if the option is explicitly passed.

For example, you need to call operations when deploying an application. Some operations should be run after the operations are deployed, and others after the application is fully launched.

To run, you need to pass the before parameter. For example, when using Deployer it would look like this:

Thus, when %command_run% is called, all operations whose before parameter is true will be executed, and after that, the remaining tasks will be executed.

If you call the %command_run% command without the before parameter, then all tasks will be executed regardless of the value of the needBefore method inside the operation class.
Some commands cannot be executed in production without confirmation. These include all commands except %command_status% and %command_run%.

Some operations are destructive, which means they may cause you to lose data. In order to protect you from running these commands against your production database, you will be prompted for confirmation before the commands are executed. To force the commands to run without a prompt, use the --force flag:

%artisan% %command_install% --force

In some cases, you need to call the code every time you deploy the application. For example, to call reindexing.

To do this, override the shouldOnce method in the operation file:

If the value is shouldOnce is false, the up method will be called every time the %command_run% command called.

In this case, information about it will not be written to the %table% table and, therefore, the down method will not be called when the rollback command is called.

When using the before parameter to run command, it is recommended to override the value of the needBefore method to false, otherwise this operation will be executed twice.
By default, the operation will run in all environments.

In some cases, it becomes necessary to execute an operation in a specific environment. For example production.

For this you can override the shouldRun method:

You can also specify multiple environment names:

You can work with exceptions in the same way:

In some cases, it becomes necessary to undo previously performed operations in the database. For example, when code execution throws an error. To do this, the code must be wrapped in a transaction.

By setting the withinTransactions to true parameter, you will ensure that your code is wrapped in a transaction without having to manually call the DB::transaction() method. This will reduce the time it takes to create the operation.

The number of code execution attempts in case of transaction errors is set in the settings file.

By default, the number of attempts is %transactions_attempts%.

In some cases, it becomes necessary to execute operations in an asynchronous manner without delaying the deployment process.

To do this, you need to override the shouldBeAsync method in the operation class:

In this case, the operation file that defines this parameter will run asynchronously using the DragonCode\LaravelDeployOperations\Jobs\OperationJob job.

The name of the connection and queue can be changed through the settings.

We remind you that in this case the queuing system must work in your application.

Operations can also be invoked when Laravel migrations are completed (php artisan migrate). The Laravel event system is used for this purpose.

To do this, add a withOperation public method to your migration file that will return the name of the file or folder to call. For example:

Now, once the migration is done, Laravel will send a MigrationEnded event, catching which the %artisan% %command_run% console command will be called passing this parameter.

The same thing will happen if you invoke the following console command:

%artisan% %command_run% --path="foo/2022_10_14_000002_test2"

This method works with all three migration methods: up, down and __invoke.

When the %artisan% migrate console command is called, the operation will call the up or __invoke method.

When the %artisan% migrate:rollback console command is called, the operation will call the down method if it exists in the operation file.

If you are using JetBrains PhpStorm with the Laravel Idea plugin installed, then autocomplete will be available to you:

To avoid entering file names manually, you can use the operation helper function. All it does is to suggest IDE paths to operation files with recursive search.

operation helper
================================================ FILE: docs/topics/snippets_composer.topic ================================================

Then you need to update the dependencies:

composer update
================================================ FILE: docs/topics/upgrade-3.topic ================================================ Guide for upgrading to version 3.x from 2.x Guide for upgrading to version 3.x from 2.x Guide for upgrading to version 3.x from 2.x Laravel 6.0 support was ended Dragon Code: Contracts (dragon-code/contracts) support was ended If you used inheritance of actions from other actions, then you will need to process these files manually.

For your convenience, we have created an upgrade console command:

composer require dragon-code/laravel-migration-actions:^3.0 %artisan% migrate:actions:upgrade %artisan% migrate

It will do the following:

  • Change the namespace of the abstract class
  • Add a strict type declaration
  • Replace the up method with __invoke if the class does not have a down method
  • Replace named classes with anonymous ones
  • Create a configuration file according to the data saved in your project
  • Changes properties from snake_case to camelCase
  • Deploy Actions for Laravel now requires PHP 8.0.2 or greater.

    You should update the following dependency in your application's composer.json file:

    { "require": { "dragon-code/laravel-migration-actions": "^3.0" } }

    Publish the config file and migrate the settings from the config/database.php file to config/actions.php.

    %artisan% vendor:publish --provider="DragonCode\LaravelActions\ServiceProvider"
    Move the action files to the actions folder in the project root, or update the actions.path option in the configuration file. Replace DragonCode\LaravelActions\Support\Actionable with DragonCode\LaravelActions\Action.

    Replace named calls to your application's classes with anonymous ones.

    For example:

    // before use DragonCode\LaravelActions\Support\Actionable; class Some extends Actionable { } // after use DragonCode\LaravelActions\Action; return new class extends Action { };
    If your class does not contain a down method, then you can replace the up method with __invoke. Just call the %artisan% migrate command to make changes to the action repository table.

    Make sure all overridden properties are typed:

    New Name Old Name
    protected bool $once protected $once
    protected bool $transactions protected $transactions
    protected int $transactionAttempts protected $transaction_attempts
    protected string\|array\|null $environment protected $environment
    protected string\|array\|null $exceptEnvironment protected $except_environment
    protected bool $before protected $before

    Before:

    2022_10_13_013321_test1 2022_10_13_013326_test2 bar/2022_10_13_013323_test3 # will not be called

    After:

    2022_10_13_013321_test1 2022_10_13_013326_test2 bar/2022_10_13_013323_test3 # will be called
    ================================================ FILE: docs/topics/upgrade-4.topic ================================================ Guide for upgrading to version 4.x from 3.x Guide for upgrading to version 4.x from 3.x Guide for upgrading to version 4.x from 3.x If you used inheritance of actions from other actions, then you will need to process these files manually.

    For your convenience, we have created an `upgrade` console command:

    composer remove dragon-code/laravel-migration-actions composer require dragon-code/laravel-actions:^4.0 %artisan% actions:upgrade %artisan% migrate

    It will do the following:

  • Renaming manually invoked commands in a project to a new name
  • You should update the following dependency in your application's composer.json file:

    Replace:

    { "require": { "dragon-code/laravel-migration-actions": "^3.0" } }

    with:

    { "require": { "dragon-code/laravel-actions": "^4.0" } }
    New Name Old Name
    %artisan% make:action %artisan% make:migration:action
    %artisan% actions %artisan% migrate:actions
    %artisan% actions:install %artisan% migrate:actions:install
    %artisan% actions:fresh %artisan% migrate:actions:fresh
    %artisan% actions:refresh %artisan% migrate:actions:refresh
    %artisan% actions:reset %artisan% migrate:actions:reset
    %artisan% actions:rollback %artisan% migrate:actions:rollback
    %artisan% actions:status %artisan% migrate:actions:status
    %artisan% actions:upgrade %artisan% migrate:actions:upgrade
    Replace DragonCode\LaravelActions\Constants\Names::MIGRATE with DragonCode\LaravelActions\Constants\Names::ACTIONS

    The new table name is `actions`.

    You can also specify any name in the application settings file.

    ================================================ FILE: docs/topics/upgrade-5.topic ================================================ Guide for upgrading to version 5.x from 4.x Guide for upgrading to version 5.x from 4.x Guide for upgrading to version 5.x from 4.x Deploy Actions now requires PHP 8.2.0 or greater.

    Deploy Actions now requires Laravel 10.0 or greater.

    If you are using Laravel 10, then you need to install the dependency:

    composer require doctrine/dbal

    You should update the following dependencies in your application's composer.json file:

    { "require": { "dragon-code/laravel-actions": "^5.0" } }
    ================================================ FILE: docs/topics/upgrade-6.topic ================================================ Guide for upgrading to version 6.x from 5.x Guide for upgrading to version 6.x from 5.x Guide for upgrading to version 6.x from 5.x If you used inheritance of actions from other actions, then you will need to process these files manually.

    For your convenience, we have created an operations:upgrade console command:

    composer remove dragon-code/laravel-actions composer require dragon-code/laravel-deploy-operations:^6.0 %artisan% operations:upgrade %artisan% migrate

    It will do the following:

  • Changing the old namespace of “actions” to a new one
  • Moves files to a new location
  • Updates the configuration file
  • Rename the stub file (if published)
  • Please note that the script allows you to automate most of the actions, but may not complete them completely. Therefore, you will need to manually check the result of the upgrade by checking this guide.

    You should change the package name in the composer.json file from dragon-code/laravel-actions to dragon-code/laravel-deploy-operations, and also change its version to ^6.0:

    { "require": { "dragon-code/laravel-deploy-operations": "^6.0" } }

    The namespace has been changed from DragonCode\LaravelActions to DragonCode\LaravelDeployOperations.

    You need to replace it in all actions of your application, as well as when using
    . You should replace DragonCode\LaravelActions\Action namespace with DragonCode\LaravelDeployOperations\Operation.
    New Name Old Name
    make:operation make:action
    operations actions
    operations:fresh actions:fresh
    operations:install actions:install
    operations:refresh actions:refresh
    operations:reset actions:reset
    operations:rollback actions:rollback
    operations:status actions:status
    operations:stub actions:stub
    operations:upgrade actions:upgrade
    New Name Old Name
    DeployOperationStarted ActionStarted
    DeployOperationEnded ActionEnded
    DeployOperationFailed ActionFailed
    NoPendingDeployOperations NoPendingActions

    Don't forget to also change the namespace from DragonCode\LaravelActions\Events to DragonCode\LaravelDeployOperations\Events.

    The type of the method property for events has been changed.

    use DragonCode\LaravelActions\Events\ActionEnded; use DragonCode\LaravelDeployOperations\Enums\MethodEnum; /** @var ActionEnded */ $event->method; // is string use DragonCode\LaravelDeployOperations\Enums\MethodEnum; use DragonCode\LaravelDeployOperations\Events\DeployOperationEnded; /** @var DeployOperationEnded */ $event->method; // is MethodEnum

    We recommend that you delete the old configuration file config/actions.php and publish a new one. This way you will see the changes made to it.

    %artisan% vendor:publish --provider="DragonCode\LaravelDeployOperations\ServiceProvider"

    If you use package constant references, you must also rename them.

    The old name was in UPPER_CASE, the new one was in PascalCase.

    For example:

    // Old class Names { public const ACTIONS = 'actions'; public const FRESH = 'actions:fresh'; // ... } // New class Names { public const Fresh = 'operations:fresh'; public const Operations = 'operations'; // ... }
    File storage directory changed to /operations from /actions.

    The following properties have been removed:

  • $transactions
  • $transactionAttempts
  • Instead, you can use the hasTransactions and transactionAttempts methods.

    The enabledTransactions method has been renamed to hasTransactions.

    The $async property has been removed from the base class. You can use the previously available isAsync method instead. If you published a stub file, then you also need to rename it from stubs/action.stub to stubs/deploy-operation.stub and make changes to its structure.

    The %artisan% operations:stub console command has been removed. Use another command instead:

    %artisan% vendor:publish --tag=stubs --provider="DragonCode\LaravelDeployOperations\ServiceProvider"
    ================================================ FILE: docs/topics/upgrade-7.topic ================================================ Guide for upgrading to version 7.x from 6.x Guide for upgrading to version 7.x from 6.x Guide for upgrading to version 7.x from 6.x

    You should update the following dependencies in your application's composer.json file:

    { "require": { "dragon-code/laravel-deploy-operations": "^7.0" } }
    Laravel 10 version is no longer supported due to the lack of event classes required for %product_short% . We decided to drop support for the php artisan operations:upgrade console command. It does not exist now. We decided to drop support for the php artisan operations:refresh console command. It does not exist now. We decided to drop support for the php artisan operations:reset console command. It does not exist now.

    A strict typification has been added to all the project files.

    declare(strict_types=1);

    The DragonCode\LaravelDeployOperations\Helpers\ConfigHelper class has been removed and DragonCode\LaravelDeployOperations\Data\Config\ConfigData, which is a Data object, is now used instead.

    For example,

    - $this->config->path('some'); // /operations/some - config('deploy-operations.transactions.enabled'); // false - config('deploy-operations.async'); // false + $this->config->path . 'some'; // /operations/some + $this->config->transactions->enabled; // false + $this->config->async; // false

    This tidied up the handling of settings and options.

    If you used a direct reference to the Helpers/Config and Values/Options classes, update your code.

    Console\Install Console\Make Console\Operations Console\Fresh Console\Rollback Console\Status Processors\Fresh Processors\Install Processors\Make Processors\Operations Processors\Rollback Processors\Status Helpers\Config Helpers\Git Helpers\Sorter Services\Migrator Services\Mutex Values\Options Console\InstallCommand Console\MakeCommand Console\OperationsCommand Console\FreshCommand Console\RollbackCommand Console\StatusCommand Processors\FreshProcessor Processors\InstallProcessor Processors\MakeProcessor Processors\OperationsProcessor Processors\RollbackProcessor Processors\StatusProcessor Data\Config\ConfigData Helpers\GitHelper Helpers\SorterHelper Services\MigratorService Services\MutexService Data\OptionsData Concerns\About Concerns\Artisan Concerns\Isolatable Concerns\Optionable Concerns\HasAbout Concerns\HasArtisan Concerns\HasIsolatable Concerns\HasOptionable

    The following properties and methods have been removed from the DragonCode\LaravelDeployOperations\Operation class:

    protected bool $once protected array|string | null $environment protected array|string | null $exceptEnvironment protected bool $before public function getConnection(): ?string public function isOnce(): bool public function enabledTransactions(): bool public function transactionAttempts(): int public function onEnvironment(): array public function exceptEnvironment(): array public function allow(): bool public function hasBefore(): bool public function isAsync(): bool public function shouldOnce(): bool public function shouldRun(): bool public function shouldRun(): bool public function needBefore(): bool Not used public bool shouldOnce(): bool public function withinTransactions(): bool It is indicated in the settings public function shouldRun(): bool public function shouldRun(): bool public function shouldRun(): bool public function needBefore(): bool public function needAsync(): bool
    ================================================ FILE: docs/topics/usage.topic ================================================ Information on how to use %product_short% Information on how to use %product_short% Information on how to use %product_short% ================================================ FILE: docs/v.list ================================================ ================================================ FILE: docs/versions.json ================================================ [ { "version": "7.x", "url": "https://deploy-operations.dragon-code.pro", "isCurrent": true }, { "version": "6.x", "url": "https://github.com/TheDragonCode/laravel-deploy-operations/tree/6.x/docs", "isCurrent": false }, { "version": "5.x", "url": "https://github.com/TheDragonCode/laravel-deploy-operations/tree/5.x/docs", "isCurrent": false }, { "version": "4.x", "url": "https://github.com/TheDragonCode/laravel-deploy-operations/tree/4.x/docs", "isCurrent": false }, { "version": "3.x", "url": "https://github.com/TheDragonCode/laravel-deploy-operations/tree/3.x/docs", "isCurrent": false } ] ================================================ FILE: docs/writerside.cfg ================================================ ================================================ FILE: ide.json ================================================ { "$schema": "https://laravel-ide.com/schema/laravel-ide-v2.json", "codeGenerations": [ { "id": "dragon-code.create-deploy-operation", "name": "Create Deploy Operation", "inputFilter": "deploy-operations", "regex": ".+", "files": [ { "directory": "/operations", "name": "${CURRENT_TIME|format:yyyy_MM_dd_HHmmss}_${INPUT_CLASS|className|replace: ,_|upperCamelCase|snakeCase}.php", "template": { "type": "stub", "path": "/stubs/deploy-operation.stub", "fallbackPath": "resources/stubs/deploy-operation.stub" } } ] } ], "completions": [ { "complete": "directoryFiles", "condition": [ { "classFqn": [ "DragonCode\\LaravelDeployOperations\\Helpers\\OperationHelper" ], "methodNames": [ "run" ], "functionFqn": [ "DragonCode\\LaravelDeployOperations\\operation" ], "place": "parameter", "parameters": [ 1 ] } ], "options": { "directory": "/operations", "suffixToClear": ".php", "recursive": true } } ] } ================================================ FILE: phpunit.xml ================================================ ./tests ./src ================================================ FILE: pint.json ================================================ { "preset": "laravel", "exclude": [ "tests/Fixtures" ], "rules": { "@PHP7x1Migration": true, "@PHP7x3Migration": true, "@PHP7x4Migration": true, "@PHP8x0Migration": true, "@PHP8x1Migration": true, "@PHP8x2Migration": true, "concat_space": { "spacing": "one" }, "blank_line_before_statement": { "statements": [ "declare", "phpdoc", "continue", "return" ] }, "class_attributes_separation": { "elements": { "case": "none", "const": "none", "method": "one", "property": "one", "trait_import": "none" } }, "class_definition": { "multi_line_extends_each_single_line": true, "single_item_single_line": true, "single_line": true, "space_before_parenthesis": true }, "combine_consecutive_issets": true, "combine_consecutive_unsets": true, "braces_position": { "allow_single_line_anonymous_functions": true, "allow_single_line_empty_anonymous_classes": true, "anonymous_classes_opening_brace": "same_line" }, "escape_implicit_backslashes": { "double_quoted": true, "heredoc_syntax": true, "single_quoted": false }, "global_namespace_import": { "import_classes": true, "import_constants": true, "import_functions": true }, "multiline_comment_opening_closing": true, "no_superfluous_elseif": true, "no_useless_else": true, "operator_linebreak": { "only_booleans": false }, "ordered_types": { "null_adjustment": "always_last", "sort_algorithm": "alpha" }, "phpdoc_line_span": { "case": "single", "class": "single", "const": "single", "method": "single", "property": "single", "other": "single" }, "return_assignment": true, "simplified_if_return": true, "phpdoc_param_order": true, "fully_qualified_strict_types": true, "declare_strict_types": true, "types_spaces": { "space_multiple_catch": "none" }, "binary_operator_spaces": { "default": "align_single_space_minimal" }, "php_unit_method_casing": { "case": "camel_case" } } } ================================================ FILE: resources/stubs/deploy-operation.stub ================================================ secure || $this->confirmToProceed(); } } ================================================ FILE: src/Concerns/HasAbout.php ================================================ getPackageName(), fn () => [ 'Version' => $this->getPackageVersion(), ]); } protected function getPackageName(): string { return Str::of($this->packageName) ->after('/') ->snake() ->replace('_', ' ') ->title() ->toString(); } protected function getPackageVersion(): string { return InstalledVersions::getPrettyVersion($this->packageName); } } ================================================ FILE: src/Concerns/HasArtisan.php ================================================ getIsolateOption()) { return is_numeric($isolate) ? $isolate : self::SUCCESS; } return self::SUCCESS; } protected function getIsolateOption(): bool|int { return $this->hasIsolateOption() ? (int) $this->option(Options::Isolated) : false; } protected function hasIsolateOption(): bool { return $this->hasOption(Options::Isolated) && $this->option(Options::Isolated); } } ================================================ FILE: src/Concerns/HasOptionable.php ================================================ specifyParameters(); } protected function getOptions(): array { return Arr::of($this->availableOptions()) ->filter(fn (array $option) => in_array($option[0], $this->options, true)) ->toArray(); } protected function getArguments(): array { return Arr::of($this->availableArguments()) ->filter(fn (array $argument) => in_array($argument[0], $this->arguments, true)) ->toArray(); } protected function availableArguments(): array { return [ [ Options::Name, InputArgument::OPTIONAL, 'The name of the operation', ], ]; } protected function availableOptions(): array { return [ [ Options::Before, null, InputOption::VALUE_NONE, 'Run operations marked as before', ], [ Options::Connection, null, InputOption::VALUE_OPTIONAL, 'The database connection to use', ], [ Options::Force, null, InputOption::VALUE_NONE, 'Force the operation to run when in production', ], [ Options::Path, null, InputOption::VALUE_OPTIONAL, 'The path to the operations files to be executed', ], [ Options::Realpath, null, InputOption::VALUE_NONE, 'Indicate any provided operation file paths are pre-resolved absolute path', ], [ Options::Step, null, InputOption::VALUE_OPTIONAL, 'Force the operations to be run so they can be rolled back individually', ], [ Options::Mute, null, InputOption::VALUE_NONE, 'Turns off the output of informational messages', ], [ Options::Isolated, null, InputOption::VALUE_OPTIONAL, 'Do not run the operations command if another instance of the operations command is already running', false, ], [ Options::Sync, null, InputOption::VALUE_OPTIONAL, 'Makes all operations run synchronously', false, ], ]; } } ================================================ FILE: src/Console/Command.php ================================================ allowToProceed()) { $this->resolveProcessor()->handle(); return self::SUCCESS; } return self::FAILURE; } protected function execute(InputInterface $input, OutputInterface $output): int { if ($this->getIsolateOption() !== false && ! $this->isolationMutex()->create($this)) { $this->comment(sprintf('The [%s] command is already running.', $this->getName())); return $this->isolatedStatusCode(); } try { return parent::execute($input, $output); } finally { if ($this->getIsolateOption() !== false) { $this->isolationMutex()->forget($this); } } } protected function resolveProcessor(): Processor { return app($this->processor, [ 'options' => $this->getOptionsData(), 'input' => $this->input, 'output' => $this->output, ]); } protected function getOptionsData(): OptionsData { return OptionsData::from(array_merge($this->options(), $this->arguments())); } } ================================================ FILE: src/Console/FreshCommand.php ================================================ map(static fn (string $path) => Str::replace(['\\', '/'], DIRECTORY_SEPARATOR, $path)) ->filter() ->all(); } } ================================================ FILE: src/Data/Casts/Config/PathCast.php ================================================ replace('\\', '/') ->replace('.php', '') ->explode('/') ->map(fn (string $path) => Str::snake($path)) ->implode(DIRECTORY_SEPARATOR) ->toString(); } } ================================================ FILE: src/Data/Casts/PathCast.php ================================================ config()->path . $value; if ($properties['realpath'] ?? false) { return $value ?: $path; } return $this->filename($path) ?: $path; } protected function filename(string $path): false|string { return realpath(Str::finish($path, '.php')); } protected function config(): ConfigData { return app(ConfigData::class); } } ================================================ FILE: src/Data/Config/ConfigData.php ================================================ 'Ran', self::Pending => 'Pending', self::Skipped => 'Skipped', }; } } ================================================ FILE: src/Events/BaseEvent.php ================================================ hasGitDirectory($path)) { return $this->exec('rev-parse --abbrev-ref HEAD', $this->resolvePath($path)); } return null; } protected function exec(string $command, ?string $path = null): ?string { return exec(sprintf('git -C "%s" %s', $path, $command)); } protected function hasGitDirectory(?string $path = null): bool { if ($path = rtrim($this->resolvePath($path), '/\\')) { return Directory::exists($path . DIRECTORY_SEPARATOR . '.git'); } return false; } protected function resolvePath(?string $path = null): string { return realpath($path ?: base_path()); } } ================================================ FILE: src/Helpers/OperationHelper.php ================================================ when($path, fn (Collection $items) => $items->put('--' . Options::Path, $path)) ->when($realpath, fn (Collection $items) => $items->put('--' . Options::Realpath, true)) ->all(); Artisan::call(OperationsCommand::class, $parameters); } } ================================================ FILE: src/Helpers/SorterHelper.php ================================================ callback()); } public function byKeys(array $items): array { return Arr::ksort($items, $this->callback()); } public function byRan(array $values, array $completed): array { foreach ($values as $value) { if (! in_array($value, $completed, true)) { $completed[] = $value; } } return $completed; } protected function callback(): Closure { return static function (string $a, string $b): int { $current = Path::filename($a); $next = Path::filename($b); if ($current === $next) { return 0; } return $current < $next ? -1 : 1; }; } } ================================================ FILE: src/Jobs/OperationJob.php ================================================ onConnection($this->config()->queue->connection); $this->onQueue($this->config()->queue->name); } public function handle(): void { Artisan::call(Names::Operations, [ '--' . Options::Path => $this->filename, '--' . Options::Sync => true, ]); } public function uniqueId(): string { return $this->filename; } protected function config(): ConfigData { return app(ConfigData::class); } } ================================================ FILE: src/Listeners/Listener.php ================================================ withOperation(); } return null; } protected function run(string $method, string $operation): void { match ($method) { 'up' => $this->call(OperationsCommand::class, $operation), 'down' => $this->call(RollbackCommand::class, $operation, ['--force' => true]), }; } protected function call(string $command, string $filename, array $parameters = []): void { Artisan::call($command, array_merge([ '--' . Options::Path => $filename, ], $parameters)); } } ================================================ FILE: src/Listeners/MigrationEndedListener.php ================================================ withOperation($event->migration)) { $this->run($event->method, $operation); } } } ================================================ FILE: src/Notifications/Notification.php ================================================ canSpeak()) { $this->components()->line($style, $string, $this->verbosity); } } public function info(string $string): void { if ($this->canSpeak()) { $this->components()->info($string, $this->verbosity); } } public function warning(string $string): void { if ($this->canSpeak()) { $this->components()->warn($string, $this->verbosity); } } public function task(string $description, Closure $task): void { if ($this->canSpeak()) { $this->components()->task($description, $task); return; } $task(); } public function twoColumn(string $first, string $second): void { if ($this->canSpeak()) { $this->components()->twoColumnDetail($first, $second, $this->verbosity); } } protected function components(): Factory { return $this->components ??= new Factory($this->output); } public function setOutput(OutputStyle $output, bool $silent = false): Notification { $this->output = $output; $this->silent = $silent; return $this; } protected function canSpeak(): bool { return ! $this->silent; } } ================================================ FILE: src/Operation.php ================================================ transactions->enabled; } /** Determines whether the given operation can be called conditionally. */ public function shouldRun(): bool { return true; } /** Defines a possible "pre-launch" of the operation. */ public function needBefore(): bool { return true; } /** Defines whether the operation will run synchronously or asynchronously. */ public function needAsync(): bool { return app(ConfigData::class)->async; } /** Method to be called when the job completes successfully. */ public function success(): void {} /** The method will be called if an error occurs. */ public function failed(): void {} } ================================================ FILE: src/Processors/FreshProcessor.php ================================================ drop(); $this->operations(); } protected function drop(): void { if ($this->repository->repositoryExists()) { $this->notification->task('Dropping all operations', fn () => $this->repository->deleteRepository()); } } protected function operations(): void { $this->runCommand(Names::Operations, [ '--' . Options::Connection => $this->options->connection, '--' . Options::Path => $this->options->path, '--' . Options::Realpath => true, ]); } } ================================================ FILE: src/Processors/InstallProcessor.php ================================================ exists()) { $this->notification->info('Operations repository already exists'); return; } $this->notification->task('Installing the operation repository', function () { $this->create(); $this->ensureDirectory(); }); } protected function isFile(string $path): bool { return Str::of($path)->lower()->endsWith('.php'); } protected function exists(): bool { return $this->repository->repositoryExists(); } protected function create(): void { $this->repository->createRepository(); } protected function ensureDirectory(): void { $this->isFile($this->options->path) ? Directory::ensureDirectory(Path::dirname($this->options->path)) : Directory::ensureDirectory($this->options->path); } } ================================================ FILE: src/Processors/MakeProcessor.php ================================================ getFullPath(); $this->notification->task($this->message($fullPath), fn () => $this->create($fullPath)); } protected function message(string $path): string { return 'Operation [' . $this->displayName($path) . '] created successfully'; } protected function create(string $path): void { File::copy($this->stubPath(), $path); } protected function displayName(string $path): string { return Str::of($path) ->when(! $this->showFullPath(), fn (Stringable $str) => $str->after(base_path())) ->replace('\\', '/') ->ltrim('./') ->toString(); } protected function getName(): string { return $this->getFilename( $this->getBranchName() ); } protected function getPath(): string { return $this->options->path; } protected function getFullPath(): string { return $this->getPath() . $this->getName(); } protected function getFilename(string $branch): string { $directory = Path::dirname($branch); $filename = Path::filename($branch); return Str::of($filename) ->snake() ->prepend($this->getTime()) ->finish('.php') ->prepend($directory . '/') ->replace('\\', '/') ->ltrim('./') ->toString(); } protected function getBranchName(): string { if ($name = trim((string) $this->options->name)) { return $name; } if ($name = $this->askForName()) { return $name; } return $this->git->currentBranch() ?? $this->fallback; } protected function askForName(): string { $prompt = $this->promptForName(); return text($prompt[0], $prompt[1], hint: $prompt[2]); } protected function promptForName(): array { return ['What should the operation be named?', 'E.g. activate articles', 'Press Enter to autodetect']; } protected function getTime(): string { return date('Y_m_d_His_'); } protected function stubPath(): string { if ($path = realpath(base_path('stubs/deploy-operation.stub'))) { return $path; } return $this->defaultStub; } protected function showFullPath(): bool { return $this->config->show->fullPath; } } ================================================ FILE: src/Processors/OperationsProcessor.php ================================================ showCaption(); $this->ensureRepository(); $this->runOperations($this->getCompleted()); } protected function showCaption(): void { $this->notification->info('Running operations'); } protected function ensureRepository(): void { $this->runCommand(Names::Install, [ '--' . Options::Connection => $this->options->connection, '--' . Options::Force => true, '--' . Options::Mute => true, ]); } protected function runOperations(array $completed): void { try { if ($files = $this->getNewFiles($completed)) { $this->fireEvent(DeployOperationStarted::class, MethodEnum::Up); $this->runEach($files, $this->getBatch()); $this->fireEvent(DeployOperationEnded::class, MethodEnum::Up); return; } $this->fireEvent(NoPendingDeployOperations::class, MethodEnum::Up); } catch (Throwable $e) { $this->fireEvent(DeployOperationFailed::class, MethodEnum::Up); throw $e; } } protected function runEach(array $files, int $batch): void { foreach ($files as $file) { $this->run($file, $batch); } } protected function run(string $filename, int $batch): void { $this->migrator->runUp($filename, $batch, $this->options); } protected function getNewFiles(array $completed): array { return $this->getFiles( path: $this->options->path, filter: fn (string $file) => ! Str::of($file)->replace('\\', '/')->contains($completed) ); } protected function getCompleted(): array { return $this->repository->getCompleted()->pluck('operation')->all(); } protected function getBatch(): int { return $this->repository->getNextBatchNumber(); } } ================================================ FILE: src/Processors/Processor.php ================================================ notification->setOutput($this->output, $this->options->mute); $this->repository->setConnection($this->options->connection); $this->migrator->setConnection($this->options->connection)->setOutput($this->output); } protected function getFiles(string $path, ?Closure $filter = null): array { $file = Str::finish($path, '.php'); $files = $this->isFile($file) ? [$file] : $this->file->names($path, $filter, true); $files = Arr::filter( $files, fn (string $path) => Str::endsWith($path, '.php') && ! Str::contains($path, $this->config->exclude) ); return Arr::of($this->sorter->byValues($files)) ->map(fn (string $value) => Str::before($value, '.php')) ->toArray(); } protected function runCommand(string $command, array $options = []): void { $this->artisan($command, array_filter($options), $this->output); } protected function tableNotFound(): bool { if (! $this->repository->repositoryExists()) { $this->notification->warning('Deploy operations table not found'); return true; } return false; } protected function fireEvent(string $event, MethodEnum $method): void { $this->events->dispatch(new $event($method, $this->options->before)); } protected function isFile(string $path): bool { return $this->file->exists($path) && $this->file->isFile($path); } } ================================================ FILE: src/Processors/RollbackProcessor.php ================================================ tableNotFound() || $this->nothingToRollback()) { $this->fireEvent(NoPendingDeployOperations::class, MethodEnum::Down); return; } if ($items = $this->getOperations($this->options->step)) { $this->fireEvent(DeployOperationStarted::class, MethodEnum::Down); $this->showCaption(); $this->run($items); $this->fireEvent(DeployOperationEnded::class, MethodEnum::Down); return; } $this->fireEvent(NoPendingDeployOperations::class, MethodEnum::Down); } protected function showCaption(): void { $this->notification->info('Rollback Operations'); } protected function run(array $rows): void { foreach ($rows as $row) { $this->rollback($row->operation); } } protected function getOperations(?int $step): array { return (int) $step > 0 ? $this->repository->getByStep($step) : $this->repository->getLast(); } protected function rollback(string $item): void { $this->migrator->runDown($item, $this->options); } protected function nothingToRollback(): bool { if ($this->count() <= 0) { $this->notification->info('Nothing To Rollback'); return true; } return false; } protected function count(): int { return $this->repository->getLastBatchNumber(); } } ================================================ FILE: src/Processors/StatusProcessor.php ================================================ Operation name'; protected string $columnStatus = 'Batch / Status'; public function handle(): void { if ($this->tableNotFound()) { return; } [$files, $completed] = $this->getData(); if ($this->isEmpty($files, $completed)) { $this->notification->info('No operations found'); return; } $this->showCaption(); $this->showHeaders(); $this->showStatus($files, $completed); } protected function showCaption(): void { $this->notification->info('Show Status'); } protected function showHeaders(): void { $this->notification->twoColumn($this->columnName, $this->columnStatus); } protected function showStatus(array $items, array $completed): void { foreach ($this->merge($items, array_keys($completed)) as $item) { $status = $this->getStatusFor($completed, $item); $this->notification->twoColumn($item, $status); } } protected function merge(array $items, array $completed): array { return $this->sorter->byRan($items, $completed); } protected function getData(): array { $files = $this->getFiles($this->options->path); $completed = $this->getCompleted(); return [$files, $completed]; } protected function getStatusFor(array $completed, string $item): string { if ($batch = Arr::get($completed, $item)) { return sprintf('[%s] %s', $batch, StatusEnum::Ran->toColor()); } return StatusEnum::Pending->toColor(); } protected function getCompleted(): array { return $this->repository->getCompleted() ->pluck('batch', 'operation') ->all(); } protected function isEmpty(array $items, array $completed): bool { return empty($items) && empty($completed); } } ================================================ FILE: src/Repositories/OperationsRepository.php ================================================ sortedTable()->get(); } public function getByStep(int $steps): array { return $this->sortedTable(Order::Desc) ->whereIn('batch', $this->getBatchNumbers($steps)) ->get() ->all(); } public function getLast(): array { return $this->sortedTable(Order::Desc) ->where('batch', $this->getLastBatchNumber()) ->get() ->all(); } public function getNextBatchNumber(): int { return $this->getLastBatchNumber() + 1; } public function getLastBatchNumber(): int { return (int) $this->table()->max('batch'); } public function log(string $operation, int $batch): void { $this->table()->insert(compact('operation', 'batch')); } public function delete(string $operation): void { $this->table()->where(compact('operation'))->delete(); } public function createRepository(): void { $this->schema()->create($this->config->table, function (Blueprint $table) { $table->bigIncrements('id'); $table->string('operation'); $table->unsignedInteger('batch'); }); } public function repositoryExists(): bool { return $this->schema()->hasTable($this->config->table); } public function deleteRepository(): void { $this->schema()->dropIfExists($this->config->table); } /** @return array */ protected function getBatchNumbers(int $steps): array { return $this->sortedTable(Order::Desc) ->pluck('batch') ->unique() ->take($steps) ->all(); } protected function sortedTable(string $order = Order::Asc): Query { return $this->table() ->orderBy('batch', $order) ->orderBy('id', $order); } protected function schema(): Builder { return $this->getConnection()->getSchemaBuilder(); } protected function table(): Query { return $this->getConnection()->table($this->config->table)->useWritePdo(); } protected function getConnection(): ConnectionInterface { return $this->resolver->connection( $this->connection ?: $this->config->connection ); } public function setConnection(?string $connection): self { $this->connection = $connection; return $this; } } ================================================ FILE: src/ServiceProvider.php ================================================ registerEvents(); $this->bootConfig(); if ($this->app->runningInConsole()) { $this->publishConfig(); $this->publishStub(); $this->registerAbout(); $this->registerCommands(); $this->registerMigrations(); } } public function register(): void { $this->registerConfig(); } protected function registerCommands(): void { $this->commands([ Console\OperationsCommand::class, Console\FreshCommand::class, Console\InstallCommand::class, Console\MakeCommand::class, Console\RollbackCommand::class, Console\StatusCommand::class, ]); } protected function registerEvents(): void { Event::listen(MigrationEnded::class, MigrationEndedListener::class); } protected function registerMigrations(): void { $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); } protected function publishConfig(): void { $this->publishes([ __DIR__ . '/../config/deploy-operations.php' => $this->app->configPath('deploy-operations.php'), ], ['config', 'deploy-operations']); } protected function publishStub(): void { $this->publishes([ __DIR__ . '/../resources/stubs/deploy-operation.stub' => $this->app->basePath( 'stubs/deploy-operation.stub' ), ], ['stubs', 'deploy-operations']); } protected function registerConfig(): void { $this->mergeConfigFrom(__DIR__ . '/../config/deploy-operations.php', 'deploy-operations'); } protected function bootConfig(): void { $this->app->bind(ConfigData::class, static fn () => ConfigData::from( config('deploy-operations') )); } } ================================================ FILE: src/Services/MigratorService.php ================================================ repository->setConnection($connection); return $this; } public function setOutput(OutputStyle $output): self { $this->notification->setOutput($output); return $this; } public function runUp(string $filename, int $batch, OptionsData $options): void { $path = $this->resolvePath($filename, $options->path); $operation = $this->resolveOperation($path); $name = $this->resolveOperationName($path); if (! $this->allowOperation($operation, $options)) { $this->notification->twoColumn($name, StatusEnum::Skipped->toColor()); return; } if ($this->needAsync($operation, $options)) { OperationJob::dispatch($name); $this->notification->twoColumn($name, StatusEnum::Pending->toColor()); return; } $this->notification->task($name, function () use ($operation, $name, $batch) { $this->hasMethod($operation, '__invoke') ? $this->runOperation($operation, '__invoke') : $this->runOperation($operation, 'up'); if ($operation->shouldOnce()) { $this->log($name, $batch); } }); } public function runDown(string $filename, OptionsData $options): void { $path = $this->resolvePath($filename, $options->path); $operation = $this->resolveOperation($path); $name = $this->resolveOperationName($path); $this->notification->task($name, function () use ($operation, $name) { $this->runOperation($operation, 'down'); $this->deleteLog($name); }); } protected function runOperation(Operation $operation, string $method): void { if ($this->hasMethod($operation, $method)) { try { $this->runMethod($operation, $method, $operation->withinTransactions()); $operation->success(); } catch (Throwable $e) { $operation->failed(); throw $e; } } } protected function hasMethod(Operation $operation, string $method): bool { return method_exists($operation, $method); } protected function needAsync(Operation $operation, OptionsData $options): bool { return ! $options->sync && $operation->needAsync(); } protected function runMethod(Operation $operation, string $method, bool $transactions): void { $callback = fn () => $this->container->call([$operation, $method]); $transactions ? DB::transaction($callback, $this->config->transactions->attempts) : $callback(); } protected function log(string $name, int $batch): void { $this->repository->log($name, $batch); } protected function deleteLog(string $name): void { $this->repository->delete($name); } protected function allowOperation(Operation $operation, OptionsData $options): bool { if (! $operation->shouldRun()) { return false; } return ! $this->disallowBefore($operation, $options); } protected function disallowBefore(Operation $operation, OptionsData $options): bool { return $options->before && ! $operation->needBefore(); } protected function resolvePath(string $filename, string $path): string { if (file_exists($path) && is_file($path)) { return $path; } $withExtension = Str::finish($filename, '.php'); if ($this->file->exists($withExtension) && $this->file->isFile($withExtension)) { return $withExtension; } return Str::finish($path . DIRECTORY_SEPARATOR . $filename, '.php'); } protected function resolveOperation(string $path): Operation { if ($this->file->exists($path)) { return require $path; } throw new FileNotFoundException($path); } protected function resolveOperationName(string $path): string { return Str::of(realpath($path)) ->after(realpath($this->config->path) . DIRECTORY_SEPARATOR) ->replace(['\\', '/'], '/') ->before('.php') ->toString(); } } ================================================ FILE: src/Services/MutexService.php ================================================ store()->add($this->name($command), true, $this->ttl()); } public function forget(Command $command): void { $this->store()->forget( $this->name($command) ); } protected function store(): Repository { return $this->cache->store($this->store); } protected function ttl(): CarbonInterval { return CarbonInterval::hour(); } protected function name(Command $command): string { return 'framework' . DIRECTORY_SEPARATOR . 'deploy-operation-' . $command->getName(); } } ================================================ FILE: src/helpers.php ================================================ copyFiles(); Event::fake(); $this->artisan(Names::Operations)->assertExitCode(0); Event::assertDispatchedTimes(DeployOperationStarted::class, 1); Event::assertDispatchedTimes(DeployOperationEnded::class, 1); Event::assertNotDispatched(DeployOperationFailed::class); } public function testFailed(): void { $this->expectException(Exception::class); $this->expectExceptionMessage('Custom exception'); $this->copyFailedMethod(); Event::fake(); try { $this->artisan(Names::Operations)->run(); } catch (Throwable $e) { Event::assertDispatchedTimes(DeployOperationStarted::class, 1); Event::assertDispatchedTimes(DeployOperationFailed::class, 1); Event::assertNotDispatched(DeployOperationEnded::class); throw $e; } } } ================================================ FILE: tests/Commands/FreshTest.php ================================================ assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Make, ['name' => 'Fresh'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 1); $this->artisan(Names::Fresh)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'fresh'); } } ================================================ FILE: tests/Commands/InstallTest.php ================================================ assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); } public function testAlreadyCreated(): void { $this->assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); } public function testMutedCreate(): void { $this->assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install, ['--' . Options::Mute => true])->assertExitCode(0); $this->assertDatabaseHasTable($this->table); } public function testMutedAlreadyCreated(): void { $this->assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install, ['--' . Options::Mute => true])->assertExitCode(0); $this->artisan(Names::Install, ['--' . Options::Mute => true])->assertExitCode(0); $this->assertDatabaseHasTable($this->table); } } ================================================ FILE: tests/Commands/MakeTest.php ================================================ getOperationsPath() . '/' . date('Y_m_d_His') . '_make_example.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make, compact('name'))->assertExitCode(0); $this->assertFileExists($path); $this->assertEquals( file_get_contents(__DIR__ . '/../fixtures/app/stubs/make_example.stub'), file_get_contents($path) ); } public function testAskedName(): void { $path = $this->getOperationsPath() . '/' . date('Y_m_d_His') . '_some_name.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make) ->expectsQuestion('What should the operation be named?', 'Some Name') ->assertExitCode(0); $this->assertFileExists($path); } public function testAutoName(): void { $path = $this->getOperationsPath() . '/' . date('Y_m_d_His') . '_auto.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make) ->expectsQuestion('What should the operation be named?', '') ->assertExitCode(0); $this->assertFileExists($path); } public function testNestedRightSlashWithoutExtension(): void { $name = 'Foo/bar/QweRty'; $path = $this->getOperationsPath() . '/foo/bar/' . date('Y_m_d_His') . '_qwe_rty.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make, compact('name'))->assertExitCode(0); $this->assertFileExists($path); $this->assertEquals( file_get_contents(__DIR__ . '/../fixtures/app/stubs/make_example.stub'), file_get_contents($path) ); } public function testNestedRightSlashWithExtension(): void { $name = 'Foo/bar/QweRty.php'; $path = $this->getOperationsPath() . '/foo/bar/' . date('Y_m_d_His') . '_qwe_rty.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make, compact('name'))->assertExitCode(0); $this->assertFileExists($path); $this->assertEquals( file_get_contents(__DIR__ . '/../fixtures/app/stubs/make_example.stub'), file_get_contents($path) ); } public function testNestedLeftSlashWithoutExtension(): void { $name = 'Foo\\bar\\QweRty'; $path = $this->getOperationsPath() . '/foo/bar/' . date('Y_m_d_His') . '_qwe_rty.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make, compact('name'))->assertExitCode(0); $this->assertFileExists($path); $this->assertEquals( file_get_contents(__DIR__ . '/../fixtures/app/stubs/make_example.stub'), file_get_contents($path) ); } public function testNestedLeftSlashWithExtension(): void { $name = 'Foo\\bar\\QweRty.php'; $path = $this->getOperationsPath() . '/foo/bar/' . date('Y_m_d_His') . '_qwe_rty.php'; $this->assertFileDoesNotExist($path); $this->artisan(Names::Make, compact('name'))->assertExitCode(0); $this->assertFileExists($path); $this->assertEquals( file_get_contents(__DIR__ . '/../fixtures/app/stubs/make_example.stub'), file_get_contents($path) ); } public function testFromCustomizedStub(): void { $name = 'MakeExample'; $stubPath = base_path('stubs/deploy-operation.stub'); $operationPath = $this->getOperationsPath() . '/' . date('Y_m_d_His') . '_make_example.php'; $this->assertFileDoesNotExist($stubPath); File::copy(__DIR__ . '/../fixtures/app/stubs/customized.stub', $stubPath); $this->assertFileExists($stubPath); $this->assertFileDoesNotExist($operationPath); $this->artisan(Names::Make, compact('name'))->assertExitCode(0); $this->assertFileExists($operationPath); $content = file_get_contents($operationPath); $this->assertStringContainsString('Foo\\Bar\\Some', $content); $this->assertStringContainsString('extends Some', $content); $this->assertStringNotContainsString('DragonCode\\LaravelDeployOperations\\Operation', $content); $this->assertStringNotContainsString('extends Operation', $content); } } ================================================ FILE: tests/Commands/MutexTest.php ================================================ command = new class extends Command { public int $ran = 0; public function handle(): int { $this->ran++; return self::SUCCESS; } }; $this->mutex = m::mock(MutexService::class); $container = Container::getInstance(); $container->instance(MutexService::class, $this->mutex); $this->command->setLaravel($container); } public function testCanRunIsolatedCommandIfNotBlocked(): void { $this->mutex->shouldReceive('create') ->andReturn(true) ->once(); $this->mutex->shouldReceive('forget') ->once(); $this->runCommand(); $this->assertSame(1, $this->command->ran); } public function testCannotRunIsolatedCommandIfBlocked(): void { $this->mutex->shouldReceive('create') ->andReturn(false) ->once(); $this->mutex->shouldReceive('forget') ->never(); $this->runCommand(); $this->assertSame(0, $this->command->ran); } public function testCanRunCommandAgainAfterOtherCommandFinished(): void { $this->mutex->shouldReceive('create') ->andReturn(true) ->twice(); $this->mutex->shouldReceive('forget') ->twice(); $this->runCommand(); $this->runCommand(); $this->assertEquals(2, $this->command->ran); } public function testCanRunCommandAgainNonAutomated(): void { $this->mutex->shouldNotHaveBeenCalled(); $this->runCommand(false); $this->assertEquals(1, $this->command->ran); } protected function runCommand($withIsolated = true) { $input = new ArrayInput(['--' . Options::Isolated => $withIsolated]); $output = new NullOutput; $this->command->run($input, $output); } } ================================================ FILE: tests/Commands/OperationsTest.php ================================================ assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Make, ['name' => 'TestMigration'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'test_migration'); } public function testSameName(): void { $this->assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Make, ['name' => 'TestMigration'])->assertExitCode(0); sleep(2); $this->artisan(Names::Make, ['name' => 'TestMigration'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($this->table, 2); $this->assertDatabaseOperationHas($this->table, 'test_migration'); } public function testOnce(): void { $this->copyFiles(); $table = 'every_time'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 3); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 4); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, $table); } public function testSuccessTransaction(): void { $this->copySuccessTransaction(); $table = 'transactions'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 3); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, $table); } public function testFailedTransaction(): void { $this->copyFailedTransaction(); $table = 'transactions'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); try { $this->artisan(Names::Operations)->assertExitCode(0); } catch (Exception $e) { $this->assertSame(Exception::class, get_class($e)); $this->assertSame('Random message', $e->getMessage()); } $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); } public function testSingleEnvironment(): void { $this->copyFiles(); $table = 'environment'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_testing'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationHas($this->table, 'run_on_testing'); $this->assertDatabaseOperationHas($this->table, 'run_except_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_testing'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationHas($this->table, 'run_on_testing'); $this->assertDatabaseOperationHas($this->table, 'run_except_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_testing'); } public function testManyEnvironments(): void { $this->copyFiles(); $table = 'environment'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_many_environments'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_many_environments'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationHas($this->table, 'run_on_testing'); $this->assertDatabaseOperationHas($this->table, 'run_on_many_environments'); $this->assertDatabaseOperationHas($this->table, 'run_except_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_many_environments'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationHas($this->table, 'run_on_testing'); $this->assertDatabaseOperationHas($this->table, 'run_on_many_environments'); $this->assertDatabaseOperationHas($this->table, 'run_except_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_except_many_environments'); } public function testAllow(): void { $this->copyFiles(); $table = 'environment'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_allow'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_disallow'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_allow'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_disallow'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_allow'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_disallow'); } public function testUpSuccess(): void { $this->copyFiles(); $table = 'success'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_success'); } public function testUpSuccessOnFailed(): void { $this->copyFiles(); $table = 'success'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success_on_failed'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success_on_failed'); try { $this->copySuccessFailureMethod(); $this->artisan(Names::Operations)->assertExitCode(0); } catch (Throwable $e) { $this->assertInstanceOf(Exception::class, $e); $this->assertSame('Custom exception', $e->getMessage()); $this->assertTrue(Str::contains($e->getFile(), 'run_success_on_failed')); } $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success_on_failed'); } public function testUpFailed(): void { $this->copyFiles(); $table = 'failed'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_failed'); } public function testUpFailedOnException(): void { $this->copyFiles(); $table = 'failed'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed_failure'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed_failure'); try { $this->copyFailedMethod(); $this->artisan(Names::Operations)->assertExitCode(0); } catch (Throwable $e) { $this->assertInstanceOf(Exception::class, $e); $this->assertSame('Custom exception', $e->getMessage()); $this->assertTrue(Str::contains($e->getFile(), 'run_failed_failure')); } $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed_failure'); } public function testPathAsFileWithExtension(): void { $this->copyFiles(); $table = 'test'; $path = 'sub_path/2021_12_15_205804_baz.php'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'baz'); $this->artisan(Names::Operations, ['--path' => $path])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'baz'); $this->assertSame('sub_path/2021_12_15_205804_baz', $this->table()->first()->operation); } public function testPathAsFileWithoutExtension(): void { $this->copyFiles(); $table = 'test'; $path = 'sub_path/2021_12_15_205804_baz'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'baz'); $this->artisan(Names::Operations, ['--path' => $path])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'baz'); $this->assertSame($path, $this->table()->first()->operation); } public function testPathAsDirectory(): void { $this->copyFiles(); $table = 'test'; $path = 'sub_path'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'baz'); $this->artisan(Names::Operations, ['--path' => $path])->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 2); $this->assertDatabaseOperationHas($this->table, 'baz'); $this->assertSame('sub_path/2021_12_15_205804_baz', $this->table()->first()->operation); } public function testOperationsNotFound(): void { $this->assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->artisan(Names::Status)->assertExitCode(0); } public function testDisabledBefore(): void { $this->copyFiles(); $table = 'before'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationHas($this->table, 'test_before_disabled'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationHas($this->table, 'test_before_disabled'); } public function testEnabledBefore(): void { $this->copyFiles(); $table = 'before'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations, ['--before' => true])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations, ['--before' => true])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); } public function testMixedBefore(): void { $this->copyFiles(); $table = 'before'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations, ['--before' => true])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations, ['--before' => true])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationHas($this->table, 'test_before_disabled'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationHas($this->table, 'test_before_disabled'); } public function testDI(): void { $this->copyDI(); $table = 'test'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke'); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke_down'); $this->assertDatabaseOperationDoesntLike($this->table, 'up_down'); $this->assertDatabaseOperationDoesntLike($table, 'up_down', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'invoke_down', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'invoke', column: 'value'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 3); $this->assertDatabaseCount($this->table, 3); $this->assertDatabaseOperationHas($this->table, 'invoke'); $this->assertDatabaseOperationHas($this->table, 'invoke_down'); $this->assertDatabaseOperationHas($this->table, 'up_down'); $this->assertDatabaseOperationHas($table, 'up_down', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke_down', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke', column: 'value'); } public function testSorting(): void { $files = []; $this->artisan(Names::Install)->assertExitCode(0); $files[] = date('Y_m_d_His_') . 'test1'; $this->artisan(Names::Make, ['name' => 'test1'])->assertExitCode(0); sleep(2); $files[] = 'foo/' . date('Y_m_d_His_') . 'test2'; $this->artisan(Names::Make, ['name' => 'foo/test2'])->assertExitCode(0); sleep(2); $files[] = 'bar/' . date('Y_m_d_His_') . 'test3'; $this->artisan(Names::Make, ['name' => 'bar/test3'])->assertExitCode(0); sleep(2); $files[] = 'foo/' . date('Y_m_d_His_') . 'test4'; $this->artisan(Names::Make, ['name' => 'foo/test4'])->assertExitCode(0); sleep(2); $files[] = 'bar/' . date('Y_m_d_His_') . 'test5'; $this->artisan(Names::Make, ['name' => 'bar/test5'])->assertExitCode(0); sleep(2); $files[] = date('Y_m_d_His_') . 'test6'; $this->artisan(Names::Make, ['name' => 'test6'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($this->table, 6); $records = DB::table($this->table)->orderBy('id')->pluck('operation')->all(); $this->assertSame($files, $records); } public function testDirectoryExclusion(): void { $this->copyFiles(); $this->app['config']->set('deploy-operations.exclude', 'sub_path'); $this->app->forgetInstance(ConfigData::class); $table = 'every_time'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 10); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 10); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 3); $this->assertDatabaseCount($this->table, 10); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 4); $this->assertDatabaseCount($this->table, 10); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2022_10_27_230732_foo'); } public function testFileExclusion(): void { $this->copyFiles(); $this->app['config']->set('deploy-operations.exclude', 'sub_path/2021_12_15_205804_baz'); $this->app->forgetInstance(ConfigData::class); $table = 'every_time'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationHas($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationHas($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 3); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationHas($this->table, 'sub_path/2022_10_27_230732_foo'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 4); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->assertDatabaseOperationDoesntLike($this->table, 'sub_path/2021_12_15_205804_baz'); $this->assertDatabaseOperationHas($this->table, 'sub_path/2022_10_27_230732_foo'); } public function testEmptyDirectory(): void { $this->copyEmptyDirectory(); $table = 'every_time'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, $table); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); } public function testAsync(): void { $this->copyAsync(); $table1 = 'test'; $table2 = 'every_time'; $queue = config('deploy-operations.queue.name'); Queue::fake(); $this->artisan(Names::Install)->assertExitCode(0); Queue::assertNothingPushed(); $this->assertDatabaseCount($table1, 0); $this->assertDatabaseCount($table2, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'foo_bar'); $this->assertDatabaseOperationDoesntLike($this->table, 'every_time'); $this->artisan(Names::Operations)->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseOperationDoesntLike($this->table, 'foo_bar'); $this->assertDatabaseOperationDoesntLike($this->table, 'every_time'); Queue::assertPushed(OperationJob::class, 2); Queue::assertPushedOn($queue, OperationJob::class, fn (OperationJob $job) => Str::contains($job->filename, [ 'foo_bar', 'every_time', ])); } public function testSync(): void { $this->copyAsync(); $table1 = 'test'; $table2 = 'every_time'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table1, 0); $this->assertDatabaseCount($table2, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'foo_bar'); $this->assertDatabaseOperationDoesntLike($this->table, 'every_time'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table1, 1); $this->assertDatabaseCount($table2, 1); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'foo_bar'); $this->assertDatabaseOperationDoesntLike($this->table, 'every_time'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table1, 1); $this->assertDatabaseCount($table2, 2); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'foo_bar'); $this->assertDatabaseOperationDoesntLike($this->table, 'every_time'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table1, 1); $this->assertDatabaseCount($table2, 3); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'foo_bar'); $this->assertDatabaseOperationDoesntLike($this->table, 'every_time'); } public function testViaMigrationMethod(): void { $this->copyViaMigrations(); $table = 'test'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'custom'); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke'); $this->assertDatabaseOperationDoesntLike($this->table, 'up_down'); $this->assertDatabaseOperationDoesntLike($table, 'custom', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'invoke', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'up_down', column: 'value'); $this->loadMigrationsFrom(__DIR__ . '/../fixtures/migrations_with_operations'); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 2); $this->assertDatabaseOperationDoesntLike($this->table, 'custom'); $this->assertDatabaseOperationHas($this->table, 'invoke'); $this->assertDatabaseOperationHas($this->table, 'up_down'); $this->assertDatabaseOperationDoesntLike($table, 'custom', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke', column: 'value'); $this->assertDatabaseOperationHas($table, 'up_down', column: 'value'); $this->artisan(RollbackCommand::class, [ '--path' => __DIR__ . '/../fixtures/migrations_with_operations', '--realpath' => true, ])->assertSuccessful(); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationDoesntLike($this->table, 'custom'); $this->assertDatabaseOperationHas($this->table, 'invoke'); $this->assertDatabaseOperationDoesntLike($this->table, 'up_down'); $this->assertDatabaseOperationDoesntLike($table, 'custom', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'up_down', column: 'value'); } } ================================================ FILE: tests/Commands/RollbackTest.php ================================================ assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Make, ['name' => 'RollbackOne'])->assertExitCode(0); $this->artisan(Names::Make, ['name' => 'RollbackTwo'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 2); $this->assertDatabaseOperationHas($this->table, 'rollback_one'); $this->assertDatabaseOperationHas($this->table, 'rollback_two'); $this->assertDatabaseOperationDoesntLike($this->table, 'rollback_tree'); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 2); $this->artisan(Names::Make, ['name' => 'RollbackTree'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 3); $this->assertDatabaseOperationHas($this->table, 'rollback_one'); $this->assertDatabaseOperationHas($this->table, 'rollback_two'); $this->assertDatabaseOperationHas($this->table, 'rollback_tree'); } public function testEnvironment(): void { $this->copyFiles(); $table = 'environment'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_many_environments'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 5); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationHas($this->table, 'run_on_testing'); $this->assertDatabaseOperationHas($this->table, 'run_on_many_environments'); $this->artisan(Names::Operations)->assertExitCode(0); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseCount($table, 10); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_all'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_production'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_testing'); $this->assertDatabaseOperationDoesntLike($this->table, 'run_on_many_environments'); } public function testDownSuccess(): void { $this->copyFiles(); $table = 'success'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_success'); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseCount($table, 4); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success'); } public function testDownSuccessOnFailed(): void { $this->copyFiles(); $table = 'success'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success_on_failed'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, 'run_success_on_failed'); try { $this->copySuccessFailureMethod(); $this->table()->insert(['operation' => '2021_12_23_165048_run_success_on_failed', 'batch' => 999]); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 13); $this->assertDatabaseOperationHas($this->table, 'run_success_on_failed'); $this->artisan(Names::Rollback)->assertExitCode(1); } catch (Throwable $e) { $this->assertInstanceOf(Exception::class, $e); $this->assertSame('Custom exception', $e->getMessage()); $this->assertTrue(Str::contains($e->getFile(), 'run_success_on_failed')); } $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 13); $this->assertDatabaseOperationHas($this->table, 'run_success_on_failed'); } public function testDownFailed(): void { $this->copyFiles(); $table = 'failed'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'run_failed'); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed'); } public function testUpFailedOnException(): void { $this->copyFiles(); $table = 'failed'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed_failure'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationDoesntLike($this->table, 'run_failed_failure'); try { $this->copyFailedMethod(); $this->table()->insert(['operation' => '2021_12_23_184029_run_failed_failure', 'batch' => 999]); $this->artisan(Names::Rollback)->assertExitCode(0); } catch (Throwable $e) { $this->assertInstanceOf(Exception::class, $e); $this->assertSame('Custom exception', $e->getMessage()); $this->assertTrue(Str::contains($e->getFile(), 'run_failed_failure')); } $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 13); $this->assertDatabaseOperationHas($this->table, 'run_failed_failure'); } public function testDisabledBefore(): void { $this->copyFiles(); $table = 'before'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 12); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationHas($this->table, 'test_before_disabled'); $this->artisan(Names::Operations)->assertExitCode(0); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseCount($table, 4); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); } public function testEnabledBefore(): void { $this->copyFiles(); $table = 'before'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations, ['--before' => true])->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 11); $this->assertDatabaseOperationHas($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); $this->artisan(Names::Operations, ['--before' => true])->assertExitCode(0); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseCount($table, 2); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_enabled'); $this->assertDatabaseOperationDoesntLike($this->table, 'test_before_disabled'); } public function testDI(): void { $this->copyDI(); $table = 'test'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke'); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke_down'); $this->assertDatabaseOperationDoesntLike($this->table, 'up_down'); $this->assertDatabaseOperationDoesntLike($table, 'up_down', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'invoke_down', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'invoke', column: 'value'); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($table, 3); $this->assertDatabaseCount($this->table, 3); $this->assertDatabaseOperationHas($this->table, 'invoke'); $this->assertDatabaseOperationHas($this->table, 'invoke_down'); $this->assertDatabaseOperationHas($this->table, 'up_down'); $this->assertDatabaseOperationHas($table, 'up_down', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke_down', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke', column: 'value'); $this->artisan(Names::Rollback)->assertExitCode(0); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke'); $this->assertDatabaseOperationDoesntLike($this->table, 'invoke_down'); $this->assertDatabaseOperationDoesntLike($this->table, 'up_down'); $this->assertDatabaseOperationDoesntLike($table, 'up_down', column: 'value'); $this->assertDatabaseOperationDoesntLike($table, 'invoke_down', column: 'value'); $this->assertDatabaseOperationHas($table, 'invoke', column: 'value'); } } ================================================ FILE: tests/Commands/StatusTest.php ================================================ assertDatabaseDoesntTable($this->table); $this->artisan(Names::Status)->assertExitCode(0); $this->assertDatabaseDoesntTable($this->table); } public function testStatusCommand(): void { $this->assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Status)->expectsTable([], [])->assertExitCode(0); $this->artisan(Names::Make, ['name' => 'Status'])->assertExitCode(0); $this->artisan(Names::Operations)->assertExitCode(0); $this->assertDatabaseCount($this->table, 1); $this->artisan(Names::Status)->assertExitCode(0); $this->assertDatabaseOperationHas($this->table, 'status'); } } ================================================ FILE: tests/Concerns/AssertDatabase.php ================================================ assertTrue( $this->hasTable($table) ); } protected function assertDatabaseDoesntTable(string $table): void { $this->assertFalse( $this->hasTable($table) ); } protected function assertDatabaseOperationHas( string $table, $value, $connection = null, string $column = 'operation' ): void { $this->assertDatabaseHasLike($table, $column, $value, $connection); } protected function assertDatabaseHasLike(string $table, string $column, $value, $connection = null): void { $exists = DB::connection($connection) ->table($table) ->whereRaw("$column like '%$value%'") ->exists(); $this->assertTrue($exists); } protected function assertDatabaseOperationDoesntLike( string $table, $value, $connection = null, string $column = 'operation' ): void { $this->assertDatabaseDoesntLike($table, $column, $value, $connection); } protected function assertDatabaseDoesntLike(string $table, string $column, $value, $connection = null): void { $exists = DB::connection($connection) ->table($table) ->whereRaw("$column like '%$value%'") ->doesntExist(); $this->assertTrue($exists); } protected function hasTable(string $table): bool { return Schema::hasTable($table); } } ================================================ FILE: tests/Concerns/Database.php ================================================ set('database.default', $this->database); $app['config']->set('database.connections.' . $this->database, [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ]); $app['config']->set('deploy-operations.connection', $this->database); $app['config']->set('deploy-operations.table', $this->table); } protected function freshDatabase(): void { $this->loadMigrationsFrom(__DIR__ . '/../fixtures/migrations'); $this->artisan('migrate')->run(); } } ================================================ FILE: tests/Concerns/Files.php ================================================ targetDirectory(), $this->stubsDirectory(), ]); } protected function copyFiles(): void { File::copyDirectory( __DIR__ . '/../fixtures/app/operations', $this->targetDirectory() ); } protected function copyAsync(): void { File::copyDirectory( __DIR__ . '/../fixtures/app/async', $this->targetDirectory() ); } protected function copyEmptyDirectory(): void { File::copyDirectory( __DIR__ . '/../fixtures/app/empty', $this->targetDirectory() ); } protected function copyDI(): void { File::copyDirectory( __DIR__ . '/../fixtures/app/di', $this->targetDirectory() ); } protected function copyViaMigrations(): void { File::copyDirectory( __DIR__ . '/../fixtures/app/via_migrations', $this->targetDirectory() ); } protected function copySuccessFailureMethod(): void { File::copy( __DIR__ . '/../fixtures/app/operations_failed/2021_12_23_165048_run_success_on_failed.php', $this->targetDirectory('2021_12_23_165048_run_success_on_failed.php') ); } protected function copyFailedMethod(): void { File::copy( __DIR__ . '/../fixtures/app/operations_failed/2021_12_23_184029_run_failed_failure.php', $this->targetDirectory('2021_12_23_184029_run_failed_failure.php') ); } protected function copySuccessTransaction(): void { File::copy( __DIR__ . '/../fixtures/app/stubs/2021_02_15_124237_test_success_transactions.stub', $this->targetDirectory('2021_02_15_124237_test_success_transactions.php') ); } protected function copyFailedTransaction(): void { File::copy( __DIR__ . '/../fixtures/app/stubs/2021_02_15_124852_test_failed_transactions.stub', $this->targetDirectory('2021_02_15_124852_test_failed_transactions.php') ); } protected function targetDirectory(string $path = ''): string { $dir = $this->getOperationsPath(); File::ensureDirectoryExists($dir); return rtrim($dir, '/\\') . '/' . ltrim($path, '/\\'); } protected function stubsDirectory(): string { return base_path('stubs'); } protected function getOperationsPath(): string { return $this->app['config']->get('deploy-operations.path'); } } ================================================ FILE: tests/Concerns/Some.php ================================================ assertNull($this->git()->currentBranch(__DIR__)); } public function testCurrentBranch(): void { $branch = $this->git()->currentBranch(__DIR__ . '/../../'); $this->assertIsString($branch); } protected function git(): GitHelper { return $this->app->make(GitHelper::class); } } ================================================ FILE: tests/Helpers/OperationTest.php ================================================ assertDatabaseDoesntTable($this->table); $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseHasTable($this->table); $this->assertDatabaseCount($this->table, 0); $this->artisan(Names::Make, ['name' => 'TestMigration'])->assertExitCode(0); OperationHelper::run(); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'test_migration'); } public function testPath(): void { $this->copyFiles(); $table = 'test'; $path = 'sub_path/2021_12_15_205804_baz.php'; $this->artisan(Names::Install)->assertExitCode(0); $this->assertDatabaseCount($table, 0); $this->assertDatabaseCount($this->table, 0); $this->assertDatabaseOperationDoesntLike($this->table, 'baz'); OperationHelper::run($path); $this->assertDatabaseCount($table, 1); $this->assertDatabaseCount($this->table, 1); $this->assertDatabaseOperationHas($this->table, 'baz'); $this->assertSame('sub_path/2021_12_15_205804_baz', $this->table()->first()->operation); } } ================================================ FILE: tests/Helpers/SorterTest.php ================================================ assertSame($expected, $this->sorter()->byValues($values)); } public function testByKeys(): void { $expected = [ '2022_10_13_013321_test1' => 1, 'foo/2022_10_13_013322_test2' => 2, 'bar/2022_10_13_013323_test3' => 3, 'foo/2022_10_13_013324_test4' => 4, 'bar/2022_10_13_013325_test5' => 5, '2022_10_13_013326_test6' => 6, ]; $values = [ '2022_10_13_013321_test1' => 1, '2022_10_13_013326_test6' => 6, 'bar/2022_10_13_013323_test3' => 3, 'bar/2022_10_13_013325_test5' => 5, 'foo/2022_10_13_013322_test2' => 2, 'foo/2022_10_13_013324_test4' => 4, ]; $this->assertSame($expected, $this->sorter()->byKeys($values)); } public function testByRan(): void { $items = [ '2022_10_13_013321_test1', 'foo/2022_10_13_013322_test2', 'bar/2022_10_13_013323_test3', 'foo/2022_10_13_013324_test4', 'bar/2022_10_13_013325_test5', '2022_10_13_013326_test6', ]; $completed = [ '2022_10_13_013321_test1', 'foo/2022_10_13_013324_test4', 'bar/2022_10_13_013325_test5', ]; $expected = [ '2022_10_13_013321_test1', 'foo/2022_10_13_013324_test4', 'bar/2022_10_13_013325_test5', 'foo/2022_10_13_013322_test2', 'bar/2022_10_13_013323_test3', '2022_10_13_013326_test6', ]; $this->assertSame($expected, $this->sorter()->byRan($items, $completed)); } protected function sorter(): SorterHelper { return new SorterHelper; } } ================================================ FILE: tests/TestCase.php ================================================ freshDatabase(); $this->freshFiles(); } protected function getPackageProviders($app): array { return [ ServiceProvider::class, LaravelDataServiceProvider::class, ]; } protected function getEnvironmentSetUp($app): void { parent::getEnvironmentSetUp($app); $this->setDatabase($app); } protected function table(): Builder { return DB::table($this->table); } protected function repository(): OperationsRepository { return Container::getInstance()->make(OperationsRepository::class); } } ================================================ FILE: tests/fixtures/app/async/2021_04_06_212742_every_time.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('every_time'); } public function shouldOnce(): bool { return false; } public function needAsync(): bool { return true; } }; ================================================ FILE: tests/fixtures/app/async/2023_04_06_212637_foo_bar.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('test'); } public function needAsync(): bool { return true; } }; ================================================ FILE: tests/fixtures/app/di/2022_10_11_234251_invoke.php ================================================ table()->insert([ 'value' => $some->get('invoke'), ]); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/di/2022_10_11_234251_invoke_down.php ================================================ table()->insert([ 'value' => $some->get('invoke_down'), ]); } public function down(Some $some): void { $this->table() ->where('value', $some->get('invoke_down')) ->delete(); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/di/2022_10_11_234312_up_down.php ================================================ table()->insert([ 'value' => $some->get('up_down'), ]); } public function down(Some $some): void { $this->table() ->where('value', $some->get('up_down')) ->delete(); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/empty/.gitkeep ================================================ ================================================ FILE: tests/fixtures/app/empty/some.txt ================================================ ================================================ FILE: tests/fixtures/app/operations/2020_12_07_153105_foo_bar.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->truncate(); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/operations/2021_01_02_020947_every_time.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('every_time'); } public function shouldOnce(): bool { return false; } }; ================================================ FILE: tests/fixtures/app/operations/2021_05_24_120003_run_on_all.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } }; ================================================ FILE: tests/fixtures/app/operations/2021_05_24_120003_run_on_many_environments.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return in_array(app()->environment(), ['testing', 'production'], true); } }; ================================================ FILE: tests/fixtures/app/operations/2021_05_24_120003_run_on_production.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return app()->environment() === 'production'; } }; ================================================ FILE: tests/fixtures/app/operations/2021_05_24_120003_run_on_testing.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return app()->environment() === 'testing'; } }; ================================================ FILE: tests/fixtures/app/operations/2021_06_07_132849_run_except_production.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return app()->environment() !== 'production'; } }; ================================================ FILE: tests/fixtures/app/operations/2021_06_07_132917_run_except_testing.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return app()->environment() !== 'testing'; } }; ================================================ FILE: tests/fixtures/app/operations/2021_06_07_134045_run_except_many_environments.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return app()->environment() === 'testing' && ! in_array(app()->environment(), ['testing', 'production'], true); } }; ================================================ FILE: tests/fixtures/app/operations/2021_10_26_143247_run_allow.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } }; ================================================ FILE: tests/fixtures/app/operations/2021_10_26_143304_run_disallow.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('environment'); } public function shouldRun(): bool { return false; } }; ================================================ FILE: tests/fixtures/app/operations/2021_12_23_165047_run_success.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } public function success(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('success'); } }; ================================================ FILE: tests/fixtures/app/operations/2021_12_23_184029_run_failed.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('success'); } }; ================================================ FILE: tests/fixtures/app/operations/2022_08_17_135147_test_before_enabled.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('before'); } public function needBefore(): bool { return true; } }; ================================================ FILE: tests/fixtures/app/operations/2022_08_17_135153_test_before_disabled.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('before'); } public function needBefore(): bool { return false; } }; ================================================ FILE: tests/fixtures/app/operations/sub_path/2021_12_15_205804_baz.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->truncate(); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/operations/sub_path/2022_10_27_230732_foo.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } public function down(): void { $this->table()->truncate(); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/operations_failed/2021_12_23_165048_run_success_on_failed.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('success'); } }; ================================================ FILE: tests/fixtures/app/operations_failed/2021_12_23_184029_run_failed_failure.php ================================================ table()->insert([ 'value' => Str::random(4), ]); } protected function table(): Builder { return DB::table('failed'); } }; ================================================ FILE: tests/fixtures/app/stubs/2021_02_15_124237_test_success_transactions.stub ================================================ table()->insert([ $this->value(), $this->value(), $this->value(), ]); } protected function table(): Builder { return DB::table('transactions'); } protected function value(): array { return ['value' => Str::random(4)]; } public function withinTransactions(): bool { return true; } }; ================================================ FILE: tests/fixtures/app/stubs/2021_02_15_124852_test_failed_transactions.stub ================================================ table()->insert([ $this->value(), $this->value(), $this->value(), ]); throw new Exception('Random message'); } protected function table(): Builder { return DB::table('transactions'); } protected function value(): array { return ['value' => Str::random(4)]; } public function withinTransactions(): bool { return true; } }; ================================================ FILE: tests/fixtures/app/stubs/customized.stub ================================================ table()->insert([ 'value' => $some->get('custom'), ]); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/via_migrations/2025_03_31_234251_invoke.php ================================================ table()->insert([ 'value' => $some->get('invoke'), ]); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/app/via_migrations/2025_03_31_234312_up_down.php ================================================ table()->insert([ 'value' => $some->get('up_down'), ]); } public function down(Some $some): void { $this->table() ->where('value', $some->get('up_down')) ->delete(); } protected function table(): Builder { return DB::table('test'); } }; ================================================ FILE: tests/fixtures/migrations/2020_12_07_164624_create_test_table.php ================================================ uuid('value')->unique(); }); } public function down(): void { Schema::dropIfExists('test'); } }; ================================================ FILE: tests/fixtures/migrations/2021_01_02_022431_create_every_time_table.php ================================================ uuid('value'); }); } public function down(): void { Schema::dropIfExists('every_time'); } }; ================================================ FILE: tests/fixtures/migrations/2021_02_15_124419_create_transactions_table.php ================================================ uuid('value'); }); } public function down(): void { Schema::dropIfExists('transactions'); } }; ================================================ FILE: tests/fixtures/migrations/2021_05_24_122027_create_environment_table.php ================================================ uuid('value'); }); } public function down(): void { Schema::dropIfExists('environment'); } }; ================================================ FILE: tests/fixtures/migrations/2021_12_23_165218_create_success_table.php ================================================ string('value'); }); } public function down(): void { Schema::dropIfExists('success'); } }; ================================================ FILE: tests/fixtures/migrations/2021_12_23_184434_create_failed_table.php ================================================ string('value'); }); } public function down(): void { Schema::dropIfExists('failed'); } }; ================================================ FILE: tests/fixtures/migrations/2022_08_17_150549_create_before_table.php ================================================ string('value'); }); } public function down(): void { Schema::dropIfExists('before'); } }; ================================================ FILE: tests/fixtures/migrations_with_operations/2025_03_31_213847_call_invokable.php ================================================