wip
This commit is contained in:
commit
f1d7d6accc
33 changed files with 1676 additions and 0 deletions
419
.editorconfig
Normal file
419
.editorconfig
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.{md,json,yaml,yml}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{csproj,props,targets,slnx}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[nuget.config]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
[*.{cs,vb}]
|
||||
|
||||
# Organize usings
|
||||
file_header_template = Copyright (c) The Geekeey Authors\nSPDX-License-Identifier: EUPL-1.2
|
||||
dotnet_separate_import_directive_groups = true
|
||||
dotnet_sort_system_directives_first = true
|
||||
dotnet_diagnostic.IDE0005.severity = suggestion # https://github.com/dotnet/roslyn/issues/41640
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||
|
||||
# Expression-level preferences
|
||||
|
||||
dotnet_diagnostic.IDE0270.severity = none
|
||||
dotnet_style_coalesce_expression = true # IDE0029,IDE0030,IDE0270
|
||||
|
||||
dotnet_style_collection_initializer = true
|
||||
dotnet_style_explicit_tuple_names = true
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_object_initializer = true
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
|
||||
dotnet_diagnostic.IDE0045.severity = suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true # IDE0045
|
||||
dotnet_diagnostic.IDE0046.severity = suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = true # IDE0046
|
||||
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_style_prefer_inferred_tuple_names = true
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
dotnet_style_namespace_match_folder = false # resharper: resharper_check_namespace_highlighting
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true
|
||||
|
||||
# Suppression preferences
|
||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
|
||||
# ReSharper preferences
|
||||
resharper_wrap_object_and_collection_initializer_style = chop_always
|
||||
resharper_check_namespace_highlighting = none
|
||||
resharper_csharp_wrap_lines = false
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
[*.cs]
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
csharp_style_var_elsewhere = true
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true
|
||||
csharp_style_expression_bodied_constructors = false
|
||||
csharp_style_expression_bodied_indexers = true
|
||||
csharp_style_expression_bodied_lambdas = true
|
||||
csharp_style_expression_bodied_local_functions = false
|
||||
csharp_style_expression_bodied_methods = false
|
||||
csharp_style_expression_bodied_operators = false
|
||||
csharp_style_expression_bodied_properties = true
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_prefer_pattern_matching = true
|
||||
csharp_style_prefer_switch_expression = true
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true
|
||||
csharp_prefer_simple_using_statement = true
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_deconstructed_variable_declaration = true
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_style_pattern_local_over_anonymous_function = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_throw_expression = true
|
||||
|
||||
dotnet_diagnostic.IDE0058.severity = suggestion
|
||||
csharp_style_unused_value_assignment_preference = discard_variable # IDE0058
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable # IDE0058
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
|
||||
# 'namespace' preferences
|
||||
csharp_style_namespace_declarations = file_scoped
|
||||
|
||||
# 'constructor' preferences
|
||||
csharp_style_prefer_primary_constructors = false
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
[*.cs]
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### .NET Naming styles ####
|
||||
[*.{cs,vb}]
|
||||
|
||||
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
|
||||
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
|
||||
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
|
||||
dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
|
||||
dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
|
||||
|
||||
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interfaces.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
|
||||
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.enums.applicable_kinds = enum
|
||||
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.enums.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
|
||||
dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
|
||||
dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
|
||||
|
||||
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
|
||||
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.type_parameters.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
|
||||
dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.methods.applicable_kinds = method
|
||||
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.methods.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
|
||||
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
|
||||
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
|
||||
|
||||
dotnet_naming_symbols.parameters.applicable_kinds = parameter
|
||||
dotnet_naming_symbols.parameters.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.parameters.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
|
||||
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.properties.applicable_kinds = property
|
||||
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.properties.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
|
||||
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.events.applicable_kinds = event
|
||||
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.events.required_modifiers =
|
||||
|
||||
# local
|
||||
|
||||
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
|
||||
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
|
||||
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
|
||||
|
||||
dotnet_naming_symbols.local_variables.applicable_kinds = local
|
||||
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
|
||||
dotnet_naming_symbols.local_variables.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
|
||||
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.local_functions.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
|
||||
dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
|
||||
dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
|
||||
|
||||
dotnet_naming_symbols.local_constants.applicable_kinds = local
|
||||
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
|
||||
dotnet_naming_symbols.local_constants.required_modifiers = const
|
||||
|
||||
# private
|
||||
|
||||
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
|
||||
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
|
||||
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
|
||||
|
||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.private_fields.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
|
||||
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
|
||||
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
|
||||
|
||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||
|
||||
|
||||
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
|
||||
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
|
||||
|
||||
|
||||
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
|
||||
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
|
||||
|
||||
# public
|
||||
|
||||
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
|
||||
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.public_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
|
||||
dotnet_naming_symbols.public_fields.required_modifiers =
|
||||
|
||||
|
||||
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
|
||||
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
|
||||
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
|
||||
|
||||
|
||||
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
|
||||
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
|
||||
dotnet_naming_symbols.public_constant_fields.required_modifiers = const
|
||||
|
||||
# others
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascalcase.required_prefix =
|
||||
dotnet_naming_style.pascalcase.required_suffix =
|
||||
dotnet_naming_style.pascalcase.word_separator =
|
||||
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.ipascalcase.required_prefix = I
|
||||
dotnet_naming_style.ipascalcase.required_suffix =
|
||||
dotnet_naming_style.ipascalcase.word_separator =
|
||||
dotnet_naming_style.ipascalcase.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.tpascalcase.required_prefix = T
|
||||
dotnet_naming_style.tpascalcase.required_suffix =
|
||||
dotnet_naming_style.tpascalcase.word_separator =
|
||||
dotnet_naming_style.tpascalcase.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style._camelcase.required_prefix = _
|
||||
dotnet_naming_style._camelcase.required_suffix =
|
||||
dotnet_naming_style._camelcase.word_separator =
|
||||
dotnet_naming_style._camelcase.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.camelcase.required_prefix =
|
||||
dotnet_naming_style.camelcase.required_suffix =
|
||||
dotnet_naming_style.camelcase.word_separator =
|
||||
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.s_camelcase.required_prefix = s_
|
||||
dotnet_naming_style.s_camelcase.required_suffix =
|
||||
dotnet_naming_style.s_camelcase.word_separator =
|
||||
dotnet_naming_style.s_camelcase.capitalization = camel_case
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_analyzer_diagnostic.category-style.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-design.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-globalization.severity = notice
|
||||
dotnet_analyzer_diagnostic.category-naming.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-performance.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-reliability.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-security.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-usage.severity = warning
|
||||
dotnet_analyzer_diagnostic.category-maintainability.severity = warning
|
||||
|
||||
dotnet_diagnostic.CA1716.severity = none # Identifiers should not match keywords
|
||||
dotnet_diagnostic.CA1816.severity = suggestion # Dispose methods should call SuppressFinalize
|
||||
dotnet_diagnostic.CA1848.severity = none # Use the LoggerMessage delegates
|
||||
dotnet_diagnostic.IDE0210.severity = none # Use top-level statements
|
||||
16
.forgejo/workflows/example.yaml
Normal file
16
.forgejo/workflows/example.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: example
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
do-stuff:
|
||||
runs-on: debian-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: https://code.geekeey.de/actions/core-test/module/checkout@master
|
||||
- name: Do stuff
|
||||
run: |
|
||||
echo "Doing stuff..."
|
||||
ls -al
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
artifacts/
|
||||
*.DotSettings.user
|
||||
37
Directory.Build.props
Normal file
37
Directory.Build.props
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<Project>
|
||||
<PropertyGroup Condition="'$(ArtifactsPath)' == ''">
|
||||
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<WarningsAsErrors Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="NuGet Package Info">
|
||||
<Authors>The Geekeey Team</Authors>
|
||||
<Copyright>Copyright (c) The Geekeey Team 2026</Copyright>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.Gitea" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NuGetAuditLevel>moderate</NuGetAuditLevel>
|
||||
<NuGetAuditMode>all</NuGetAuditMode>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
2
Directory.Build.targets
Normal file
2
Directory.Build.targets
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<Project>
|
||||
</Project>
|
||||
16
Directory.Packages.props
Normal file
16
Directory.Packages.props
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<GlobalPackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="5.0.0-1.25277.114" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.SourceLink.Gitea" Version="10.0.102" />
|
||||
<PackageVersion Include="TUnit" Version="1.11.51" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="10.0.2" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
4
core.slnx
Normal file
4
core.slnx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<Solution>
|
||||
<Project Path="src/core.next/core.next.csproj" />
|
||||
<Project Path="src/core/core.csproj" />
|
||||
</Solution>
|
||||
11
global.json
Normal file
11
global.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "https://www.schemastore.org/global.json",
|
||||
"sdk": {
|
||||
"version": "10.0.0",
|
||||
"rollForward": "latestMinor"
|
||||
},
|
||||
"msbuild-sdks": {},
|
||||
"test": {
|
||||
"runner": "Microsoft.Testing.Platform"
|
||||
}
|
||||
}
|
||||
13
module/artifact-pull/README.md
Normal file
13
module/artifact-pull/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# artifact-pull
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
uses: actions/core/module/artifact-pull@1.0.0
|
||||
with:
|
||||
name: artifact
|
||||
pattern: |
|
||||
build/**/*
|
||||
!build/exclude-*
|
||||
run-id: 0001
|
||||
```
|
||||
38
module/artifact-pull/action.yml
Normal file
38
module/artifact-pull/action.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: "Release"
|
||||
description: "Pull build artifacts from the pipeline artifact storage"
|
||||
author: "Louis Seubert"
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
description: >
|
||||
The name of the artifact to download. The name is used to identify the artifact in the
|
||||
artifact storage and must be unique within the repository.
|
||||
pattern:
|
||||
required: false
|
||||
description: >
|
||||
Paths of files which will be extracted from the artifact. Can be multiple lines with file system globbing
|
||||
patterns. A line starting with ! will negate the pattern and act as exclude pattern. If this input is not provided,
|
||||
all files of the artifact will be extracted.
|
||||
run-id:
|
||||
required: false
|
||||
description: >
|
||||
The id of the workflow run from which the artifact should be downloaded. If this input is not provided, the
|
||||
artifact from the current workflow run will be downloaded.
|
||||
default: "${{ github.run_id }}"
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'code.geekeey.de/actions/core:1.0.0'
|
||||
args:
|
||||
- '--server'
|
||||
- '${{ github.server_url }}'
|
||||
- '--token'
|
||||
- '${{ github.token }}'
|
||||
- 'artifact'
|
||||
- 'pull'
|
||||
- '--name'
|
||||
- '${{ inputs.name }}'
|
||||
- '--pattern'
|
||||
- '${{ inputs.pattern }}'
|
||||
- '--run-id'
|
||||
- '${{ inputs.run-id }}'
|
||||
16
module/artifact-push/README.md
Normal file
16
module/artifact-push/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# artifact-push
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
uses: actions/core/module/artifact-push@1.0.0
|
||||
with:
|
||||
name: artifact
|
||||
path: |
|
||||
build/**/*
|
||||
!build/exclude-*
|
||||
compression: 0
|
||||
retention-days: 30
|
||||
overwrite: false
|
||||
include-hidden-files: false
|
||||
```
|
||||
54
module/artifact-push/action.yml
Normal file
54
module/artifact-push/action.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: "Release"
|
||||
description: "Push build artifacts to the pipeline artifact storage"
|
||||
author: "Louis Seubert"
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
description: >
|
||||
The name of the artifact to upload. The name is used to identify the artifact in the
|
||||
artifact storage and must be unique within the repository.
|
||||
pattern:
|
||||
required: true
|
||||
description: >
|
||||
Paths to files which will be uploaded as artifact. Can be multiple lines with file system
|
||||
globbing patterns. A line starting with ! will negate the pattern and act as exclude
|
||||
pattern.
|
||||
retention-days:
|
||||
required: false
|
||||
description: >
|
||||
The number of days to keep the artifact in the artifact storage. After this period, the artifact will be
|
||||
automatically deleted. The default is 0 days which is the defaul value of the runner.
|
||||
default: 0
|
||||
overwrite:
|
||||
required: false
|
||||
description: >
|
||||
Whether an artifact with the same name should be overwritten if it already exists in the artifact storage.
|
||||
The default is false.
|
||||
default: "false"
|
||||
include-hidden:
|
||||
required: false
|
||||
description: >
|
||||
Whether hidden files should be included in the artifact. A hidden file is a file which
|
||||
starts with a dot (.) and is not the current or parent directory. Default is false.
|
||||
default: "false"
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'code.geekeey.de/actions/core:1.0.0'
|
||||
args:
|
||||
- '--server'
|
||||
- '${{ github.server_url }}'
|
||||
- '--token'
|
||||
- '${{ github.token }}'
|
||||
- 'artifact'
|
||||
- 'push'
|
||||
- '--name'
|
||||
- '${{ inputs.name }}'
|
||||
- '--pattern'
|
||||
- '${{ inputs.pattern }}'
|
||||
- '--retention-days'
|
||||
- '${{ inputs.retention-days }}'
|
||||
- '--overwrite'
|
||||
- '${{ inputs.overwrite }}'
|
||||
- '--include-hidden'
|
||||
- '${{ inputs.include-hidden }}'
|
||||
11
module/checkout/README.md
Normal file
11
module/checkout/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# checkout
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
uses: actions/core/module/checkout@1.0.0
|
||||
with:
|
||||
repository: ${{ github.server_url }}/${{ github.repository }}.git
|
||||
path: ${{github.workspace}}
|
||||
ref: ${{ github.ref || github.sha }}
|
||||
```
|
||||
35
module/checkout/action.yml
Normal file
35
module/checkout/action.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: "Checkout"
|
||||
description: "Checkout a Git repository at a particular version"
|
||||
author: "Louis Seubert"
|
||||
inputs:
|
||||
repository:
|
||||
required: false
|
||||
description: >
|
||||
The path to the repository to checkout. Must be accessible with the
|
||||
pipeline token or publicly. Can be either an HTTPS or SSH URL.
|
||||
default: "${{ github.server_url }}/${{ github.repository }}.git"
|
||||
path:
|
||||
required: false
|
||||
description: >
|
||||
The directory to checkout the repository into. If the directory does not
|
||||
exist, it will be created.
|
||||
default: "${{ github.workspace }}"
|
||||
ref:
|
||||
required: false
|
||||
description: >
|
||||
The branch, tag or SHA to checkout. When checking out the repository that
|
||||
triggered a workflow, this defaults to the reference or SHA for that
|
||||
event. Otherwise, uses the default branch.
|
||||
default: "${{ github.ref || github.sha }}"
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'docker://code.geekeey.de/actions/core:1.0.0'
|
||||
args:
|
||||
- 'checkout'
|
||||
- '--repository'
|
||||
- '${{ inputs.repository }}'
|
||||
- '--path'
|
||||
- '${{ inputs.path }}'
|
||||
- '--reference'
|
||||
- '${{ inputs.ref }}'
|
||||
17
module/release/README.md
Normal file
17
module/release/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# release
|
||||
|
||||
## Usage
|
||||
|
||||
```yml
|
||||
uses: actions/core/module/release@1.0.0
|
||||
with:
|
||||
repository: ${{ github.server_url }}/${{ github.repository }}
|
||||
version: ${{ github.ref_name }}
|
||||
draft: false # or pattern to match agains version e.g. '\d+\.\d+\.\d+'
|
||||
prerelease: false # or pattern to match agains version e.g. '.*-rc\.\d+'
|
||||
title: Release ${{ github.ref_name }}
|
||||
notes: ${{ steps.changelog.outputs.changelog }}
|
||||
attachments: |
|
||||
${{ github.workspace }}/build/*.zip
|
||||
!${{ github.workspace }}/build/exclude-*.zip
|
||||
```
|
||||
69
module/release/action.yml
Normal file
69
module/release/action.yml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: "Release"
|
||||
description: "Create a release from a Git tag and attache file artifacts"
|
||||
author: "Louis Seubert"
|
||||
inputs:
|
||||
repository:
|
||||
required: false
|
||||
description: >
|
||||
The owner and repository name seperated by a slash of the repository in which
|
||||
the release should be created. The repository must be on the same instance as
|
||||
the action is running on.
|
||||
default: "${{ github.server_url }}/${{ github.repository }}"
|
||||
version:
|
||||
required: false
|
||||
description: >
|
||||
The tag name of the git tag for which the release should be created and to which
|
||||
the artifacts should be attached to.
|
||||
default: "${{ github.ref_name }}"
|
||||
draft:
|
||||
required: false
|
||||
description: >
|
||||
A boolean value or a regex pattern which will be evaluated against the version
|
||||
to determine if the created release will be saved as draft.
|
||||
default: "false"
|
||||
prerelease:
|
||||
required: false
|
||||
description: >
|
||||
A boolean value or a regex pattern which will be evaluated against the version
|
||||
to determine if the created release will be marked as pre-release.
|
||||
default: "false"
|
||||
title:
|
||||
required: false
|
||||
description: >
|
||||
The title of the created release. Leave this empty to use the label of the
|
||||
referenced git tag.
|
||||
notes:
|
||||
required: false
|
||||
description: >
|
||||
The message of the created release. Leave this empty to use the notes of the
|
||||
referenced git tag.
|
||||
attachments:
|
||||
required: false
|
||||
description: >
|
||||
Paths to files which will be attached to the release. Can be multiple lines
|
||||
with file system globbing patterns. A line starting with ! will negate the
|
||||
pattern and act as exclude pattern.
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'code.geekeey.de/actions/core:1.0.0'
|
||||
args:
|
||||
- '--server'
|
||||
- '${{ github.server_url }}'
|
||||
- '--token'
|
||||
- '${{ github.token }}'
|
||||
- 'release'
|
||||
- '--repository'
|
||||
- '${{ inputs.repository }}'
|
||||
- '--version'
|
||||
- '${{ inputs.version }}'
|
||||
- '--draft'
|
||||
- '${{ inputs.draft }}'
|
||||
- '--prerelease'
|
||||
- '${{ inputs.prerelease }}'
|
||||
- '--title'
|
||||
- '${{ inputs.title }}'
|
||||
- '--notes'
|
||||
- '${{ inputs.notes }}'
|
||||
- '--attachments'
|
||||
- '${{ inputs.attachments }}'
|
||||
19
nuget.config
Normal file
19
nuget.config
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<config>
|
||||
<add key="defaultPushSource" value="geekeey" />
|
||||
</config>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="geekeey" value="https://code.geekeey.de/api/packages/geekeey/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<packageSource key="nuget">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
<packageSource key="geekeey">
|
||||
<package pattern="Geekeey.*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
105
src/core.next/Commands/Checkout.cs
Normal file
105
src/core.next/Commands/Checkout.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.CommandLine;
|
||||
using System.Text;
|
||||
|
||||
namespace Geekeey.Actions.Core.Commands;
|
||||
|
||||
internal sealed class Checkout : Command
|
||||
{
|
||||
#pragma warning disable format // @formatter:off
|
||||
private static readonly Option<Uri> Repository = new("--repository") { Required = true };
|
||||
private static readonly Option<DirectoryInfo> Destination = new("--path") { Required = true };
|
||||
private static readonly Option<string> Reference = new("--reference") { Required = true };
|
||||
#pragma warning restore format // @formatter:on
|
||||
|
||||
internal Checkout() : base("checkout")
|
||||
{
|
||||
Add(Repository);
|
||||
Add(Destination);
|
||||
Add(Reference);
|
||||
|
||||
SetAction(HandleAsync);
|
||||
}
|
||||
|
||||
private async Task<int> HandleAsync(ParseResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
var server = result.GetRequiredValue(Program.Server);
|
||||
var access = result.GetRequiredValue(Program.Token);
|
||||
|
||||
var workspace = result.GetRequiredValue(Destination);
|
||||
|
||||
await $"git init -q {workspace.FullName}";
|
||||
await $"git -C {workspace.FullName} config set --local protocol.version 2";
|
||||
await $"git -C {workspace.FullName} config set --local gc.auto 0";
|
||||
|
||||
var repository = result.GetRequiredValue(Repository);
|
||||
|
||||
if (!repository.IsAbsoluteUri)
|
||||
{
|
||||
// relative repository urls are resolved against the server url
|
||||
repository = new Uri(server, repository);
|
||||
|
||||
await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf git@{repository.Host}";
|
||||
await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf ssh://git@{repository.Host}";
|
||||
await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf git://{repository.Host}";
|
||||
|
||||
var header = $"Authorization: Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token::${access}"))}";
|
||||
await $"git -C {workspace.FullName} config set --local http.{repository}.extraheader '{header}'";
|
||||
}
|
||||
|
||||
var origin = "origin";
|
||||
var reference = result.GetRequiredValue(Reference);
|
||||
|
||||
await $"git -C {workspace.FullName} remote add {origin} {repository}";
|
||||
var list = (await $"git -C {workspace.FullName} ls-remote {origin} {reference}").Split('\t');
|
||||
|
||||
if (list.Length < 2)
|
||||
{
|
||||
throw new InvalidOperationException("git ls-remote resolved nothing");
|
||||
}
|
||||
|
||||
var @sha = list[0].Trim();
|
||||
var @ref = list[1].Trim();
|
||||
|
||||
if (@ref.TryCutPrefix("refs/heads/", out var name))
|
||||
{
|
||||
var remote = $"refs/remotes/{origin}/{name}";
|
||||
var branch = name;
|
||||
|
||||
await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} +{@sha}:{remote}";
|
||||
await $"git -C {workspace.FullName} checkout --force -B {branch} {remote}";
|
||||
}
|
||||
else if (@ref.TryCutPrefix("refs/pull/", out name))
|
||||
{
|
||||
var remote = $"refs/remotes/pull/{name}";
|
||||
|
||||
// Best-effort parity with the Go code's env.BaseRef/env.HeadRef:
|
||||
var baseRef = Environment.GetEnvironmentVariable("GITHUB_BASE_REF");
|
||||
var headRef = Environment.GetEnvironmentVariable("GITHUB_HEAD_REF");
|
||||
|
||||
var branch =
|
||||
!string.IsNullOrEmpty(baseRef) ? baseRef :
|
||||
!string.IsNullOrEmpty(headRef) ? headRef :
|
||||
throw new InvalidOperationException("pull request can not find base ref for branch");
|
||||
|
||||
await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} +{@sha}:{remote}";
|
||||
await $"git -C {workspace.FullName} checkout --force -B {branch} {branch}";
|
||||
}
|
||||
else if (@ref.TryCutPrefix("refs/tags/", out name))
|
||||
{
|
||||
var remote = $"refs/tags/{name}";
|
||||
|
||||
await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} +{@sha}:{remote}";
|
||||
await $"git -C {workspace.FullName} checkout --force {remote}";
|
||||
}
|
||||
else
|
||||
{
|
||||
await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} {@ref}";
|
||||
await $"git -C {workspace.FullName} checkout --force {@ref}";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
51
src/core.next/Program.cs
Normal file
51
src/core.next/Program.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
|
||||
using Geekeey.Actions.Core.Commands;
|
||||
|
||||
namespace Geekeey.Actions.Core;
|
||||
|
||||
internal sealed class Program : RootCommand
|
||||
{
|
||||
#pragma warning disable format // @formatter:off
|
||||
public static readonly Option<Uri> Server = new GitHubContextOption<Uri>("--server", env: "GITHUB_SERVER_URL"){CustomParser = Uri};
|
||||
public static readonly Option<string> Token = new GitHubContextOption<string>("--token", env: "GITHUB_TOKEN");
|
||||
#pragma warning restore format // @formatter:on
|
||||
|
||||
public static Uri? Uri(ArgumentResult result)
|
||||
{
|
||||
string value;
|
||||
if (!result.Tokens.Any())
|
||||
{
|
||||
value = Environment.GetEnvironmentVariable("GITHUB_SERVER_URL")!;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = result.Tokens.Single().Value;
|
||||
}
|
||||
|
||||
if (!System.Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
result.AddError("Not a valid URI.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private Program()
|
||||
{
|
||||
Add(Server);
|
||||
Add(Token);
|
||||
|
||||
Add(new Checkout());
|
||||
}
|
||||
|
||||
private static Task<int> Main(string[] args)
|
||||
{
|
||||
return new Program().Parse(args).InvokeAsync();
|
||||
}
|
||||
}
|
||||
118
src/core.next/ShellLikeString.cs
Normal file
118
src/core.next/ShellLikeString.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Geekeey.Actions.Core;
|
||||
|
||||
internal static class ShellLikeString
|
||||
{
|
||||
public static TaskAwaiter<string> GetAwaiter(this string command)
|
||||
{
|
||||
return RunProcessAsync(command).GetAwaiter();
|
||||
}
|
||||
|
||||
private static async Task<string> RunProcessAsync(string command)
|
||||
{
|
||||
var strings = ShellSplit(command);
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = strings.First(),
|
||||
ArgumentList =
|
||||
{
|
||||
strings.Skip(1)
|
||||
},
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi)!;
|
||||
|
||||
var stdout = process.StandardOutput.ReadToEndAsync();
|
||||
var stderr = process.StandardError.ReadToEndAsync();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var outputs = await Task.WhenAll(stdout, stderr);
|
||||
|
||||
if (process.ExitCode is not 0)
|
||||
{
|
||||
throw new ProcessExitException($"Process exited with code {process.ExitCode}: {stderr}");
|
||||
}
|
||||
|
||||
return outputs[0];
|
||||
}
|
||||
|
||||
private static List<string> ShellSplit(string input)
|
||||
{
|
||||
var args = new List<string>();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return args;
|
||||
}
|
||||
|
||||
var current = new StringBuilder();
|
||||
var state = ShellSplitState.None;
|
||||
foreach (var c in input)
|
||||
{
|
||||
if (state.HasFlag(ShellSplitState.Escape))
|
||||
{
|
||||
current.Append(c);
|
||||
state &= ~ShellSplitState.Escape;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\\':
|
||||
state |= ShellSplitState.Escape;
|
||||
break;
|
||||
case '\'' when !state.HasFlag(ShellSplitState.InDoubleQuote):
|
||||
state ^= ShellSplitState.InSingleQuote;
|
||||
break;
|
||||
case '\"' when !state.HasFlag(ShellSplitState.InSingleQuote):
|
||||
state ^= ShellSplitState.InDoubleQuote;
|
||||
break;
|
||||
default:
|
||||
if (char.IsWhiteSpace(c) && !state.HasFlag(ShellSplitState.InSingleQuote) && !state.HasFlag(ShellSplitState.InDoubleQuote))
|
||||
{
|
||||
if (current.Length > 0)
|
||||
{
|
||||
args.Add(current.ToString());
|
||||
current.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Append(c);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current.Length > 0)
|
||||
{
|
||||
args.Add(current.ToString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ShellSplitState
|
||||
{
|
||||
None,
|
||||
InSingleQuote,
|
||||
InDoubleQuote,
|
||||
Escape,
|
||||
}
|
||||
|
||||
private sealed class ProcessExitException(string message) : Exception(message);
|
||||
}
|
||||
13
src/core.next/_extensions/collection.cs
Normal file
13
src/core.next/_extensions/collection.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
internal static partial class Extensions
|
||||
{
|
||||
public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
collection.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/core.next/_extensions/string.cs
Normal file
17
src/core.next/_extensions/string.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
internal static partial class Extensions
|
||||
{
|
||||
public static bool TryCutPrefix(this string value, string prefix, out string rest)
|
||||
{
|
||||
if (value.StartsWith(prefix, StringComparison.Ordinal))
|
||||
{
|
||||
rest = value[prefix.Length..];
|
||||
return true;
|
||||
}
|
||||
|
||||
rest = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
23
src/core.next/core.next.csproj
Normal file
23
src/core.next/core.next.csproj
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<RootNamespace>Geekeey.Actions.Core</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ContainerRegistry>code.geekeey.de</ContainerRegistry>
|
||||
<ContainerRepository>actions/core</ContainerRepository>
|
||||
<ContainerImageTag>1.0.0</ContainerImageTag>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
136
src/core/Commands/Artifact.cs
Normal file
136
src/core/Commands/Artifact.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
using System.CommandLine;
|
||||
using System.IO.Compression;
|
||||
|
||||
using Geekeey.Core.Properties;
|
||||
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
|
||||
namespace Geekeey.Core.Commands;
|
||||
|
||||
internal sealed class Artifact : Command
|
||||
{
|
||||
public Artifact() : base("artifact", Resources.ArtifactCommandDescription)
|
||||
{
|
||||
Add(new Pull());
|
||||
Add(new Push());
|
||||
}
|
||||
|
||||
private static async Task CreateArtifactFromDirectoryAsync(Stream stream, DirectoryInfo directory, Matcher matcher, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true);
|
||||
|
||||
var files = matcher.GetResultsInFullPath(directory.FullName);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var path = Path.GetRelativePath(directory.FullName, file);
|
||||
var name = path.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
var entry = archive.CreateEntry(name, CompressionLevel.Optimal);
|
||||
entry.LastWriteTime = File.GetLastWriteTime(file);
|
||||
|
||||
await using var zos = await entry.OpenAsync(cancellationToken);
|
||||
await using var fis = File.OpenRead(file);
|
||||
await fis.CopyToAsync(zos, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ExtractArtifactToDirectoryAsync(Stream stream, DirectoryInfo directory, Matcher matcher, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
|
||||
|
||||
var entries = archive.Entries.Where(entry => matcher.Match(entry.FullName).HasMatches);
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var path = Path.Combine(directory.FullName, entry.FullName);
|
||||
var file = new FileInfo(path);
|
||||
|
||||
file.Directory?.Create();
|
||||
|
||||
await using var zis = await entry.OpenAsync(cancellationToken);
|
||||
await using var fos = File.Create(file.FullName);
|
||||
await zis.CopyToAsync(fos, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Pull : Command
|
||||
{
|
||||
private static readonly Option<string> Name = new("--name")
|
||||
{
|
||||
Required = true
|
||||
};
|
||||
|
||||
private static readonly Option<Matcher?> Pattern = new("--pattern")
|
||||
{
|
||||
CustomParser = Parsers.Matcher
|
||||
};
|
||||
|
||||
private static readonly Option<int?> RunId = new("--run-id")
|
||||
{
|
||||
Required = false
|
||||
};
|
||||
|
||||
public Pull() : base("pull")
|
||||
{
|
||||
Add(Name);
|
||||
Add(Pattern);
|
||||
Add(RunId);
|
||||
SetAction(HandleAsync);
|
||||
}
|
||||
|
||||
private Task<int> HandleAsync(ParseResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
// Implementation for checkout command goes here.
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Push : Command
|
||||
{
|
||||
private static readonly Option<string> Name = new("--name")
|
||||
{
|
||||
Required = true
|
||||
};
|
||||
|
||||
private static readonly Option<Matcher?> Pattern = new("--pattern")
|
||||
{
|
||||
CustomParser = Parsers.Matcher
|
||||
};
|
||||
|
||||
private static readonly Option<bool> IncludeHidden = new("--include-hidden")
|
||||
{
|
||||
Required = false
|
||||
};
|
||||
|
||||
private static readonly Option<int> RetentionDays = new("--retention-days")
|
||||
{
|
||||
Required = false
|
||||
};
|
||||
|
||||
private static readonly Option<bool> Overwrite = new("--overwrite")
|
||||
{
|
||||
Required = false
|
||||
};
|
||||
|
||||
public Push() : base("push")
|
||||
{
|
||||
Add(Name);
|
||||
Add(Pattern);
|
||||
Add(IncludeHidden);
|
||||
Add(RetentionDays);
|
||||
Add(Overwrite);
|
||||
SetAction(HandleAsync);
|
||||
}
|
||||
|
||||
private Task<int> HandleAsync(ParseResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
// Implementation for checkout command goes here.
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/core/Commands/Checkout.cs
Normal file
53
src/core/Commands/Checkout.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System.CommandLine;
|
||||
using System.Text;
|
||||
using Geekeey.Core.Properties;
|
||||
|
||||
namespace Geekeey.Core.Commands;
|
||||
|
||||
internal sealed class Checkout : Command
|
||||
{
|
||||
private static readonly Option<Uri> Repository = new("--repository") { Required = true, CustomParser = Parsers.Uri };
|
||||
private static readonly Option<DirectoryInfo> Destination = new("--path") { Required = true };
|
||||
private static readonly Option<string> Reference = new("--reference") { Required = true };
|
||||
private static readonly Option<bool> TreeLessClone = new("--tree-less-clone") { Description = "Use tree-less (blob-less) clone to reduce bandwidth" };
|
||||
|
||||
public Checkout() : base("checkout", Resources.CheckoutCommandDescription)
|
||||
{
|
||||
Add(Repository);
|
||||
Add(Destination);
|
||||
Add(Reference);
|
||||
Add(TreeLessClone);
|
||||
SetAction(HandleAsync);
|
||||
}
|
||||
|
||||
private async Task<int> HandleAsync(ParseResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
var server = result.GetRequiredValue(Program.Server);
|
||||
var access = result.GetRequiredValue(Program.Token);
|
||||
|
||||
// relative repository urls are resolved against the server url
|
||||
var repository = result.GetRequiredValue(Repository);
|
||||
if (!repository.IsAbsoluteUri)
|
||||
{
|
||||
repository = new Uri(new Uri(server), repository);
|
||||
}
|
||||
|
||||
var workspace = result.GetRequiredValue(Destination);
|
||||
|
||||
await $"git init -q {workspace.FullName}";
|
||||
await $"git -C {workspace.FullName} config set --local protocol.version 2";
|
||||
await $"git -C {workspace.FullName} config set --local gc.auto 0";
|
||||
|
||||
await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf git@{repository.Host}";
|
||||
await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf ssh://git@{repository.Host}";
|
||||
await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf git://{repository.Host}";
|
||||
|
||||
var header = $"Authorization: Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(access))}";
|
||||
await $"git -C {workspace.FullName} config set --local http.{repository}.extraheader '{header}'";
|
||||
|
||||
await $"git -C {workspace.FullName} remote add origin {repository}";
|
||||
await $"git -C {workspace.FullName} ls-remote origin {result.GetRequiredValue(Reference)}";
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
57
src/core/Commands/Release.cs
Normal file
57
src/core/Commands/Release.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
using System.CommandLine;
|
||||
using Geekeey.Core.Properties;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
|
||||
namespace Geekeey.Core.Commands;
|
||||
|
||||
internal sealed class Release : Command
|
||||
{
|
||||
private static readonly Option<Uri> Repository = new("--repository") { Required = true, CustomParser = Parsers.Uri };
|
||||
private static readonly Option<string> Version = new("--version") { Required = true };
|
||||
|
||||
private static readonly Option<Pattern> Draft = new("--draft") { CustomParser = Parsers.Pattern };
|
||||
private static readonly Option<Pattern> PreRelease = new("--prerelease") { CustomParser = Parsers.Pattern };
|
||||
|
||||
private static readonly Option<string> Title = new("--title");
|
||||
private static readonly Option<string> Notes = new("--notes");
|
||||
private static readonly Option<Matcher> Attachments = new("--attachments") { CustomParser = Parsers.Matcher };
|
||||
|
||||
public Release() : base("release", Resources.ReleaseCommandDescription)
|
||||
{
|
||||
Add(Repository);
|
||||
Add(Version);
|
||||
|
||||
Add(Draft);
|
||||
Add(PreRelease);
|
||||
|
||||
Add(Title);
|
||||
Add(Notes);
|
||||
Add(Attachments);
|
||||
|
||||
SetAction(HandleAsync);
|
||||
}
|
||||
|
||||
private Task<int> HandleAsync(ParseResult result, CancellationToken cancellationToken)
|
||||
{
|
||||
var server = result.GetRequiredValue(Program.Server);
|
||||
var access = result.GetRequiredValue(Program.Token);
|
||||
|
||||
// relative repository urls are resolved against the server url
|
||||
var repository = result.GetRequiredValue(Repository);
|
||||
if (!repository.IsAbsoluteUri)
|
||||
{
|
||||
repository = new Uri(new Uri(server), repository);
|
||||
}
|
||||
|
||||
var version = result.GetRequiredValue(Version);
|
||||
var draft = result.GetValue(Draft);
|
||||
var prerelease = result.GetValue(PreRelease);
|
||||
|
||||
var title = result.GetValue(Title);
|
||||
var notes = result.GetValue(Notes);
|
||||
var attachments = result.GetValue(Attachments);
|
||||
|
||||
// Implementation for checkout command goes here.
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
14
src/core/GitHubEnvironmentContext.cs
Normal file
14
src/core/GitHubEnvironmentContext.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
namespace Geekeey.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to GitHub Actions environment context from environment variables.
|
||||
/// </summary>
|
||||
internal sealed class GitHubEnvironmentContext : IGitHubEnvironmentContext
|
||||
{
|
||||
public string? BaseRef => Environment.GetEnvironmentVariable("GITHUB_BASE_REF");
|
||||
|
||||
public string? HeadRef => Environment.GetEnvironmentVariable("GITHUB_HEAD_REF");
|
||||
}
|
||||
22
src/core/IGitHubEnvironmentContext.cs
Normal file
22
src/core/IGitHubEnvironmentContext.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) The Geekeey Authors
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
namespace Geekeey.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to GitHub Actions environment context.
|
||||
/// </summary>
|
||||
public interface IGitHubEnvironmentContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base reference for pull requests.
|
||||
/// Corresponds to the GITHUB_BASE_REF environment variable.
|
||||
/// </summary>
|
||||
string? BaseRef { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the head reference for pull requests.
|
||||
/// Corresponds to the GITHUB_HEAD_REF environment variable.
|
||||
/// </summary>
|
||||
string? HeadRef { get; }
|
||||
}
|
||||
102
src/core/Parsers.cs
Normal file
102
src/core/Parsers.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System.CommandLine.Parsing;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
|
||||
namespace Geekeey.Core;
|
||||
|
||||
internal static class Parsers
|
||||
{
|
||||
public static Uri? Uri(ArgumentResult result)
|
||||
{
|
||||
if (!result.Tokens.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!System.Uri.TryCreate(result.Tokens.Single().Value, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
result.AddError("Not a valid URI.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
public static Pattern? Pattern(ArgumentResult result)
|
||||
{
|
||||
if (!result.Tokens.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bool.TryParse(result.Tokens.Single().Value, out var value))
|
||||
{
|
||||
return new Pattern(value);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var regex = new Regex(result.Tokens.Single().Value, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
return new Pattern(regex);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
result.AddError("Not a valid boolean or regex pattern.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Matcher? Matcher(ArgumentResult result)
|
||||
{
|
||||
if (!result.Tokens.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var matcher = new Matcher();
|
||||
|
||||
foreach (var token in result.Tokens)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token.Value.StartsWith('!'))
|
||||
{
|
||||
matcher.AddExclude(token.Value[1..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
matcher.AddInclude(token.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
result.AddError($"Not a valid glob pattern: {token.Value}. {exception.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return matcher;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Pattern
|
||||
{
|
||||
private readonly bool _value;
|
||||
private readonly Regex? _pattern;
|
||||
|
||||
public Pattern(bool value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public Pattern(Regex pattern)
|
||||
{
|
||||
_pattern = pattern;
|
||||
}
|
||||
|
||||
public bool Match(string version)
|
||||
{
|
||||
return _pattern?.IsMatch(version) ?? _value;
|
||||
}
|
||||
}
|
||||
26
src/core/Program.cs
Normal file
26
src/core/Program.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System.CommandLine;
|
||||
using Geekeey.Core.Commands;
|
||||
|
||||
namespace Geekeey.Core;
|
||||
|
||||
internal sealed class Program : RootCommand
|
||||
{
|
||||
public static readonly Option<string> Server = new("--server") { Required = true, Recursive = true };
|
||||
public static readonly Option<string> Token = new("--token") { Required = true, Recursive = true };
|
||||
|
||||
private Program()
|
||||
{
|
||||
Add(Server);
|
||||
Add(Token);
|
||||
|
||||
Add(new Checkout());
|
||||
Add(new Release());
|
||||
Add(new Artifact());
|
||||
}
|
||||
|
||||
public static Task<int> Main(string[] args)
|
||||
{
|
||||
var program = new Program();
|
||||
return program.Parse(args).InvokeAsync();
|
||||
}
|
||||
}
|
||||
35
src/core/Properties/Resources.resx
Normal file
35
src/core/Properties/Resources.resx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="ArtifactCommandDescription" xml:space="preserve">
|
||||
<value>X</value>
|
||||
</data>
|
||||
<data name="CheckoutCommandDescription" xml:space="preserve">
|
||||
<value>Checkout a specific version or branch of a git repository.</value>
|
||||
</data>
|
||||
<data name="ReleaseCommandDescription" xml:space="preserve">
|
||||
<value>Release a version to the release page on the repository.</value>
|
||||
</data>
|
||||
</root>
|
||||
90
src/core/Shellify.cs
Normal file
90
src/core/Shellify.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
internal static class Shellify
|
||||
{
|
||||
[Flags]
|
||||
private enum ShellSplitState
|
||||
{
|
||||
None = 0,
|
||||
InSingleQuote = 1 << 0,
|
||||
InDoubleQuote = 1 << 1,
|
||||
Escape = 1 << 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a shell-escaped string into arguments, handling quotes and escapes.
|
||||
/// </summary>
|
||||
public static List<string> ShellSplit(string input)
|
||||
{
|
||||
var args = new List<string>();
|
||||
if (string.IsNullOrEmpty(input)) return args;
|
||||
var current = new System.Text.StringBuilder();
|
||||
var state = ShellSplitState.None;
|
||||
foreach (char c in input)
|
||||
{
|
||||
if (state.HasFlag(ShellSplitState.Escape))
|
||||
{
|
||||
current.Append(c);
|
||||
state &= ~ShellSplitState.Escape;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
state |= ShellSplitState.Escape;
|
||||
}
|
||||
else if (c == '\'' && !state.HasFlag(ShellSplitState.InDoubleQuote))
|
||||
{
|
||||
state ^= ShellSplitState.InSingleQuote;
|
||||
}
|
||||
else if (c == '"' && !state.HasFlag(ShellSplitState.InSingleQuote))
|
||||
{
|
||||
state ^= ShellSplitState.InDoubleQuote;
|
||||
}
|
||||
else if (char.IsWhiteSpace(c) && !state.HasFlag(ShellSplitState.InSingleQuote) && !state.HasFlag(ShellSplitState.InDoubleQuote))
|
||||
{
|
||||
if (current.Length > 0)
|
||||
{
|
||||
args.Add(current.ToString());
|
||||
current.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (current.Length > 0)
|
||||
args.Add(current.ToString());
|
||||
return args;
|
||||
}
|
||||
|
||||
public static TaskAwaiter<string> GetAwaiter(this string command)
|
||||
{
|
||||
return RunProcessAsync(command).GetAwaiter();
|
||||
}
|
||||
|
||||
private static async Task<string> RunProcessAsync(string command)
|
||||
{
|
||||
var strings = ShellSplit(command);
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = strings.First(),
|
||||
ArgumentList =
|
||||
{
|
||||
strings.Skip(1)
|
||||
},
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
string output = await process.StandardOutput.ReadToEndAsync();
|
||||
string error = await process.StandardError.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
if (process.ExitCode != 0)
|
||||
throw new System.Exception($"Process exited with code {process.ExitCode}: {error}");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
35
src/core/core.csproj
Normal file
35
src/core/core.csproj
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<RootNamespace>Geekeey.Core</RootNamespace>
|
||||
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
|
||||
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
|
||||
<ContainerFamily>alpine</ContainerFamily>
|
||||
<ContainerRuntimeIdentifiers>linux-x64;linux-arm64</ContainerRuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<AnalysisMode>Recommended</AnalysisMode>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Commands\Checkout.messages.resx" ClassName="Checkout">
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Add table
Add a link
Reference in a new issue