Skip to content

release: merge develop into main - #91

Merged
mcpolo99 merged 18 commits into
mainfrom
develop
Jul 4, 2026
Merged

release: merge develop into main#91
mcpolo99 merged 18 commits into
mainfrom
develop

Conversation

@mcpolo99

@mcpolo99 mcpolo99 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Release merge of develop into main. Brings 16 commits (75 files) accumulated since the last release.

⚠️ Merge method

Use a merge commit — NOT squash (project convention: keep develop/main history aligned).

Included since last release

Release note (Actions currently disabled)

GitHub Actions are disabled (minutes overflow), so merging this will not auto-cut a release — no build/package/tag/publish will run. To release once minutes reset:

  1. Re-enable Actions.
  2. After this merge, main has the new ci.yml with workflow_dispatch — trigger it manually from the Actions tab to build/package/publish (the merge push won't retro-trigger).

Alternatively, re-enable Actions before merging so the merge push to main triggers the release automatically (costs the full build minutes).

Validation

All work was validated locally via scripts/local-ci.sh (full build + test + coverage, all green) on develop.

mcpolo99 and others added 17 commits June 11, 2026 06:29
…utput (#78)

Before: ci.yml and test.yml each did a full MSBuild restore + build (~4 min each)
After: ci.yml builds + uploads artifacts, test.yml downloads and runs tests only

Changes:
- ci.yml: add build-output artifact upload (1-day retention)
- ci.yml: add paths-ignore (skip on docs-only changes)
- ci.yml: add concurrency (cancel in-progress on new push)
- test.yml: trigger on workflow_run (waits for CI completion)
- test.yml: download build-output instead of building
- test.yml: no more restore/build steps
- format.yml: only trigger when .cs/.vb/.editorconfig files change

Saves ~4 min of Windows runner time per push by not building twice.
All 3 workflows remain separate with their own status checks.

Co-authored-by: RandomCrocodile <mawi@polosab.com>
CodeQL was still on windows-2022 (MSBuild 17) which can't resolve
.NET 10 SDK from global.json. Also removed obsolete .NET 4.6.1
targeting pack install (net48 doesn't need it) and bumped checkout to v5.
- Issue template config: point discussions to our repo, add Ideas + General links
- CODEOWNERS: mkaring → mcpolo99
- FUNDING.yml: mkaring → mcpolo99
- MSBuild.Tasks PackageProjectUrl: point to our repo
- Test [Trait("Issue")] attributes kept as upstream attribution (historical)
…ents

workflow_run doesn't associate with PRs reliably — test results and
coverage comments were not posted on PR #79. Reverted to direct
push/pull_request triggers with its own build step.

Still optimized with:
- paths-ignore (skip on docs-only changes)
- concurrency (cancel in-progress on new push)
- NuGet cache (shared cache key with ci.yml)
- Removed build-output artifact from ci.yml (no longer needed)
…elop (#82)

* chore: rename master→main, pre-release→develop, adopt GitHub branch conventions

Branch renames:
- master → main (default branch)
- pre-release → develop (integration branch)

All references updated:
- ci.yml: triggers, release jobs, dev-release job (was pre-release)
- test.yml, format.yml, codeql-analysis.yml: branch triggers
- dependabot.yml: target branch
- README.md: contributing section, CI badge
- CONTRIBUTING.md: branch naming convention, PR target

New branch naming convention (documented in CONTRIBUTING.md):
- Issue-linked: {number}-{slug} via gh issue develop
- Feature: feature/{description}
- Chore: chore/{description}
- Docs: docs/{description}
- Hotfix: hotfix/{description} (from main)
- Experiment: experiment/{description}

* feat(ci): lightweight lint on push, full CI on PR only (#81)

New workflow layout:
- lint.yml (NEW): every push + PR — format + style + Roslyn analyzers
  without building. Posts results to PR comment or issue comment
  (parses issue number from branch name). ~2 min.
- ci.yml: PR + main/develop push only — full build + package
- test.yml: PR only — full build + test + coverage
- codeql: weekly (Mon 3am, skips if no commits) + manual trigger
- format.yml: DELETED — replaced by lint.yml

Cost per event:
- Feature branch push: ~2 min (was ~9 min) — 77% reduction
- PR: ~11 min (lint + ci + test, unchanged)
- Merge to main: ~5 min (ci only)
- CodeQL: weekly conditional, not on every push/PR

---------

Co-authored-by: RandomCrocodile <mawi@polosab.com>
# Conflicts:
#	.github/workflows/ci.yml
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/test.yml
#	CONTRIBUTING.md
…Logging (#85)

* refactor: extract IProgressReporter from ILogger (#64)

Separate progress reporting (Progress, EndProgress, Finish) from logging
into a new IProgressReporter interface. This is the first step toward
replacing the custom ILogger with Microsoft.Extensions.Logging + Serilog.

- Add IProgressReporter interface and NullProgressReporter
- Remove Progress, EndProgress, Finish from ILogger and all implementations
- Remove dead BeginModule/EndModule from NullLogger
- Add ProgressReporter property to ConfuserParameters and ConfuserContext
- Replace PackerLogger (full ILogger decorator) with PackerProgressReporter
- Update all WithProgress call sites to use context.ProgressReporter
- Fix MSBuildLogger.Finish bug (was setting HasError=false on failure)
- Seal NullLogger class

* feature: add M.E.L abstraction and MelLoggerAdapter (#64)

Add Microsoft.Extensions.Logging.Abstractions to Confuser.Core
(netstandard2.0 compatible) and a MelLoggerAdapter that bridges
M.E.L ILogger to the internal Confuser.Core.ILogger interface.

This allows callers to pass a standard M.E.L logger (backed by
Serilog or any other provider) into ConfuserEngine without
changing any internal code yet.

* feature: replace CLI ConsoleLogger with Serilog (#64)

Wire up Serilog as the logging provider in the CLI via
Microsoft.Extensions.Logging and MelLoggerAdapter.

- Add Serilog, Serilog.Extensions.Logging, Serilog.Sinks.Console
- Delete custom ConsoleLogger — Serilog handles all console output
- Add --verbose (-v, -vv, -vvv) and --quiet (-q) CLI flags
- Default: Information level; -q: Warning; -v: Debug; -vv+: Verbose

* feature: replace GUI logger with Serilog FlowDocument sink (#64)

Wire up Serilog in the WPF GUI via a custom FlowDocumentSink that
renders color-coded log output to the protection log panel.

- Add Serilog and Serilog.Extensions.Logging to ConfuserEx
- Create FlowDocumentSink — custom Serilog sink for WPF Paragraph
- Remove ILogger from ProtectTabVM — now uses MelLoggerAdapter
- ProtectTabVM keeps only IProgressReporter (progress bar + finish)
- Delete ~40 lines of manual ILogger boilerplate

* chore: add local-ci.sh script mirroring GitHub Actions pipeline

Full local CI script that replicates lint.yml, ci.yml, and test.yml:
- lint: whitespace, style, and analyzer checks via dotnet format
- build: dotnet build for SDK projects + MSBuild.exe for C++/CLI
- test: discovers all *.Test.csproj, runs with coverage, summary
- package: creates CLI, GUI, and combined zip archives

Usage: ./scripts/local-ci.sh [lint|build|test|package|all]

* refactor: replace Confuser.Core.ILogger with Microsoft.Extensions.Logging.ILogger (#64)

Complete migration from the custom 13-method ILogger interface to the
standard M.E.L ILogger abstraction across all projects.

- Change ConfuserContext.Logger and ConfuserParameters.Logger to M.E.L ILogger
- Convert all ~80 call sites: Debug→LogDebug, Info→LogInformation,
  Warn→LogWarning, Error→LogError, *Exception→swap parameter order
- Rewrite MSBuildLogger as MSBuildMelLogger implementing M.E.L ILogger
- Rewrite XUnitLogger implementing M.E.L ILogger + IProgressReporter
- Remove MelLoggerAdapter (no longer needed — M.E.L is the native type)
- Delete Confuser.Core.ILogger, NullLogger (replaced by M.E.L NullLogger)
- Add test-results/ and coverage/ to .gitignore

* chore: fix import ordering to pass CI lint (#64)

* fix: dispose GUI logger factory after async protection completes (#64)

DoProtect used 'using var loggerFactory' which disposed the factory
(and the Serilog logger via dispose:true) as soon as DoProtect returned.
Since ConfuserEngine.Run executes asynchronously on a background thread,
this disposed the logger before the protection actually used it, silently
dropping log output mid-run. Move disposal into the ContinueWith
continuation so the factory lives for the full protection lifetime.

* test: fix flaky GUI protect test — deterministic tab navigation (#64)

Gui_ProtectSampleApp_ShowsSuccess intermittently failed at the Protect!
button lookup (~1 in 3 runs). Two root causes:

1. ByText("Protect!") ambiguously matched both the tab header and the
   Protect! button (they share the caption), so the wrong element could
   be clicked and the tab never actually got selected.
2. WPF virtualizes inactive tab content — the Protect! button does not
   enter the UIA tree until the tab is selected AND rendered. The 5s
   button-find timeout was too short under load.

Fix: match the tab by TabItem control type + name, Select() it and wait
for IsSelected, then find the button with a 15s timeout. 5/5 consecutive
full-suite runs now pass 3/3 (previously ~1/3 failed).

---------

Co-authored-by: RandomCrocodile <mawi@polosab.com>
* fix: make all test projects discoverable and green (#84)

~30 test projects were silently skipped by dotnet test — they lacked
Microsoft.NET.Test.Sdk / xunit.runner.visualstudio, so discovery found
0 tests and exited 0. Making them run revealed several pre-existing
failures the skip had hidden. This fixes discovery AND all exposed
failures: 144 tests now run and pass (was 29 across 3 projects).

Test infrastructure:
- Add Microsoft.NET.Test.Sdk + xunit runner to Tests/Directory.Build.props
  for every *.Test project; remove the now-duplicate refs from the 3
  projects that had them; trim the shared Confuser.UnitTest library to
  just xunit (it has no tests)
- Copy Confuser.Runtime.dll (net20) next to every test binary — the
  protection pipeline needs it at obfuscation time
- Move Coverlet coverage to Directory.Build.targets, enabled only for
  .NET (Core) test projects: Coverlet instruments the signed Confuser.*
  assemblies in-place, breaking their strong name, which .NET Framework
  refuses to load (net4x tests still run, just without coverage)

Product robustness (analyzers must not crash on unresolvable BCL refs):
- TypeBlobAnalyzer: skip external/unresolvable attribute and generic
  types instead of ResolveTypeDefThrow — they are never renamed anyway
- ManifestResourceAnalyzer: build the GetManifestResourceStream(string)
  reference from the call's own signature when the BCL declaring type
  can't be resolved; unchanged when resolution succeeds

Test brittleness (assert on stable data, not volatile obfuscated names):
- MethodOverloading: assert original names are present in the symbols
  map values instead of looking up by hardcoded obfuscated keys
- MessageDeobfuscation: verify the symbols map contains the expected
  originals and round-trips through the deobfuscator, instead of
  asserting exact obfuscated identifiers

Fixes #84

* fix: drive test coverage via RunSettingsFilePath, not --collect (#84)

Both local-ci.sh and the test.yml CI workflow forced
--collect:"XPlat Code Coverage" on the command line for every test
project. That flag overrides the per-project RunSettingsFilePath gate
(Tests/Directory.Build.targets), forcing Coverlet to instrument the
signed Confuser.* assemblies on net4x — which breaks their strong name
so .NET Framework refuses to load them, failing every net4x test.

Drop --collect and let RunSettingsFilePath enable coverage only for the
.NET (Core) test projects. Coverage is still collected for the 5 Core
assemblies; net4x projects run clean. Full local CI: 144 passed, 0 failed.

* chore: skip Confuser.GUI.Test by default in local-ci (#84)

Confuser.GUI.Test is a FlaUI smoke test that launches and drives the real
ConfuserEx WPF window — it pops UI on screen and takes ~35s. Skip it by
default for quiet local runs; set RUN_GUI_TESTS=1 to include it. It still
runs in CI on the headless Windows runner.

* chore: ignore release .zip packages (#84)

The CI/local-ci package step produces ConfuserEx-CLI.zip, ConfuserEx-GUI.zip
and ConfuserEx.zip in the repo root — ignore them so they aren't committed.

---------

Co-authored-by: RandomCrocodile <mawi@polosab.com>
* chore: upgrade dnlib from 3.6.0 to 4.5.0 (#70)

Evaluated and upgraded dnlib to the latest 4.x. The only breaking API
change affecting ConfuserEx: dnlib 4.x added IChunk.CalculateAlignment().
Implemented it on the two custom chunk classes in the AntiTamper JIT mode
(JITMethodBody, JITBodyIndex), returning 0 (default/no alignment) to
preserve the pre-4.x behaviour.

Evaluation notes:
- netstandard2.0 target preserved (dnlib 4.5.0 still ships net35/net45/
  netstandard2.0/net6.0) — Confuser.Core net48+netstandard2.0 unaffected
- DnlibUtils extension methods compile unchanged
- Resolve/ResolveThrow, ModuleWriter events, AssemblyResolver: no API
  changes affecting us
- Full suite green: 144 passed, 0 failed (incl. AntiTamper, Compressor,
  packer, and all cross-framework obfuscation tests)

Fixes #70

* perf: route HasAttribute through dnlib IsDefined; test ref-struct generics (#70)

Two low-risk wins enabled by the dnlib 4.5 upgrade:

- HasAttribute now calls CustomAttributeCollection.IsDefined(fullName)
  instead of a manual LINQ scan over CustomAttributes. This is the
  by-full-name lookup dnlib 4.2 optimized, and it benefits all 10 callers
  (including the per-definition renamer analysis path). Behaviour is
  identical — full suite stays green.

- Add a regression test locking in that obfuscation preserves the
  'allows ref struct' generic constraint (GenericParamAttributes.
  AllowByRefLike, named by dnlib 4.x). The net10 library subject now
  carries such a constraint, and Library_Net10_PreservesAllowByRefLike
  loads the obfuscated output and asserts the flag survives. ConfuserEx
  only renames generic parameters, so the attribute bits are preserved —
  this guards against a future change silently dropping them.

145 tests pass (was 144).

---------

Co-authored-by: RandomCrocodile <mawi@polosab.com>
* feature: add DiagnosticCollector and report formatter (#65)

Core of the diagnostic report collector: a DiagnosticCollector that decorates
both ILogger and IProgressReporter, capturing a full-verbosity transcript,
timing and outcome during an obfuscation run, plus a DiagnosticReport formatter
that renders a self-contained markdown report.

- Full-verbosity capture independent of display level; bounded ring buffer
  (last 2000 entries) with dropped-entry accounting.
- Last-wins Finish so a packer's nested run does not clobber the top-level
  result/elapsed.
- Redaction: never emits strong-name passwords; scrubs the user-profile path
  from config and log lines. Dynamic code-fence prevents markdown break-out.
- 19 unit tests, TDD.

* feature: add --dump diagnostic report flag to CLI (#65)

Wraps the logger and progress reporter with a DiagnosticCollector when --dump
is passed, and writes the markdown report after the run completes (success or
failure). --dump uses a default filename; --dump=<file> writes to a custom path.
The report path is printed to the console. E2E test asserts the report is
written with the expected sections.

* feature: add Copy Report button to GUI protect tab (#65)

The protect tab now wraps the logger and progress reporter with a
DiagnosticCollector during each run. A 'Copy Report' button (enabled once a run
completes, success or failure) copies the markdown diagnostic report to the
clipboard for pasting into a bug report. Clipboard failures are swallowed so a
transient lock cannot crash the app.

* feature: add best-effort target framework to diagnostic report (#65)

Reads each input module's TargetFrameworkAttribute (via dnlib, from an in-memory
copy so the file is never locked) and adds a 'Target Framework' line to the
report's configuration section. Best-effort: silently omitted when a module is
missing, not a valid assembly, or predates the attribute (net2.0-3.5). Verified
end-to-end — a net8 library reports '.NETCoreApp,Version=v8.0'; a failing run on
an invalid assembly still produces a clean FAILED report. 4 new tests.

---------

Co-authored-by: RandomCrocodile <mawi@polosab.com>
* chore: auto-install reportgenerator and emit markdown coverage in local-ci

The offline CI now installs the reportgenerator global tool on demand (mirroring
.github/workflows/test.yml) instead of silently skipping the coverage report
when it is absent. It also emits the same report types as CI — HTML, Cobertura,
TextSummary and MarkdownSummaryGithub — so a local run produces SummaryGithub.md,
the offline equivalent of the coverage summary CI posts to the PR.

* chore: add opt-in PR coverage-comment posting to local-ci

Since GitHub Actions minutes are limited, local-ci now produces the same coverage
markdown as CI (SummaryGithub.md) and can optionally post it to a PR as a single
sticky comment via the gh CLI, gated behind POST_COVERAGE_PR=<num>. It edits a
prior marked comment instead of spamming, mirroring the CI sticky comment.
Posting is opt-in and never runs unless the variable is set; all posting failures
are warnings that never fail the pipeline.

---------

Co-authored-by: RandomCrocodile <mawi@polosab.com>
GitHub Actions minutes are limited, so the cloud pipeline now runs the full build
only where it is required: PRs into main and pushes to main. PRs into develop are
validated locally with scripts/local-ci.sh and do not run the pipeline
automatically.

- ci.yml/test.yml/lint.yml: auto-run only for PRs into main (+ push to main for
  ci release). For develop PRs the jobs run only when an admin adds the run-ci
  label or dispatches manually; a job-level if guards this.
- lint.yml: removed the any-branch push trigger that fired a lint run on every
  push.
- Removed format.yml (dead: targeted non-existent master/pre-release branches;
  superseded by lint.yml).
- docs/building.md: refreshed the stale CI/CD table to match.

This change is workflow + docs only, nothing to build or test.
[skip ci]

Co-authored-by: RandomCrocodile <mawi@polosab.com>
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Lint Results

All checks passed

Check Result
Whitespace (indentation, line endings)
Style (IDE rules, naming, var usage)
Analyzers (CA*, RCS* rules)

)

Releases are no longer cut automatically on every push to main. A dedicated
release.yml handles them two ways, both minute-conscious:

- Manual: workflow_dispatch builds, tags and publishes a GitHub Release from main
  on demand (with a force input to release even without new commits).
- Monthly: a cron on the 1st runs a cheap Ubuntu check that compares main to the
  last v* tag and only spends the Windows build+publish when there are new commits.

ci.yml is reduced to build validation: removed the push-to-main release job and
the now-dead dev-release job (develop no longer triggers ci after the gating in
#90), and dropped the push trigger. docs/building.md updated.

This change is workflow + docs only, nothing to build or test.
[skip ci]

Co-authored-by: RandomCrocodile <mawi@polosab.com>
@mcpolo99
mcpolo99 merged commit d114301 into main Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant