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
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"docfx": {
"version": "2.78.5",
"commands": [
"docfx"
],
"rollForward": false
}
}
}
17 changes: 17 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Every change requests a review from the maintainer.
# Later rules win, so the catch-all comes first.
* @AdCodicem

# The surface consumers write against. A change here has to stay in step with
# README.md, website/docs/ and skills/value-objects/ — see CONTRIBUTING.md.
/src/AdCodicem.ValueObjects.Abstractions/ @AdCodicem
/src/AdCodicem.ValueObjects.Generators/ @AdCodicem
/skills/ @AdCodicem

# Release and supply-chain wiring: a change here can publish packages or
# weaken the checks that guard them.
/.github/workflows/ @AdCodicem
/.releaserc.json @AdCodicem
/Directory.Build.props @AdCodicem
/Directory.Packages.props @AdCodicem
/global.json @AdCodicem
21 changes: 21 additions & 0 deletions .github/DISCUSSION_TEMPLATE/q-a.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
labels: ['question']
body:
- type: textarea
id: question
attributes:
label: What are you trying to do?
description: The goal, not only the error — there is often a shorter path to it.
validations:
required: true

- type: textarea
id: code
attributes:
label: What you have so far
render: csharp

- type: input
id: version
attributes:
label: Package version
placeholder: '0.1.0-preview.2'
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Only takes effect once GitHub Sponsors is enabled on the account.
github: [AdCodicem]
89 changes: 89 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Bug report
description: Something generated, validated, or serialized differently than it should have.
labels: ['bug']
body:
- type: markdown
attributes:
value: |
Most bugs here live in one of two places: the code the generator emits, or one of the
integrations that carries a value across a boundary. Knowing which one saves a round trip.

- type: textarea
id: declaration
attributes:
label: The value object declaration
description: >-
The `[ValueObject<T>]` or `[EntityId]` declaration involved, including any hook
(`IValueObjectNormalizer<T>`, `IValueObjectValidator<T>`, …) it implements.
render: csharp
placeholder: |
[ValueObject<string>(MaxLength = 34)]
public readonly partial struct Iban : IValueObjectValidator<string>
{
public static ValidationResult ValidateValue(in string value) => ...;
}
validations:
required: true

- type: textarea
id: expected-actual
attributes:
label: What you expected, and what happened
description: Include the exact exception, diagnostic id, or serialized output if there is one.
validations:
required: true

- type: textarea
id: repro
attributes:
label: Reproduction
description: >-
The smallest code that shows it. If the problem only appears through an integration,
say which call path reaches it — model binding, EF Core read, Dapper, JSON, OpenAPI.
render: csharp
validations:
required: true

