Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
root = true

# -------------------------------
# General
# -------------------------------
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

# -------------------------------
# C# files
# -------------------------------
[*.cs]

indent_size = 4

# New lines & braces
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true:warning

# Using directives
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = true

# var usage (Spectre-style: pragmatic)
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = false:suggestion

# Expression-bodied members (used where clean)
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion

# Pattern matching / modern C#
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion

# Nullability helpers
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion

# Readonly fields
dotnet_style_readonly_field = true:suggestion

# -------------------------------
# Naming
# -------------------------------

# Private fields: _camelCase
dotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_with_underscore

dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private

dotnet_naming_style.camel_case_with_underscore.capitalization = camel_case
dotnet_naming_style.camel_case_with_underscore.required_prefix = _

# Interfaces: IMyInterface
dotnet_naming_rule.interfaces_should_start_with_i.severity = suggestion
dotnet_naming_rule.interfaces_should_start_with_i.symbols = interfaces
dotnet_naming_rule.interfaces_should_start_with_i.style = interface_prefix

dotnet_naming_symbols.interfaces.applicable_kinds = interface

dotnet_naming_style.interface_prefix.required_prefix = I
dotnet_naming_style.interface_prefix.capitalization = pascal_case

# -------------------------------
# Analyzers
# -------------------------------

# Keep warnings visible but not painful
dotnet_analyzer_diagnostic.severity = warning

# Unused usings
dotnet_diagnostic.IDE0005.severity = warning

# Simplification
dotnet_diagnostic.IDE0007.severity = suggestion
dotnet_diagnostic.IDE0008.severity = suggestion

# Documentation (Spectre.Console is pragmatic here)
dotnet_diagnostic.CS1591.severity = silent

# -------------------------------
# JSON / YAML
# -------------------------------
[*.json]
indent_size = 2

[*.yml]
indent_size = 2

[*.yaml]
indent_size = 2
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* stuart.meeks@stuartmeeks.net
85 changes: 85 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: ci

on:
push:
branches: [master]
pull_request:
branches: [master]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
DOTNET_VERSION: "10.0.x"
DOTNET_NOLOGO: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"

