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<<EOF'
echo "$NEWLINE_VERSIONS"
echo 'EOF'
} >> $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
================================================
<Solution>
<Project Path="src/SmallSharp/SmallSharp.csproj" />
</Solution>
================================================
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
================================================
 SmallSharp
============
[](https://www.nuget.org/packages/SmallSharp)
[](https://www.nuget.org/packages/SmallSharp)
[](https://github.com/devlooped/SmallSharp/blob/main/osmfeula.txt)
[](https://github.com/devlooped/SmallSharp/blob/main/license.txt)
<!-- #description -->
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` 😍
<!-- #description -->
<!-- include https://github.com/devlooped/.github/raw/main/osmf.md -->
## 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).
<!-- https://github.com/devlooped/.github/raw/main/osmf.md -->
<!-- #content -->
## 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.

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
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<!-- 👇 allows c# file to override the TF via a #:property -->
<TargetFramework Condition="$(TargetFramework) == ''">net10.0</TargetFramework>
<!-- 👇 additional properties required in package mode -->
<ImportProjectExtensionProps>true</ImportProjectExtensionProps>
<ImportProjectExtensionTargets>true</ImportProjectExtensionTargets>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SmallSharp" Version="*" PrivateAssets="all" />
</ItemGroup>
</Project>
```
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
<Project Sdk="SmallSharp/2.2.3">
<PropertyGroup>
<OutputType>Exe</OutputType>
<!-- 👇 allows c# file to override the TF via a #:property -->
<TargetFramework Condition="$(TargetFramework) == ''">net10.0</TargetFramework>
</PropertyGroup>
</Project>
```
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 `<Compile>` by the default SDK
includes and include them explicitly as `<None>` 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 `<Compile>` 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.


<!-- #content -->
<!-- include https://github.com/devlooped/sponsors/raw/main/footer.md -->
# Sponsors
<!-- sponsors.md -->
[](https://github.com/clarius)
[](https://github.com/MFB-Technologies-Inc)
[](https://github.com/khamza85)
[](https://github.com/sandrock)
[](https://github.com/drivenet)
[](https://github.com/Keflon)
[](https://github.com/tbolon)
[](https://github.com/kfrancis)
[](https://github.com/rbnswartz)
[](https://github.com/jfoshee)
[](https://github.com/Mrxx99)
[](https://github.com/eajhnsn1)
[](https://github.com/Jonathan-Hickey)
[](https://github.com/KenBonny)
[](https://github.com/SimonCropp)
[](https://github.com/agileworks-eu)
[](https://github.com/arsdragonfly)
[](https://github.com/vezel-dev)
[](https://github.com/ChilliCream)
[](https://github.com/4OTC)
[](https://github.com/DominicSchell)
[](https://github.com/adalon)
[](https://github.com/torutek)
[](https://github.com/mccaffers)
[](https://github.com/SeikaLogiciel)
[](https://github.com/wizardness)
[](https://github.com/latonz)
[](https://github.com/prime167)
<!-- sponsors.md -->
[](https://github.com/sponsors/devlooped)
[Learn more about GitHub Sponsors](https://github.com/sponsors)
<!-- https://github.com/devlooped/sponsors/raw/main/footer.md -->
================================================
FILE: src/Demo/.gitignore
================================================
Demo.sln
================================================
FILE: src/Demo/Demo.csproj
================================================
<Project Sdk="SmallSharp">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Update="nuget.config;global.json" Visible="false" />
</ItemGroup>
</Project>
================================================
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<string, string>
{
{ "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 $"> ";
})
.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();
/// <summary>Saves the LaTeX rendering preferences to configuration.</summary>
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
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="kzu" value="https://pkg.kzu.app/index.json" />
<add key="local" value="../../bin/" />
</packageSources>
<activePackageSource>
<add key="All" value="(Aggregate source)" />
</activePackageSource>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="kzu">
<package pattern="SmallSharp" />
</packageSource>
<packageSource key="local">
<package pattern="SmallSharp" />
</packageSource>
</packageSourceMapping>
</configuration>
================================================
FILE: src/Directory.Build.props
================================================
<Project TreatAsLocalProperty="VersionPrefix">
<!-- To extend/change the defaults, create a Directory.props alongside this file -->
<PropertyGroup Label="CI" Condition="'$(CI)' == ''">
<CI>false</CI>
<!-- GH, CircleCI, GitLab and BitBucket already use CI -->
<CI Condition="'$(TF_BUILD)' == 'true' or
'$(TEAMCITY_VERSION)' != '' or
'$(APPVEYOR)' != '' or
'$(BuildRunner)' == 'MyGet' or
'$(JENKINS_URL)' != '' or
'$(TRAVIS)' == 'true' or
'$(BUDDY)' == 'true'">true</CI>
</PropertyGroup>
<PropertyGroup>
<!-- The Microsoft.Managed.Core.targets use this property to use deterministic source paths in CI builds -->
<ContinuousIntegrationBuild>$(CI)</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Label="NuGet">
<Authors>Daniel Cazzulino</Authors>
<Company>Devlooped</Company>
<Copyright>Copyright (C) Daniel Cazzulino and Contributors. All rights reserved.</Copyright>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!-- Pick src-level readme+icon automatically -->
<PackageIcon Condition="Exists('$(MSBuildThisFileDirectory)icon.png')">icon.png</PackageIcon>
<PackageReadmeFile Condition="'$(PackReadme)' != 'false' and Exists('$(MSBuildThisFileDirectory)readme.md')">readme.md</PackageReadmeFile>
<!-- Pick project-level readme+icon overrides automatically -->
<PackageIcon Condition="Exists('$(MSBuildProjectDirectory)\icon.png')">icon.png</PackageIcon>
<PackageReadmeFile Condition="'$(PackReadme)' != 'false' and Exists('$(MSBuildProjectDirectory)\readme.md')">readme.md</PackageReadmeFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<GenerateRepositoryUrlAttribute>true</GenerateRepositoryUrlAttribute>
<PackageOutputPath Condition="'$(PackageOutputPath)' == ''">$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\bin'))</PackageOutputPath>
<!-- Use Directory.Packages.props if possible. NOTE: other MSBuild SDKs (i.e. NoTargets/Traversal) do not support central packages -->
<ManagePackageVersionsCentrally Condition="Exists('$(MSBuildThisFileDirectory)Directory.Packages.props')">true</ManagePackageVersionsCentrally>
<CentralPackageFloatingVersionsEnabled>true</CentralPackageFloatingVersionsEnabled>
<!-- Ensure MSBuild tooling can access package artifacts always via PKG_[PackageId] -->
<GeneratePathProperty>true</GeneratePathProperty>
<!-- Avoid warnings for test projects when we run dotnet pack on the whole solution. -->
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
<!-- See https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#prunepackagereference-specification -->
<RestoreEnablePackagePruning>true</RestoreEnablePackagePruning>
</PropertyGroup>
<PropertyGroup Label="Build">
<Configuration Condition="'$(Configuration)' == '' and $(CI)">Release</Configuration>
<LangVersion>Latest</LangVersion>
<!-- See https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies -->
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<!-- Because they are small and super useful and supported everywhere -->
<DebugType>embedded</DebugType>
<DebugSymbols>true</DebugSymbols>
<Nullable>enable</Nullable>
<!-- See https://www.meziantou.net/csharp-compiler-strict-mode.htm -->
<Features>strict</Features>
<!-- Simplify namespaces by defaulting to the single root name -->
<RootNamespace>$(MSBuildProjectName)</RootNamespace>
<RootNamespaceDot>$(MSBuildProjectName.IndexOf('.'))</RootNamespaceDot>
<RootNamespace Condition="'$(RootNamespaceDot)' != '-1'">$(MSBuildProjectName.Substring(0, $(RootNamespaceDot)))</RootNamespace>
<!-- We typically don't want these files shown in the solution explorer -->
<DefaultItemExcludes>$(DefaultItemExcludes);*.binlog;*.zip;*.rsp;*.items;**/TestResults/**/*.*</DefaultItemExcludes>
<EnableSourceLink>true</EnableSourceLink>
<EnableSourceControlManagerQueries>true</EnableSourceControlManagerQueries>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<UseSourceLink>true</UseSourceLink>
<!-- Generate satellite assemblies using csc.exe to avoid some al.exe issues. See https://github.com/dotnet/msbuild/pull/2726 -->
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<!-- See: https://www.cazzulino.com/project-dependencies-as-project-references.html -->
<AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
<!-- Don't warn for packages using semver 2.0 -->
<NoWarn>NU5105;$(NoWarn)</NoWarn>
<!-- Turn warnings into errors in CI or Release builds -->
<WarningsAsErrors Condition="$(CI) or '$(Configuration)' == 'Release'">true</WarningsAsErrors>
<!-- Preserve transitively copied content in VS: https://github.com/dotnet/msbuild/issues/1054#issuecomment-847959047 -->
<MSBuildCopyContentTransitively>true</MSBuildCopyContentTransitively>
<!-- Global tools should run on whatever latest runtime is installed. See https://docs.microsoft.com/en-us/dotnet/core/versions/selection#framework-dependent-apps-roll-forward -->
<RollForward>LatestMinor</RollForward>
</PropertyGroup>
<PropertyGroup Label="StrongName" Condition="Exists('$(MSBuildThisFileDirectory)kzu.snk')">
<!-- We use a single oss signing key for consumers that need strong-named assemblies -->
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)kzu.snk</AssemblyOriginatorKeyFile>
<!-- These properties make it easier to add internals visible to other projects, even when signing is involved.
For example, you can simply add:
<InternalsVisibleTo Include="MyProject.UnitTests" />
and the key will be appended automatically.
-->
<PublicKey>002400000480000094000000060200000024000052534131000400000100010051155fd0ee280be78d81cc979423f1129ec5dd28edce9cd94fd679890639cad54c121ebdb606f8659659cd313d3b3db7fa41e2271158dd602bb0039a142717117fa1f63d93a2d288a1c2f920ec05c4858d344a45d48ebd31c1368ab783596b382b611d8c92f9c1b3d338296aa21b12f3bc9f34de87756100c172c52a24bad2db</PublicKey>
<PublicKeyToken>00352124762f2aa5</PublicKeyToken>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup Label="Version">
<!-- Versioning: when building locally, it's always 42.42.42.
This makes it always bigger than any public package version, and
consistent and fixed for dogfooding.
NuGetizer nukes the package cache on build, making this straightforward too.
CI (non-release) builds pass in a VersionSuffix property to append a label
after the fixed prefix. This allows dogfooding a branch build.
The suffix is sanitized and optionally turned into
-->
<VersionPrefix Condition="$(VersionPrefix) == ''">42.42.42</VersionPrefix>
</PropertyGroup>
<PropertyGroup Label="Version" Condition="$(VersionLabel) != ''">
<_VersionLabel>$(VersionLabel.Replace('refs/heads/', ''))</_VersionLabel>
<_VersionLabel>$(_VersionLabel.Replace('refs/tags/v', ''))</_VersionLabel>
<!-- For PRs, we just need a fixed package version numbered after the PR # itself, so remove the commits # at the end -->
<_VersionLabel Condition="$(_VersionLabel.Contains('refs/pull/'))">$(VersionLabel.TrimEnd('.0123456789'))</_VersionLabel>
<!-- Next replace the prefix for simply 'pr', so we end up with 'pr99/merge' by default -->
<_VersionLabel>$(_VersionLabel.Replace('refs/pull/', 'pr'))</_VersionLabel>
<!-- Remove the /merge now, if present -->
<_VersionLabel>$(_VersionLabel.Replace('/merge', ''))</_VersionLabel>
<!-- Finally sanitize the branch with dashes, so we can build path-separated branches, like rel/v1.0.0 or feature/foo -->
<_VersionLabel>$(_VersionLabel.Replace('/', '-'))</_VersionLabel>
<!-- And underscores which are also invalid labels, so we can use branches like dev/feature_foo -->
<_VersionLabel>$(_VersionLabel.Replace('_', '-'))</_VersionLabel>
<!-- Set sanitized version to the actual version suffix used in build/pack -->
<VersionSuffix Condition="!$(VersionLabel.Contains('refs/tags/'))">$(_VersionLabel)</VersionSuffix>
<!-- Special case for tags, the label is actually the version. Backs compat since passed-in value overrides MSBuild-set one -->
<Version Condition="$(VersionLabel.Contains('refs/tags/'))">$(_VersionLabel)</Version>
<!-- In order for latest from main/master to always be greatest when using -prerelease switch on install/run,
we change the scheme as follows:
- main/master remain as today: VersionPrefix: 42.42.${{ github.run_number }} (from yaml)
- others: VersionPrefix: 42.42.0-[label].${{ github.run_number }}
-->
<IsMaster Condition="$(VersionLabel.Contains('refs/heads/main')) or $(VersionLabel.Contains('refs/heads/master'))">true</IsMaster>
<VersionPrefix Condition="'$(IsMaster)' != 'true'">42.42.0</VersionPrefix>
<VersionSuffix Condition="'$(IsMaster)' != 'true'">$(VersionSuffix).$(GITHUB_RUN_NUMBER)</VersionSuffix>
</PropertyGroup>
<ItemGroup Label="ThisAssembly.Project">
<ProjectProperty Include="CI" />
<ProjectProperty Include="Version" />
<ProjectProperty Include="VersionPrefix" />
<ProjectProperty Include="VersionSuffix" />
<ProjectProperty Include="PublicKey" />
<ProjectProperty Include="PublicKeyToken" />
</ItemGroup>
<ItemGroup Label="Throw">
<Using Include="System.ArgumentException" Static="true" />
<Using Include="System.ArgumentOutOfRangeException" Static="true" />
<Using Include="System.ArgumentNullException" Static="true" />
</ItemGroup>
<ItemGroup Label="OSMF" Condition="Exists('$(MSBuildThisFileDirectory)..\osmfeula.txt')">
<Content Include="$(MSBuildThisFileDirectory)..\osmfeula.txt" Link="osmfeula.txt" Pack="true" PackagePath="OSMFEULA.txt" />
</ItemGroup>
<Import Project="Directory.props" Condition="Exists('Directory.props')"/>
<Import Project="Directory.props.user" Condition="Exists('Directory.props.user')" />
<!-- If the imported props changed ManagePackageVersionsCentrally, we need to replicate
the Version defaults from Microsoft.NET.DefaultAssemblyInfo.targets since it's too
early here and Directory.Packages.props will be imported right after this time,
meaning dependencies that expect to use the currently building Version would not
get the expected value.
-->
<PropertyGroup Condition="'$(ManagePackageVersionsCentrally)' == 'true' and '$(Version)' == ''">
<VersionPrefix Condition=" '$(VersionPrefix)' == '' ">1.0.0</VersionPrefix>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix)-$(VersionSuffix)</Version>
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>
</PropertyGroup>
<!-- Implemented by SDK in .targets, guaranteeing it's overwritten. Added here since we add a DependsOnTargets to it.
Covers backwards compatiblity with non-SDK projects. -->
<Target Name="InitializeSourceControlInformation" />
</Project>
================================================
FILE: src/Directory.Build.targets
================================================
<Project>
<!-- To extend/change the defaults, create a Directory.targets alongside this file -->
<PropertyGroup Condition="'$(CI)' == 'true' and '$(Language)' == 'C#'">
<DefineConstants>CI;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="Build">
<!-- Tests projects don't need API docs, typically -->
<GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == '' and $(IsTestProject) == 'true'">false</GenerateDocumentationFile>
<GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == '' and $(MSBuildProjectName.Contains('Tests'))">false</GenerateDocumentationFile>
<GenerateDocumentationFile Condition="$(GenerateDocumentationFile) == ''">true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(IsPackable)' == ''">
<IsPackable Condition="'$(PackAsTool)' == 'true'">true</IsPackable>
<IsPackable Condition="'$(PackFolder)' != ''">true</IsPackable>
</PropertyGroup>
<PropertyGroup Condition="'$(IsPackable)' == ''">
<!-- The Sdks\NuGet.Build.Tasks.Pack\build\NuGet.Build.Tasks.Pack.targets unconditionally sets
PackageId=AssemblyName if no PackageId is provided, and then defaults IsPackable=true if
a PackageId is set (?!), meaning that by default everything is packable in Sdk-style
projects.
The Directory.Build.targets are imported after the user's project properties have been
read, and therefore gives us a chance to inspect if an explicit PackageId was provided,
before the NuGet SDK target is imported and defaults it. At this point, we can give
IsPackable a more sensible default, making it false if no PackageId was provided at this
point. -->
<IsPackable Condition="'$(PackageId)' == ''">false</IsPackable>
<IsPackable Condition="'$(PackageId)' != ''">true</IsPackable>
</PropertyGroup>
<ItemGroup Condition="'$(IsPackable)' == 'true'" Label="NuGet">
<!-- This is compatible with nugetizer and SDK pack -->
<!-- Only difference is we don't copy either to output directory -->
<!-- Project-level icon/readme will already be part of None items -->
<None Update="@(None -> WithMetadataValue('Filename', 'icon'))"
Pack="true" PackagePath="%(Filename)%(Extension)"
CopyToOutputDirectory="Never"
Condition="'$(PackageIcon)' != ''" />
<None Update="@(None -> WithMetadataValue('Filename', 'readme'))"
Pack="true" PackagePath="%(Filename)%(Extension)"
CopyToOutputDirectory="Never"
Condition="'$(PackReadme)' != 'false' and '$(PackageReadmeFile)' != ''" />
<!-- src-level will need explicit inclusion -->
<None Include="$(MSBuildThisFileDirectory)icon.png" Link="icon.png" Visible="false"
Pack="true" PackagePath="%(Filename)%(Extension)"
CopyToOutputDirectory="Never"
Condition="Exists('$(MSBuildThisFileDirectory)icon.png') and !Exists('$(MSBuildProjectDirectory)\icon.png')" />
<None Include="$(MSBuildThisFileDirectory)readme.md" Link="readme.md"
Pack="true" PackagePath="%(Filename)%(Extension)"
CopyToOutputDirectory="Never"
Condition="'$(PackReadme)' != 'false' and Exists('$(MSBuildThisFileDirectory)readme.md') and !Exists('$(MSBuildProjectDirectory)\readme.md')" />
</ItemGroup>
<!-- Microsoft.NET.Sdk\targets\Microsoft.NET.DefaultAssemblyInfo.targets does this and is imported
before Directory.Build.targets, but it's not imported for .msbuildproj -->
<PropertyGroup Condition="'$(Version)' == ''">
<VersionPrefix Condition="'$(VersionPrefix)' == ''">1.0.0</VersionPrefix>
<Version Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix)</Version>
<Version Condition="'$(Version)' == ''">$(VersionPrefix)</Version>
</PropertyGroup>
<!-- Append $(PackFolder) directory to output and intermediate paths to prevent bin clashes between targets. -->
<PropertyGroup Condition="'$(AppendPackFolderToOutputPath)' == 'true' and '$(PackFolder)' != ''">
<PackFolderPath>$(PackFolder)</PackFolderPath>
<PackFolderPath Condition="'$(TargetFramework)' != ''">$(PackFolderPath.Replace('\$(TargetFramework)', ''))</PackFolderPath>
<IntermediateOutputPath>$(IntermediateOutputPath)$(PackFolderPath)\</IntermediateOutputPath>
<OutputPath>$(OutputPath)$(PackFolderPath)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(RepositoryBranch)' == ''">
<!-- GitHub Actions: https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(GITHUB_REF)' != '' and $(GITHUB_REF.Contains('refs/pull/'))">pr$(GITHUB_REF.Replace('refs/pull/', '').Replace('/merge', ''))</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(GITHUB_REF)' != ''">$(GITHUB_REF.Replace('refs/heads/', '').Replace('refs/tags/', ''))</RepositoryBranch>
<!-- Azure DevOps: https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(BUILD_SOURCEBRANCH)' != ''">$(BUILD_SOURCEBRANCH.Replace('refs/heads/', '').Replace('refs/tags/', ''))</RepositoryBranch>
<!-- AppVeyor: https://www.appveyor.com/docs/environment-variables/ -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(APPVEYOR_PULL_REQUEST_NUMBER)' != ''">pr$(APPVEYOR_PULL_REQUEST_NUMBER)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(APPVEYOR_REPO_TAG_NAME)' != ''">$(APPVEYOR_REPO_TAG_NAME)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(APPVEYOR_REPO_BRANCH)' != ''">$(APPVEYOR_REPO_BRANCH)</RepositoryBranch>
<!-- TeamCity: https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Branch-Related+Parameters -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(TEAMCITY_BUILD_BRANCH)' != ''">$(TEAMCITY_BUILD_BRANCH)</RepositoryBranch>
<!--TravisCI: https://docs.travis-ci.com/user/environment-variables/ -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(TRAVIS_PULL_REQUEST)' != '' and '$(TRAVIS_PULL_REQUEST)' != 'false'">pr$(TRAVIS_PULL_REQUEST)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(TRAVIS_BRANCH)' != ''">$(TRAVIS_BRANCH)</RepositoryBranch>
<!-- CircleCI: https://circleci.com/docs/2.0/env-vars/ -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CIRCLE_PR_NUMBER)' != ''">pr$(CIRCLE_PR_NUMBER)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CIRCLE_TAG)' != ''">$(CIRCLE_TAG)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CIRCLE_BRANCH)' != ''">$(CIRCLE_BRANCH)</RepositoryBranch>
<!-- GitLab: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CI_COMMIT_TAG)' != ''">$(CI_COMMIT_TAG)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CI_MERGE_REQUEST_IID)' != ''">pr$(CI_MERGE_REQUEST_IID)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CI_EXTERNAL_PULL_REQUEST_IID)' != ''">pr$(CI_EXTERNAL_PULL_REQUEST_IID)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(CI_COMMIT_BRANCH)' != ''">$(CI_COMMIT_BRANCH)</RepositoryBranch>
<!-- Buddy: https://buddy.works/docs/pipelines/environment-variables#default-environment-variables -->
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(BUDDY_EXECUTION_PULL_REQUEST_NO)' != ''">pr$(BUDDY_EXECUTION_PULL_REQUEST_NO)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(BUDDY_EXECUTION_TAG)' != ''">$(BUDDY_EXECUTION_TAG)</RepositoryBranch>
<RepositoryBranch Condition="'$(RepositoryBranch)' == '' and '$(BUDDY_EXECUTION_BRANCH)' != ''">$(BUDDY_EXECUTION_BRANCH)</RepositoryBranch>
</PropertyGroup>
<PropertyGroup>
<!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html -->
<CoreCompileDependsOn>CoreResGen;$(CoreCompileDependsOn)</CoreCompileDependsOn>
</PropertyGroup>
<ItemGroup>
<!-- Consider the project out of date if any of these files changes -->
<UpToDateCheck Include="@(None);@(Content);@(EmbeddedResource)" />
<!-- Opt-in to typed resource generation by setting custom tool to MSBuild:Compile -->
<EmbeddedResource Update="@(EmbeddedResource -> WithMetadataValue('Generator', 'MSBuild:Compile'))" Type="Resx">
<!-- Default to Just Works resources generation. See https://www.cazzulino.com/resources.html -->
<StronglyTypedFileName>$(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension)</StronglyTypedFileName>
<StronglyTypedLanguage>$(Language)</StronglyTypedLanguage>
<StronglyTypedNamespace Condition="'%(RelativeDir)' == ''">$(RootNamespace)</StronglyTypedNamespace>
<StronglyTypedNamespace Condition="'%(RelativeDir)' != ''">$(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))</StronglyTypedNamespace>
<StronglyTypedClassName>%(Filename)</StronglyTypedClassName>
</EmbeddedResource>
</ItemGroup>
<Target Name="IsPackable" Returns="@(IsPackable)">
<ItemGroup>
<IsPackable Include="$(MSBuildProjectFullPath)" IsPackable="$(IsPackable)" PackageId="$(PackageId)" />
</ItemGroup>
</Target>
<ItemGroup>
<!-- Make these available via ThisAssembly.Project -->
<ProjectProperty Include="RepositoryBranch" />
<ProjectProperty Include="RepositorySha" />
<ProjectProperty Include="RepositoryCommit" />
<ProjectProperty Include="RepositoryRoot" />
<ProjectProperty Include="RepositoryUrl" />
</ItemGroup>
<!-- Make sure the source control info is available before calling source generators -->
<Target Name="EnsureProjectInformation"
BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun"
AfterTargets="InitializeSourceControlInformation"
DependsOnTargets="InitializeSourceControlInformation">
<PropertyGroup Condition="'$(SourceControlInformationFeatureSupported)' == 'true'">
<!-- The project must specify PublishRepositoryUrl=true in order to publish the URL, in order to prevent inadvertent leak of internal URL. -->
<RepositoryUrl Condition="'$(RepositoryUrl)' == '' and '$(PublishRepositoryUrl)' == 'true'">$(PrivateRepositoryUrl)</RepositoryUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(SourceRevisionId)' != ''">
<RepositoryCommit Condition="'$(RepositoryCommit)' == ''">$(SourceRevisionId)</RepositoryCommit>
<RepositorySha Condition="'$(RepositorySha)' == ''">$(SourceRevisionId.Substring(0, 9))</RepositorySha>
<!-- This allows the commit label added to the InformationalVersion to be the short sha instead :) -->
<SourceRevisionId>$(RepositorySha)</SourceRevisionId>
</PropertyGroup>
<!-- Add SourceRoot as a project property too -->
<ItemGroup>
<_GitSourceRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" />
</ItemGroup>
<PropertyGroup>
<RepositoryRoot>@(_GitSourceRoot)</RepositoryRoot>
<!-- Only change if it wasn't just the default from Microsoft.NET.DefaultAssemblyInfo.targets -->
<ProductFromUrl Condition="'$(SourceControlInformationFeatureSupported)' == 'true'">$([System.IO.Path]::GetFileNameWithoutExtension($(PrivateRepositoryUrl)))</ProductFromUrl>
<Product Condition="'$(Product)' == '$(AssemblyName)' and '$(ProductFromUrl)' != ''">$(ProductFromUrl)</Product>
</PropertyGroup>
</Target>
<Target Name="UpdatePackageMetadata"
BeforeTargets="PrepareForBuild;GenerateMSBuildEditorConfigFileShouldRun;GetAssemblyVersion;GetPackageMetadata;GenerateNuspec;Pack"
DependsOnTargets="EnsureProjectInformation;UpdatePackageLicense"
Condition="'$(SourceControlInformationFeatureSupported)' == 'true' And
'$(IsPackable)' == 'true'">
<PropertyGroup>
<PackageProjectUrl Condition="'$(PackageProjectUrl)' == '' and '$(PublishRepositoryUrl)' == 'true'">$(RepositoryUrl.Replace('.git', ''))</PackageProjectUrl>
<PackageDescription>$(Description)</PackageDescription>
<PackageReleaseNotes Condition="'$(RepositoryUrl)' != '' and Exists('$(MSBuildThisFileDirectory)..\changelog.md')">$(RepositoryUrl.Replace('.git', ''))/blob/main/changelog.md</PackageReleaseNotes>
</PropertyGroup>
</Target>
<Target Name="UpdatePackageLicense">
<!-- If project has a None/Content item with PackagePath=OSMFEULA.txt -->
<PropertyGroup Condition="@(None -> WithMetadataValue('PackagePath', 'OSMFEULA.txt')) != '' or @(Content -> WithMetadataValue('PackagePath', 'OSMFEULA.txt')) != ''">
<PackageLicenseExpression></PackageLicenseExpression>
<PackageLicenseFile>OSMFEULA.txt</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
</Target>
<!-- Import before UsingTask because first to declare tasks wins -->
<Import Project="Directory.targets" Condition="Exists('Directory.targets')"/>
<Import Project="Directory.targets.user" Condition="Exists('Directory.targets.user')" />
</Project>
================================================
FILE: src/Directory.props
================================================
<Project>
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<PackOnBuild>true</PackOnBuild>
<PackageProjectUrl>https://clarius.org/SmallSharp</PackageProjectUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<Product>SmallSharp</Product>
<NoWarn>NU1702;$(NoWarn)</NoWarn>
</PropertyGroup>
</Project>
================================================
FILE: src/Directory.targets
================================================
<Project InitialTargets="SetLocalVersion">
<Target Name="SetLocalVersion" Condition="!$(CI)">
<GetVersion>
<Output TaskParameter="Version" PropertyName="Version" />
</GetVersion>
</Target>
<UsingTask TaskName="GetVersion" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Version Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="Microsoft.Build.Framework"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
var version = this.BuildEngine4.GetRegisteredTaskObject("Version", RegisteredTaskObjectLifetime.Build);
if (version == null)
{
var epoc = DateTime.Parse("2024-03-15");
var days = Math.Truncate(DateTime.UtcNow.Subtract(epoc).TotalDays);
var time = Math.Floor(DateTime.UtcNow.TimeOfDay.TotalMinutes);
version = "42." + days + "." + time;
this.BuildEngine4.RegisterTaskObject("Version", version, RegisteredTaskObjectLifetime.Build, false);
}
Version = (string)version;
]]>
</Code>
</Task>
</UsingTask>
</Project>
================================================
FILE: src/SmallSharp/ConsoleEncodingInitializer.cs
================================================
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace System;
/// <summary>
/// Ensures that when running from Visual Studio on Windows, the console encoding is set to UTF-8
/// to support full Unicode and emoji output.
/// </summary>
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+(?<sdk>[^@]+?)(@(?<version>.+))?$");
static readonly Regex packageExpr = new(@"^#:package\s+(?<id>[^@]+)@(?<version>.+)$");
static readonly Regex propertyExpr = new(@"^#:property\s+(?<name>[^=]+)=(?<value>.+)$");
[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<ITaskItem>();
var sdkItems = new List<ITaskItem>();
var propItems = new List<ITaskItem>();
var filePath = StartupFile.GetMetadata("FullPath");
var contents = File.ReadAllLines(filePath);
var items = new List<XElement>();
var properties = new List<XElement>();
var sdks = new List<XAttribute[]>();
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
================================================
<Project>
<!-- This is needed since the Microsoft.NET.SDK Sdk.props imports the Microsoft.Common.props unconditionally -->
</Project>
================================================
FILE: src/SmallSharp/Sdk.props
================================================
<Project>
<PropertyGroup>
<ImportProjectExtensionProps>true</ImportProjectExtensionProps>
<ImportProjectExtensionTargets>true</ImportProjectExtensionTargets>
<!-- Since we use this to build StartupFile list and as an SDK, we might not have .NET SDK imported yet (i.e. no C# files at all) -->
<UsingSmallSharpSDK>true</UsingSmallSharpSDK>
<!-- Workaround https://github.com/dotnet/sdk/issues/50573 -->
<AlternateCommonProps>$(MSBuildThisFileDirectory)\Sdk.Empty.props</AlternateCommonProps>
<!-- Since we emit duplicate package references from SDK as well as .props -->
<NoWarn>$(NoWarn);NU1504</NoWarn>
</PropertyGroup>
<!-- Import Common.props explicitly we're too early here to use MSBuildProjectExtensionsPath -->
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="'$(MicrosoftCommonPropsHasBeenImported)' != 'true' and Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"
Condition="!Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.props')" />
<Import Project="$(MSBuildProjectExtensionsPath)SmallSharp.sdks.props"
Condition="Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.props')" />
<Import Project="..\build\SmallSharp.props" />
<!-- In SDK mode, we don't get the content files like we do in package mode, so we add it explicitly here -->
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\contentFiles\cs\netstandard2.0\ConsoleEncodingInitializer.cs"
NuGetPackageId="SmallSharp" />
</ItemGroup>
</Project>
================================================
FILE: src/SmallSharp/Sdk.targets
================================================
<Project>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"
Condition="!Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.targets')" />
<Import Project="$(MSBuildProjectExtensionsPath)SmallSharp.sdks.targets"
Condition="Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.targets')" />
<Import Project="..\build\SmallSharp.targets" />
<Target Name="ImplicitPackageReferenceFromStartupFile"
BeforeTargets="_GenerateProjectRestoreGraphPerFramework;ResolvePackageAssets;CompileDesignTime;CollectUpToDateCheckInputDesignTime;CollectPackageReferences"
DependsOnTargets="StartupFile"
Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)')">
<!-- Optimize for restore success on first run without previously running our targets -->
<ReadLinesFromFile File="$(StartupFile)">
<Output TaskParameter="Lines" ItemName="_StartupFileLines" />
</ReadLinesFromFile>
<ItemGroup>
<_PkgLines Include="@(_StartupFileLines)"
Condition="$([MSBuild]::ValueOrDefault('%(Identity)', '').StartsWith('#:package '))" />
</ItemGroup>
<ItemGroup Condition="'@(_PkgLines)' != ''">
<_PkgReference Include="$([MSBuild]::ValueOrDefault('%(_PkgLines.Identity)', '').Substring(10))" />
<PackageReference Condition="'@(_PkgReference)' != ''" Include="$([MSBuild]::ValueOrDefault('%(_PkgReference.Identity)', '').Split('@')[0])">
<Version>$([MSBuild]::ValueOrDefault('%(_PkgReference.Identity)', '').Split('@')[1])</Version>
</PackageReference>
</ItemGroup>
</Target>
</Project>
================================================
FILE: src/SmallSharp/SmallSharp.Before.props
================================================
<Project>
<PropertyGroup>
<_ImportProjectExtensionProps>$(ImportProjectExtensionProps)</_ImportProjectExtensionProps>
<_ImportProjectExtensionTargets>$(ImportProjectExtensionTargets)</_ImportProjectExtensionTargets>
</PropertyGroup>
</Project>
================================================
FILE: src/SmallSharp/SmallSharp.Version.props
================================================
<Project>
<PropertyGroup>
<SmallSharpVersion>42.42.42</SmallSharpVersion>
</PropertyGroup>
</Project>
================================================
FILE: src/SmallSharp/SmallSharp.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>SmallSharp</PackageId>
<Description>
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 😍
</Description>
<PackFolder>build</PackFolder>
<PackNone>true</PackNone>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<DevelopmentDependency>true</DevelopmentDependency>
<StartAction>Program</StartAction>
<StartProgram>$(VsInstallRoot)\Common7\IDE\devenv.exe</StartProgram>
<PackageLicenseExpression></PackageLicenseExpression>
<PackageLicenseFile>OSMFEULA.txt</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>dotnet csharp run</PackageTags>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\osmfeula.txt" Link="osmfeula.txt" PackagePath="OSMFEULA.txt" />
<Compile Update="ConsoleEncodingInitializer.cs" Pack="true" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.13.26" Pack="false" />
<PackageReference Include="NuGetizer" Version="1.4.7" />
<PackageReference Include="JsonPoke" Version="1.2.0" Pack="false" GeneratePathProperty="true" />
<PackageReference Include="NuGet.Versioning" Version="6.14.0" PrivateAssets="all" />
<PackageReference Include="PolySharp" Version="1.15.0" PrivateAssets="all" />
<PackageReference Include="ThisAssembly.Project" Version="2.1.2" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="..\_._" PackFolder="lib\netstandard2.0" Visible="false" />
<None Update="SmallSharp.props" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="SmallSharp.Version.props" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="SmallSharp.targets" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="SmallSharp.Before.targets" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="Sdk.*" PackFolder="Sdk" CopyToOutputDirectory="PreserveNewest" />
<None Include="$(PkgJsonPoke)\build\JsonPoke.dll" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" Visible="false" />
<None Include="$(PkgJsonPoke)\build\Newtonsoft.Json.dll" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" Visible="false" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Include="SmallSharp.targets;SmallSharp.Before.targets;Sdk.props;Sdk.Empty.props;Sdk.targets" />
<ProjectProperty Include="PackageVersion" />
</ItemGroup>
<Target Name="UpdatePackagingVersion" BeforeTargets="Pack">
<!-- Update packaging version targets -->
<XmlPoke XmlInputPath="$(OutputPath)SmallSharp.Version.props" Query="/Project/PropertyGroup/SmallSharpVersion" Value="$(Version)" />
</Target>
</Project>
================================================
FILE: src/SmallSharp/SmallSharp.props
================================================
<Project>
<PropertyGroup>
<!-- This disables compilation error in VS for #: prefix -->
<Features>$(Features);FileBasedProgram</Features>
<!-- Capture project-level properties before they are defaulted by Microsoft.Common.targets -->
<CustomBeforeMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)\SmallSharp.Before.props</CustomBeforeMicrosoftCSharpTargets>
<UsingSmallSharpSDK Condition="'$(UsingSmallSharpSDK)' == ''">false</UsingSmallSharpSDK>
</PropertyGroup>
<Import Project="SmallSharp.Version.props"/>
</Project>
================================================
FILE: src/SmallSharp/SmallSharp.targets
================================================
<Project>
<UsingTask AssemblyFile="SmallSharp.dll" TaskName="EmitTargets" />
<UsingTask AssemblyFile="JsonPoke.dll" TaskName="JsonPoke"/>
<PropertyGroup>
<UserProjectNamespace>
<Namespace Prefix="msb" Uri="http://schemas.microsoft.com/developer/msbuild/2003" />
</UserProjectNamespace>
<!-- Backs compat -->
<ActiveFile Condition="'$(ActiveCompile)' != ''">$(ActiveCompile)</ActiveFile>
<StartupFile>$(ActiveFile)</StartupFile>
<StartupFile Condition="'$(StartupFile)' == ''">$(Start)</StartupFile>
<StartupFile Condition="'$(StartupFile)' == ''">$(S)</StartupFile>
<StartupFile Condition="'$(StartupFile)' == ''">$(ActiveDebugProfile)</StartupFile>
<FindStartupFile Condition="'$(StartupFile)' == '' or !Exists('$(StartupFile)')">true</FindStartupFile>
<StartupFileDependsOn>EnsureProperties;CollectStartupFile;ResolveStartupFile;SelectStartupFile;SelectTopLevelCompile;UpdateLaunchSettings;EmitTargets</StartupFileDependsOn>
<!-- For CLI dotnet run, users must set ImportProjectExtensionProps/ImportProjectExtensionTargets=true -->
<SmallSharpPackagesProps>$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).smallsharp.props</SmallSharpPackagesProps>
<SmallSharpPackagesTargets>$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).smallsharp.targets</SmallSharpPackagesTargets>
</PropertyGroup>
<ItemGroup>
<!-- Ensures all top-level files show up in the IDE -->
<None Include="*.cs" Exclude="$(ActiveDebugProfile);$(ActiveFile)" />
<Compile Remove="*.cs" />
<!-- Ensure changes we make to this file trigger a new DTB -->
<UpToDateCheckBuilt Include="Properties\launchSettings.json" />
<UpToDateCheckBuilt Include="$(SmallSharpPackagesProps);$(SmallSharpPackagesTargets)" />
<Compile Update="@(Compile -> WithMetadataValue('NuGetPackageId', 'SmallSharp'))" Visible="false" />
<!-- Include linked compile files too -->
<_LinkedCompile Include="@(Compile -> HasMetadata('Link'))" />
<_NuGetLinked Include="@(_LinkedCompile -> HasMetadata('NuGetPackageId'))" />
<_NoNuGetLinked Include="@(_LinkedCompile)" Exclude="@(_NuGetLinked)" />
<LinkedCompile Include="@(_NoNuGetLinked -> WithoutMetadataValue('Visible', 'false'))" />
<Compile Remove="@(LinkedCompile -> WithoutMetadataValue('Link', '$(ActiveDebugProfile)'))" />
<None Include="@(LinkedCompile -> WithoutMetadataValue('Link', '$(ActiveFile)'))" />
</ItemGroup>
<!-- When restoring, if we include the source files, we'd get duplicate references. -->
<ItemGroup Condition="'$(MSBuildIsRestoring)' != 'true'">
<Compile Include="$(ActiveDebugProfile)" Condition="Exists('$(ActiveDebugProfile)')" />
<Compile Include="$(ActiveFile)" Condition="Exists('$(ActiveFile)') and '$(ActiveFile)' != '$(ActiveDebugProfile)'" />
</ItemGroup>
<Target Name="StartupFile" BeforeTargets="ResolvePackageAssets;CompileDesignTime;CollectUpToDateCheckInputDesignTime" DependsOnTargets="$(StartupFileDependsOn)" />
<Target Name="EnsureProperties" Condition="'$(CheckSmallSharpRequirements)' != 'false'">
<Error Code="SCS02" Condition="'$(_ImportProjectExtensionProps)' != 'true' or '$(_ImportProjectExtensionTargets)' != 'true'"
Text="Setting ImportProjectExtensionProps and ImportProjectExtensionTargets project properties to 'true' is required by SmallSharp to support C# package and project directives." />
<Error Code="SCS03" Condition="'$(ManagePackageVersionsCentrally)' == 'true'"
Text="Setting ManagePackageVersionsCentrally to 'true' is not supported by SmallSharp since C# program files can declare package references via #:package directives." />
<Warning Code="SCS04" Condition="'$(UsingSmallSharpSDK)' != 'true'"
Text='For maximum compatibility with file-based apps, use SmallSharp as an SDK instead of a package reference: <Project Sdk="SmallSharp/$(SmallSharpVersion)">' />
</Target>
<Target Name="CollectStartupFile">
<ItemGroup>
<StartupFile Include="*.cs" />
<StartupFile Include="@(LinkedCompile)" />
</ItemGroup>
</Target>
<Target Name="ResolveStartupFile" Condition="'$(FindStartupFile)' == 'true'">
<ItemGroup>
<!-- Find if the specified StartupFile is a filename that matches an available StartupFile item -->
<_MatchingStartupFile Include="@(StartupFile)" Condition="'$(StartupFile)' != '' and '%(Filename)%(Extension)' == '$(StartupFile)'" />
</ItemGroup>
<PropertyGroup Condition="'@(_MatchingStartupFile)' != ''">
<StartupFile>@(_MatchingStartupFile -> '%(Identity)')</StartupFile>
<FindStartupFile>false</FindStartupFile>
</PropertyGroup>
</Target>
<!-- Defaults the startup file to the first Compile, if none previously selected. -->
<Target Name="SelectStartupFile" Condition="'$(FindStartupFile)' == 'true'" Returns="$(StartupFile)">
<ItemGroup>
<ReversedCompile Include="@(StartupFile -> Reverse())" />
</ItemGroup>
<PropertyGroup>
<StartupFile>%(ReversedCompile.Identity)</StartupFile>
</PropertyGroup>
<!-- If .user file doesn't exist at all, create it now -->
<PropertyGroup Condition="!Exists('$(MSBuildProjectFullPath).user')">
<UserProject>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>$(StartupFile)</ActiveDebugProfile>
</PropertyGroup>
</Project>
</UserProject>
<WriteStartupFile>false</WriteStartupFile>
</PropertyGroup>
<WriteLinesToFile File="$(MSBuildProjectFullPath).user"
Lines="$(UserProject)"
Condition="!Exists('$(MSBuildProjectFullPath).user')" />
<XmlPoke XmlInputPath="$(MSBuildProjectFullPath).user"
Value="$(StartupFile)"
Query="/msb:Project/msb:PropertyGroup/msb:ActiveDebugProfile"
Namespaces="$(UserProjectNamespace)"
Condition="'$(WriteStartupFile)' != 'false' and '$(StartupFile)' != ''"/>
<XmlPeek XmlInputPath="$(MSBuildProjectFullPath).user"
Query="/msb:Project/msb:PropertyGroup/msb:ActiveDebugProfile/text()"
Namespaces="$(UserProjectNamespace)">
<Output TaskParameter="Result" PropertyName="StartupDebugProfile" />
</XmlPeek>
<PropertyGroup Condition="'$(StartupFile)' != '' and '$(StartupDebugProfile)' != '$(StartupFile)'">
<ActiveDebugProfileProperty>
<ActiveDebugProfile>$(StartupFile)</ActiveDebugProfile>
</ActiveDebugProfileProperty>
</PropertyGroup>
<!-- The ActiveDebugProfile property element may be missing, failing to write the value -->
<XmlPoke XmlInputPath="$(MSBuildProjectFullPath).user"
Value="$(ActiveDebugProfileProperty)"
Query="/msb:Project/msb:PropertyGroup"
Namespaces="$(UserProjectNamespace)"
Condition="'$(StartupFile)' != '' and '$(StartupDebugProfile)' != '$(StartupFile)'"/>
<!-- Read again after we poke the ActiveDebugProfile property -->
<XmlPeek XmlInputPath="$(MSBuildProjectFullPath).user"
Query="/msb:Project/msb:PropertyGroup/msb:ActiveDebugProfile/text()"
Namespaces="$(UserProjectNamespace)">
<Output TaskParameter="Result" PropertyName="StartupDebugProfile" />
</XmlPeek>
<!-- The entire PropertyGroup could have been missing, failing to write the value -->
<PropertyGroup Condition="'$(StartupFile)' != '' and '$(StartupDebugProfile)' != '$(StartupFile)'">
<UserPropertyGroup>
<PropertyGroup>
<ActiveDebugProfile>$(StartupFile)</ActiveDebugProfile>
</PropertyGroup>
</UserPropertyGroup>
</PropertyGroup>
<XmlPoke XmlInputPath="$(MSBuildProjectFullPath).user"
Value="$(UserPropertyGroup)"
Query="/msb:Project"
Namespaces="$(UserProjectNamespace)"
Condition="'$(StartupFile)' != '' and '$(StartupDebugProfile)' != '$(StartupFile)'"/>
<!-- Read again after we poke the PropertyGroup -->
<XmlPoke XmlInputPath="$(MSBuildProjectFullPath).user"
Value="$(StartupFile)"
Query="/msb:Project/msb:PropertyGroup/msb:ActiveDebugProfile"
Namespaces="$(UserProjectNamespace)"
Condition="'$(StartupFile)' != '' and '$(StartupDebugProfile)' != '$(StartupFile)'"/>
<Warning Text="Could not set ActiveDebugProfile=$(StartupFile). Run the project once to fix it."
Condition="'$(StartupFile)' != '' and '$(StartupDebugProfile)' != '$(StartupFile)'"/>
</Target>
<Target Name="SelectTopLevelCompile">
<ItemGroup>
<!-- We remove all top-level from Compile because copy/pasting startup files may end up
causing those items to be hardcoded in the .csproj -->
<Compile Remove="@(Compile -> WithMetadataValue('RelativeDir', ''))" />
<!-- Remove linked compile files too -->
<Compile Remove="@(Compile -> HasMetadata('Link'))" />
<Compile Include="$(StartupFile)" Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)')" />
<UpToDateCheckInput Include="$(StartupFile)" Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)')" />
</ItemGroup>
</Target>
<Target Name="AfterClean">
<Delete Files="$(MSBuildProjectDirectory)\Properties\launchSettings.json"
Condition="Exists('$(MSBuildProjectDirectory)\Properties\launchSettings.json')" />
</Target>
<Target Name="UpdateLaunchSettings">
<WriteLinesToFile File="$(MSBuildProjectDirectory)\Properties\launchSettings.json"
Lines="{ }"
Condition="!Exists('$(MSBuildProjectDirectory)\Properties\launchSettings.json')" />
<SortItems Items="@(StartupFile)">
<Output TaskParameter="SortedItems" ItemName="SortedStartupFile" />
</SortItems>
<JsonPoke ContentPath="$(MSBuildProjectDirectory)\Properties\launchSettings.json"
Query="$.profiles['%(SortedStartupFile.Filename)%(SortedStartupFile.Extension)'].commandName"
Value="Project" />
</Target>
<Target Name="EmitTargets" DependsOnTargets="CollectStartupFile;SelectTopLevelCompile;SelectStartupFile"
Inputs="@(Compile);$(ActiveDebugProfile);$(ActiveFile);Properties\launchSettings.json"
Outputs="$(SmallSharpPackagesProps);$(SmallSharpPackagesTargets)">
<EmitTargets StartupFile="$(StartupFile)"
UsingSDK="$(UsingSmallSharpSDK)"
PackageReferences="@(PackageReferences)"
PropsFile="$(SmallSharpPackagesProps)"
TargetsFile="$(SmallSharpPackagesTargets)"
BaseIntermediateOutputPath="$(BaseIntermediateOutputPath)">
<Output TaskParameter="Packages" ItemName="FileBasedPackage" />
<Output TaskParameter="Properties" PropertyName="FileBasedProperty" />
<Output TaskParameter="Sdks" PropertyName="FileBasedSdk" />
<Output TaskParameter="RestoreNeeded" PropertyName="RestoreNeeded" />
</EmitTargets>
<Error Code="SCS001" Condition="'@(FileBasedSdk)' != '' and '$(UsingSmallSharpSDK)' != 'true'"
Text="Using #:sdk directives requires using SmallSharp as an SDK instead of a package reference: <Project Sdk='SmallSharp/$(SmallSharpVersion)'>" />
</Target>
<UsingTask TaskName="SortItems" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<!-- ITaskItem[] input and sorted output -->
<Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<SortedItems ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
SortedItems = Items.OrderBy(i => i.ItemSpec).ToArray();
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="DumpItems" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<ItemName />
</ParameterGroup>
<Task>
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Using Namespace="System" />
<Using Namespace="System.Linq" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var itemName = ItemName ?? "Item";
if (Items.Length == 0)
Log.LogMessage(MessageImportance.High, "No {0} items received to dump.", ItemName ?? "");
else
Log.LogMessage(MessageImportance.High, "Dumping {0} {1} items.", Items.Length, ItemName ?? "");
foreach (var item in Items.OrderBy(i => i.ItemSpec))
{
Log.LogMessage(MessageImportance.High, "{0}: {1}", itemName, item.ItemSpec);
foreach (var name in item.MetadataNames.OfType<string>().OrderBy(_ => _))
{
try
{
Log.LogMessage(MessageImportance.High, "\t{0}={1}", name, item.GetMetadata(name));
}
catch { }
}
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
================================================
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<string, string> 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
================================================
[](osmfeula.txt)
[](license.txt)
<!-- include ../../readme.md#description -->
<!-- include https://github.com/devlooped/.github/raw/main/osmf.md -->
<!-- include ../../readme.md#content -->
<!-- include https://github.com/devlooped/sponsors/raw/main/footer.md -->
<!-- exclude -->
================================================
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
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="signatureValidationMode" value="accept" />
</config>
<trustedSigners>
<author name="Microsoft">
<certificate fingerprint="3F9001EA83C560D712C24CF213C3D312CB3BFF51EE89435D3430BD06B5D0EECE" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="AA12DA22A49BCE7D5C1AE64CC1F3D892F150DA76140F210ABD2CBFFCA2C18A27" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="566A31882BE208BE4422F7CFD66ED09F5D4524A5994F50CCC8B05EC0528C1353" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="1F4B311D9ACC115C8DC8018B5A49E00FCE6DA8E2855F9F014CA6F34570BC482D" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
</author>
<repository name="nuget.org" serviceIndex="https://api.nuget.org/v3/index.json">
<certificate fingerprint="0E5F38F57DC1BCC806D8494F4F90FBCEDD988B46760709CBEEC6F4219AA6157D" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="5A2901D6ADA3D18260B9C6DFE2133C95D74B9EEF6AE0E5DC334C8454D1477DF4" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="CF7AC17AD047ECD5FDC36822031B12D4EF078B6F2B4C5E6BA41F8FF2CF4BAD67" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="C474CE76007D02394E0DA5E4DE7C14C680F9E282013CFEF653EF5DB71FDF61F8" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
<certificate fingerprint="1F4B311D9ACC115C8DC8018B5A49E00FCE6DA8E2855F9F014CA6F34570BC482D" hashAlgorithm="SHA256" allowUntrustedRoot="true" />
</repository>
</trustedSigners>
</configuration>
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
SYMBOL INDEX (8 symbols across 3 files)
FILE: src/SmallSharp/ConsoleEncodingInitializer.cs
class ConsoleEncodingInitializer (line 11) | class ConsoleEncodingInitializer
method Init (line 14) | [ModuleInitializer]
FILE: src/SmallSharp/EmitTargets.cs
class EmitTargets (line 14) | public class EmitTargets : Task
method Execute (line 49) | public override bool Execute()
method WriteXml (line 153) | void WriteXml(string path, XElement root)
FILE: src/SmallSharp/TaskItemFactory.cs
class TaskItemFactory (line 7) | static class TaskItemFactory
method NewTaskItem (line 9) | public static TaskItem NewTaskItem(string itemSpec, Dictionary<string,...
method NewTaskItem (line 11) | public static TaskItem NewTaskItem(string itemSpec, params (string Key...
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (146K chars).
[
{
"path": ".editorconfig",
"chars": 3945,
"preview": "# EditorConfig is awesome:http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Don't use tabs for indent"
},
{
"path": ".gitattributes",
"chars": 214,
"preview": "# normalize by default\n* text=auto encoding=UTF-8\n*.sh text eol=lf\n*.sbn eol=lf\n\n# These are windows specific files whic"
},
{
"path": ".github/FUNDING.yml",
"chars": 18,
"preview": "github: devlooped\n"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 410,
"preview": "contact_links:\n - name: Questions\n url: https://github.com/devlooped/SmallSharp/discussions?discussions_q=category%3"
},
{
"path": ".github/actions/dotnet/action.yml",
"chars": 1196,
"preview": "name: ⚙ dotnet\ndescription: Configures dotnet if the repo/org defines the DOTNET custom property\n\nruns:\n using: composi"
},
{
"path": ".github/copilot-instructions.md",
"chars": 4203,
"preview": "# .NET Repository\n\n**Always reference these instructions first and fallback to search or bash commands only when you enc"
},
{
"path": ".github/dependabot.yml",
"chars": 1082,
"preview": "# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repositor"
},
{
"path": ".github/dotnet.json",
"chars": 13,
"preview": "[\n \"10.x\"\n]\n"
},
{
"path": ".github/release.yml",
"chars": 602,
"preview": "changelog:\n exclude:\n labels:\n - bydesign\n - dependencies\n - duplicate\n - question\n - inval"
},
{
"path": ".github/workflows/build.yml",
"chars": 2969,
"preview": "# Builds and runs tests in all three supported OSes\n# Pushes CI feed if secrets.SLEET_CONNECTION is provided\n\nname: bui"
},
{
"path": ".github/workflows/changelog.config",
"chars": 345,
"preview": "usernames-as-github-logins=true\nissues_wo_labels=true\npr_wo_labels=true\nexclude-labels=bydesign,dependencies,duplicate,d"
},
{
"path": ".github/workflows/changelog.yml",
"chars": 1116,
"preview": "name: changelog\non:\n workflow_dispatch:\n release:\n types: [released]\n\njobs:\n changelog:\n runs-on: ubuntu-lates"
},
{
"path": ".github/workflows/combine-prs.yml",
"chars": 6359,
"preview": "# Source: https://github.com/hrvey/combine-prs-workflow\n# Tweaks: regex support for branch\n\nname: '⛙ combine-prs'\n\non:\n "
},
{
"path": ".github/workflows/demos.yml",
"chars": 1392,
"preview": "name: demos\non: \n push:\n branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ]\n paths-ignore:\n - changelog"
},
{
"path": ".github/workflows/dotnet-env.yml",
"chars": 1067,
"preview": "name: dotnet-env\non:\n workflow_dispatch:\n push:\n branches: \n - main\n paths:\n - '**/*.*proj'\n\njobs:\n w"
},
{
"path": ".github/workflows/dotnet-file.yml",
"chars": 463,
"preview": "# Synchronizes .netconfig-configured files with dotnet-file\nname: dotnet-file\non:\n workflow_dispatch:\n schedule:\n -"
},
{
"path": ".github/workflows/includes.yml",
"chars": 1784,
"preview": "name: +Mᐁ includes\non: \n workflow_dispatch:\n push:\n branches:\n - 'main'\n paths:\n - '**.md'\n - '!c"
},
{
"path": ".github/workflows/pages.yml",
"chars": 2300,
"preview": "# Workflow to cross-post a jekyll site (or GitHub Pages)\n# to another org/repo. \n# Required secrets in repository consum"
},
{
"path": ".github/workflows/publish.yml",
"chars": 1901,
"preview": "# Builds a final release version and pushes to nuget.org \n# whenever a release is published.\n# Requires: secrets.NUGET_"
},
{
"path": ".github/workflows/triage.yml",
"chars": 4367,
"preview": "name: 'triage'\non:\n schedule:\n - cron: '42 0 * * *'\n\n workflow_dispatch:\n # Manual triggering through the GitHub"
},
{
"path": ".gitignore",
"chars": 387,
"preview": "bin\nobj\nartifacts\npack\nTestResults\nresults\nBenchmarkDotNet.Artifacts\n/app\n.vs\n.vscode\n.genaiscript\n.idea\nlocal.settings."
},
{
"path": ".netconfig",
"chars": 7183,
"preview": "[file]\n\turl = https://github.com/devlooped/oss\n\turl = https://github.com/clarius/pages\n[file \".netconfig\"]\n\turl = https:"
},
{
"path": "Directory.Build.rsp",
"chars": 129,
"preview": "# See https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files\n-nr:false\n-m:1\n-v:m\n-clp:Summary;Forc"
},
{
"path": "Gemfile",
"chars": 84,
"preview": "source 'https://rubygems.org'\n\ngem 'github-pages', '~> 231', group: :jekyll_plugins\n"
},
{
"path": "SmallSharp.slnx",
"chars": 77,
"preview": "<Solution>\n <Project Path=\"src/SmallSharp/SmallSharp.csproj\" />\n</Solution>\n"
},
{
"path": "_config.yml",
"chars": 87,
"preview": "theme: jekyll-theme-slate\n\nexclude: [ 'src/', '*.sln', '*.slnx', 'Gemfile*', '*.rsp' ]\n"
},
{
"path": "assets/css/style.scss",
"chars": 338,
"preview": "---\n---\n\n@import \"jekyll-theme-slate\";\n\n.inner {\n max-width: 960px;\n}\n\npre, code {\n background-color: unset;\n f"
},
{
"path": "changelog.md",
"chars": 11532,
"preview": "# Changelog\n\n## [v2.3.1](https://github.com/devlooped/SmallSharp/tree/v2.3.1) (2026-04-09)\n\n[Full Changelog](https://git"
},
{
"path": "license.txt",
"chars": 1096,
"preview": "The MIT License (MIT)\n\nCopyright (c) Daniel Cazzulino and Contributors\n\nPermission is hereby granted, free of charge, to"
},
{
"path": "osmfeula.txt",
"chars": 3338,
"preview": "End User License Agreement\n\nThis Open Source Maintenance Fee Agreement (\"Agreement\") is a legal agreement\nbetween you (\""
},
{
"path": "readme.md",
"chars": 12947,
"preview": " SmallSharp\n============\n\n[](osmfeula.txt)\n[. The extraction includes 58 files (134.6 KB), approximately 37.1k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.