- type: dropdown
id: surface
attributes:
label: Where does it surface?
options:
- Generated code (constructor, Create/TryCreate, Parse/TryParse, ToString/TryFormat, equality)
- A generator diagnostic (VO####) that fired, or failed to fire
- System.Text.Json or Newtonsoft.Json
- Entity Framework Core
- Dapper
- ASP.NET Core model binding or ProblemDetails
- OpenAPI schema
- FluentValidation
- Entity identifiers (AdCodicem.ValueObjects.Identifiers)
- Somewhere else / not sure
validations:
required: true

- type: input
id: version
attributes:
label: Package version
placeholder: '0.1.0-preview.2'
validations:
required: true

- type: input
id: sdk
attributes:
label: .NET SDK version
description: Output of `dotnet --version`.
placeholder: '10.0.100'
validations:
required: true

- type: textarea
id: generated
attributes:
label: Generated source (optional)
description: >-
Often decisive for a generator bug. Set `<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>`
and paste the relevant part of the emitted file.
render: csharp
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Question or idea
url: https://github.com/AdCodicem/AdCodicem.ValueObjects/discussions
about: Ask how something works, or float an idea before it becomes a proposal.
- name: Security vulnerability
url: https://github.com/AdCodicem/AdCodicem.ValueObjects/security/advisories/new
about: Report privately. Please do not open a public issue for a vulnerability.
- name: Documentation
url: https://adcodicem.github.io/AdCodicem.ValueObjects/
about: The authoring guide, the integrations, and the diagnostics reference.
53 changes: 53 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Feature request
description: Propose a new capability, or an option on an existing one.
labels: ['enhancement']
body:
- type: markdown
attributes:
value: |
A rule in this library is declared once and has to reach everywhere it matters — validation,
the EF column, the OpenAPI schema. The most useful proposals say what a rule should mean in
each of those places, not just how it should be spelled.

- type: textarea
id: problem
attributes:
label: The problem
description: What are you trying to express today, and what makes it awkward or impossible?
validations:
required: true

- type: textarea
id: proposal
attributes:
label: Proposed shape
description: How it would look in a consumer's code.
render: csharp
validations:
required: true

- type: textarea
id: reach
attributes:
label: Where the rule should reach
description: >-
If this is a new option on `[ValueObject<T>]`: what does it validate at run time, what does
it do to the database column, and what does it become in the OpenAPI document? Leave blank
if it is not that kind of change.
placeholder: |
Validation: ...
EF Core column: ...
OpenAPI schema: ...

- type: textarea
id: alternatives
attributes:
label: Alternatives you considered
description: Including what a hook (a normalizer or validator you write yourself) cannot do here.

- type: checkboxes
id: breaking
attributes:
label: Compatibility
options:
- label: This would be a breaking change to the existing surface.
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ updates:
dotnet:
update-types: ["minor", "patch"]

# Release tooling (semantic-release and its plugins) in the root package.json.
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "ci(deps)"
groups:
semantic-release:
update-types: ["minor", "patch"]

- package-ecosystem: "npm"
directory: "/website"
schedule:
Expand Down
78 changes: 78 additions & 0 deletions .github/scripts/docfx-postprocess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Make DocFX's markdown output safe for Docusaurus.

DocFX writes heading anchors as raw HTML:

### <a id="AdCodicem_ValueObjects_ValidationResult_ErrorCode"></a> ErrorCode

Docusaurus then wraps every heading in its own anchor link, producing nested
<a> elements -- invalid HTML that the build's minifier reports several hundred
times. Docusaurus has explicit heading ids for exactly this, so rewrite to:

### ErrorCode {#AdCodicem_ValueObjects_ValidationResult_ErrorCode}

The id is preserved verbatim, so the cross-references DocFX generates between
pages keep resolving.
"""
import pathlib
import re
import sys

HEADING_ANCHOR = re.compile(
r'^(?P<hashes>#{1,6}) <a id="(?P<id>[^"]+)"></a>[ ]*(?P<title>.*)$',
re.MULTILINE,
)


def rewrite(text: str) -> tuple[str, int]:
count = 0

def replace(match: re.Match[str]) -> str:
nonlocal count
title = match["title"].strip()
if not title:
# A heading with no text left would render as an empty anchor; leave
# these alone rather than emitting '## {#id}'.
return match[0]
count += 1
return f'{match["hashes"]} {title} {{#{match["id"]}}}'

return HEADING_ANCHOR.sub(replace, text), count


def main(directory: str) -> int:
root = pathlib.Path(directory)
if not root.is_dir():
print(f"error: {root} is not a directory", file=sys.stderr)
return 1

files = sorted(root.rglob("*.md"))
if not files:
print(f"error: no markdown found under {root}", file=sys.stderr)
return 1

total = 0
for path in files:
original = path.read_text(encoding="utf-8")
rewritten, count = rewrite(original)
if count:
path.write_text(rewritten, encoding="utf-8")
total += count

print(f"Rewrote {total} heading anchors across {len(files)} files.")

# Only headings that carry a title should have been rewritten; a bare
# '<a id="x"></a>' with no text is skipped on purpose above, so counting it
# here would fail the run over the very case the rewrite declines to touch.
remaining = sum(
len([m for m in HEADING_ANCHOR.finditer(p.read_text(encoding="utf-8")) if m["title"].strip()])
for p in files
)
if remaining:
print(f"error: {remaining} heading anchors were left unconverted", file=sys.stderr)
return 1
return 0


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1] if len(sys.argv) > 1 else "website/docs/api"))
44 changes: 44 additions & 0 deletions .github/scripts/release-pack.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Packs every publishable project at the version semantic-release computed.
#
# Usage: release-pack.sh <next-version> <release-type> [last-version]
#
# MinVer normally derives the version from git tags. At this point in the run the
# tag for this release does not exist yet -- semantic-release creates it after
# publishing -- so MinVer would stamp a preview version. MINVERVERSIONOVERRIDE is
# MinVer's documented hook for exactly this: an external tool that has already
# decided the version. It is an environment variable, not an MSBuild property.
set -euo pipefail

next_version="${1:?next version required}"
release_type="${2:?release type required}"
last_version="${3:-}"

args=(--configuration Release --output artifacts/packages)

# EnablePackageValidation compares the new package's API against a published
# baseline. That check is meaningful for a patch or a minor, and actively wrong
# for a release that is *supposed* to break: a major says so in its version
# number, and while the version is still 0.x a minor is the breaking channel by
# convention, so neither is held to the baseline.
if [[ -z "$last_version" ]]; then
echo "No previous release: skipping package validation baseline."
elif [[ "$release_type" == "major" ]]; then
echo "Major release: skipping package validation baseline (breaks are intended)."
elif [[ "$next_version" == 0.* && "$release_type" == "minor" ]]; then
echo "Pre-1.0 minor: skipping package validation baseline (0.x minor is the breaking channel)."
else
echo "Validating API compatibility against $last_version."
args+=("-p:PackageValidationBaselineVersion=$last_version")
fi

echo "Packing $next_version ($release_type)."
MINVERVERSIONOVERRIDE="$next_version" dotnet pack "${args[@]}"

# A silent mismatch here would publish a preview-numbered package as a stable
# release, so fail loudly instead of trusting the override took effect.
if ! ls "artifacts/packages/AdCodicem.ValueObjects.${next_version}.nupkg" >/dev/null 2>&1; then
echo "::error::Expected artifacts/packages/AdCodicem.ValueObjects.${next_version}.nupkg. MinVer did not honour MINVERVERSIONOVERRIDE." >&2
ls -1 artifacts/packages/ >&2 || true
exit 1
fi
Loading
Loading