Repository: devlooped/SmallSharp Branch: main Commit: 88e91cf619f3 Files: 58 Total size: 134.6 KB Directory structure: gitextract_llx4ryxm/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ └── config.yml │ ├── actions/ │ │ └── dotnet/ │ │ └── action.yml │ ├── copilot-instructions.md │ ├── dependabot.yml │ ├── dotnet.json │ ├── release.yml │ └── workflows/ │ ├── build.yml │ ├── changelog.config │ ├── changelog.yml │ ├── combine-prs.yml │ ├── demos.yml │ ├── dotnet-env.yml │ ├── dotnet-file.yml │ ├── includes.yml │ ├── pages.yml │ ├── publish.yml │ └── triage.yml ├── .gitignore ├── .netconfig ├── Directory.Build.rsp ├── Gemfile ├── SmallSharp.slnx ├── _config.yml ├── assets/ │ └── css/ │ └── style.scss ├── changelog.md ├── license.txt ├── osmfeula.txt ├── readme.md └── src/ ├── Demo/ │ ├── .gitignore │ ├── Demo.csproj │ ├── Hello.cs │ ├── Humanizer.cs │ ├── Mcp.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── global.json │ └── nuget.config ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.props ├── Directory.targets ├── SmallSharp/ │ ├── ConsoleEncodingInitializer.cs │ ├── EmitTargets.cs │ ├── Sdk.Empty.props │ ├── Sdk.props │ ├── Sdk.targets │ ├── SmallSharp.Before.props │ ├── SmallSharp.Version.props │ ├── SmallSharp.csproj │ ├── SmallSharp.props │ ├── SmallSharp.targets │ ├── TaskItemFactory.cs │ └── readme.md ├── _._ ├── demo.ps1 └── nuget.config ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome:http://EditorConfig.org # top-most EditorConfig file root = true # Don't use tabs for indentation. [*] indent_style = space # (Please don't specify an indent_size here; that has too many unintended consequences.) # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 # Xml project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,msbuildproj,props,targets}] indent_size = 2 # Xml config files [*.{ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # YAML files [*.{yaml,yml}] indent_size = 2 # JSON files [*.json] indent_size = 2 # Dotnet code style settings: [*.{cs,vb}] tab_width = 4 # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion # Use language keywords instead of framework type names for type references dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion # Suggest more modern language features when available dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion # CSharp code style settings: # IDE0040: Add accessibility modifiers dotnet_style_require_accessibility_modifiers = omit_if_default:error # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = error # IDE1100: Error reading content of source file 'Project.TargetFrameworkMoniker' (i.e. from ThisAssembly) dotnet_diagnostic.IDE1100.severity = none [*.cs] # Top-level files are definitely OK csharp_using_directive_placement = outside_namespace:silent csharp_style_namespace_declarations = block_scoped:silent csharp_prefer_simple_using_statement = true:suggestion csharp_prefer_braces = true:silent # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_elsewhere = true:suggestion # Prefer method-like constructs to have an expression-body csharp_style_expression_bodied_methods = true:none csharp_style_expression_bodied_constructors = true:none csharp_style_expression_bodied_operators = true:none # Prefer property-like constructs to have an expression-body csharp_style_expression_bodied_properties = true:none csharp_style_expression_bodied_indexers = true:none csharp_style_expression_bodied_accessors = true:none # Suggest more modern language features when available csharp_style_pattern_matching_over_is_with_cast_check = true:error csharp_style_pattern_matching_over_as_with_null_check = true:error csharp_style_inlined_variable_declaration = true:suggestion csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Newline settings 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 # Test settings [**/*Tests*/**{.cs,.vb}] # xUnit1013: Public method should be marked as test. Allows using records as test classes dotnet_diagnostic.xUnit1013.severity = none # CS9113: Parameter is unread (usually, ITestOutputHelper) dotnet_diagnostic.CS9113.severity = none # Default severity for analyzer diagnostics with category 'Style' dotnet_analyzer_diagnostic.category-Style.severity = none # VSTHRD200: Use "Async" suffix for async methods dotnet_diagnostic.VSTHRD200.severity = none ================================================ FILE: .gitattributes ================================================ # normalize by default * text=auto encoding=UTF-8 *.sh text eol=lf *.sbn eol=lf # These are windows specific files which we may as well ensure are # always crlf on checkout *.bat text eol=crlf *.cmd text eol=crlf ================================================ FILE: .github/FUNDING.yml ================================================ github: devlooped ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: Questions url: https://github.com/devlooped/SmallSharp/discussions?discussions_q=category%3AQ%26A about: Want to know how to do something? Check out the Discussions > Q&A area! - name: Ideas url: https://github.com/devlooped/SmallSharp/discussions?discussions_q=category%3AIdeas about: Suggest an idea to make the project better through the Discussions > Ideas area! ================================================ FILE: .github/actions/dotnet/action.yml ================================================ name: ⚙ dotnet description: Configures dotnet if the repo/org defines the DOTNET custom property runs: using: composite steps: - name: 🔎 dotnet id: dotnet shell: bash run: | VERSIONS=$(gh api repos/${{ github.repository }}/properties/values | jq -r '.[] | select(.property_name == "DOTNET") | .value') # Remove extra whitespace from VERSIONS VERSIONS=$(echo "$VERSIONS" | tr -s ' ' | tr -d ' ') # Convert comma-separated to newline-separated NEWLINE_VERSIONS=$(echo "$VERSIONS" | tr ',' '\n') # Validate versions while IFS= read -r version; do if ! [[ $version =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?(\.x)?$ ]]; then echo "Error: Invalid version format: $version" exit 1 fi done <<< "$NEWLINE_VERSIONS" # Write multiline output to $GITHUB_OUTPUT { echo 'versions<> $GITHUB_OUTPUT - name: ⚙ dotnet if: steps.dotnet.outputs.versions != '' uses: actions/setup-dotnet@v4 with: dotnet-version: | ${{ steps.dotnet.outputs.versions }} ================================================ FILE: .github/copilot-instructions.md ================================================ # .NET Repository **Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.** ## Working Effectively ### Essential Build Commands - **Restore dependencies**: `dotnet restore` - **Build the entire solution**: `dotnet build` - **Run tests**: `dnx --yes retest` - Runs all unit tests across the solution - If tests fail due to Azure Storage, run the following commands and retry: `npm install azurite` and `npx azurite &` ### Build Validation and CI Requirements - **Always run before committing**: * `dnx --yes retest` * `dotnet format whitespace -v:diag --exclude ~/.nuget` * `dotnet format style -v:diag --exclude ~/.nuget` ### Project Structure and Navigation | Directory | Description | |-----------|-------------| | `src/` | Contains the repo source code. | | `bin/` | Contains built packages (if any) | ### Code Style and Formatting #### EditorConfig Rules The repository uses `.editorconfig` at the repo root for consistent code style. - **Indentation**: 4 spaces for C# files, 2 spaces for XML/YAML/JSON - **Line endings**: LF (Unix-style) - **Sort using directives**: System.* namespaces first (`dotnet_sort_system_directives_first = true`) - **Type references**: Prefer language keywords over framework type names (`int` vs `Int32`) - **Modern C# features**: Use object/collection initializers, coalesce expressions when possible, use var when the type is apparent from the right-hand side of the assignment - **Visibility modifiers**: only explicitly specify visibility when different from the default (e.g. `public` for classes, no `internal` for classes or `private` for fields, etc.) #### Formatting Validation - CI enforces formatting with `dotnet format whitespace` and `dotnet format style` - Run locally: `dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget` - Fix formatting: `dotnet format` (without `--verify-no-changes`) ### Testing Practices #### Test Framework - **xUnit** for all unit and integration tests - **Moq** for mocking dependencies - Located in `src/*.Tests/` #### Test Attributes Custom xUnit attributes are sometimes used for conditional test execution: - `[SecretsFact("XAI_API_KEY")]` - Skips test if required secrets are missing from user secrets or environment variables - `[LocalFact("SECRET")]` - Runs only locally (skips in CI), requires specified secrets - `[CIFact]` - Runs only in CI environment ### Dependency Management #### Adding Dependencies - Add to appropriate `.csproj` file - Run `dotnet restore` to update dependencies - Ensure version consistency across projects where applicable #### CI/CD Pipeline - **Build workflow**: `.github/workflows/build.yml` - runs on PR and push to main/rel/feature branches - **Publish workflow**: Publishes to Sleet feed when `SLEET_CONNECTION` secret is available - **OS matrix**: Configured in `.github/workflows/os-matrix.json` (defaults to ubuntu-latest) ### Special Files and Tools #### dnx Command - **Purpose**: built-in tool for running arbitrary dotnet tools that are published on nuget.org. `--yes` auto-confirms install before run. - **Example**: `dnx --yes retest` - runs tests with automatic retry on transient failures (retest being a tool package published at https://www.nuget.org/packages/retest) - **In CI**: `dnx --yes retest -- --no-build` (skips build, runs tests only) #### Directory.Build.rsp - MSBuild response file with default build arguments - `-nr:false` - disables node reuse - `-m:1` - single-threaded build (for stability) - `-v:m` - minimal verbosity #### Code Quality - All PRs must pass format validation - Tests must pass on all target frameworks - Follow existing patterns and conventions in the codebase ## Documenting Work Project implemention details, design and key decisions should be documented in a top-level AGENTS.md file at the repo root. Keep this file updated whenever you make change significant changes for future reference. User-facing features and APIs should be documented to highlight (not extensively, as an overview) key project features and capabilities, in the readme.md file at the repo root. ================================================ FILE: .github/dependabot.yml ================================================ # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: nuget directory: / schedule: interval: daily groups: Azure: patterns: - "Azure*" - "Microsoft.Azure*" Identity: patterns: - "System.IdentityModel*" - "Microsoft.IdentityModel*" System: patterns: - "System*" exclude-patterns: - "System.IdentityModel*" Extensions: patterns: - "Microsoft.Extensions*" exclude-patterns: - "Microsoft.Extensions.AI*" ExtensionsAI: patterns: - "Microsoft.Extensions.AI*" Web: patterns: - "Microsoft.AspNetCore*" Tests: patterns: - "Microsoft.NET.Test*" - "xunit*" - "coverlet*" ThisAssembly: patterns: - "ThisAssembly*" ProtoBuf: patterns: - "protobuf-*" Spectre: patterns: - "Spectre.Console*" ================================================ FILE: .github/dotnet.json ================================================ [ "10.x" ] ================================================ FILE: .github/release.yml ================================================ changelog: exclude: labels: - bydesign - dependencies - duplicate - question - invalid - wontfix - need info - techdebt authors: - devlooped-bot - dependabot - github-actions categories: - title: ✨ Implemented enhancements labels: - enhancement - title: 🐛 Fixed bugs labels: - bug - title: 📝 Documentation updates labels: - docs - documentation - title: 🔨 Other labels: - '*' exclude: labels: - dependencies ================================================ FILE: .github/workflows/build.yml ================================================ # Builds and runs tests in all three supported OSes # Pushes CI feed if secrets.SLEET_CONNECTION is provided name: build on: workflow_dispatch: inputs: configuration: type: choice description: Configuration options: - Release - Debug push: branches: [ main, 'feature/*', 'rel/*' ] paths-ignore: - changelog.md - readme.md pull_request: types: [opened, synchronize, reopened] env: DOTNET_NOLOGO: true PackOnBuild: true GeneratePackageOnBuild: true VersionPrefix: 42.42.${{ github.run_number }} VersionLabel: ${{ github.ref }} GH_TOKEN: ${{ secrets.GH_TOKEN }} MSBUILDTERMINALLOGGER: auto Configuration: ${{ github.event.inputs.configuration || 'Release' }} SLEET_FEED_URL: ${{ vars.SLEET_FEED_URL }} defaults: run: shell: bash jobs: os-matrix: runs-on: ubuntu-latest outputs: matrix: ${{ steps.lookup.outputs.matrix }} steps: - name: 🤘 checkout uses: actions/checkout@v4 - name: 🔎 lookup id: lookup shell: pwsh run: | $path = './.github/workflows/os-matrix.json' $os = if (test-path $path) { cat $path } else { '["ubuntu-latest"]' } echo "matrix=$os" >> $env:GITHUB_OUTPUT build: needs: os-matrix name: build-${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: ${{ fromJSON(needs.os-matrix.outputs.matrix) }} steps: - name: 🤘 checkout uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: ⚙ dotnet uses: devlooped/actions-dotnet-env@v1 - name: 🙏 build run: dotnet build -m:1 -bl:build.binlog - name: 🧪 test shell: pwsh run: dnx --yes retest -- --no-build - name: 🐛 logs uses: actions/upload-artifact@v4 if: runner.debug && always() with: name: logs path: '*.binlog' - name: 🚀 sleet env: SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }} if: env.SLEET_CONNECTION != '' run: | dotnet tool update sleet -g --allow-downgrade --version $(curl -s --compressed ${{ vars.SLEET_FEED_URL }} | jq '.["sleet:version"]' -r) sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found" dotnet-format: runs-on: ubuntu-latest steps: - name: 🤘 checkout uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: ⚙ dotnet uses: devlooped/actions-dotnet-env@v1 - name: ✓ ensure format run: | dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget dotnet format style --verify-no-changes -v:diag --exclude ~/.nuget ================================================ FILE: .github/workflows/changelog.config ================================================ usernames-as-github-logins=true issues_wo_labels=true pr_wo_labels=true exclude-labels=bydesign,dependencies,duplicate,discussion,question,invalid,wontfix,need info,docs enhancement-label=:sparkles: Implemented enhancements: bugs-label=:bug: Fixed bugs: issues-label=:hammer: Other: pr-label=:twisted_rightwards_arrows: Merged: unreleased=false ================================================ FILE: .github/workflows/changelog.yml ================================================ name: changelog on: workflow_dispatch: release: types: [released] jobs: changelog: runs-on: ubuntu-latest steps: - name: 🤖 defaults uses: devlooped/actions-bot@v1 with: name: ${{ secrets.BOT_NAME }} email: ${{ secrets.BOT_EMAIL }} gh_token: ${{ secrets.GH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: 🤘 checkout uses: actions/checkout@v4 with: fetch-depth: 0 ref: main token: ${{ env.GH_TOKEN }} - name: ⚙ ruby uses: ruby/setup-ruby@v1 with: ruby-version: 3.0.3 - name: ⚙ changelog run: | gem install github_changelog_generator github_changelog_generator --user ${GITHUB_REPOSITORY%/*} --project ${GITHUB_REPOSITORY##*/} --token $GH_TOKEN --o changelog.md --config-file .github/workflows/changelog.config - name: 🚀 changelog run: | git add changelog.md (git commit -m "🖉 Update changelog with ${GITHUB_REF#refs/*/}" && git push) || echo "Done" ================================================ FILE: .github/workflows/combine-prs.yml ================================================ # Source: https://github.com/hrvey/combine-prs-workflow # Tweaks: regex support for branch name: '⛙ combine-prs' on: workflow_dispatch: inputs: branchExpression: description: 'Regular expression to match against PR branches to find combinable PRs' required: true default: 'dependabot' mustBeGreen: description: 'Only combine PRs that are green (status is success)' required: true default: true combineTitle: description: 'Title of the combined PR' required: true default: '⬆️ Bump dependencies' combineBranchName: description: 'Name of the branch to combine PRs into' required: true default: 'combine-prs' ignoreLabel: description: 'Exclude PRs with this label' required: true default: 'nocombine' jobs: combine-prs: name: ${{ github.event.inputs.combineBranchName }} runs-on: ubuntu-latest steps: - uses: actions/github-script@v6 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', { owner: context.repo.owner, repo: context.repo.repo }); const branchRegExp = new RegExp(`${{github.event.inputs.branchExpression}}`); let branchesAndPRStrings = []; let baseBranch = null; let baseBranchSHA = null; for (const pull of pulls) { const branch = pull['head']['ref']; console.log('Pull for branch: ' + branch); if (branchRegExp.test(branch)) { console.log('Branch matched: ' + branch); let statusOK = true; if(${{ github.event.inputs.mustBeGreen }}) { console.log('Checking green status: ' + branch); const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number:$pull_number) { commits(last: 1) { nodes { commit { statusCheckRollup { state } } } } } } }` const vars = { owner: context.repo.owner, repo: context.repo.repo, pull_number: pull['number'] }; const result = await github.graphql(stateQuery, vars); const [{ commit }] = result.repository.pullRequest.commits.nodes; const state = commit.statusCheckRollup.state console.log('Validating status: ' + state); if(state != 'SUCCESS') { console.log('Discarding ' + branch + ' with status ' + state); statusOK = false; } } console.log('Checking labels: ' + branch); const labels = pull['labels']; for(const label of labels) { const labelName = label['name']; console.log('Checking label: ' + labelName); if(labelName == '${{ github.event.inputs.ignoreLabel }}') { console.log('Discarding ' + branch + ' with label ' + labelName); statusOK = false; } } if (statusOK) { console.log('Adding branch to array: ' + branch); const prString = '#' + pull['number'] + ' ' + pull['title']; branchesAndPRStrings.push({ branch, prString }); baseBranch = pull['base']['ref']; baseBranchSHA = pull['base']['sha']; } } } if (branchesAndPRStrings.length == 0) { core.setFailed('No PRs/branches matched criteria'); return; } if (branchesAndPRStrings.length == 1) { core.setFailed('Only one PR/branch matched criteria'); return; } try { await github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}', sha: baseBranchSHA }); } catch (error) { console.log(error); core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?'); return; } let combinedPRs = []; let mergeFailedPRs = []; for(const { branch, prString } of branchesAndPRStrings) { try { await github.rest.repos.merge({ owner: context.repo.owner, repo: context.repo.repo, base: '${{ github.event.inputs.combineBranchName }}', head: branch, }); console.log('Merged branch ' + branch); combinedPRs.push(prString); } catch (error) { console.log('Failed to merge branch ' + branch); mergeFailedPRs.push(prString); } } console.log('Creating combined PR'); const combinedPRsString = combinedPRs.join('\n'); let body = '⛙ Combined PRs:\n' + combinedPRsString; if(mergeFailedPRs.length > 0) { const mergeFailedPRsString = mergeFailedPRs.join('\n'); body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString } await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, title: '⛙ ${{github.event.inputs.combineTitle}}', head: '${{ github.event.inputs.combineBranchName }}', base: baseBranch, body: body }); ================================================ FILE: .github/workflows/demos.yml ================================================ name: demos on: push: branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ] paths-ignore: - changelog.md - readme.md pull_request: types: [opened, synchronize, reopened] env: DOTNET_NOLOGO: true PackOnBuild: true GeneratePackageOnBuild: true VersionPrefix: 42.42.${{ github.run_number }} VersionLabel: ${{ github.ref }} GH_TOKEN: ${{ secrets.GH_TOKEN }} MSBUILDTERMINALLOGGER: auto defaults: run: shell: pwsh jobs: demos: runs-on: ubuntu-latest steps: - name: 🤘 checkout uses: actions/checkout@v4 - name: ⚙ dotnet uses: devlooped/actions-dotnet-env@v1 - name: 🙏 build run: dotnet build -m:1 -bl:build.binlog - name: 🚀 demo run: | # detect and set the version of the SDK we just built $version = gci bin | select -first 1 -expandproperty BaseName | %{ $_.Substring(11) } pushd src/Demo jq --arg version "$version" '.["msbuild-sdks"].SmallSharp = $version' global.json > temp.json && mv temp.json global.json # build with each top-level file as the active one foreach ($file in gci *.cs) { dotnet build -p:ActiveFile=$($file.Name) if ($LASTEXITCODE -ne 0) { Write-Error "Build failed for $($file.Name)" exit $LASTEXITCODE } } ================================================ FILE: .github/workflows/dotnet-env.yml ================================================ name: dotnet-env on: workflow_dispatch: push: branches: - main paths: - '**/*.*proj' jobs: which-dotnet: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - name: 🤖 defaults uses: devlooped/actions-bot@v1 with: name: ${{ secrets.BOT_NAME }} email: ${{ secrets.BOT_EMAIL }} gh_token: ${{ secrets.GH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: 🤘 checkout uses: actions/checkout@v4 with: token: ${{ env.GH_TOKEN }} - name: 🤌 dotnet uses: devlooped/actions-which-dotnet@v1 - name: ✍ pull request uses: peter-evans/create-pull-request@v7 with: base: main branch: which-dotnet delete-branch: true labels: dependencies title: "⚙ Update dotnet versions" body: "Update dotnet versions" commit-message: "Update dotnet versions" token: ${{ env.GH_TOKEN }} ================================================ FILE: .github/workflows/dotnet-file.yml ================================================ # Synchronizes .netconfig-configured files with dotnet-file name: dotnet-file on: workflow_dispatch: schedule: - cron: "0 0 * * *" push: branches: [ 'dotnet-file' ] env: DOTNET_NOLOGO: true jobs: run: permissions: contents: write uses: devlooped/oss/.github/workflows/dotnet-file-core.yml@main secrets: BOT_NAME: ${{ secrets.BOT_NAME }} BOT_EMAIL: ${{ secrets.BOT_EMAIL }} GH_TOKEN: ${{ secrets.GH_TOKEN }} ================================================ FILE: .github/workflows/includes.yml ================================================ name: +Mᐁ includes on: workflow_dispatch: push: branches: - 'main' paths: - '**.md' - '!changelog.md' - 'osmfeula.txt' jobs: includes: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - name: 🤖 defaults uses: devlooped/actions-bot@v1 with: name: ${{ secrets.BOT_NAME }} email: ${{ secrets.BOT_EMAIL }} gh_token: ${{ secrets.GH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: 🤘 checkout uses: actions/checkout@v4 with: token: ${{ env.GH_TOKEN }} - name: +Mᐁ includes uses: devlooped/actions-includes@v1 - name: 📝 OSMF EULA shell: pwsh run: | $file = "osmfeula.txt" $props = "src/Directory.Build.props" if (-not (test-path $file) -or -not (test-path $props)) { exit 0 } $product = dotnet msbuild $props -getproperty:Product if (-not $product) { write-error 'To use OSMF EULA, ensure the $(Product) property is set in Directory.props' exit 1 } ((get-content -raw $file) -replace '\$product\$',$product).trim() | set-content $file - name: ✍ pull request uses: peter-evans/create-pull-request@v8 with: add-paths: | **.md *.txt base: main branch: markdown-includes delete-branch: true labels: dependencies author: ${{ env.BOT_AUTHOR }} committer: ${{ env.BOT_AUTHOR }} commit-message: +Mᐁ includes title: +Mᐁ includes body: +Mᐁ includes token: ${{ env.GH_TOKEN }} ================================================ FILE: .github/workflows/pages.yml ================================================ # Workflow to cross-post a jekyll site (or GitHub Pages) # to another org/repo. # Required secrets in repository consuming this workflow: # - PAGES_ORGANIZATION: the target organization to publish # pages to. # - PAGES_ACCESS_TOKEN: a token that is valid in the target # org/repo for pushing the resulting site # - PAGES_REPOSITORY: optional repository name under the # target organization. Defaults to source repo name. name: pages on: workflow_dispatch: push: branches: - main - pages - docs env: PAGES_ORGANIZATION: ${{ secrets.PAGES_ORGANIZATION }} PAGES_REPOSITORY: ${{ secrets.PAGES_REPOSITORY }} jobs: gh-pages: runs-on: ubuntu-latest env: PAGES_ORGANIZATION: ${{ secrets.PAGES_ORGANIZATION }} PAGES_REPOSITORY: ${{ secrets.PAGES_REPOSITORY }} PAGES_ACCESS_TOKEN: ${{ secrets.PAGES_ACCESS_TOKEN }} steps: - name: ✅ organization if: env.PAGES_ORGANIZATION == '' run: | echo "::error title=PAGES_ORGANIZATION secret is required." exit 1 - name: ✅ token if: env.PAGES_ACCESS_TOKEN == '' run: | echo "::error title=PAGES_ACCESS_TOKEN secret is required." exit 1 - name: 🤘 checkout uses: actions/checkout@v2 - name: ⚙ jekyll run: | sudo gem install bundler sudo bundle install - name: 🖉 default repo if: env.PAGES_REPOSITORY == '' run: echo "PAGES_REPOSITORY=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV - name: 🙏 build run: bundle exec jekyll build -b ${{ env.PAGES_REPOSITORY }} env: JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: ✓ commit run: | cd _site git init git add -A git config --local user.email "bot@clarius.org" git config --local user.name "bot@clarius.org" git commit -m "Publish pages from ${GITHUB_REPOSITORY}@${GITHUB_SHA:0:9}" - name: 🚀 push uses: ad-m/github-push-action@v0.6.0 with: github_token: ${{ env.PAGES_ACCESS_TOKEN }} repository: ${{ env.PAGES_ORGANIZATION }}/${{ env.PAGES_REPOSITORY }} branch: gh-pages force: true directory: ./_site ================================================ FILE: .github/workflows/publish.yml ================================================ # Builds a final release version and pushes to nuget.org # whenever a release is published. # Requires: secrets.NUGET_API_KEY name: publish on: release: types: [prereleased, released] env: DOTNET_NOLOGO: true Configuration: Release PackOnBuild: true GeneratePackageOnBuild: true VersionLabel: ${{ github.ref }} GH_TOKEN: ${{ secrets.GH_TOKEN }} MSBUILDTERMINALLOGGER: auto SLEET_FEED_URL: https://api.nuget.org/v3/index.json jobs: publish: runs-on: ${{ vars.PUBLISH_AGENT || 'ubuntu-latest' }} steps: - name: 🤘 checkout uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: ⚙ dotnet uses: devlooped/actions-dotnet-env@v1 - name: 🙏 build run: dotnet build -m:1 -bl:build.binlog - name: 🧪 test shell: pwsh run: dnx --yes retest -- --no-build - name: 🐛 logs uses: actions/upload-artifact@v4 if: runner.debug && always() with: name: logs path: '*.binlog' - name: 🚀 nuget env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} if: ${{ env.NUGET_API_KEY != '' && github.event.action != 'prereleased' }} working-directory: bin run: dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate - name: 🚀 sleet env: SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }} if: env.SLEET_CONNECTION != '' run: | dotnet tool update sleet -g --allow-downgrade --version $(curl -s --compressed ${{ vars.SLEET_FEED_URL }} | jq '.["sleet:version"]' -r) sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found" ================================================ FILE: .github/workflows/triage.yml ================================================ name: 'triage' on: schedule: - cron: '42 0 * * *' workflow_dispatch: # Manual triggering through the GitHub UI, API, or CLI inputs: daysBeforeClose: description: "Days before closing stale or need info issues" required: true default: "30" daysBeforeStale: description: "Days before labeling stale" required: true default: "180" daysSinceClose: description: "Days since close to lock" required: true default: "30" daysSinceUpdate: description: "Days since update to lock" required: true default: "30" permissions: actions: write # For managing the operation state cache issues: write contents: read jobs: stale: # Do not run on forks if: github.repository_owner == 'devlooped' runs-on: ubuntu-latest steps: - name: ⌛ rate shell: pwsh if: github.event_name != 'workflow_dispatch' env: GH_TOKEN: ${{ secrets.DEVLOOPED_TOKEN }} run: | # add random sleep since we run on fixed schedule $wait = get-random -max 180 echo "Waiting random $wait seconds to start" sleep $wait # get currently authenticated user rate limit info $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate # if we don't have at least 100 requests left, wait until reset if ($rate.remaining -lt 100) { $wait = ($rate.reset - (Get-Date (Get-Date).ToUniversalTime() -UFormat %s)) if ($wait -gt 300) { echo "Rate limit remaining is $($rate.remaining), reset in $wait seconds (more than 5'). Aborting." exit 1 } echo "Rate limit remaining is $($rate.remaining), waiting $wait seconds to reset" sleep $wait $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate echo "Rate limit has reset to $($rate.remaining) requests" } - name: ✏️ stale labeler # pending merge: https://github.com/actions/stale/pull/1176 uses: kzu/stale@c8450312ba97b204bf37545cb249742144d6ca69 with: ascending: true # Process the oldest issues first stale-issue-label: 'stale' stale-issue-message: | Due to lack of recent activity, this issue has been labeled as 'stale'. It will be closed if no further activity occurs within ${{ fromJson(inputs.daysBeforeClose || 30 ) }} more days. Any new comment will remove the label. close-issue-message: | This issue will now be closed since it has been labeled 'stale' without activity for ${{ fromJson(inputs.daysBeforeClose || 30 ) }} days. days-before-stale: ${{ fromJson(inputs.daysBeforeStale || 180) }} days-before-close: ${{ fromJson(inputs.daysBeforeClose || 30 ) }} days-before-pr-close: -1 # Do not close PRs labeled as 'stale' exempt-all-milestones: true exempt-all-assignees: true exempt-issue-labels: priority,sponsor,backed exempt-authors: kzu - name: 🤘 checkout actions uses: actions/checkout@v4 with: repository: 'microsoft/vscode-github-triage-actions' ref: v42 - name: ⚙ install actions run: npm install --production - name: 🔒 issues locker uses: ./locker with: token: ${{ secrets.DEVLOOPED_TOKEN }} ignoredLabel: priority daysSinceClose: ${{ fromJson(inputs.daysSinceClose || 30) }} daysSinceUpdate: ${{ fromJson(inputs.daysSinceUpdate || 30) }} - name: 🔒 need info closer uses: ./needs-more-info-closer with: token: ${{ secrets.DEVLOOPED_TOKEN }} label: 'need info' closeDays: ${{ fromJson(inputs.daysBeforeClose || 30) }} closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity.\n\nHappy Coding!" pingDays: 80 pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information." ================================================ FILE: .gitignore ================================================ bin obj artifacts pack TestResults results BenchmarkDotNet.Artifacts /app .vs .vscode .genaiscript .idea local.settings.json .env *.local *.suo *.sdf *.userprefs *.user *.nupkg *.metaproj *.tmp *.log *.cache *.binlog *.zip __azurite*.* __*__ .nuget *.lock.json *.nuget.props *.nuget.targets node_modules _site .jekyll-metadata .jekyll-cache .sass-cache Gemfile.lock package-lock.json ================================================ FILE: .netconfig ================================================ [file] url = https://github.com/devlooped/oss url = https://github.com/clarius/pages [file ".netconfig"] url = https://github.com/devlooped/oss/blob/main/.netconfig skip [file "src/icon.png"] url = https://github.com/devlooped/oss/blob/main/src/icon.png skip [file "readme.md"] url = https://github.com/devlooped/oss/blob/main/readme.md skip [file ".github/ISSUE_TEMPLATE/config.yml"] url = https://github.com/devlooped/oss/blob/main/.github/ISSUE_TEMPLATE/config.yml skip [file "SponsorLink.sln"] url = https://github.com/devlooped/oss/blob/main/SponsorLink.sln skip [file ".editorconfig"] url = https://github.com/devlooped/oss/blob/main/.editorconfig etag = b5e919b472a52d4b522f86494f0f2c0ba74a6d9601454e20e4cbaf744317ff62 weak sha = a62c45934ac2952f2f5d54d8aea4a7ebc1babaff [file ".gitattributes"] url = https://github.com/devlooped/oss/blob/main/.gitattributes etag = 09cad18280ed04b67f7f87591e5481510df04d44c3403231b8af885664d8fd58 weak sha = 4a9aa321c4982b83c185cf8dffed181ff84667d5 [file ".github/FUNDING.yml"] url = https://github.com/devlooped/.github/blob/main/.github/FUNDING.yml etag = a944c728facd033bbdfb4ff8d0ef10d0b3a457c277dc499458df0ffc7e6409da weak sha = 39f4c591716ff50dd035d2fade35e5822489ab7f [file ".github/dependabot.yml"] url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml etag = 3bf8d9214a15c049ca5cfe80d212a8cbe4753b8a638a9804ef73d34c7def9618 weak sha = e733294084fb3e75d517a2e961e87df8faae7dc6 [file ".github/workflows/build.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml etag = 851af098748f7cfa5bc3cfd4cc404a6de930532b59ceb2b3b535282c41226f3a weak sha = 5da103cfbc1c4f9b5f59cfa698d2afbd744a7525 [file ".github/workflows/changelog.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml etag = ad1efa56d6024ee1add2bcda81a7e4e38d0e9069473c6ff70374d5ce06af1f5a weak sha = 5fb172362c767bef7c36478f1a6bdc264723f8f9 [file ".github/workflows/publish.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml etag = c60411d1aa4e98e7f69e2d34cbccb8eb7e387ec11f6f8e78ee8d8b92122d7025 weak sha = 7f5f9ee9f362f7e8f951d618f8f799033550e687 [file ".github/workflows/release-artifacts.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/release-artifacts.yml skip [file ".gitignore"] url = https://github.com/devlooped/oss/blob/main/.gitignore etag = 20a8b49d57024abbd85aac5b0020c30e5eb68e0384b2761e93727c8780c4a991 weak sha = a225b7a9f609f26bcc24e0d84f663387be251a7d [file "Directory.Build.rsp"] url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp etag = 0ccae83fc51f400bfd7058170bfec7aba11455e24a46a0d7e6a358da6486e255 weak sha = 0f7f7f7e8a29de9b535676f75fe7c67e629a5e8c [file "_config.yml"] url = https://github.com/devlooped/oss/blob/main/_config.yml etag = d608aa0ddaedc2d8a87260f50756e8d8314964ad4671b76bd085bcb458757010 weak sha = 68b409c486842062e0de0e5b11e6fdb7cd12d6e2 [file "assets/css/style.scss"] url = https://github.com/devlooped/oss/blob/main/assets/css/style.scss etag = f710d8919abfd5a8d00050b74ba7d0bb05c6d02e40842a3012eb96555c208504 weak sha = 9db26e2710b084d219d6355339d822f159bf5780 [file "license.txt"] url = https://github.com/devlooped/oss/blob/main/license.txt etag = 2c6335b37e4ae05eea7c01f5d0c9d82b49c488f868a8b5ba7bff7c6ff01f3994 weak sha = 0683ee777d7d878d4bf013d7deea352685135a05 [file "src/Directory.Build.props"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props etag = bd05f9f240823c0ac79ddfefe654061550c36f82dd94fa513b82900e92686a5f weak sha = 4b84c540df6f37c30fb38df7947d79871c34f036 [file "src/Directory.Build.targets"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets etag = 907682e5632a2ba430357e6e042a4ca33cb8c94a3a215d3091aa03f5958a4877 weak sha = 4b84c540df6f37c30fb38df7947d79871c34f036 [file "src/kzu.snk"] url = https://github.com/devlooped/oss/blob/main/src/kzu.snk skip [file ".github/workflows/pages.yml"] url = https://github.com/clarius/pages/blob/main/.github/workflows/pages.yml etag = c52b3f0463b88abf696ddf2c6902675e0bc9d1e812bb317cb758221d75330b56 weak sha = afcb0421af6507eba5ceba913b8fc37261efc085 [file "Gemfile"] url = https://github.com/clarius/pages/blob/main/Gemfile etag = 3dd7febc8ae6760f19abfe787711f469c288cd803a6f1c545edec34264d48e71 weak sha = 90fa16ed0e7300a78a38ee1d23c34a7e875aab27 [file ".github/workflows/dotnet-file.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file.yml sha = 8fa147d4799d73819040736c399d0b1db2c2d86c etag = 1ca805a23656e99c03f9d478dba8ccef6e571f5de2ac0e9bb7e3c5216c99a694 weak [file ".github/workflows/includes.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml sha = 06628725a6303bb8c4cf3076a384fc982a91bc0b etag = 478f91d4126230e57cc601382da1ba23f9daa054645b4af89800d8dd862e64fd weak [file ".github/workflows/combine-prs.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/combine-prs.yml sha = c1610886eba42cb250e3894aed40c0a258cd383d etag = 598ee294649a44d4c5d5934416c01183597d08aa7db7938453fd2bbf52a4e64d weak [file ".github/workflows/changelog.config"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.config sha = 08d83cb510732f861416760d37702f9f55bd7f9e etag = 556a28914eeeae78ca924b1105726cdaa211af365671831887aec81f5f4301b4 weak [file ".github/release.yml"] url = https://github.com/devlooped/oss/blob/main/.github/release.yml sha = 0c23e24704625cf75b2cb1fdc566cef7e20af313 etag = 310df162242c95ed19ed12e3c96a65f77e558b46dced676ad5255eb12caafe75 weak [file ".github/workflows/triage.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/triage.yml sha = 33000c0c4ab4eb4e0e142fa54515b811a189d55c etag = 152cd3a559c08da14d1da12a5262ba1d2e0ed6bed6d2eabf5bd209b0c35d8a75 weak [file ".github/workflows/dotnet-file-core.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file-core.yml skip [file ".github/actions/dotnet/action.yml"] url = https://github.com/devlooped/oss/blob/main/.github/actions/dotnet/action.yml sha = f2b690ce307acb76c5b8d7faec1a5b971a93653e etag = 27ea11baa2397b3ec9e643a935832da97719c4e44215cfd135c49cad4c29373f weak [file ".github/workflows/dotnet-env.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-env.yml sha = 77e83f238196d2723640abef0c7b6f43994f9747 etag = fcb9759a96966df40dcd24906fd328ddec05953b7e747a6bb8d0d1e4c3865274 weak [file "src/nuget.config"] url = https://github.com/devlooped/oss/blob/main/src/nuget.config sha = 032439dbf180fca0539a5bd3a019f18ab3484b76 etag = da7c0104131bd474b52fc9bc9f9bda6470e24ae38d4fb9f5c4f719bc01370ab5 weak [file "readme.tmp.md"] url = https://github.com/devlooped/oss/blob/main/readme.tmp.md skip [file "oss.cs"] url = https://github.com/devlooped/oss/blob/main/oss.cs skip [file ".github/copilot-instructions.md"] url = https://github.com/devlooped/oss/blob/main/.github/copilot-instructions.md etag = 6ee650d118a57494d3545d54718ccaa5257b09d54504e9c21514fe596edd9678 weak ================================================ FILE: Directory.Build.rsp ================================================ # See https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files -nr:false -m:1 -v:m -clp:Summary;ForceNoAlign ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' gem 'github-pages', '~> 231', group: :jekyll_plugins ================================================ FILE: SmallSharp.slnx ================================================ ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-slate exclude: [ 'src/', '*.sln', '*.slnx', 'Gemfile*', '*.rsp' ] ================================================ FILE: assets/css/style.scss ================================================ --- --- @import "jekyll-theme-slate"; .inner { max-width: 960px; } pre, code { background-color: unset; font-size: unset; } code { font-size: 0.80em; } h1 > img { border: unset; box-shadow: unset; vertical-align: middle; -moz-box-shadow: unset; -o-box-shadow: unset; -ms-box-shadow: unset; } ================================================ FILE: changelog.md ================================================ # Changelog ## [v2.3.1](https://github.com/devlooped/SmallSharp/tree/v2.3.1) (2026-04-09) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.3.0...v2.3.1) :sparkles: Implemented enhancements: - Add support for linked files [\#174](https://github.com/devlooped/SmallSharp/pull/174) (@kzu) ## [v2.3.0](https://github.com/devlooped/SmallSharp/tree/v2.3.0) (2025-12-17) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.2.5...v2.3.0) :sparkles: Implemented enhancements: - Ensure properties are always set in props+targets [\#171](https://github.com/devlooped/SmallSharp/pull/171) (@kzu) ## [v2.2.5](https://github.com/devlooped/SmallSharp/tree/v2.2.5) (2025-10-16) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.2.4...v2.2.5) ## [v2.2.4](https://github.com/devlooped/SmallSharp/tree/v2.2.4) (2025-09-24) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.2.3...v2.2.4) :sparkles: Implemented enhancements: - Ensure when running from VS, output supports UTF-8 [\#161](https://github.com/devlooped/SmallSharp/pull/161) (@kzu) ## [v2.2.3](https://github.com/devlooped/SmallSharp/tree/v2.2.3) (2025-09-24) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.2.2...v2.2.3) :sparkles: Implemented enhancements: - Switch to $\(Start\) to select startup file [\#160](https://github.com/devlooped/SmallSharp/pull/160) (@kzu) :bug: Fixed bugs: - Don't warn during restore about duplicate package refs [\#159](https://github.com/devlooped/SmallSharp/pull/159) (@kzu) :twisted_rightwards_arrows: Merged: - Upgrade to latest SLNX format [\#158](https://github.com/devlooped/SmallSharp/pull/158) (@kzu) ## [v2.2.2](https://github.com/devlooped/SmallSharp/tree/v2.2.2) (2025-09-24) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.2.1...v2.2.2) :twisted_rightwards_arrows: Merged: - Emit package references in SDK mode too [\#157](https://github.com/devlooped/SmallSharp/pull/157) (@kzu) ## [v2.2.1](https://github.com/devlooped/SmallSharp/tree/v2.2.1) (2025-09-10) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.2.0...v2.2.1) :bug: Fixed bugs: - Fix error when file has no package references [\#153](https://github.com/devlooped/SmallSharp/pull/153) (@kzu) :twisted_rightwards_arrows: Merged: - Improve directive matching by using named groups [\#154](https://github.com/devlooped/SmallSharp/pull/154) (@kzu) ## [v2.2.0](https://github.com/devlooped/SmallSharp/tree/v2.2.0) (2025-09-10) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.1.0...v2.2.0) :sparkles: Implemented enhancements: - Align \#:property Name=Value with RC syntax [\#152](https://github.com/devlooped/SmallSharp/pull/152) (@kzu) :bug: Fixed bugs: - We only ever support C\#, so use .cs extension [\#151](https://github.com/devlooped/SmallSharp/pull/151) (@kzu) ## [v2.1.0](https://github.com/devlooped/SmallSharp/tree/v2.1.0) (2025-09-04) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.1.0-beta...v2.1.0) ## [v2.1.0-beta](https://github.com/devlooped/SmallSharp/tree/v2.1.0-beta) (2025-09-04) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.1.0-alpha...v2.1.0-beta) :sparkles: Implemented enhancements: - Improve automatic restore support in SDK mode [\#147](https://github.com/devlooped/SmallSharp/pull/147) (@kzu) :twisted_rightwards_arrows: Merged: - Demo can now rely on plain build working [\#145](https://github.com/devlooped/SmallSharp/pull/145) (@kzu) ## [v2.1.0-alpha](https://github.com/devlooped/SmallSharp/tree/v2.1.0-alpha) (2025-09-01) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.0.0...v2.1.0-alpha) :sparkles: Implemented enhancements: - Update package references instead of emitting duplicates [\#140](https://github.com/devlooped/SmallSharp/issues/140) - SmallSharp generated targets are not imported from dotnet build/run [\#139](https://github.com/devlooped/SmallSharp/issues/139) - Add support for \#:sdk directive and no restore failures [\#144](https://github.com/devlooped/SmallSharp/pull/144) (@kzu) - Ensure dotnet build works too [\#141](https://github.com/devlooped/SmallSharp/pull/141) (@kzu) :hammer: Other: - Add support for \#:sdk directive [\#143](https://github.com/devlooped/SmallSharp/issues/143) ## [v2.0.0](https://github.com/devlooped/SmallSharp/tree/v2.0.0) (2025-07-22) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.0.0-rc...v2.0.0) ## [v2.0.0-rc](https://github.com/devlooped/SmallSharp/tree/v2.0.0-rc) (2025-07-22) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v2.0.0-beta...v2.0.0-rc) ## [v2.0.0-beta](https://github.com/devlooped/SmallSharp/tree/v2.0.0-beta) (2025-07-22) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.3.0...v2.0.0-beta) :sparkles: Implemented enhancements: - Fix issue with properties containing spaces [\#130](https://github.com/devlooped/SmallSharp/pull/130) (@kzu) - Add support for \#:property directive [\#129](https://github.com/devlooped/SmallSharp/pull/129) (@kzu) - Clean launchsettings.json if explicitly cleaned [\#128](https://github.com/devlooped/SmallSharp/pull/128) (@kzu) - Add run file \#:package support [\#125](https://github.com/devlooped/SmallSharp/pull/125) (@kzu) ## [v1.3.0](https://github.com/devlooped/SmallSharp/tree/v1.3.0) (2025-07-19) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.2.0...v1.3.0) :sparkles: Implemented enhancements: - Remove brittle active document tracking [\#122](https://github.com/devlooped/SmallSharp/pull/122) (@kzu) ## [v1.2.0](https://github.com/devlooped/SmallSharp/tree/v1.2.0) (2024-07-17) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.7...v1.2.0) :sparkles: Implemented enhancements: - Make sure startup file is an up-to-date check input [\#115](https://github.com/devlooped/SmallSharp/pull/115) (@kzu) - Make sure file names are sorted alphabetically [\#112](https://github.com/devlooped/SmallSharp/pull/112) (@kzu) ## [v1.1.7](https://github.com/devlooped/SmallSharp/tree/v1.1.7) (2022-11-16) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.6...v1.1.7) :sparkles: Implemented enhancements: - Make SmallSharp a development dependency [\#95](https://github.com/devlooped/SmallSharp/issues/95) ## [v1.1.6](https://github.com/devlooped/SmallSharp/tree/v1.1.6) (2022-11-16) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.5...v1.1.6) :twisted_rightwards_arrows: Merged: - +M▼ includes [\#79](https://github.com/devlooped/SmallSharp/pull/79) (@github-actions[bot]) - +M▼ includes [\#76](https://github.com/devlooped/SmallSharp/pull/76) (@github-actions[bot]) ## [v1.1.5](https://github.com/devlooped/SmallSharp/tree/v1.1.5) (2022-02-03) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.4...v1.1.5) :sparkles: Implemented enhancements: - Simplify editing of launchSettings.json by using Devlooped.JsonPoke [\#64](https://github.com/devlooped/SmallSharp/issues/64) ## [v1.1.4](https://github.com/devlooped/SmallSharp/tree/v1.1.4) (2021-07-05) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.2...v1.1.4) :hammer: Other: - Include package readme for better discoverability in gallery [\#49](https://github.com/devlooped/SmallSharp/issues/49) ## [v1.1.2](https://github.com/devlooped/SmallSharp/tree/v1.1.2) (2021-05-17) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.1...v1.1.2) :sparkles: Implemented enhancements: - When trying to attach to the IDE ActiveDocument, increase wait between retries [\#41](https://github.com/devlooped/SmallSharp/issues/41) - Populate launchSettings with candidate top-level compile files [\#40](https://github.com/devlooped/SmallSharp/issues/40) ## [v1.1.1](https://github.com/devlooped/SmallSharp/tree/v1.1.1) (2021-04-08) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.1.0...v1.1.1) :bug: Fixed bugs: - Failure with VS 16.10 preview [\#36](https://github.com/devlooped/SmallSharp/issues/36) :hammer: Other: - Ensure Properties folder exists before updating the launchSettings.json [\#33](https://github.com/devlooped/SmallSharp/issues/33) :twisted_rightwards_arrows: Merged: - Ensure the directory for launchSettings.json exists [\#34](https://github.com/devlooped/SmallSharp/pull/34) (@kzu) ## [v1.1.0](https://github.com/devlooped/SmallSharp/tree/v1.1.0) (2021-02-15) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.0.3...v1.1.0) :sparkles: Implemented enhancements: - Adding new C\# file should result in a new startup file [\#9](https://github.com/devlooped/SmallSharp/issues/9) :twisted_rightwards_arrows: Merged: - 🗁 When adding new file, set as startup file [\#23](https://github.com/devlooped/SmallSharp/pull/23) (@kzu) ## [v1.0.3](https://github.com/devlooped/SmallSharp/tree/v1.0.3) (2021-02-12) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.0.2...v1.0.3) :sparkles: Implemented enhancements: - Set up Discussions for the project [\#21](https://github.com/devlooped/SmallSharp/issues/21) :bug: Fixed bugs: - Fix icon that was replaced with clarius.org one [\#22](https://github.com/devlooped/SmallSharp/issues/22) ## [v1.0.2](https://github.com/devlooped/SmallSharp/tree/v1.0.2) (2021-02-12) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.0.1...v1.0.2) :sparkles: Implemented enhancements: - Allow consuming CI/main package from the sleet feed [\#19](https://github.com/devlooped/SmallSharp/issues/19) :bug: Fixed bugs: - When changing active files quickly, opening startup file may fail [\#18](https://github.com/devlooped/SmallSharp/issues/18) - Debug.Fail dialogs should not be shown on publicly released packages [\#17](https://github.com/devlooped/SmallSharp/issues/17) - Renaming file crashes Visual Studio [\#15](https://github.com/devlooped/SmallSharp/issues/15) :twisted_rightwards_arrows: Merged: - Improve resiliency when invoking DTE [\#20](https://github.com/devlooped/SmallSharp/pull/20) (@kzu) - typo: fix roslyn [\#14](https://github.com/devlooped/SmallSharp/pull/14) (@alastairtree) ## [v1.0.1](https://github.com/devlooped/SmallSharp/tree/v1.0.1) (2020-11-20) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v1.0.0...v1.0.1) :sparkles: Implemented enhancements: - How to install? [\#10](https://github.com/devlooped/SmallSharp/issues/10) ## [v1.0.0](https://github.com/devlooped/SmallSharp/tree/v1.0.0) (2020-11-18) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v0.3.0...v1.0.0) :twisted_rightwards_arrows: Merged: - Open startup file upon selection [\#8](https://github.com/devlooped/SmallSharp/pull/8) (@kzu) - Version improvements [\#6](https://github.com/devlooped/SmallSharp/pull/6) (@kzu) - Push and add CI package version for dogfooding purposes. [\#5](https://github.com/devlooped/SmallSharp/pull/5) (@kzu) ## [v0.3.0](https://github.com/devlooped/SmallSharp/tree/v0.3.0) (2020-10-07) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/v0.2.0...v0.3.0) ## [v0.2.0](https://github.com/devlooped/SmallSharp/tree/v0.2.0) (2020-10-01) [Full Changelog](https://github.com/devlooped/SmallSharp/compare/b42f339e41771204a132fda34b061236b78c8511...v0.2.0) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* ================================================ FILE: license.txt ================================================ The MIT License (MIT) Copyright (c) Daniel Cazzulino and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: osmfeula.txt ================================================ End User License Agreement This Open Source Maintenance Fee Agreement ("Agreement") is a legal agreement between you ("User") and Devlooped ("Project") for the use of SmallSharp ("Software"), an open source software project licensed under the MIT License ("OSI License"), an OSI-approved open source license. Project offers a Binary Release of the Software to Users in exchange for a maintenance fee ("Fee"). "Binary Release" refers to pre-compiled executable versions of the Software provided by Project. By accessing or using the Binary Release, User agrees to be bound by the terms of this Agreement. 1. Applicability Project agrees to provide User with the Binary Release in exchange for the Fees outlined in Section 2, subject to the terms of this Agreement. The Fee applies only to Users that generate revenue by the Software. Non-revenue-generating use of the Software is exempt from this Fee. In addition, Users who pay separate support and/or maintenance fees to the maintainers of the Software are exempt from the Fee outlined in this Agreement. This distinction ensures that duplicate fees are not imposed, promoting fairness and consistency while respecting alternative support arrangements. 2. Monthly Fee and Payment Terms Revenue-generating Users required to pay the Fee shall follow the payment terms set forth by the Project. Failure to comply with these terms may result in suspending access to the Binary Release. However, this does not restrict the User from obtaining or redistributing binaries from other sources or self-compiling them. 3. Nature of the Fee The Fee is not a license fee. The Software's source code is licensed to User under the OSI License and remains freely distributable under the terms of the OSI License and any applicable open-source licenses. 4. Conflicts with OSI License To the extent any term of this Agreement conflicts with User's rights under the OSI License regarding the Software, the OSI License shall govern. This Agreement applies only to the Binary Release and does not limit User's ability to access, modify, or distribute the Software's source code or self-compiled binaries. User may independently compile binaries from the Software's source code without this Agreement, subject to OSI License terms. User may redistribute the Binary Release received under this Agreement, provided such redistribution complies with the OSI License (e.g., including copyright and permission notices). This Agreement imposes no additional restrictions on such rights. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND BINARY RELEASE ARE PROVIDED BY THE PROJECT "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE AND BINARY RELEASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: readme.md ================================================ ![Icon](https://raw.githubusercontent.com/devlooped/SmallSharp/main/assets/img/icon-32.png) SmallSharp ============ [![Version](https://img.shields.io/nuget/v/SmallSharp.svg?color=royalblue)](https://www.nuget.org/packages/SmallSharp) [![Downloads](https://img.shields.io/nuget/dt/SmallSharp?color=darkmagenta)](https://www.nuget.org/packages/SmallSharp) [![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](https://github.com/devlooped/SmallSharp/blob/main/osmfeula.txt) [![OSS](https://img.shields.io/github/license/devlooped/SmallSharp.svg?color=blue)](https://github.com/devlooped/SmallSharp/blob/main/license.txt) Create, edit and run multiple C# file-based apps in the same project in the IDE, honoring per-file `#:package` references, `#:property` project values and even `#:sdk` 😍 ## Open Source Maintenance Fee To ensure the long-term sustainability of this project, users of this package who generate revenue must pay an [Open Source Maintenance Fee](https://opensourcemaintenancefee.org). While the source code is freely available under the terms of the [License](license.txt), this package and other aspects of the project require [adherence to the Maintenance Fee](osmfeula.txt). To pay the Maintenance Fee, [become a Sponsor](https://github.com/sponsors/devlooped) at the proper OSMF tier. A single fee covers all of [Devlooped packages](https://www.nuget.org/profiles/Devlooped). ## Overview C# [top-level programs](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements) allow a very intuitive, simple and streamlined experience for quickly spiking or learning C#. The addition of [file-based apps](https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/) in .NET 10 [takes this further](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps) by allowing package references and even MSBuild properties to be specified per file: ```csharp #:package Humanizer@2.14.1 #:property ImplicitUsings=true #:property Nullable=enable using Humanizer; var dotNet9Released = DateTimeOffset.Parse("2024-12-03"); var since = DateTimeOffset.Now - dotNet9Released; Console.WriteLine($"It has been {since.Humanize()} since .NET 9 was released."); ``` Editing these standalone files in VSCode, however, is suboptimal compared with the full C# experience in Visual Studio. In Visual Studio, though, you can only have one top-level program in a project, and as of now, you cannot leverage the `#:package` and `#:property` directives at all. **SmallSharp** allows dynamically selecting the file to run, right from the Start button/dropdown (for compilation and launch/debug). It also automatically restores the `#:package` references so the project system can resolve them, and even emits the `#:property` directives if present to customize the build as needed. ![start button](https://raw.githubusercontent.com/devlooped/SmallSharp/main/assets/img/launchSettings.png) This list is automatically kept in sync as you add more `.cs` files to the project. When you select one target C# file, that becomes the only top-level program to be compiled, so you don't have to modify any of the others since they automatically become *None* items. > [!TIP] > An initial build after selection change migh be needed to restore the packages and compile the > selected file, unless you're using the SDK mode for SmallSharp (see below). All compile files directly under the project directory root are considered top-level programs for selection and compilation purposes. If you need to share code among them, you can place additional files in subdirectories and those will behave like normal compile items. ## Usage SmallSharp works by just installing the [SmallSharp](https://nuget.org/packages/SmallSharp) nuget package in a C# console project and adding a couple extra properties to the project file: ```xml Exe net10.0 true true ``` There are some limitations with this mode, however: * You cannot use the `#:sdk` [directive](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps) to specify a different SDK per file, since the project file already specifies one. * CLI-based builds may require multiple passes to restore and build the selected file, since the package is only restored after the first build. * You must add ImportProjectExtensionProps/ImportProjectExtensionTargets manually, polluting the project file. So the recommended way to use SmallSharp is via the SDK mode, which results in a more streamlined and seamless experience across IDE and CLI builds: ```xml Exe net10.0 ``` The SDK mode will always produce a successful build in a single `dotnet build` pass even if you change the `ActiveFile` between builds. > [!IMPORTANT] > If no `#:sdk` directive is provided by a specific C# file-based app, the `Microsoft.NET.SDK` will be > used by default in this SDK mode. Keep adding as many top-level programs as you need, and switch between them easily by simply selecting the desired file from the Start button dropdown. When running from the command-line, you can select the file to run by passing it as an argument to `dotnet run`: ```bash dotnet run -p:start=program1.cs ``` You can also use the shortcut `-p:s=[FILE]`. ## How It Works This nuget package leverages in concert the following standalone and otherwise unrelated features of the compiler, nuget and MSBuild: 1. The C# compiler only allows one top-level program per compilation. 2. Launch profiles (the entries in the Run dropdown) are populated from the Properties\launchSettings.json file 3. Whenever changed, the dropdown selection is persisted as the `$(ActiveDebugProfile)` MSBuild property in a file named after the project with the `.user` extension 4. This file is imported before NuGet-provided MSBuild targets 5. VS ignores `#:` directives when adding the flag `FileBasedProgram` to the `$(Features)` project property. Using the above features in concert, **SmallSharp** essentially does the following: * Emit top-level files as a `launchSettings.json` profile and set the `$(ActiveDebugProfile)`. * Exclude `.cs` files at the project level from being included as `` by the default SDK includes and include them explicitly as `` instead so they show up in the solution explorer. This prevents the compiler from causing an error for multiple top-level programs. * Explicitly include as `` only the `$(ActiveDebugProfile)` property value. * Emit `#:package` and `#:property` directive to an automatically imported `obj\SmallSharp.targets` file * SmallSharp MSBuild SDK automatically imports the `SmallSharp.targets` file, which causes a new restore to automatically happen in Visual Studio, bringing all required dependencies automatically. This basically mean that this it will also work consistently if you use `dotnet run` from the command-line, since the "Main" file selection is performed exclusively via MSBuild item manipulation. > [!TIP] > It is recommended to keep the project file to its bare minimum, usually having just the SmallSharp > SDK reference, and do all project/package references in the top-level files using the `#:package` and > `#:property` directives for improved isolation between the different file-based apps. ![run humanizer file](https://raw.githubusercontent.com/devlooped/SmallSharp/main/assets/img/runfile1.png) ![run mcp file](https://raw.githubusercontent.com/devlooped/SmallSharp/main/assets/img/runfile2.png) # Sponsors [![Clarius Org](https://avatars.githubusercontent.com/u/71888636?v=4&s=39 "Clarius Org")](https://github.com/clarius) [![MFB Technologies, Inc.](https://avatars.githubusercontent.com/u/87181630?v=4&s=39 "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) [![Khamza Davletov](https://avatars.githubusercontent.com/u/13615108?u=11b0038e255cdf9d1940fbb9ae9d1d57115697ab&v=4&s=39 "Khamza Davletov")](https://github.com/khamza85) [![SandRock](https://avatars.githubusercontent.com/u/321868?u=99e50a714276c43ae820632f1da88cb71632ec97&v=4&s=39 "SandRock")](https://github.com/sandrock) [![DRIVE.NET, Inc.](https://avatars.githubusercontent.com/u/15047123?v=4&s=39 "DRIVE.NET, Inc.")](https://github.com/drivenet) [![Keith Pickford](https://avatars.githubusercontent.com/u/16598898?u=64416b80caf7092a885f60bb31612270bffc9598&v=4&s=39 "Keith Pickford")](https://github.com/Keflon) [![Thomas Bolon](https://avatars.githubusercontent.com/u/127185?u=7f50babfc888675e37feb80851a4e9708f573386&v=4&s=39 "Thomas Bolon")](https://github.com/tbolon) [![Kori Francis](https://avatars.githubusercontent.com/u/67574?u=3991fb983e1c399edf39aebc00a9f9cd425703bd&v=4&s=39 "Kori Francis")](https://github.com/kfrancis) [![Reuben Swartz](https://avatars.githubusercontent.com/u/724704?u=2076fe336f9f6ad678009f1595cbea434b0c5a41&v=4&s=39 "Reuben Swartz")](https://github.com/rbnswartz) [![Jacob Foshee](https://avatars.githubusercontent.com/u/480334?v=4&s=39 "Jacob Foshee")](https://github.com/jfoshee) [![](https://avatars.githubusercontent.com/u/33566379?u=bf62e2b46435a267fa246a64537870fd2449410f&v=4&s=39 "")](https://github.com/Mrxx99) [![Eric Johnson](https://avatars.githubusercontent.com/u/26369281?u=41b560c2bc493149b32d384b960e0948c78767ab&v=4&s=39 "Eric Johnson")](https://github.com/eajhnsn1) [![Jonathan ](https://avatars.githubusercontent.com/u/5510103?u=98dcfbef3f32de629d30f1f418a095bf09e14891&v=4&s=39 "Jonathan ")](https://github.com/Jonathan-Hickey) [![Ken Bonny](https://avatars.githubusercontent.com/u/6417376?u=569af445b6f387917029ffb5129e9cf9f6f68421&v=4&s=39 "Ken Bonny")](https://github.com/KenBonny) [![Simon Cropp](https://avatars.githubusercontent.com/u/122666?v=4&s=39 "Simon Cropp")](https://github.com/SimonCropp) [![agileworks-eu](https://avatars.githubusercontent.com/u/5989304?v=4&s=39 "agileworks-eu")](https://github.com/agileworks-eu) [![Zheyu Shen](https://avatars.githubusercontent.com/u/4067473?v=4&s=39 "Zheyu Shen")](https://github.com/arsdragonfly) [![Vezel](https://avatars.githubusercontent.com/u/87844133?v=4&s=39 "Vezel")](https://github.com/vezel-dev) [![ChilliCream](https://avatars.githubusercontent.com/u/16239022?v=4&s=39 "ChilliCream")](https://github.com/ChilliCream) [![4OTC](https://avatars.githubusercontent.com/u/68428092?v=4&s=39 "4OTC")](https://github.com/4OTC) [![domischell](https://avatars.githubusercontent.com/u/66068846?u=0a5c5e2e7d90f15ea657bc660f175605935c5bea&v=4&s=39 "domischell")](https://github.com/DominicSchell) [![Adrian Alonso](https://avatars.githubusercontent.com/u/2027083?u=129cf516d99f5cb2fd0f4a0787a069f3446b7522&v=4&s=39 "Adrian Alonso")](https://github.com/adalon) [![torutek](https://avatars.githubusercontent.com/u/33917059?v=4&s=39 "torutek")](https://github.com/torutek) [![mccaffers](https://avatars.githubusercontent.com/u/16667079?u=110034edf51097a5ee82cb6a94ae5483568e3469&v=4&s=39 "mccaffers")](https://github.com/mccaffers) [![Seika Logiciel](https://avatars.githubusercontent.com/u/2564602?v=4&s=39 "Seika Logiciel")](https://github.com/SeikaLogiciel) [![Andrew Grant](https://avatars.githubusercontent.com/devlooped-user?s=39 "Andrew Grant")](https://github.com/wizardness) [![Lars](https://avatars.githubusercontent.com/u/1727124?v=4&s=39 "Lars")](https://github.com/latonz) [![prime167](https://avatars.githubusercontent.com/u/3722845?v=4&s=39 "prime167")](https://github.com/prime167) [![Sponsor this project](https://avatars.githubusercontent.com/devlooped-sponsor?s=118 "Sponsor this project")](https://github.com/sponsors/devlooped) [Learn more about GitHub Sponsors](https://github.com/sponsors) ================================================ FILE: src/Demo/.gitignore ================================================ Demo.sln ================================================ FILE: src/Demo/Demo.csproj ================================================  Exe net10.0 ================================================ FILE: src/Demo/Hello.cs ================================================ #:property ImplicitUsings=true #:package Spectre.Console@0.51.* using Spectre.Console; AnsiConsole.MarkupLine(":globe_showing_americas: Hello, World!"); ================================================ FILE: src/Demo/Humanizer.cs ================================================ #:property ImplicitUsings=true #:property Nullable=enable #:package Humanizer@2.14.1 using Humanizer; var dotNet9Released = DateTimeOffset.Parse("2024-12-03"); var since = DateTimeOffset.Now - dotNet9Released; Console.WriteLine($"It has been {since.Humanize()} since .NET 9 was released."); ================================================ FILE: src/Demo/Mcp.cs ================================================ #:property Title=LaTeX to Image MCP #:property Version=0.5.0 #:property ImplicitUsings=true #:package Smith@0.2.5 #:package DotNetConfig.Configuration@1.2.* #:package ModelContextProtocol@0.3.0-preview.* #:package Microsoft.Extensions.Http@9.* using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; var builder = App.CreateBuilder(args); builder.Configuration.AddDotNetConfig(); var initialized = false; bool? darkMode = bool.TryParse(builder.Configuration["latex:darkMode"], out var dm) ? dm : null; string? fontSize = builder.Configuration["latex:fontSize"]; // See https://editor.codecogs.com/docs/4-LaTeX_rendering.php#overview_anchor var fonts = new Dictionary { { "Tiny", "tiny" }, { "Small", "small" }, { "Large", "large" }, { "LARGE", "LARGE" }, { "Huge", "huge"} }; builder.Services .AddHttpClient() .AddMcpServer() .WithStdioServerTransport() .WithTool( name: "latex", title: "LaTeX to Image", description: """ Converts LaTeX equations into markdown-formatted images for inline display. Users can use #latex_setprefs tool to set preferences for dark mode and font size. Available font sizes: tiny, small, large, LARGE, huge. """, tool: async (IHttpClientFactory httpFactory, IMcpServer server, [Description("The LaTeX equation to render.")] string latex) => { // On first tool run, we ask for preferences for dark mode and font size. if (!initialized) { initialized = true; (darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize); } var colors = darkMode switch { true => @"\fg{white}", false => @"\fg{black}", null => @"\bg{white}\fg{black}" }; var query = WebUtility.UrlEncode(@"\dpi{300}\" + (fontSize ?? "small") + colors + new string([.. latex.Where(c => !char.IsWhiteSpace(c))])); var url = $"https://latex.codecogs.com/png.image?{query}"; using var client = httpFactory.CreateClient(); using var response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); var base64 = Convert.ToBase64String(await response.Content.ReadAsByteArrayAsync()); return $"> ![LaTeX Equation](data:image/png;base64,{base64})"; }) .WithTool( name: "latex_getprefs", title: "Get LaTeX Preferences", description: "Gets the saved LaTeX rendering preferences for dark mode and font size.", tool: () => new { darkMode, fontSize }, options: ToolJsonOptions.Default) .WithTool( name: "latex_setprefs", title: "Set LaTeX Preferences", description: "Sets the LaTeX rendering preferences for dark mode and font size.", tool: async (IMcpServer server, [Description("Use dark mode by inverting the colors in the output.")] bool? darkMode = null, [Description("Font size to use in the output: tiny=5pt, small=9pt, large=12pt, LARGE=18pt, huge=20pt")] string? fontSize = null) => (darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize), options: ToolJsonOptions.Default); await builder.Build().RunAsync(); /// Saves the LaTeX rendering preferences to configuration. async ValueTask<(bool? darkMode, string? fontSize)> SetPreferences(IMcpServer server, bool? darkMode, string? fontSize) { if ((darkMode is null || fontSize is null || !fonts.ContainsValue(fontSize)) && server.ClientCapabilities?.Elicitation != null) { var result = await server.ElicitAsync(new() { Message = "Specify LaTeX rendering preferences", RequestedSchema = new() { Required = ["darkMode", "fontSize"], Properties = { { "darkMode", new ElicitRequestParams.BooleanSchema() { Title = "Dark Mode", Description = "Use dark mode?", Default = darkMode } }, { "fontSize", new ElicitRequestParams.EnumSchema() { Title = "Font Size", Description = "Font size to use for the LaTeX rendering.", Enum = [.. fonts.Values], EnumNames = [.. fonts.Keys], } }, }, } }); if (result.Action == "accept" && result.Content is { } content) { darkMode = content["darkMode"].GetBoolean(); fontSize = content["fontSize"].GetString() ?? "tiny"; DotNetConfig.Config.Build(DotNetConfig.ConfigLevel.Global) .GetSection("latex") .SetBoolean("darkMode", darkMode.Value) .SetString("fontSize", fontSize); } // action == cancel is not supported in vscode // actoin == decline would be equal to "ignore" so we just don't set anything. return (darkMode, fontSize); } else { // We persist to ~/.netconfig var config = DotNetConfig.Config.Build(DotNetConfig.ConfigLevel.Global).GetSection("latex"); if (darkMode != null) config = config.SetBoolean("darkMode", darkMode.Value); if (fontSize != null && fonts.ContainsValue(fontSize)) config = config.SetString("fontSize", fontSize); else fontSize = null; return (darkMode, fontSize); } } ================================================ FILE: src/Demo/Properties/launchSettings.json ================================================ { "profiles": { "Humanizer.cs": { "commandName": "Project" }, "Mcp.cs": { "commandName": "Project" }, "Hello.cs": { "commandName": "Project" } } } ================================================ FILE: src/Demo/global.json ================================================ { "msbuild-sdks": { "SmallSharp": "42.537.1166" } } ================================================ FILE: src/Demo/nuget.config ================================================  ================================================ FILE: src/Directory.Build.props ================================================ false true $(CI) Daniel Cazzulino Devlooped Copyright (C) Daniel Cazzulino and Contributors. All rights reserved. false MIT icon.png readme.md icon.png readme.md true true $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\bin')) true true true false true Release Latest false embedded true enable strict $(MSBuildProjectName) $(MSBuildProjectName.IndexOf('.')) $(MSBuildProjectName.Substring(0, $(RootNamespaceDot))) $(DefaultItemExcludes);*.binlog;*.zip;*.rsp;*.items;**/TestResults/**/*.* true true true true true false NU5105;$(NoWarn) true true LatestMinor $(MSBuildThisFileDirectory)kzu.snk 002400000480000094000000060200000024000052534131000400000100010051155fd0ee280be78d81cc979423f1129ec5dd28edce9cd94fd679890639cad54c121ebdb606f8659659cd313d3b3db7fa41e2271158dd602bb0039a142717117fa1f63d93a2d288a1c2f920ec05c4858d344a45d48ebd31c1368ab783596b382b611d8c92f9c1b3d338296aa21b12f3bc9f34de87756100c172c52a24bad2db 00352124762f2aa5 true 42.42.42 <_VersionLabel>$(VersionLabel.Replace('refs/heads/', '')) <_VersionLabel>$(_VersionLabel.Replace('refs/tags/v', '')) <_VersionLabel Condition="$(_VersionLabel.Contains('refs/pull/'))">$(VersionLabel.TrimEnd('.0123456789')) <_VersionLabel>$(_VersionLabel.Replace('refs/pull/', 'pr')) <_VersionLabel>$(_VersionLabel.Replace('/merge', '')) <_VersionLabel>$(_VersionLabel.Replace('/', '-')) <_VersionLabel>$(_VersionLabel.Replace('_', '-')) $(_VersionLabel) $(_VersionLabel) true 42.42.0 $(VersionSuffix).$(GITHUB_RUN_NUMBER) 1.0.0 $(VersionPrefix)-$(VersionSuffix) $(VersionPrefix) ================================================ FILE: src/Directory.Build.targets ================================================ CI;$(DefineConstants) false false true true true false true 1.0.0 $(VersionPrefix)-$(VersionSuffix) $(VersionPrefix) $(PackFolder) $(PackFolderPath.Replace('\$(TargetFramework)', '')) $(IntermediateOutputPath)$(PackFolderPath)\ $(OutputPath)$(PackFolderPath)\ $(OutputPath) pr$(GITHUB_REF.Replace('refs/pull/', '').Replace('/merge', '')) $(GITHUB_REF.Replace('refs/heads/', '').Replace('refs/tags/', '')) $(BUILD_SOURCEBRANCH.Replace('refs/heads/', '').Replace('refs/tags/', '')) pr$(APPVEYOR_PULL_REQUEST_NUMBER) $(APPVEYOR_REPO_TAG_NAME) $(APPVEYOR_REPO_BRANCH) $(TEAMCITY_BUILD_BRANCH) pr$(TRAVIS_PULL_REQUEST) $(TRAVIS_BRANCH) pr$(CIRCLE_PR_NUMBER) $(CIRCLE_TAG) $(CIRCLE_BRANCH) $(CI_COMMIT_TAG) pr$(CI_MERGE_REQUEST_IID) pr$(CI_EXTERNAL_PULL_REQUEST_IID) $(CI_COMMIT_BRANCH) pr$(BUDDY_EXECUTION_PULL_REQUEST_NO) $(BUDDY_EXECUTION_TAG) $(BUDDY_EXECUTION_BRANCH) CoreResGen;$(CoreCompileDependsOn) $(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension) $(Language) $(RootNamespace) $(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.')) %(Filename) $(PrivateRepositoryUrl) $(SourceRevisionId) $(SourceRevisionId.Substring(0, 9)) $(RepositorySha) <_GitSourceRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" /> @(_GitSourceRoot) $([System.IO.Path]::GetFileNameWithoutExtension($(PrivateRepositoryUrl))) $(ProductFromUrl) $(RepositoryUrl.Replace('.git', '')) $(Description) $(RepositoryUrl.Replace('.git', ''))/blob/main/changelog.md OSMFEULA.txt true ================================================ FILE: src/Directory.props ================================================ false true https://clarius.org/SmallSharp true SmallSharp NU1702;$(NoWarn) ================================================ FILE: src/Directory.targets ================================================ ================================================ FILE: src/SmallSharp/ConsoleEncodingInitializer.cs ================================================ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace System; /// /// Ensures that when running from Visual Studio on Windows, the console encoding is set to UTF-8 /// to support full Unicode and emoji output. /// class ConsoleEncodingInitializer { #pragma warning disable CA2255 // The 'ModuleInitializer' attribute should not be used in libraries [ModuleInitializer] #pragma warning restore CA2255 // The 'ModuleInitializer' attribute should not be used in libraries public static void Init() { #if DEBUG if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8; #endif } } ================================================ FILE: src/SmallSharp/EmitTargets.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using static SmallSharp.TaskItemFactory; namespace SmallSharp; public class EmitTargets : Task { static readonly Regex sdkExpr = new(@"^#:sdk\s+(?[^@]+?)(@(?.+))?$"); static readonly Regex packageExpr = new(@"^#:package\s+(?[^@]+)@(?.+)$"); static readonly Regex propertyExpr = new(@"^#:property\s+(?[^=]+)=(?.+)$"); [Required] public required ITaskItem StartupFile { get; set; } [Required] public required string BaseIntermediateOutputPath { get; set; } [Required] public required string PropsFile { get; set; } [Required] public required string TargetsFile { get; set; } [Required] public required bool UsingSDK { get; set; } public ITaskItem[] PackageReferences { get; set; } = []; [Output] public ITaskItem[] Packages { get; set; } = []; [Output] public ITaskItem[] Sdks { get; set; } = []; [Output] public ITaskItem[] Properties { get; set; } = []; [Output] public bool RestoreNeeded { get; set; } = false; public override bool Execute() { if (StartupFile is null) return false; var packages = new List(); var sdkItems = new List(); var propItems = new List(); var filePath = StartupFile.GetMetadata("FullPath"); var contents = File.ReadAllLines(filePath); var items = new List(); var properties = new List(); var sdks = new List(); foreach (var line in contents) { if (packageExpr.Match(line) is { Success: true } match) { var id = match.Groups["id"].Value.Trim(); var version = match.Groups["version"].Value.Trim(); packages.Add(NewTaskItem(id, [("Version", version)])); items.Add(new XElement("PackageReference", new XAttribute("Include", id), new XAttribute("Version", version))); } else if (sdkExpr.Match(line) is { Success: true } sdkMatch) { var name = sdkMatch.Groups["sdk"].Value.Trim(); var version = sdkMatch.Groups["version"].Value.Trim(); if (!string.IsNullOrEmpty(version)) { sdkItems.Add(NewTaskItem(name, [("Version", version)])); sdks.Add([new XAttribute("Sdk", name), new XAttribute("Version", version)]); } else { sdkItems.Add(new TaskItem(name)); sdks.Add([new XAttribute("Sdk", name)]); } } else if (propertyExpr.Match(line) is { Success: true } propMatch) { var name = propMatch.Groups["name"].Value.Trim(); var value = propMatch.Groups["value"].Value.Trim(); propItems.Add(NewTaskItem(name, [("Value", value)])); properties.Add(new XElement(name, value)); } } Packages = [.. packages]; Sdks = [.. sdkItems]; Properties = [.. propItems]; // We only emit the default SDK if the SmallSharpSDK is in use, since otherwise the // project file is expected to define its own SDK and we'd be duplicating it. if (sdks.Count == 0) sdks.Add([new XAttribute("Sdk", "Microsoft.NET.Sdk")]); WriteXml(TargetsFile, new XElement("Project", new XElement("PropertyGroup", properties), // We emit the package references always, even if UsingSDK is true, because // this works better with the background restore that VS does, and nuget // deduplicates package references anyway. new XElement("ItemGroup", items) )); WriteXml(Path.Combine(BaseIntermediateOutputPath, "SmallSharp.sdk.props"), new XElement("Project", sdks.Select(x => new XElement("Import", [new XAttribute("Project", "Sdk.props"), .. x])))); WriteXml(Path.Combine(BaseIntermediateOutputPath, "SmallSharp.sdk.targets"), new XElement("Project", sdks.Select(x => new XElement("Import", [new XAttribute("Project", "Sdk.targets"), .. x])))); // We emit properties both to .props and .targets to ensure all values // are strictly what's set in the script, either before or after .NET SDK reads them. WriteXml(PropsFile, new XElement("Project", new XElement("PropertyGroup", properties), new XElement("PropertyGroup", [new XElement("SmallSharpProjectExtensionPropsImported", "true")]))); // Determine if a restore is needed: if any discovered #:package (id+version) is not already // present in the incoming PackageReferences list. foreach (var pkg in packages) { var id = pkg.ItemSpec; var version = pkg.GetMetadata("Version"); var exists = PackageReferences?.Any(r => string.Equals(r.ItemSpec, id, StringComparison.OrdinalIgnoreCase) && string.Equals(r.GetMetadata("Version"), version, StringComparison.OrdinalIgnoreCase)) == true; if (!exists) { RestoreNeeded = true; break; } } return true; } void WriteXml(string path, XElement root) { if (Path.GetDirectoryName(path) is { } dir) Directory.CreateDirectory(dir); using var writer = XmlWriter.Create(path, new XmlWriterSettings { Indent = true }); root.Save(writer); } } ================================================ FILE: src/SmallSharp/Sdk.Empty.props ================================================ ================================================ FILE: src/SmallSharp/Sdk.props ================================================ true true true $(MSBuildThisFileDirectory)\Sdk.Empty.props $(NoWarn);NU1504 ================================================ FILE: src/SmallSharp/Sdk.targets ================================================ <_PkgLines Include="@(_StartupFileLines)" Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('#:package '))" /> <_PkgReference Include="$([MSBuild]::ValueOrDefault('%(_PkgLines.Identity)', '').Substring(10))" /> $([MSBuild]::ValueOrDefault('%(_PkgReference.Identity)', '').Split('@')[1]) ================================================ FILE: src/SmallSharp/SmallSharp.Before.props ================================================ <_ImportProjectExtensionProps>$(ImportProjectExtensionProps) <_ImportProjectExtensionTargets>$(ImportProjectExtensionTargets) ================================================ FILE: src/SmallSharp/SmallSharp.Version.props ================================================ 42.42.42 ================================================ FILE: src/SmallSharp/SmallSharp.csproj ================================================ netstandard2.0 SmallSharp Create, edit and run multiple C# top-level programs in the same project in the IDE, respecting per-file `#:package` references and `#:property` project values 😍 build true false true Program $(VsInstallRoot)\Common7\IDE\devenv.exe OSMFEULA.txt true dotnet csharp run ================================================ FILE: src/SmallSharp/SmallSharp.props ================================================ $(Features);FileBasedProgram $(MSBuildThisFileDirectory)\SmallSharp.Before.props false ================================================ FILE: src/SmallSharp/SmallSharp.targets ================================================ $(ActiveCompile) $(ActiveFile) $(Start) $(S) $(ActiveDebugProfile) true EnsureProperties;CollectStartupFile;ResolveStartupFile;SelectStartupFile;SelectTopLevelCompile;UpdateLaunchSettings;EmitTargets $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).smallsharp.props $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).smallsharp.targets <_LinkedCompile Include="@(Compile -> HasMetadata('Link'))" /> <_NuGetLinked Include="@(_LinkedCompile -> HasMetadata('NuGetPackageId'))" /> <_NoNuGetLinked Include="@(_LinkedCompile)" Exclude="@(_NuGetLinked)" /> <_MatchingStartupFile Include="@(StartupFile)" Condition="'$(StartupFile)' != '' and '%(Filename)%(Extension)' == '$(StartupFile)'" /> @(_MatchingStartupFile -> '%(Identity)') false %(ReversedCompile.Identity) ProjectDebugger $(StartupFile) false $(StartupFile) $(StartupFile) i.ItemSpec).ToArray(); ]]> i.ItemSpec)) { Log.LogMessage(MessageImportance.High, "{0}: {1}", itemName, item.ItemSpec); foreach (var name in item.MetadataNames.OfType().OrderBy(_ => _)) { try { Log.LogMessage(MessageImportance.High, "\t{0}={1}", name, item.GetMetadata(name)); } catch { } } } ]]> ================================================ FILE: src/SmallSharp/TaskItemFactory.cs ================================================ using System.Collections.Generic; using System.Linq; using Microsoft.Build.Utilities; namespace SmallSharp; static class TaskItemFactory { public static TaskItem NewTaskItem(string itemSpec, Dictionary metadata) => new(itemSpec, metadata); public static TaskItem NewTaskItem(string itemSpec, params (string Key, string Value)[] metadata) => new(itemSpec, metadata.ToDictionary(x => x.Key, x => x.Value)); } ================================================ FILE: src/SmallSharp/readme.md ================================================ [![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black&color=C9FF30)](osmfeula.txt) [![OSS](https://img.shields.io/github/license/devlooped/oss.svg?color=blue)](license.txt) ================================================ FILE: src/_._ ================================================ ================================================ FILE: src/demo.ps1 ================================================ pushd $PSScriptRoot/.. dotnet build -p:PackOnBuild=true $version = gci bin | select -first 1 -expandproperty BaseName | %{ $_.Substring(11) } pushd src/Demo jq --arg version "$version" '.["msbuild-sdks"].SmallSharp = $version' global.json > temp.json && del global.json && mv temp.json global.json # build with each top-level file as the active one foreach ($file in gci *.cs) { # rm -r -fo obj -ea 0 dotnet build Demo.csproj -p:start=$($file.Name) -bl:"$($file.BaseName).binlog" if ($LASTEXITCODE -ne 0) { Write-Error "Build failed for $($file.Name)" popd; popd; exit $LASTEXITCODE } } popd; popd; ================================================ FILE: src/nuget.config ================================================