commit d9dfa44a22b30cb27e1c599c62dcbe20330285f8 Author: ronnie Date: Fri Apr 10 18:59:27 2026 -0400 feat: init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9fdf044 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,415 @@ +# --------------------------------------------------------------------------- # +# Chickensoft C# Style — .editorconfig # +# --------------------------------------------------------------------------- # +# Godot-friendly coding style with a bit of Dart-style flair thrown in. # +# --------------------------------------------------------------------------- # +# # +# # +# ╓╗_▄╗_╓▄_ # +# ▄▄╟▓▓▓▓▓▓▓▓ # +# ╙▓▓▓▀▀╠╠╦╦╓,_ # +# ,φ╠╠╠╠╠╠╠╠╠╠▒╥ # +# φ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╦ # +# @╠╠╫▌╠╟▌╠╠╠╠╠╠╠╠╠ # +# ╠╠╠▄▄▄▒╠╠╠╠╠╠╠╠╠╠b # +# ╠╠╨███▌╠╠╠╠╠╠╠▒╠╠▒_ ç╓ # +# ╠╠╠╠▒▒╠╠╠╠╠╠╠╠▒Å▄╠╬▒φ╩ε # +# ╚╠╠╠╠╠╠╠╠╠╠╠▒█▒╫█Å╠╠╩ # +# ╠╠╠╠╠╠╠╠╠╠╠╠╠╟╫▒╠╠╩ # +# ╙╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╜ # +# ╙╚╠╠╠╠╠╠╠╠╩╙ # +# ╒ µ # +# ▌ ▓ # +# ^▀▀ "▀ª # +# # +# # +# --------------------------------------------------------------------------- # +# +# Based on: +# - https://github.com/RehanSaeed/EditorConfig/blob/main/.editorconfig +# - https://gist.github.com/FaronBracy/155d8d7ad98b4ceeb526b9f47543db1b +# - various other gists floating around :) +# +# Have a problem? Encounter an issue? +# Come visit our Discord and let us know! https://discord.gg/MjA6HUzzAE +# +# Based on https://github.com/RehanSaeed/EditorConfig/blob/main/.editorconfig +# and https://gist.github.com/FaronBracy/155d8d7ad98b4ceeb526b9f47543db1b + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +########################################## +# File Extension Settings +########################################## + +# GDScript Files +[*.{gd,gdshader,gdshaderinc}] +indent_style = tab + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.{md,mdx}] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Makefiles +[Makefile] +indent_style = tab + +[*{_Generated.cs,.g.cs,.generated.cs}] +# Ignore a lack of documentation for generated code. Doesn't apply to builds, +# just to viewing generation output. +dotnet_diagnostic.CS1591.severity = none + +########################################## +# Default .NET Code Style Severities +########################################## + +[*.cs] +# Default Severity for all .NET Code Style rules below +dotnet_analyzer_diagnostic.severity = warning + +########################################## +# Language Rules +########################################## + +# .NET Style Rules + +# "this." and "Me." qualifiers +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_property = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_event = false + +# Language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning +dotnet_style_readonly_field = true:warning + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning + +# Expression-level preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_diagnostic.IDE0046.severity = suggestion +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning + +# Null-checking preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning + +# Unused parameters and methods +dotnet_diagnostic.IDE0060.severity = warn +dotnet_diagnostic.IDE0051.severity = warn + +# File header preferences +# Keep operators at end of line when wrapping. +dotnet_style_operator_placement_when_wrapping = end_of_line +csharp_style_prefer_null_check_over_type_check = true:warning + +# Code block preferences +csharp_prefer_braces = true:warning +csharp_prefer_simple_using_statement = true:suggestion +dotnet_diagnostic.IDE0063.severity = suggestion + +# C# Style Rules + +# 'var' preferences +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning +# Expression-bodied members +csharp_style_expression_bodied_methods = when_on_single_line:warning +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_not_pattern = true:warning +# Expression-level preferences +csharp_style_inlined_variable_declaration = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning +# "Null" checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# 'using' directive preferences +csharp_using_directive_placement = inside_namespace:warning +# Use discard variable for unused expression values. +csharp_style_unused_value_expression_statement_preference = discard_variable +csharp_style_unused_value_assignment_preference = discard_variable + +########################################## +# Formatting Rules +########################################## + +# .NET formatting rules + +# Organize using directives +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# Dotnet namespace options +# +# We don't care about namespaces matching folder structure. Games and apps +# are complicated and you are free to organize them however you like. Change +# this if you want to enforce it. +dotnet_style_namespace_match_folder = false +dotnet_diagnostic.IDE0130.severity = none + +# C# formatting rules + +# Newline options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation options +csharp_indent_switch_labels = true +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false + +# Spacing options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_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_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false + +# Wrap options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +# Namespace options +csharp_style_namespace_declarations = file_scoped:warning + +########################################## +# Unnecessary Code Rules +########################################## + +# .NET Unnecessary code rules + +dotnet_code_quality_unused_parameters = non_public:suggestion +dotnet_remove_unnecessary_suppression_exclusions = none + +########################################## +# .NET Naming Rules +########################################## + +########################################## +# Chickensoft Naming Conventions & Styles +# These deviate heavily from Microsoft's Official Naming Conventions. +########################################## + +# Allow underscores in names. +dotnet_diagnostic.CA1707.severity = none + +# Styles +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.upper_case_style.capitalization = all_upper +dotnet_naming_style.upper_case_style.word_separator = _ + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Use uppercase for all constant fields. +dotnet_naming_rule.constants_uppercase.severity = suggestion +dotnet_naming_rule.constants_uppercase.symbols = constant_fields +dotnet_naming_rule.constants_uppercase.style = upper_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +# Non-public fields should be _camelCase +dotnet_naming_rule.non_public_fields_under_camel.severity = suggestion +dotnet_naming_rule.non_public_fields_under_camel.symbols = non_public_fields +dotnet_naming_rule.non_public_fields_under_camel.style = camel_case_underscore_style +dotnet_naming_symbols.non_public_fields.applicable_kinds = field +dotnet_naming_symbols.non_public_fields.required_modifiers = +dotnet_naming_symbols.non_public_fields.applicable_accessibilities = private,private_protected,internal,protected,protected_internal + +# Public fields should be PascalCase +dotnet_naming_rule.public_fields_pascal.severity = suggestion +dotnet_naming_rule.public_fields_pascal.symbols = public_fields +dotnet_naming_rule.public_fields_pascal.style = pascal_case_style +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.required_modifiers = +dotnet_naming_symbols.public_fields.applicable_accessibilities = public + +# Async methods should have "Async" suffix. +# Disabled because it makes tests too verbose. +# dotnet_naming_style.end_in_async.required_suffix = Async +# dotnet_naming_style.end_in_async.capitalization = pascal_case +# dotnet_naming_rule.methods_end_in_async.symbols = methods_async +# dotnet_naming_rule.methods_end_in_async.style = end_in_async +# dotnet_naming_rule.methods_end_in_async.severity = warning +# dotnet_naming_symbols.methods_async.applicable_kinds = method +# dotnet_naming_symbols.methods_async.required_modifiers = async +# dotnet_naming_symbols.methods_async.applicable_accessibilities = * + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +# Anything not specified uses camel case. +dotnet_naming_rule.unspecified_naming.severity = warning +dotnet_naming_rule.unspecified_naming.symbols = unspecified +dotnet_naming_rule.unspecified_naming.style = camel_case_style +dotnet_naming_symbols.unspecified.applicable_kinds = * +dotnet_naming_symbols.unspecified.applicable_accessibilities = * + +########################################## +# Chickensoft Rule Overrides +########################################## + +# Allow protected fields. +dotnet_diagnostic.CA1051.severity = none +# Don't warn about checking values that are supposedly never null. Sometimes +# they are actually null. +dotnet_diagnostic.CS8073.severity = none +# Allow expression values to go unused, even without discard variable. +# Otherwise, using Moq would be way too verbose. +dotnet_diagnostic.IDE0058.severity = none +# Allow me to use the word Collection if I want. +dotnet_diagnostic.CA1711.severity = none +# Don't warn about using reserved keywords (e.g., methods named "On") +dotnet_diagnostic.CA1716.severity = none +# Don't warn about public methods that can be marked static +# (tests frequently don't access member data, and GoDotTest won't call static methods) +dotnet_code_quality.CA1822.api_surface = private +# No primary constructors — not supported well by tooling. +dotnet_diagnostic.IDE0290.severity = none +# Let me write dumb if statements for readability. +dotnet_diagnostic.IDE0046.severity = none +# DO make me populate a *switch expression* +dotnet_diagnostic.IDE0072.severity = warning +# Don't make me populate a *switch statement* +dotnet_diagnostic.IDE0010.severity = none +# Make local functions static +dotnet_diagnostic.IDE0062.severity = warning +# Don't make me use properties if I don't want to. +dotnet_diagnostic.IDE0032.severity = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..088ebf0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,45 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf + +# Image formats +*.bmp filter=lfs diff=lfs merge=lfs -text +*.dds filter=lfs diff=lfs merge=lfs -text +*.exr filter=lfs diff=lfs merge=lfs -text +*.hdr filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text + +# Audio and Video formats +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.ogx filter=lfs diff=lfs merge=lfs -text +*.ogv filter=lfs diff=lfs merge=lfs -text + +# 3D formats +*.gltf filter=lfs diff=lfs merge=lfs -text +*.blend filter=lfs diff=lfs merge=lfs -text +*.blend1 filter=lfs diff=lfs merge=lfs -text +*.glb filter=lfs diff=lfs merge=lfs -text +*.dae filter=lfs diff=lfs merge=lfs -text +*.obj filter=lfs diff=lfs merge=lfs -text +*.fbx filter=lfs diff=lfs merge=lfs -text + +# Build +*.dll filter=lfs diff=lfs merge=lfs -text +*.exe filter=lfs diff=lfs merge=lfs -text +*.pdb filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text +*.dylib filter=lfs diff=lfs merge=lfs -text + +# Packaging +*.zip filter=lfs diff=lfs merge=lfs -text +*.7z filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.file filter=lfs diff=lfs merge=lfs -text +*.dylib filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/auto_release.yaml b/.github/workflows/auto_release.yaml new file mode 100644 index 0000000..8c5fb45 --- /dev/null +++ b/.github/workflows/auto_release.yaml @@ -0,0 +1,81 @@ +# This workflow will run whenever tests finish running. If tests pass, it will +# look at the last commit message to see if it contains the phrase +# "chore(deps): update all dependencies". +# +# If it finds a commit with that phrase, and the testing workflow has passed, +# it will automatically release a new version of the project by running the +# publish workflow. +# +# The commit message phrase above is always used by renovatebot when opening +# PR's to update dependencies. If you have renovatebot enabled and set to +# automatically merge in dependency updates, this can automatically release and +# publish the updated version of the project. +# +# You can disable this action by setting the DISABLE_AUTO_RELEASE repository +# variable to true. + +name: '🦾 Auto-Release' +on: + workflow_run: + workflows: ["🚥 Tests"] + branches: + - main + types: + - completed + +jobs: + auto_release: + name: 🦾 Auto-Release + runs-on: ubuntu-latest + outputs: + should_release: ${{ steps.release.outputs.should_release }} + steps: + - name: 🧾 Checkout + uses: actions/checkout@v6 + with: + # Use your GitHub Personal Access Token variable here. + token: ${{ secrets.GH_BASIC }} + lfs: true + submodules: 'recursive' + + - name: 🧑‍🔬 Check Test Results + id: tests + run: | + echo "passed=${{ github.event.workflow_run.conclusion == 'success' }}" >> "$GITHUB_OUTPUT" + + - name: 📄 Check If Dependencies Changed + id: deps + run: | + message=$(git log -1 --pretty=%B) + + if [[ $message == *"chore(deps)"* ]]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + - name: 📝 Check Release Status + id: release + run: | + echo "Tests passed: ${{ steps.tests.outputs.passed }}" + echo "Dependencies changed: ${{ steps.deps.outputs.changed }}" + disable_auto_release='${{ vars.DISABLE_AUTO_RELEASE }}' + echo "DISABLE_AUTO_RELEASE=$disable_auto_release" + + if [[ ${{ steps.tests.outputs.passed }} == "true" && ${{ steps.deps.outputs.changed }} == "true" && $disable_auto_release != "true" ]]; then + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "🦾 Creating a release!" + else + echo "should_release=false" >> "$GITHUB_OUTPUT" + echo "✋ Not creating a release." + fi + + trigger_release: + uses: './.github/workflows/release.yaml' + needs: auto_release + if: needs.auto_release.outputs.should_release == 'true' + secrets: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + GH_BASIC: ${{ secrets.GH_BASIC }} + with: + bump: patch diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..910100f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,99 @@ +name: '📦 Release' +on: + # Make a release whenever the developer wants. + workflow_dispatch: + inputs: + bump: + type: string + description: "major, minor, or patch" + required: true + default: "patch" + # Make a release whenever we're told to by another workflow. + workflow_call: + secrets: + NUGET_API_KEY: + description: "API key for Nuget" + required: true + GH_BASIC: + description: "Personal access token (PAT) for GitHub" + required: true + # Input unifies with the workflow dispatch since it's identical. + inputs: + bump: + type: string + description: "major, minor, or patch" + required: true + default: "patch" +jobs: + release: + name: '📦 Release' + runs-on: ubuntu-latest + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + steps: + - name: 🧾 Checkout + uses: actions/checkout@v6 + with: + token: ${{ secrets.GH_BASIC }} + lfs: true + submodules: 'recursive' + fetch-depth: 0 # So we can get all tags. + + - name: 🔎 Read Current Project Version + id: current-version + uses: WyriHaximus/github-action-get-previous-tag@v2 + with: + fallback: "0.0.0-devbuild" + + - name: 🖨 Print Current Version + run: | + echo "Current Version: ${{ steps.current-version.outputs.tag }}" + + - name: 🧮 Compute Next Version + uses: chickensoft-games/next-godot-csproj-version@v1 + id: next-version + with: + project-version: ${{ steps.current-version.outputs.tag }} + godot-version: global.json + bump: ${{ inputs.bump }} + + - uses: actions/setup-dotnet@v5 + name: 💽 Setup .NET SDK + with: + # Use the .NET SDK from global.json in the root of the repository. + global-json-file: global.json + + # Write version to file so .NET will build correct version. + - name: 📝 Write Version to File + uses: jacobtomlinson/gha-find-replace@v3 + with: + find: "0.0.0-devbuild" + replace: ${{ steps.next-version.outputs.version }} + regex: false + include: GodotHelper/GodotHelper.csproj + + - name: 📦 Build + working-directory: GodotHelper + run: dotnet build -c Release + + - name: 🔎 Get Package Path + id: package-path + run: | + package=$(find ./GodotHelper/nupkg -name "*.nupkg") + echo "package=$package" >> "$GITHUB_OUTPUT" + echo "📦 Found package: $package" + + - name: ✨ Create Release + env: + GITHUB_TOKEN: ${{ secrets.GH_BASIC }} + run: | + version="${{ steps.next-version.outputs.version }}" + gh release create --title "v$version" --generate-notes "$version" \ + "${{ steps.package-path.outputs.package }}" + + - name: 🛜 Publish to Nuget + run: | + dotnet nuget push "${{ steps.package-path.outputs.package }}" \ + --api-key "${{ secrets.NUGET_API_KEY }}" \ + --source "https://api.nuget.org/v3/index.json" --skip-duplicate diff --git a/.github/workflows/spellcheck.yaml b/.github/workflows/spellcheck.yaml new file mode 100644 index 0000000..1bc099c --- /dev/null +++ b/.github/workflows/spellcheck.yaml @@ -0,0 +1,26 @@ +name: '🧑‍🏫 Spellcheck' +on: + push: + pull_request: + +jobs: + spellcheck: + name: '🧑‍🏫 Spellcheck' + # Only run the workflow if it's not a PR or if it's a PR from a fork. + # This prevents duplicate workflows from running on PR's that originate + # from the repository itself. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + runs-on: ubuntu-latest + defaults: + run: + working-directory: '.' + steps: + - uses: actions/checkout@v6 + name: 🧾 Checkout + + - uses: streetsidesoftware/cspell-action@v8 + name: 📝 Check Spelling + with: + config: './cspell.json' + incremental_files_only: false + root: '.' diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..5862751 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,64 @@ +name: 🚥 Tests +on: + push: + pull_request: + +jobs: + tests: + name: 🧪 Evaluate Tests on ${{ matrix.os }} + # Only run the workflow if it's not a PR or if it's a PR from a fork. + # This prevents duplicate workflows from running on PR's that originate + # from the repository itself. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + runs-on: ${{ matrix.os }} + strategy: + # Don't cancel other OS runners if one fails. + fail-fast: false + matrix: + # Put the operating systems you want to run on here. + os: [ubuntu-latest, macos-latest, windows-2025] + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + defaults: + run: + # Use bash shells on all platforms. + shell: bash + steps: + - name: 🧾 Checkout + uses: actions/checkout@v6 + with: + lfs: true + submodules: 'recursive' + + - name: 💽 Setup .NET SDK + uses: actions/setup-dotnet@v5 + with: + # Use the .NET SDK from global.json in the root of the repository. + global-json-file: global.json + + - name: 📦 Restore Dependencies + run: dotnet restore + + - name: 🤖 Setup Godot + uses: chickensoft-games/setup-godot@v2 + with: + # Version must include major, minor, and patch, and be >= 4.0.0 + # Pre-release label is optional. + # + # In this case, we are using the version from global.json. + # + # This allows checks on renovatebot PR's to succeed whenever + # renovatebot updates the Godot SDK version. + version: global.json + + - name: 🧑‍🔬 Generate .NET Bindings + working-directory: GodotHelper.Tests + run: godot --headless --build-solutions --quit || exit 0 + + - name: 🦺 Build Projects + run: dotnet build + + - name: 🧪 Run Tests + working-directory: GodotHelper.Tests + run: godot --headless --run-tests --quit-on-finish diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..377ed34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +**/coverage/* +!**/coverage/.gdignore +nupkg/ +.godot/ +bin/ +obj/ +.generated/ +.vs/ +.DS_Store +*.DotSettings.user +*.binlog diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ac45447 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "ms-dotnettools.csharp", + "selcukermaya.se-csproj-extensions", + "josefpihrt-vscode.roslynator", + "streetsidesoftware.code-spell-checker", + "VisualStudioExptTeam.vscodeintellicode", + "DavidAnson.vscode-markdownlint" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b042b71 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + "version": "0.2.0", + "configurations": [ + // For these launch configurations to work, you need to setup a GODOT + // environment variable. On mac or linux, this can be done by adding + // the following to your .zshrc, .bashrc, or .bash_profile file: + // export GODOT="/Applications/Godot.app/Contents/MacOS/Godot" + { + "name": "🧪 Debug Tests", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${env:GODOT}", + "args": [ + // These command line flags are used by GoDotTest to run tests. + "--run-tests", + "--quit-on-finish" + ], + "cwd": "${workspaceFolder}/GodotHelper.Tests", + "stopAtEntry": false, + }, + { + "name": "🔬 Debug Current Test", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${env:GODOT}", + "args": [ + // These command line flags are used by GoDotTest to run tests. + "--run-tests=${fileBasenameNoExtension}", + "--quit-on-finish" + ], + "cwd": "${workspaceFolder}/GodotHelper.Tests", + "stopAtEntry": false, + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8b8965e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,169 @@ +{ + "[csharp]": { + "editor.codeActionsOnSave": { + "source.addMissingImports": "explicit", + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnType": false + }, + "csharp.semanticHighlighting.enabled": true, + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + // Required to keep the C# language server from getting confused about which + // solution to open. + "dotnet.defaultSolution": "GodotHelper.sln", + "dotnet.server.useOmnisharp": false, + "editor.semanticHighlighting.enabled": true, + // C# doc comment colorization gets lost with semantic highlighting, but we + // need semantic highlighting for proper syntax highlighting with record + // shorthand. + // + // Here's a workaround for doc comment highlighting from + // https://github.com/OmniSharp/omnisharp-vscode/issues/3816 + "editor.tokenColorCustomizations": { + "[*]": { + // Themes that don't include the word "Dark" or "Light" in them. + // These are some bold colors that show up well against most dark and + // light themes. + // + // Change them to something that goes well with your preferred theme :) + "textMateRules": [ + { + "scope": "comment.documentation", + "settings": { + "foreground": "#0091ff" + } + }, + { + "scope": "comment.documentation.attribute", + "settings": { + "foreground": "#8480ff" + } + }, + { + "scope": "comment.documentation.cdata", + "settings": { + "foreground": "#0091ff" + } + }, + { + "scope": "comment.documentation.delimiter", + "settings": { + "foreground": "#aa00ff" + } + }, + { + "scope": "comment.documentation.name", + "settings": { + "foreground": "#ef0074" + } + } + ] + }, + "[*Dark*]": { + // Themes that include the word "Dark" in them. + "textMateRules": [ + { + "scope": "comment.documentation", + "settings": { + "foreground": "#608B4E" + } + }, + { + "scope": "comment.documentation.attribute", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "comment.documentation.cdata", + "settings": { + "foreground": "#E9D585" + } + }, + { + "scope": "comment.documentation.delimiter", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "comment.documentation.name", + "settings": { + "foreground": "#569CD6" + } + } + ] + }, + "[*Light*]": { + // Themes that include the word "Light" in them. + "textMateRules": [ + { + "scope": "comment.documentation", + "settings": { + "foreground": "#008000" + } + }, + { + "scope": "comment.documentation.attribute", + "settings": { + "foreground": "#282828" + } + }, + { + "scope": "comment.documentation.cdata", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "comment.documentation.delimiter", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "comment.documentation.name", + "settings": { + "foreground": "#808080" + } + } + ] + } + }, + "markdownlint.config": { + // Allow non-unique heading names so we don't break the changelog. + "MD024": false, + // Allow html in markdown. + "MD033": false + }, + "markdownlint.lintWorkspaceGlobs": [ + "!**/LICENSE" + ], + "omnisharp.enableEditorConfigSupport": true, + "omnisharp.enableMsBuildLoadProjectsOnDemand": false, + "omnisharp.maxFindSymbolsItems": 3000, + "omnisharp.useModernNet": true, + // Remove these if you're happy with your terminal profiles. + "terminal.integrated.defaultProfile.windows": "Git Bash", + "terminal.integrated.profiles.windows": { + "Command Prompt": { + "icon": "terminal-cmd", + "path": [ + "${env:windir}\\Sysnative\\cmd.exe", + "${env:windir}\\System32\\cmd.exe" + ] + }, + "Git Bash": { + "icon": "terminal", + "source": "Git Bash" + }, + "PowerShell": { + "icon": "terminal-powershell", + "source": "PowerShell" + } + }, + "dotnet.formatting.organizeImportsOnFormat": true +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7d02ece --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,40 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "--no-restore" + ], + "problemMatcher": "$msCompile", + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "coverage", + "group": "test", + "command": "${workspaceFolder}/GodotHelper.Tests/coverage.sh", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}/GodotHelper.Tests" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + }, + }, + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..80d775f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +Thank you for taking the time to read this contributing guide and for showing interest in helping this project! + +## Getting Started + +Need a helping hand to get started? Check out these resources! + +- [Discord Server][discord] +- [Chickensoft Website][chickensoft] + +Please read our [code of conduct](#code-of-conduct). We do our best to treat others fairly and foster a welcoming environment. + +## Project Setup + +This is a C# nuget package, for use with the .NET SDK 6 or 7. As such, the `dotnet` tool will allow you to restore packages and build projects. + +The `GodotHelper.Tests` project must be built with the Godot editor at least once before `dotnet build` will succeed. Godot has to generate the .NET bindings for the project, since tests run in an actual game environment. + +## Coding Guidelines + +Your IDE should automatically adhere to the style guidelines in the provided `.editorconfig` file. Please try to keep lines under 80 characters long whenever possible. + +We try to write tests for our projects to ensure a certain level of quality. We are willing to give you support and guidance if you need help! + +## Code of Conduct + +We follow the [Contributor Covenant][covenant]. + +In short: + +> We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + + + +[discord]: https://discord.gg/gSjaPgMmYW +[chickensoft]: https://chickensoft.games +[covenant]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ diff --git a/GodotHelper.Tests/.editorconfig b/GodotHelper.Tests/.editorconfig new file mode 100644 index 0000000..9ea24c1 --- /dev/null +++ b/GodotHelper.Tests/.editorconfig @@ -0,0 +1,8 @@ +# EditorConfig for the test project. + +root = false + +[*.{cs,csx,cake}] + +# Not disposing of objects in a test is normal within Godot +dotnet_diagnostic.CA1001.severity = none diff --git a/GodotHelper.Tests/GodotHelper.Tests.csproj b/GodotHelper.Tests/GodotHelper.Tests.csproj new file mode 100644 index 0000000..e109ad3 --- /dev/null +++ b/GodotHelper.Tests/GodotHelper.Tests.csproj @@ -0,0 +1,36 @@ + + + net8.0 + disable + enable + true + preview + GodotHelper.Tests + full + true + + + + true + + + true + .generated + + + + + + + + + + + + + + + + + + diff --git a/GodotHelper.Tests/GodotHelper.Tests.csproj.old b/GodotHelper.Tests/GodotHelper.Tests.csproj.old new file mode 100644 index 0000000..6dec5e5 --- /dev/null +++ b/GodotHelper.Tests/GodotHelper.Tests.csproj.old @@ -0,0 +1,36 @@ + + + net8.0 + disable + enable + true + preview + GodotHelper.Tests + full + true + + + + true + + + true + .generated + + + + + + + + + + + + + + + + + + diff --git a/GodotHelper.Tests/GodotHelper.Tests.sln b/GodotHelper.Tests/GodotHelper.Tests.sln new file mode 100644 index 0000000..029493c --- /dev/null +++ b/GodotHelper.Tests/GodotHelper.Tests.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotHelper.Tests", "GodotHelper.Tests.csproj", "{477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {477F44D4-2CF2-4A5C-A0FE-070F090BF2D5}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/GodotHelper.Tests/badges/.gdignore b/GodotHelper.Tests/badges/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/GodotHelper.Tests/badges/branch_coverage.svg b/GodotHelper.Tests/badges/branch_coverage.svg new file mode 100644 index 0000000..eafd285 --- /dev/null +++ b/GodotHelper.Tests/badges/branch_coverage.svg @@ -0,0 +1,113 @@ + + + Code coverage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generated by: ReportGenerator 5.1.26.0 + + + + Coverage + Coverage + + N/AN/A + + + + + + Branch coverage + + + diff --git a/GodotHelper.Tests/badges/line_coverage.svg b/GodotHelper.Tests/badges/line_coverage.svg new file mode 100644 index 0000000..28d1811 --- /dev/null +++ b/GodotHelper.Tests/badges/line_coverage.svg @@ -0,0 +1,113 @@ + + + Code coverage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generated by: ReportGenerator 5.1.26.0 + + + + Coverage + Coverage + 100%100% + + + + + + Line coverage + + + + diff --git a/GodotHelper.Tests/coverage.ps1 b/GodotHelper.Tests/coverage.ps1 new file mode 100644 index 0000000..9ab758f --- /dev/null +++ b/GodotHelper.Tests/coverage.ps1 @@ -0,0 +1,82 @@ +# To collect code coverage, you will need the following environment setup: +# +# - A "GODOT" environment variable pointing to the Godot executable +# - ReportGenerator installed +# +# dotnet tool install -g dotnet-reportgenerator-globaltool +# +# - A version of coverlet > 3.2.0. +# +# As of Jan 2023, this is not yet released. +# +# The included `nuget.config` file will allow you to install a nightly +# version of coverlet from the coverlet nightly nuget feed. +# +# dotnet tool install --global coverlet.console --prerelease. +# +# You can build coverlet yourself, but you will need to edit the path to +# coverlet below to point to your local build of the coverlet dll. +# +# If you need help with coverage, feel free to join the Chickensoft Discord. +# https://chickensoft.games + +dotnet build --no-restore + +coverlet ` + "./.godot/mono/temp/bin/Debug" --verbosity detailed ` + --target $env:GODOT ` + --targetargs "--run-tests --coverage --quit-on-finish" ` + --format "opencover" ` + --output "./coverage/coverage.xml" ` + --exclude-by-file "**/test/**/*.cs" ` + --exclude-by-file "**/*Microsoft.NET.Test.Sdk.Program.cs" ` + --exclude-by-file "**/Godot.SourceGenerators/**/*.cs" ` + --exclude-assemblies-without-sources "missingall" + +# Projects included via will be collected in code coverage. +# If you want to exclude them, replace the string below with the names of +# the assemblies to ignore. e.g., +# $ASSEMBLIES_TO_REMOVE="-AssemblyToRemove1;-AssemblyToRemove2" +$ASSEMBLIES_TO_REMOVE = "-GodotHelper.Tests" + +reportgenerator ` + -reports:"./coverage/coverage.xml" ` + -targetdir:"./coverage/report" ` + "-assemblyfilters:$ASSEMBLIES_TO_REMOVE" ` + "-classfilters:-GodotPlugins.Game.Main" ` + -reporttypes:"Html;Badges" + +# Copy badges into their own folder. The badges folder should be included in +# source control so that the README.md in the root can reference the badges. + +$badgesPath = "./badges" +if (-not (Test-Path $badgesPath)) +{ + mkdir $badgesPath +} + +mv -Force "./coverage/report/badge_branchcoverage.svg" "$badgesPath/branch_coverage.svg" +mv -Force "./coverage/report/badge_linecoverage.svg" "$badgesPath/line_coverage.svg" + +# Determine OS, open coverage accordingly. + +$os = (Get-CimInstance -ClassName Win32_OperatingSystem).Caption +if ($os -match "Windows") +{ + Write-Output 'MS Windows' + Start-Process "coverage/report/index.htm" +} +elseif ($os -match "Linux") +{ + Write-Output 'Linux' + xdg-open "coverage/report/index.htm" +} +elseif ($os -match "Mac OS X") +{ + Write-Output 'Mac OS X' + open "coverage/report/index.htm" +} +else +{ + Write-Output 'Other OS' +} diff --git a/GodotHelper.Tests/coverage.sh b/GodotHelper.Tests/coverage.sh new file mode 100644 index 0000000..5c39021 --- /dev/null +++ b/GodotHelper.Tests/coverage.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# To collect code coverage, you will need the following environment setup: +# +# - A "GODOT" environment variable pointing to the Godot executable +# - ReportGenerator installed +# +# dotnet tool install -g dotnet-reportgenerator-globaltool +# +# - A version of coverlet > 3.2.0. +# +# As of Jan 2023, this is not yet released. +# +# The included `nuget.config` file will allow you to install a nightly +# version of coverlet from the coverlet nightly nuget feed. +# +# dotnet tool install --global coverlet.console --prerelease. +# +# You can build coverlet yourself, but you will need to edit the path to +# coverlet below to point to your local build of the coverlet dll. +# +# If you need help with coverage, feel free to join the Chickensoft Discord. +# https://chickensoft.games + +dotnet build --no-restore + +coverlet \ + "./.godot/mono/temp/bin/Debug" --verbosity detailed \ + --target "$GODOT" \ + --targetargs "--run-tests --coverage --quit-on-finish" \ + --format "opencover" \ + --output "./coverage/coverage.xml" \ + --exclude-by-file "**/test/**/*.cs" \ + --exclude-by-file "**/*Microsoft.NET.Test.Sdk.Program.cs" \ + --exclude-by-file "**/Godot.SourceGenerators/**/*.cs" \ + --exclude-assemblies-without-sources "missingall" + +# Projects included via will be collected in code coverage. +# If you want to exclude them, replace the string below with the names of +# the assemblies to ignore. e.g., +# ASSEMBLIES_TO_REMOVE="-AssemblyToRemove1;-AssemblyToRemove2" +ASSEMBLIES_TO_REMOVE="-GodotHelper.Tests" + +reportgenerator \ + -reports:"./coverage/coverage.xml" \ + -targetdir:"./coverage/report" \ + "-assemblyfilters:$ASSEMBLIES_TO_REMOVE" \ + "-classfilters:-GodotPlugins.Game.Main" \ + -reporttypes:"Html;Badges" + +# Copy badges into their own folder. The badges folder should be included in +# source control so that the README.md in the root can reference the badges. + +mkdir -p ./badges +mv ./coverage/report/badge_branchcoverage.svg ./badges/branch_coverage.svg +mv ./coverage/report/badge_linecoverage.svg ./badges/line_coverage.svg + +# Determine OS, open coverage accordingly. + +case "$(uname -s)" in + + Darwin) + echo 'Mac OS X' + open coverage/report/index.htm + ;; + + Linux) + echo 'Linux' + open coverage/report/index.htm + ;; + + CYGWIN*|MINGW32*|MSYS*|MINGW*) + echo 'MS Windows' + start coverage/report/index.htm + ;; + + *) + echo 'Other OS' + ;; +esac diff --git a/GodotHelper.Tests/coverage/.gdignore b/GodotHelper.Tests/coverage/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/GodotHelper.Tests/icon.svg b/GodotHelper.Tests/icon.svg new file mode 100644 index 0000000..adc26df --- /dev/null +++ b/GodotHelper.Tests/icon.svg @@ -0,0 +1 @@ + diff --git a/GodotHelper.Tests/icon.svg.import b/GodotHelper.Tests/icon.svg.import new file mode 100644 index 0000000..64fcabd --- /dev/null +++ b/GodotHelper.Tests/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://da2tcc2mhkfgi" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/GodotHelper.Tests/project.godot b/GodotHelper.Tests/project.godot new file mode 100644 index 0000000..9d570d3 --- /dev/null +++ b/GodotHelper.Tests/project.godot @@ -0,0 +1,24 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="GodotHelper.Tests" +run/main_scene="res://test/Tests.tscn" +config/features=PackedStringArray("4.2", "C#", "Mobile") +config/icon="res://icon.svg" + +[dotnet] + +project/assembly_name="GodotHelper.Tests" + +[rendering] + +renderer/rendering_method="mobile" diff --git a/GodotHelper.Tests/test/Tests.cs b/GodotHelper.Tests/test/Tests.cs new file mode 100644 index 0000000..af6ddd1 --- /dev/null +++ b/GodotHelper.Tests/test/Tests.cs @@ -0,0 +1,13 @@ +namespace GodotHelper.Tests; + +using System.Reflection; +using Chickensoft.GoDotTest; +using Godot; + +public partial class Tests : Node2D +{ + public override void _Ready() => CallDeferred(MethodName.RunTests); + + public void RunTests() => + GoTest.RunTests(Assembly.GetExecutingAssembly(), this); +} diff --git a/GodotHelper.Tests/test/Tests.cs.uid b/GodotHelper.Tests/test/Tests.cs.uid new file mode 100644 index 0000000..20b862b --- /dev/null +++ b/GodotHelper.Tests/test/Tests.cs.uid @@ -0,0 +1 @@ +uid://d1ejsfsekh8yr diff --git a/GodotHelper.Tests/test/Tests.tscn b/GodotHelper.Tests/test/Tests.tscn new file mode 100644 index 0000000..14ebf60 --- /dev/null +++ b/GodotHelper.Tests/test/Tests.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bv5dxd8hrc5g4"] + +[ext_resource type="Script" path="res://test/Tests.cs" id="1_310o6"] + +[node name="Node2D" type="Node2D"] +script = ExtResource("1_310o6") diff --git a/GodotHelper.Tests/test/src/PackageTest.cs b/GodotHelper.Tests/test/src/PackageTest.cs new file mode 100644 index 0000000..7b0a75f --- /dev/null +++ b/GodotHelper.Tests/test/src/PackageTest.cs @@ -0,0 +1,24 @@ +namespace GodotHelper.Tests; + +using Chickensoft.GoDotTest; +using Godot; +using Shouldly; + +public class PackageTest : TestClass +{ + public PackageTest(Node testScene) : base(testScene) { } + + [Test] + public void Initializes() + { + var package = new Package(); + package.ShouldBeAssignableTo(); + } + + [Test] + public void MethodReturnsString() + { + var package = new Package(); + package.Method().ShouldBe("Hello, world!"); + } +} diff --git a/GodotHelper.Tests/test/src/PackageTest.cs.uid b/GodotHelper.Tests/test/src/PackageTest.cs.uid new file mode 100644 index 0000000..b6f2b5f --- /dev/null +++ b/GodotHelper.Tests/test/src/PackageTest.cs.uid @@ -0,0 +1 @@ +uid://c5puoyi2jgtlm diff --git a/GodotHelper.sln b/GodotHelper.sln new file mode 100644 index 0000000..101dc83 --- /dev/null +++ b/GodotHelper.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotHelper", "GodotHelper\GodotHelper.csproj", "{60A9D927-4B12-4E5F-B026-F0E8B0A9AD8F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotHelper.Tests", "GodotHelper.Tests\GodotHelper.Tests.csproj", "{8C8F7DDB-82ED-49FB-8C73-1F27182B62F5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60A9D927-4B12-4E5F-B026-F0E8B0A9AD8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60A9D927-4B12-4E5F-B026-F0E8B0A9AD8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60A9D927-4B12-4E5F-B026-F0E8B0A9AD8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60A9D927-4B12-4E5F-B026-F0E8B0A9AD8F}.Release|Any CPU.Build.0 = Release|Any CPU + {8C8F7DDB-82ED-49FB-8C73-1F27182B62F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C8F7DDB-82ED-49FB-8C73-1F27182B62F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C8F7DDB-82ED-49FB-8C73-1F27182B62F5}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {8C8F7DDB-82ED-49FB-8C73-1F27182B62F5}.Release|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection +EndGlobal diff --git a/GodotHelper/GodotHelper.csproj b/GodotHelper/GodotHelper.csproj new file mode 100644 index 0000000..fbb611c --- /dev/null +++ b/GodotHelper/GodotHelper.csproj @@ -0,0 +1,52 @@ + + + net8.0 + true + preview + true + true + enable + true + GodotHelper + true + ./nupkg + portable + + GodotHelper + 0.0.0-devbuild + GodotHelper description. + © 2024 Ronnie Kisner + Ronnie Kisner + Ronnie Kisner + + GodotHelper + GodotHelper release. + icon.png + + README.md + LICENSE + + + git + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/GodotHelper/icon.png b/GodotHelper/icon.png new file mode 100644 index 0000000..977d714 Binary files /dev/null and b/GodotHelper/icon.png differ diff --git a/GodotHelper/src/Package.cs b/GodotHelper/src/Package.cs new file mode 100644 index 0000000..ef9427b --- /dev/null +++ b/GodotHelper/src/Package.cs @@ -0,0 +1,12 @@ +namespace GodotHelper; + +/// +/// Package class. +/// +public class Package +{ + /// + /// Method. + /// + public string Method() => "Hello, world!"; +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9152fc3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2023 Ronnie Kisner + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..34a86d9 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# GodotHelper + +[![Chickensoft Badge][chickensoft-badge]][chickensoft-website] [![Discord][discord-badge]][discord] [![Read the docs][read-the-docs-badge]][docs] ![line coverage][line-coverage] ![branch coverage][branch-coverage] + +A .NET template for quickly creating a C# nuget package for use with Godot 4. + +--- + +