jobs:
core-tests:
name: Core build + tests (ubuntu)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
cache: true
cache-dependency-path: |
**/packages.lock.json
**/*.csproj

- name: Restore Core projects
run: |
dotnet restore src/Snipdeck.Core/Snipdeck.Core.csproj
dotnet restore tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj

- name: Build Core
run: dotnet build src/Snipdeck.Core/Snipdeck.Core.csproj --configuration Release --no-restore

- name: Build + run Core tests
run: dotnet test tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-restore --logger "trx;LogFileName=core-tests.trx" --results-directory TestResults

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: core-test-results
path: TestResults/

app-build:
name: App build (windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
cache: true
cache-dependency-path: |
**/packages.lock.json
**/*.csproj

- name: Restore
run: dotnet restore

- name: Build solution
run: dotnet build --configuration Release --no-restore

- name: Run Core tests (sanity check on Windows)
run: dotnet test tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-build --logger "trx;LogFileName=core-tests-windows.trx" --results-directory TestResults

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: windows-test-results
path: TestResults/
103 changes: 103 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: release

# Triggered by tags of the form vMAJOR.MINOR.PATCH (stable) or
# vMAJOR.MINOR.PATCH-suffix (pre-release; the hyphen marks it as a pre-release
# on GitHub and feeds Velopack's channel).
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-*"

permissions:
contents: write

env:
DOTNET_VERSION: "10.0.x"
DOTNET_NOLOGO: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
PACK_ID: "Snipdeck"
PACK_AUTHORS: "Stuart Meeks"
PACK_TITLE: "Snipdeck"

jobs:
release:
name: Build + publish Velopack release
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
cache: true
cache-dependency-path: |
**/packages.lock.json
**/*.csproj

- name: Derive version + channel from tag
id: meta
shell: pwsh
run: |
$tag = "${{ github.ref_name }}"
$version = $tag.TrimStart('v')
$isPrerelease = $version.Contains('-')
if ($isPrerelease) {
$channel = ($version -split '-', 2)[1] -replace '\..*$', ''
if ([string]::IsNullOrWhiteSpace($channel)) { $channel = 'beta' }
} else {
$channel = 'stable'
}
"version=$version" >> $env:GITHUB_OUTPUT
"isPrerelease=$isPrerelease" >> $env:GITHUB_OUTPUT
"channel=$channel" >> $env:GITHUB_OUTPUT
Write-Host "Tag : $tag"
Write-Host "Version : $version"
Write-Host "Pre-release : $isPrerelease"
Write-Host "Channel : $channel"

- name: Restore
run: dotnet restore

- name: Run Core tests
run: dotnet test tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-restore

- name: Publish Snipdeck.App (win-x64)
run: dotnet publish src/Snipdeck.App/Snipdeck.App.csproj --configuration Release --runtime win-x64 --self-contained true --output publish

- name: Install Velopack CLI
run: dotnet tool install -g vpk

- name: Pack with Velopack
shell: pwsh
run: |
$version = "${{ steps.meta.outputs.version }}"
$channel = "${{ steps.meta.outputs.channel }}"
vpk pack `
--packId "${{ env.PACK_ID }}" `
--packVersion $version `
--packDir publish `
--mainExe Snipdeck.App.exe `
--packTitle "${{ env.PACK_TITLE }}" `
--packAuthors "${{ env.PACK_AUTHORS }}" `
--channel $channel `
--outputDir Releases

- name: Upload Velopack artifacts
uses: actions/upload-artifact@v4
with:
name: snipdeck-${{ steps.meta.outputs.version }}
path: Releases/

- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
prerelease: ${{ steps.meta.outputs.isPrerelease }}
generate_release_notes: true
files: |
Releases/*
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Changelog

All notable changes to Snipdeck are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Repository scaffold: `Snipdeck.Core` (net10.0, UI-free), `Snipdeck.App` (WinUI 3,
net10.0-windows), `Snipdeck.Core.Tests` (xUnit).
- Apache 2.0 licence.
- README and changelog.
- Core domain models: `Cli`, `Snip`, `Parameter` (Text / Choice), `Tag` as a
string list, root `SnipStoreDocument` with `SchemaVersion`.
- `SubstitutionEngine` — replaces `{placeholder}` tokens against a value
dictionary and returns both the resolved text and the list of unresolved tokens
in first-appearance order. Only `[A-Za-z_][A-Za-z0-9_]*` is treated as a token,
so JSON braces and other literal braces pass through.
- `ISnipStore` / `JsonSnipStore` — `System.Text.Json`-backed store with
temp-file-then-rename atomic writes, schema-version guarding, and a single
semaphore protecting concurrent access.
- `ISettingsStore` / `JsonSettingsStore` — application settings stored separately
from the snip store, with defaults (`Theme = System`, `CloseBehaviour =
HideToTray`, hotkey = Ctrl+Alt+S) applied when the file is missing.
- `IBackupService` / `BackupService` — copies the snip store to a timestamped
filename on demand, prunes to the configured retention (default 20), and
exposes a newest-first listing.
- `IClock` / `SystemClock` for testable time.
- `ExamplesSeed` — first-run seed producing one "Examples" CLI with a handful of
representative Snips (Text + Choice parameters, tags, a favourite).
- GitHub Actions CI workflow: builds Core on Ubuntu, builds the full solution on
Windows, runs Core tests on both.
- GitHub Actions release workflow: tag-triggered (`v*.*.*` stable, `v*.*.*-*`
pre-release), publishes the app, packs with Velopack, and attaches the
artefacts to a GitHub Release.
- `.editorconfig` taken verbatim from
`StuartMeeks/NextIteration.SpectreConsole.SelfUpdate`. Enforces brace style,
`using` ordering, naming conventions, and analyser severities.
- `Directory.Build.props` at the repo root: shared `Authors` ("Stuart Meeks"),
`Company` ("Next Iteration"), copyright, nullability, implicit usings, latest
`LangVersion`, and **`TreatWarningsAsErrors=true`** + `EnforceCodeStyleInBuild`.
- `Directory.Packages.props` at the repo root for Central Package Management
(`ManagePackageVersionsCentrally=true`,
`CentralPackageTransitivePinningEnabled=true`).
- `CONTRIBUTING.md` covering prerequisites, the non-negotiables, the
Core / App boundary discipline, the release process, and how to ask for
direction when something's genuinely ambiguous.

### Changed
- All Core source converted to block-scoped namespaces, collection expressions
(`[]`), and a `GeneratedRegex`-backed token regex to comply with the
editorconfig under `TreatWarningsAsErrors`.
- `JsonSnipStore`, `JsonSettingsStore`, and `BackupService` now implement
`IDisposable` to release their internal semaphores (CA1001).
- `Snipdeck.App.csproj` no longer carries `<Nullable>` (now inherited from
`Directory.Build.props`).
- Bumped test-project dependencies to latest stable: `coverlet.collector` 6.0.4 →
10.0.1, `Microsoft.NET.Test.Sdk` 17.14.1 → 18.6.0, `xunit.runner.visualstudio`
3.1.4 → 3.1.5.
Loading
Loading