+GodotHelper +

+ +## 🥚 Getting Started + +This template allows you to easily create a nuget package for use in Godot 4 C# projects. Microsoft's `dotnet` tool allows you to easily create, install, and use templates. + +```sh +# Install this template +dotnet new --install GodotHelper + +# Generate a new project based on this template +dotnet new chickenpackage --name "MyPackageName" --param:author "My Name" + +# Use Godot to generate files needed to compile the package's test project. +cd MyPackageName/MyPackageName.Tests/ +$GODOT --headless --build-solutions --quit +dotnet build +``` + +## 💁 Getting Help + +*Is this template broken? Encountering obscure C# build problems?* We'll be happy to help you in the [Chickensoft Discord server][discord]. + +## 🏝 Environment Setup + +For the provided debug configurations and test coverage to work correctly, you must setup your development environment correctly. The [Chickensoft Setup Docs][setup-docs] describe how to setup your Godot and C# development environment, following Chickensoft's best practices. + +### VSCode Settings + +This template includes some Visual Studio Code settings in `.vscode/settings.json`. The settings facilitate terminal environments on Windows (Git Bash, PowerShell, Command Prompt) and macOS (zsh), as well as fixing some syntax colorization issues that Omnisharp suffers from. You'll also find settings that enable editor config support in Omnisharp and the .NET Roslyn analyzers for a more enjoyable coding experience. + +> Please double-check that the provided VSCode settings don't conflict with your existing settings. + +## .NET Versioning + +The included [`global.json`](./global.json) specifies the version of the .NET SDK that the included projects should use. It also specifies the `Godot.NET.Sdk` version that the included test project should use (since tests run inside an actual Godot game so you can use the full Godot API to verify your package is working as intended). + +## 🐞 Debugging + +You can debug the included test project for your package in `GodotHelper.Tests/` by opening the root of this repository in VSCode and selecting one of the launch configurations: `Debug Tests` or `Debug Current Test`. + +> For the launch profile `Debug Current Test` to work, your test file must share the same name as the test class inside of it. For example, a test class named `PackageTest` must reside in a test file named `PackageTest.cs`. + +The launch profiles will trigger a build (without restoring packages) and then instruct .NET to run Godot 4 (while communicating with VSCode for interactive debugging). + +> **Important:** You must setup a `GODOT` environment variable for the launch configurations above. If you're using [GodotEnv] to install and manage Godot versions, you're already setup! For more info, see the [Chickensoft Setup Docs][setup-docs]. + +## 👷 Testing + +By default, a test project in `GodotHelper.Tests/` is created for you to write tests for your package. [GoDotTest] is already included and setup, allowing you to focus on development and testing. + +[GoDotTest] is an easy-to-use testing framework for Godot and C# that allows you to run tests from the command line, collect code coverage, and debug tests in VSCode. + +The project is configured to allow tests to be easily run and debugged from VSCode or executed via CI/CD workflows, without having to include the test files or test dependencies in the final release build. + +The `Main.tscn` and `Main.cs` scene and script file are the entry point of your game. In general, you probably won't need to modify these unless you're doing something highly custom. If the game isn't running in test mode (or it's a release build), it will just immediately change the scene to `game/Game.tscn`. In general, prefer editing `game/Game.tscn` over `Main.tscn`. +If you run Godot with the `--run-tests` command line argument, the game will run the tests instead of switching to the game scene located at `game/Game.tscn`. The provided debug configurations in `.vscode/launch.json` allow you to easily debug tests (or just the currently open test, provided its filename matches its class name). + +Please see `test/ExampleTest.cs` and the [GoDotTest] readme for more examples. + +## 🚦 Test Coverage + +Code coverage requires a few `dotnet` global tools to be installed first. You should install these tools from the root of the project directory. + +```sh +dotnet tool install --global coverlet.console +dotnet tool update --global coverlet.console +dotnet tool install --global dotnet-reportgenerator-globaltool +dotnet tool update --global dotnet-reportgenerator-globaltool +``` + +> Running `dotnet tool update` for the global tool is often necessary on Apple Silicon computers to ensure the tools are installed correctly. + +You can collect code coverage and generate coverage badges by running the bash script in `test/coverage.sh` (on Windows, you can use the Git Bash shell that comes with git). + +```sh +# Must give coverage script permission to run the first time it is used. +chmod +x test/.coverage.sh + +# Run code coverage: +cd GodotHelper.Tests +./coverage.sh +``` + +You can also run test coverage through VSCode by opening the command palette and selecting `Tasks: Run Task` and then choosing `coverage`. + +## 🏭 CI/CD + +This package includes various GitHub Actions workflows to make developing and deploying your package easier. + +### 🚥 Tests + +Tests run on every push or pull request to the repository. You can configure which platforms you want to run tests on in [`.github/workflows/tests.yaml`](.github/workflows/tests.yaml). + +By default, tests run each platform (macOS, Windows, and Linux) using the latest beta version of Godot 4. + +Tests are executed by running the Godot test project in `GodotHelper.Tests` from the command line and passing in the relevant arguments to Godot so that [GoDotTest] can discover and run tests. + +### 🧑‍🏫 Spellcheck + +A spell check runs on every push or pull request to the repository. Spellcheck settings can be configured in [`.github/workflows/spellcheck.yaml`](.github/workflows/spellcheck.yaml) + +The [Code Spell Checker][cspell] plugin for VSCode is recommended to help you catch typos before you commit them. If you need add a word to the dictionary, you can add it to the `cspell.json` file. + +You can also words to the local `cspell.json` file from VSCode by hovering over a misspelled word and selecting `Quick Fix...` and then `Add "{word}" to config: GodotPackage/cspell.json`. + +![Fix Spelling](docs/spelling_fix.png) + +### 📦 Release + +The included workflow in [`.github/workflows/release.yaml`](.github/workflows/publish.yaml) can be manually dispatched when you're ready to make a new release. Once you specify `major`, `minor`, or `patch` for the version bump strategy, the workflow will build your package with the updated version and release it on both GitHub and nuget. + +The accompanying [`.github/workflows/auto_release.yaml`](.github/workflows/auto_release.yaml) will trigger the publish workflow if it detects a new commit in main that is a routine dependency update from renovatebot. Since Renovatebot is configured to auto-merge dependency updates, your package will automatically be published to Nuget when a new version of Godot.NET.Sdk is released or other packages you depend on are updated. If this behavior is undesired, remove the `"automerge": true` property from [`renovate.json`](./renovate.json). + +> To publish to nuget, you need to configure a repository or organization secret within GitHub named `NUGET_API_KEY` that contains your Nuget API key. Make sure you setup `NUGET_API_KEY` as a **secret** (rather than an environment variable) to keep it safe! + +### 🏚 Renovatebot + +This repository includes a [`renovate.json`](./renovate.json) configuration for use with [Renovatebot]. Renovatebot can automatically open and merge pull requests to help you keep your dependencies up to date when it detects new dependency versions have been released. + +![Renovatebot Pull Request](docs/renovatebot_pr.png) + +> Unlike Dependabot, Renovatebot is able to combine all dependency updates into a single pull request — a must-have for Godot C# repositories where each sub-project needs the same Godot.NET.Sdk versions. If dependency version bumps were split across multiple repositories, the builds would fail in CI. + +The easiest way to add Renovatebot to your repository is to [install it from the GitHub Marketplace][get-renovatebot]. Note that you have to grant it access to each organization and repository you want it to monitor. + +The included `renovate.json` includes a few configuration options to limit how often Renovatebot can open pull requests as well as regex's to filter out some poorly versioned dependencies to prevent invalid dependency version updates. + +If your project is setup to require approvals before pull requests can be merged *and* you wish to take advantage of Renovatebot's auto-merge feature, you can install the [Renovate Approve][renovate-approve] bot to automatically approve the Renovate dependency PR's. If you need two approvals, you can install the identical [Renovate Approve 2][renovate-approve-2] bot. See [this][about-renovate-approvals] for more information. + +--- + +🐣 Package generated from a 🐤 Chickensoft Template — + +[chickensoft-badge]: https://chickensoft.games/img/badges/chickensoft_badge.svg +[chickensoft-website]: https://chickensoft.games +[discord-badge]: https://chickensoft.games/img/badges/discord_badge.svg +[discord]: https://discord.gg/gSjaPgMmYW +[read-the-docs-badge]: https://chickensoft.games/img/badges/read_the_docs_badge.svg +[docs]: https://chickensoft.games/docs +[line-coverage]: GodotHelper.Tests/badges/line_coverage.svg +[branch-coverage]: GodotHelper.Tests/badges/branch_coverage.svg + +[GoDotTest]: https://github.com/chickensoft-games/go_dot_test +[setup-docs]: https://chickensoft.games/docs/setup +[cspell]: https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker +[Renovatebot]: https://www.mend.io/free-developer-tools/renovate/ +[get-renovatebot]: https://github.com/apps/renovate +[renovate-approve]: https://github.com/apps/renovate-approve +[renovate-approve-2]: https://github.com/apps/renovate-approve-2 +[about-renovate-approvals]: https://stackoverflow.com/a/66575885 +[GodotEnv]: https://github.com/chickensoft-games/GodotEnv diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..2283c7b --- /dev/null +++ b/cspell.json @@ -0,0 +1,70 @@ +{ + "files": [ + "**/*.*" + ], + "ignorePaths": [ + "**/*.tscn", + "**/*.import", + "**/badges/**/*.*", + "**/coverage/**/*.*", + "**/.godot/**/*.*", + "**/obj/**/*.*", + "**/bin/**/*.*", + "**/nupkg/**/*.*" + ], + "words": [ + "assemblyfilters", + "automerge", + "branchcoverage", + "brandedoutcast", + "buildtransitive", + "camelcase", + "chickenpackage", + "Chickensoft", + "classfilters", + "contentfiles", + "CYGWIN", + "devbuild", + "endregion", + "Finalizer", + "Finalizers", + "globaltool", + "godotengine", + "godotpackage", + "issuecomment", + "justalemon", + "lcov", + "lihop", + "linecoverage", + "methodcoverage", + "missingall", + "msbuild", + "MSYS", + "nameof", + "Nerdbank", + "netstandard", + "NOLOGO", + "nupkg", + "Omnisharp", + "opencover", + "OPTOUT", + "paramref", + "pascalcase", + "Postinitialize", + "Predelete", + "renovatebot", + "reportgenerator", + "reporttypes", + "Shouldly", + "subfolders", + "targetargs", + "targetdir", + "tscn", + "typeof", + "typeparam", + "typeparamref", + "ulong", + "Unparented", + "Xunit" + ] +} \ No newline at end of file diff --git a/docs/renovatebot_pr.png b/docs/renovatebot_pr.png new file mode 100644 index 0000000..0f276a0 Binary files /dev/null and b/docs/renovatebot_pr.png differ diff --git a/docs/spelling_fix.png b/docs/spelling_fix.png new file mode 100644 index 0000000..354fbe2 Binary files /dev/null and b/docs/spelling_fix.png differ diff --git a/global.json b/global.json new file mode 100644 index 0000000..f42c6a3 --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "8.0.125", + "rollForward": "latestMinor" + }, + "msbuild-sdks": { + "Godot.NET.Sdk": "4.6.2" + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..0436e07 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>chickensoft-games/renovate:godot"] +}