Skip to content

Rename Library project to NuGetLibrary - #62

Merged
ptr727 merged 2 commits into
developfrom
nugetlibrary-rename
May 3, 2026
Merged

Rename Library project to NuGetLibrary#62
ptr727 merged 2 commits into
developfrom
nugetlibrary-rename

Conversation

@ptr727

@ptr727 ptr727 commented May 3, 2026

Copy link
Copy Markdown
Owner

Summary

Disambiguates the .NET project name from the upcoming Python PyPiLibrary sibling. Folder, csproj filename, RootNamespace, and namespace declarations move from Library to NuGetLibrary. The companion GitHub Actions reusable workflow build-library-task.yml is renamed to build-nugetlibrary-task.yml; the produced artifact name and 7z filename track the rename.

Preserved on purpose

  • <PackageId>ptr727.ProjectTemplate.Library</PackageId> is kept so the published nupkg keeps its identity. No orphaned package, no 404 on existing consumers, no new badge URL needed.
  • README NuGet badges (nuget-link, nugetreleaseversion-shield, nugetprereleaseversion-shield) still point at ptr727.ProjectTemplate.Library and continue to work.
  • Class names TemplateLibrary and StaticTemplateLibrary are unchanged — they describe types, not the project.
  • InternalsVisibleTo declarations stay (Console, Tests, Benchmarks assembly names are unchanged).

Files touched

  • git mv Library/ NuGetLibrary/ (folder)
  • git mv NuGetLibrary/Library.csproj NuGetLibrary/NuGetLibrary.csproj
  • git mv .github/workflows/build-library-task.yml .github/workflows/build-nugetlibrary-task.yml — paths, job key build-nugetlibrary, artifact name nugetlibrary-build, zip NuGetLibrary.7z
  • .github/workflows/build-release-task.yml — caller updated: job key, uses:, needs: array, artifact-id reference
  • Console/Console.csproj, Tests/Tests.csproj, Benchmarks/Benchmarks.csproj<ProjectReference> paths
  • Console/Program.cs, Tests/LoggingTests.csusing statement
  • NuGetLibrary/{Library,Options,LogOptions,Extensions}.cs — namespace declaration
  • NuGetLibrary/NuGetLibrary.csprojRootNamespace
  • ProjectTemplate.slnx — 4 project path entries + 1 GitHub Actions folder entry
  • ProjectTemplate.code-workspacecSpell.words add nugetlibrary
  • AGENTS.md, .github/copilot-instructions.md — project list, namespace examples, structure section

Test plan

  • dotnet build — 0 warnings, 0 errors locally
  • dotnet test — 15 passed, 0 failed
  • dotnet pack ./NuGetLibrary/NuGetLibrary.csproj — produces ptr727.ProjectTemplate.Library.1.0.0-pre.nupkg (PackageId preserved)
  • Repo-wide grep for Library/Library.csproj, build-library-task — zero remaining hits
  • CI green on the PR (test-pull-request workflow invokes the renamed reusable workflow)
  • After merge, next prerelease produces NuGetLibrary.7z artifact attached to the GitHub release

Disambiguate the .NET project name in preparation for adding a sibling Python
PyPi project. The folder, csproj filename, RootNamespace, and namespace
declarations move from `Library` to `NuGetLibrary`. The companion GitHub
Actions reusable workflow `build-library-task.yml` is renamed to
`build-nugetlibrary-task.yml` for the same reason; the artifact name and zip
filename track the rename.

The published NuGet package id is intentionally preserved as
`ptr727.ProjectTemplate.Library` via an explicit `<PackageId>` element so
existing consumers and the README NuGet badges continue to work without a
new package or a 404 on the existing nuget.org URL.

Class names `TemplateLibrary` and `StaticTemplateLibrary` are left alone —
they describe the type, not the project, and are referenced by tests and
benchmarks.

dotnet build: 0 warnings, 0 errors.
dotnet test: 15 passed, 0 failed.
dotnet pack: produces ptr727.ProjectTemplate.Library.1.0.0-pre.nupkg as expected.
Copilot AI review requested due to automatic review settings May 3, 2026 15:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Renames the .NET “Library” project to “NuGetLibrary” to avoid ambiguity with an upcoming Python sibling, while keeping the published NuGet package identity (ptr727.ProjectTemplate.Library) unchanged. This updates project paths, namespaces, and CI workflows so builds and releases continue to work under the new project name.

Changes:

  • Renamed project references and solution/workspace entries from Library to NuGetLibrary.
  • Updated source namespaces and RootNamespace to ptr727.ProjectTemplate.NuGetLibrary (plus added GlobalUsings.cs for logging types).
  • Renamed the reusable build workflow and updated release workflow wiring/artifact naming accordingly.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Tests/Tests.csproj Updates ProjectReference to the renamed NuGetLibrary project.
Tests/LoggingTests.cs Updates using to the new ptr727.ProjectTemplate.NuGetLibrary namespace.
Console/Console.csproj Updates ProjectReference to NuGetLibrary/NuGetLibrary.csproj.
Console/Program.cs Updates using to the new library namespace.
Benchmarks/Benchmarks.csproj Updates ProjectReference to the renamed library project.
NuGetLibrary/NuGetLibrary.csproj Sets RootNamespace to the new namespace while preserving PackageId.
NuGetLibrary/Library.cs Updates namespace declaration to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/Options.cs Updates namespace declaration to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/LogOptions.cs Updates namespace declaration to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/Extensions.cs Updates namespace declaration to ptr727.ProjectTemplate.NuGetLibrary.
NuGetLibrary/GlobalUsings.cs Adds global usings to simplify logging-related references across the project.
NuGetLibrary/.editorconfig Adds a project-scoped editorconfig (CS1591 suppression) for the renamed library folder.
.github/workflows/build-nugetlibrary-task.yml Renames and retargets the reusable workflow to build/package NuGetLibrary.
.github/workflows/build-release-task.yml Updates release workflow to call the renamed reusable build workflow and artifact references.
ProjectTemplate.slnx Updates solution project paths and workflow file entry to the renamed files/paths.
ProjectTemplate.code-workspace Adds nugetlibrary to cSpell to avoid spelling noise after the rename.
AGENTS.md Updates project structure documentation to reflect NuGetLibrary naming and packaging identity.
.github/copilot-instructions.md Updates project overview and namespace examples to reflect NuGetLibrary.

# Conflicts:
#	.github/workflows/build-release-task.yml
@ptr727
ptr727 merged commit 40627a3 into develop May 3, 2026
20 checks passed
@ptr727
ptr727 deleted the nugetlibrary-rename branch May 3, 2026 18:55
ptr727 added a commit that referenced this pull request May 3, 2026
## Summary

Adds a single unified [Dev Container](https://containers.dev/) hosting
both the .NET 10 SDK and the upcoming Python `uv` toolchain, plus three
focused docs files that decompose host setup, devcontainer setup, and
SSH commit signing per-OS. On Windows, the **devcontainer flow**
requires WSL2 (the bind-mounts use POSIX paths). The **host-install
flow** in `README.md` supports native Windows via winget — see
`docs/host-setup.md` for the per-flow scope.

> **Stacked on [#62](#62)
(NuGetLibrary rename)**. The diff against `develop` will show PR #62's
changes until that PR merges; once it does, the diff here cleans up to
just the devcontainer + docs work.

## Changes

**New files**:

- `.devcontainer/devcontainer.json` — base
`mcr.microsoft.com/devcontainers/dotnet:1-10.0`, `gh` and `common-utils`
features. Bind-mounts `~/.ssh/id_ed25519.pub` (read-only),
`~/.config/git/allowed_signers` (read-only), `~/.config/gh`
(read-write). `${localEnv:HOME}${localEnv:USERPROFILE}` form covers
Linux/macOS and WSL2 hosts. Extension list mirrors
`ProjectTemplate.code-workspace` `recommendations`.
- `.devcontainer/post-create.sh` (executable, mode 100755) — installs
`uv` from `astral.sh` (pinned to a specific version via the
version-prefixed install URL; re-installs on version mismatch so the pin
holds even when uv is already on PATH), runs `dotnet tool restore`,
installs Husky.Net hooks, pre-syncs `PyPiLibrary` if it exists (guarded
so this script works before PR 5 lands).
- `docs/host-setup.md` — git identity, Ed25519 SSH key,
`allowed_signers`, `gh auth login`, per-OS ssh-agent setup, verify
checklist.
- `docs/devcontainer.md` — bind-mount table, lifecycle commands, `gh`
credential-store nuance (Keychain vs libsecret vs file), verify
checklist, troubleshooting.
- `docs/ssh-signing.md` — per-OS deltas (systemd ssh-agent on Linux,
Apple Keychain on macOS, WSL2 caveats), `allowed_signers` format,
devcontainer interaction, troubleshooting.

**Modified**:

- `README.md` — adds a "Recommended (devcontainer)" path to the
Development Environment Setup section linking to the new docs; the
existing host-install path stays. Template Project Setup section now
points at the docs files for prerequisites instead of inlining them.
- `ProjectTemplate.code-workspace` — adds `ms-python.python` and
`charliermarsh.ruff` to `recommendations` (mirrors the devcontainer
list); adds `unwantedRecommendations` for `ms-pyright.pyright`
(deprecated; pyright is provided by Pylance which `ms-python.python`
auto-installs), `ms-python.mypy-type-checker`, `ms-python.pylint`,
`ms-python.flake8`, `ms-python.isort`, and `ms-python.black-formatter`
so contributors aren't prompted to install tools that overlap with ruff
+ Pylance and would surface "could not find binary" connection errors
against the venv. Also adds `astral`, `devcontainer`, `hatchling`,
`Keychain`, `libsecret`, `onCreateCommand`, `postCreateCommand`,
`pyproject`, `pypi`, `pypilibrary`, `pyright`, `ruff` to `cSpell.words`.

## Why a single unified container

VS Code Dev Containers does not support per-folder containers in the
same multi-root window — only a picker per session. A single image with
both .NET and `uv` is the simplest mental model and lets downstream
users delete the language they don't need by removing a feature line and
a postCreateCommand step. See [VS Code Dev Containers
docs](https://code.visualstudio.com/remote/advancedcontainers/connect-multiple-containers)
for the limitation.

## Why bind-mount the public key, not the private key

The private key never enters the container. Signing happens via the SSH
agent socket forwarded by VS Code Dev Containers (`SSH_AUTH_SOCK`). The
public key plus `allowed_signers` is enough for git to know which key to
delegate signing to and to verify signatures in `git log
--show-signature`.

## Why an `onCreateCommand` chown

On macOS hosts the bind-mount surfaces `/home/vscode/.ssh` as root-owned
inside the container, which would block `gh` from updating
`known_hosts`. The chown is idempotent on Linux and WSL2 so it stays
unconditional rather than gated on host detection.

## Test plan

- [x] `gh pr create --base develop --head devcontainer-docs` succeeded
- [ ] CI green on the PR (test-pull-request workflow)
- [ ] Build the devcontainer on Linux, run `git -c gpg.format=ssh commit
-S --allow-empty -m verify` inside, verify it signs
- [ ] Build the devcontainer on macOS host, verify the `onCreateCommand`
chown lets `gh auth status` work
- [ ] Build on WSL2 host, verify behavior matches Linux
- [ ] Confirm `recommendations` in `code-workspace` and
`customizations.vscode.extensions` in `devcontainer.json` are identical
- [ ] Markdown lint passes on the new docs files
ptr727 added a commit that referenced this pull request May 3, 2026
## Summary

Adds a Python PyPi template project alongside the .NET `NuGetLibrary`,
completing the polyglot template. Modern 2026 stack: hatchling backend,
`uv` for env/deps/publish, ruff for lint+format, pyright for typing,
pytest for tests, PyPI Trusted Publishing via OIDC.

> **Stacked on [#62](#62)
(NuGetLibrary rename, merged) and
[#63](#63) (devcontainer +
docs)**. Once #63 merges this PR's diff cleans up to just the
PyPiLibrary work.

## Naming

- Folder: `PyPiLibrary/` — qualifier on disk to disambiguate from
`NuGetLibrary/`
- Published PyPI name: `ptr727-projecttemplate-library` — **no `pypi`
qualifier**, mirrors the NuGet identity
- Python import name: `ptr727_projecttemplate_library`

## New tree

```text
PyPiLibrary/
  pyproject.toml          # hatchling backend, ruff/pyright/pytest config, PEP 735 [dependency-groups]
  README.md               # what this PyPi template is + uv quickstart + Trusted Publisher setup
  uv.lock                 # committed for reproducible CI
  src/
    ptr727_projecttemplate_library/
      __init__.py
      _version.py         # __version__ = "0.0.0" placeholder; see README.md "Template Adoption" for version-scheme options
      example.py          # trivial greet() function
  tests/
    test_example.py       # 3 tests
```

## Workflow plumbing

The split-by-purpose layout was chosen so `id-token: write` (required by
Trusted Publishing) only has to be granted on the entry-point job, not
propagated through reusable-workflow chains:

- **New** `.github/workflows/build-pypilibrary-task.yml` — reusable
workflow that **only builds**: setup uv (pinned to `0.11.8` to match the
devcontainer), sync, ruff check, ruff format --check, pyright, pytest,
`uv build`, upload artifact. **No publish job here**, no `id-token:
write`.
- **Modified** `.github/workflows/build-release-task.yml` — adds a
`build-pypilibrary` job calling the new reusable workflow, includes it
in the `github-release` `needs:` list. Build runs unconditionally
(matches the always-validate-on-PR semantic of the rest of the
workflow). **No `pypi: bool` input** — would require id-token
propagation through the test-pull-request chain (and triggered
`startup_failure`, fixed in 4c939f6).
- **Modified** `.github/workflows/publish-release.yml` — adds a
top-level `publish-pypi` job that runs after `create-release`, downloads
the `pypilibrary-build` artifact by name (artifacts uploaded by reusable
workflows are accessible to sibling jobs in the same run), and publishes
via Trusted Publishing. **`id-token: write` lives only here**, alongside
the explicit `contents: read` and `actions: read` needed for
`actions/download-artifact`. Uses `skip-existing: true` so the
placeholder `0.0.0` version doesn't fail the workflow on repeated
pushes.
- **Modified** `.github/workflows/test-release-task.yml` — no
PyPi-specific input needed; the build runs as part of the existing
chain.

## Other plumbing

- **`.github/dependabot.yml`** — adds `package-ecosystem: "uv"`
targeting `/PyPiLibrary` with the `pypi-deps` group label. Existing
`nuget` and `github-actions` blocks normalized to standard two-space
indentation under `updates:`.
- **`.husky/task-runner.json`** — adds `Ruff Format` and `Ruff Check`
tasks scoped to `PyPiLibrary/**/*.py`. Both pass `${staged}` as
positional args via `bash -c "..." -- ${staged}` so paths with spaces
survive; both gate on `command -v uv` so a `.cs`-only commit on a
contributor without uv installed doesn't fail.
- **`ProjectTemplate.code-workspace`** — adds Python format-on-save with
ruff, the `[python]` formatter binding,
`python.terminal.activateEnvironment: false`. No hard-coded venv paths
(those caused "could not find ruff binary" popups before `uv sync` ran).
Adds `unwantedRecommendations` for mypy / pylint / flake8 / isort /
black / standalone pyright so contributors aren't prompted to install
tools that overlap with ruff and Pylance.
- **`ProjectTemplate.slnx`** — adds `build-pypilibrary-task.yml` to the
GitHub Actions folder.
- **`.gitignore`** — adds `.venv/`, `dist/`, `__pycache__/`,
`*.py[cod]`, `*.egg-info/`, `.pytest_cache/`, `.ruff_cache/`,
`.pyright/`.
- **`README.md`** — PyPI badge + link in the build/distribution and
releases sections; template TODO list reminds the deriver to delete the
unused language side. `gh` "pre-authenticated" wording softened to call
out the Keychain/libsecret credential-store limitation.

## Trusted Publisher setup (one-time, on PyPI side)

1. PyPI → **Account settings** → **Publishing** → **Add a new pending
publisher**
   - Project name: `ptr727-projecttemplate-library`
   - Owner: `ptr727`
   - Repo: `ProjectTemplate`
   - Workflow: `publish-release.yml`
   - Environment: `pypi`
2. GitHub repo → **Settings** → **Environments** → create `pypi`
environment (optionally with required reviewers).

The first successful release converts the pending publisher to a real
publisher.

## Versioning gap

`_version.py` ships with `__version__ = "0.0.0"`. Trusted Publishing
with `skip-existing: true` means the workflow won't fail, but no new
PyPI versions land until you wire `_version.py` to something that
increments — see `PyPiLibrary/README.md` "Template Adoption" for the
three usual options (hatch-vcs / version.json bridge / manual bumps).

## Test plan

- [x] `uv sync` clean (host: uv 0.11.8)
- [x] `uv run ruff check` — All checks passed
- [x] `uv run ruff format --check` — clean
- [x] `uv run pyright` — 0 errors, 0 warnings, 0 informations
- [x] `uv run pytest` — 3 passed
- [x] `uv build` — produces
`ptr727_projecttemplate_library-0.0.0.tar.gz` and wheel
- [x] `dotnet build` — 0 warnings, 0 errors
- [x] `dotnet test` — 15 passed (no .NET regression)
- [ ] CI green on the PR (test-release-task exercises ruff, pyright,
pytest, uv build via the same reusable workflow that publish uses)
- [ ] After Trusted Publisher is configured on PyPI and a real version
scheme is wired in `_version.py`, next merge to `main` smoke-tests the
publish path
ptr727 added a commit that referenced this pull request May 4, 2026
## Summary

Disambiguates the .NET project name from the upcoming Python
`PyPiLibrary` sibling. Folder, csproj filename, `RootNamespace`, and
namespace declarations move from `Library` to `NuGetLibrary`. The
companion GitHub Actions reusable workflow `build-library-task.yml` is
renamed to `build-nugetlibrary-task.yml`; the produced artifact name and
7z filename track the rename.

## Preserved on purpose

- `<PackageId>ptr727.ProjectTemplate.Library</PackageId>` is **kept** so
the published nupkg keeps its identity. No orphaned package, no 404 on
existing consumers, no new badge URL needed.
- README NuGet badges (`nuget-link`, `nugetreleaseversion-shield`,
`nugetprereleaseversion-shield`) still point at
`ptr727.ProjectTemplate.Library` and continue to work.
- Class names `TemplateLibrary` and `StaticTemplateLibrary` are
unchanged — they describe types, not the project.
- `InternalsVisibleTo` declarations stay (`Console`, `Tests`,
`Benchmarks` assembly names are unchanged).

## Files touched

- `git mv Library/ NuGetLibrary/` (folder)
- `git mv NuGetLibrary/Library.csproj NuGetLibrary/NuGetLibrary.csproj`
- `git mv .github/workflows/build-library-task.yml
.github/workflows/build-nugetlibrary-task.yml` — paths, job key
`build-nugetlibrary`, artifact name `nugetlibrary-build`, zip
`NuGetLibrary.7z`
- `.github/workflows/build-release-task.yml` — caller updated: job key,
`uses:`, `needs:` array, artifact-id reference
- `Console/Console.csproj`, `Tests/Tests.csproj`,
`Benchmarks/Benchmarks.csproj` — `<ProjectReference>` paths
- `Console/Program.cs`, `Tests/LoggingTests.cs` — `using` statement
- `NuGetLibrary/{Library,Options,LogOptions,Extensions}.cs` — namespace
declaration
- `NuGetLibrary/NuGetLibrary.csproj` — `RootNamespace`
- `ProjectTemplate.slnx` — 4 project path entries + 1 GitHub Actions
folder entry
- `ProjectTemplate.code-workspace` — `cSpell.words` add `nugetlibrary`
- `AGENTS.md`, `.github/copilot-instructions.md` — project list,
namespace examples, structure section

## Test plan

- [x] `dotnet build` — 0 warnings, 0 errors locally
- [x] `dotnet test` — 15 passed, 0 failed
- [x] `dotnet pack ./NuGetLibrary/NuGetLibrary.csproj` — produces
`ptr727.ProjectTemplate.Library.1.0.0-pre.nupkg` (PackageId preserved)
- [x] Repo-wide grep for `Library/Library.csproj`, `build-library-task`
— zero remaining hits
- [ ] CI green on the PR (test-pull-request workflow invokes the renamed
reusable workflow)
- [ ] After merge, next prerelease produces `NuGetLibrary.7z` artifact
attached to the GitHub release
ptr727 added a commit that referenced this pull request May 4, 2026
## Summary

Adds a single unified [Dev Container](https://containers.dev/) hosting
both the .NET 10 SDK and the upcoming Python `uv` toolchain, plus three
focused docs files that decompose host setup, devcontainer setup, and
SSH commit signing per-OS. On Windows, the **devcontainer flow**
requires WSL2 (the bind-mounts use POSIX paths). The **host-install
flow** in `README.md` supports native Windows via winget — see
`docs/host-setup.md` for the per-flow scope.

> **Stacked on [#62](#62)
(NuGetLibrary rename)**. The diff against `develop` will show PR #62's
changes until that PR merges; once it does, the diff here cleans up to
just the devcontainer + docs work.

## Changes

**New files**:

- `.devcontainer/devcontainer.json` — base
`mcr.microsoft.com/devcontainers/dotnet:1-10.0`, `gh` and `common-utils`
features. Bind-mounts `~/.ssh/id_ed25519.pub` (read-only),
`~/.config/git/allowed_signers` (read-only), `~/.config/gh`
(read-write). `${localEnv:HOME}${localEnv:USERPROFILE}` form covers
Linux/macOS and WSL2 hosts. Extension list mirrors
`ProjectTemplate.code-workspace` `recommendations`.
- `.devcontainer/post-create.sh` (executable, mode 100755) — installs
`uv` from `astral.sh` (pinned to a specific version via the
version-prefixed install URL; re-installs on version mismatch so the pin
holds even when uv is already on PATH), runs `dotnet tool restore`,
installs Husky.Net hooks, pre-syncs `PyPiLibrary` if it exists (guarded
so this script works before PR 5 lands).
- `docs/host-setup.md` — git identity, Ed25519 SSH key,
`allowed_signers`, `gh auth login`, per-OS ssh-agent setup, verify
checklist.
- `docs/devcontainer.md` — bind-mount table, lifecycle commands, `gh`
credential-store nuance (Keychain vs libsecret vs file), verify
checklist, troubleshooting.
- `docs/ssh-signing.md` — per-OS deltas (systemd ssh-agent on Linux,
Apple Keychain on macOS, WSL2 caveats), `allowed_signers` format,
devcontainer interaction, troubleshooting.

**Modified**:

- `README.md` — adds a "Recommended (devcontainer)" path to the
Development Environment Setup section linking to the new docs; the
existing host-install path stays. Template Project Setup section now
points at the docs files for prerequisites instead of inlining them.
- `ProjectTemplate.code-workspace` — adds `ms-python.python` and
`charliermarsh.ruff` to `recommendations` (mirrors the devcontainer
list); adds `unwantedRecommendations` for `ms-pyright.pyright`
(deprecated; pyright is provided by Pylance which `ms-python.python`
auto-installs), `ms-python.mypy-type-checker`, `ms-python.pylint`,
`ms-python.flake8`, `ms-python.isort`, and `ms-python.black-formatter`
so contributors aren't prompted to install tools that overlap with ruff
+ Pylance and would surface "could not find binary" connection errors
against the venv. Also adds `astral`, `devcontainer`, `hatchling`,
`Keychain`, `libsecret`, `onCreateCommand`, `postCreateCommand`,
`pyproject`, `pypi`, `pypilibrary`, `pyright`, `ruff` to `cSpell.words`.

## Why a single unified container

VS Code Dev Containers does not support per-folder containers in the
same multi-root window — only a picker per session. A single image with
both .NET and `uv` is the simplest mental model and lets downstream
users delete the language they don't need by removing a feature line and
a postCreateCommand step. See [VS Code Dev Containers
docs](https://code.visualstudio.com/remote/advancedcontainers/connect-multiple-containers)
for the limitation.

## Why bind-mount the public key, not the private key

The private key never enters the container. Signing happens via the SSH
agent socket forwarded by VS Code Dev Containers (`SSH_AUTH_SOCK`). The
public key plus `allowed_signers` is enough for git to know which key to
delegate signing to and to verify signatures in `git log
--show-signature`.

## Why an `onCreateCommand` chown

On macOS hosts the bind-mount surfaces `/home/vscode/.ssh` as root-owned
inside the container, which would block `gh` from updating
`known_hosts`. The chown is idempotent on Linux and WSL2 so it stays
unconditional rather than gated on host detection.

## Test plan

- [x] `gh pr create --base develop --head devcontainer-docs` succeeded
- [ ] CI green on the PR (test-pull-request workflow)
- [ ] Build the devcontainer on Linux, run `git -c gpg.format=ssh commit
-S --allow-empty -m verify` inside, verify it signs
- [ ] Build the devcontainer on macOS host, verify the `onCreateCommand`
chown lets `gh auth status` work
- [ ] Build on WSL2 host, verify behavior matches Linux
- [ ] Confirm `recommendations` in `code-workspace` and
`customizations.vscode.extensions` in `devcontainer.json` are identical
- [ ] Markdown lint passes on the new docs files
ptr727 added a commit that referenced this pull request May 4, 2026
## Summary

Adds a Python PyPi template project alongside the .NET `NuGetLibrary`,
completing the polyglot template. Modern 2026 stack: hatchling backend,
`uv` for env/deps/publish, ruff for lint+format, pyright for typing,
pytest for tests, PyPI Trusted Publishing via OIDC.

> **Stacked on [#62](#62)
(NuGetLibrary rename, merged) and
[#63](#63) (devcontainer +
docs)**. Once #63 merges this PR's diff cleans up to just the
PyPiLibrary work.

## Naming

- Folder: `PyPiLibrary/` — qualifier on disk to disambiguate from
`NuGetLibrary/`
- Published PyPI name: `ptr727-projecttemplate-library` — **no `pypi`
qualifier**, mirrors the NuGet identity
- Python import name: `ptr727_projecttemplate_library`

## New tree

```text
PyPiLibrary/
  pyproject.toml          # hatchling backend, ruff/pyright/pytest config, PEP 735 [dependency-groups]
  README.md               # what this PyPi template is + uv quickstart + Trusted Publisher setup
  uv.lock                 # committed for reproducible CI
  src/
    ptr727_projecttemplate_library/
      __init__.py
      _version.py         # __version__ = "0.0.0" placeholder; see README.md "Template Adoption" for version-scheme options
      example.py          # trivial greet() function
  tests/
    test_example.py       # 3 tests
```

## Workflow plumbing

The split-by-purpose layout was chosen so `id-token: write` (required by
Trusted Publishing) only has to be granted on the entry-point job, not
propagated through reusable-workflow chains:

- **New** `.github/workflows/build-pypilibrary-task.yml` — reusable
workflow that **only builds**: setup uv (pinned to `0.11.8` to match the
devcontainer), sync, ruff check, ruff format --check, pyright, pytest,
`uv build`, upload artifact. **No publish job here**, no `id-token:
write`.
- **Modified** `.github/workflows/build-release-task.yml` — adds a
`build-pypilibrary` job calling the new reusable workflow, includes it
in the `github-release` `needs:` list. Build runs unconditionally
(matches the always-validate-on-PR semantic of the rest of the
workflow). **No `pypi: bool` input** — would require id-token
propagation through the test-pull-request chain (and triggered
`startup_failure`, fixed in 4c939f6).
- **Modified** `.github/workflows/publish-release.yml` — adds a
top-level `publish-pypi` job that runs after `create-release`, downloads
the `pypilibrary-build` artifact by name (artifacts uploaded by reusable
workflows are accessible to sibling jobs in the same run), and publishes
via Trusted Publishing. **`id-token: write` lives only here**, alongside
the explicit `contents: read` and `actions: read` needed for
`actions/download-artifact`. Uses `skip-existing: true` so the
placeholder `0.0.0` version doesn't fail the workflow on repeated
pushes.
- **Modified** `.github/workflows/test-release-task.yml` — no
PyPi-specific input needed; the build runs as part of the existing
chain.

## Other plumbing

- **`.github/dependabot.yml`** — adds `package-ecosystem: "uv"`
targeting `/PyPiLibrary` with the `pypi-deps` group label. Existing
`nuget` and `github-actions` blocks normalized to standard two-space
indentation under `updates:`.
- **`.husky/task-runner.json`** — adds `Ruff Format` and `Ruff Check`
tasks scoped to `PyPiLibrary/**/*.py`. Both pass `${staged}` as
positional args via `bash -c "..." -- ${staged}` so paths with spaces
survive; both gate on `command -v uv` so a `.cs`-only commit on a
contributor without uv installed doesn't fail.
- **`ProjectTemplate.code-workspace`** — adds Python format-on-save with
ruff, the `[python]` formatter binding,
`python.terminal.activateEnvironment: false`. No hard-coded venv paths
(those caused "could not find ruff binary" popups before `uv sync` ran).
Adds `unwantedRecommendations` for mypy / pylint / flake8 / isort /
black / standalone pyright so contributors aren't prompted to install
tools that overlap with ruff and Pylance.
- **`ProjectTemplate.slnx`** — adds `build-pypilibrary-task.yml` to the
GitHub Actions folder.
- **`.gitignore`** — adds `.venv/`, `dist/`, `__pycache__/`,
`*.py[cod]`, `*.egg-info/`, `.pytest_cache/`, `.ruff_cache/`,
`.pyright/`.
- **`README.md`** — PyPI badge + link in the build/distribution and
releases sections; template TODO list reminds the deriver to delete the
unused language side. `gh` "pre-authenticated" wording softened to call
out the Keychain/libsecret credential-store limitation.

## Trusted Publisher setup (one-time, on PyPI side)

1. PyPI → **Account settings** → **Publishing** → **Add a new pending
publisher**
   - Project name: `ptr727-projecttemplate-library`
   - Owner: `ptr727`
   - Repo: `ProjectTemplate`
   - Workflow: `publish-release.yml`
   - Environment: `pypi`
2. GitHub repo → **Settings** → **Environments** → create `pypi`
environment (optionally with required reviewers).

The first successful release converts the pending publisher to a real
publisher.

## Versioning gap

`_version.py` ships with `__version__ = "0.0.0"`. Trusted Publishing
with `skip-existing: true` means the workflow won't fail, but no new
PyPI versions land until you wire `_version.py` to something that
increments — see `PyPiLibrary/README.md` "Template Adoption" for the
three usual options (hatch-vcs / version.json bridge / manual bumps).

## Test plan

- [x] `uv sync` clean (host: uv 0.11.8)
- [x] `uv run ruff check` — All checks passed
- [x] `uv run ruff format --check` — clean
- [x] `uv run pyright` — 0 errors, 0 warnings, 0 informations
- [x] `uv run pytest` — 3 passed
- [x] `uv build` — produces
`ptr727_projecttemplate_library-0.0.0.tar.gz` and wheel
- [x] `dotnet build` — 0 warnings, 0 errors
- [x] `dotnet test` — 15 passed (no .NET regression)
- [ ] CI green on the PR (test-release-task exercises ruff, pyright,
pytest, uv build via the same reusable workflow that publish uses)
- [ ] After Trusted Publisher is configured on PyPI and a real version
scheme is wired in `_version.py`, next merge to `main` smoke-tests the
publish path
ptr727 added a commit that referenced this pull request May 11, 2026
…me (#66)

Release merge: brings five squashed PRs from develop into main.

## Squashed PRs included

- **#61 — Pin release action SHA, target_commitish, agent conventions.**
`softprops/action-gh-release` pinned to a commit SHA with
`target_commitish: ${{ github.sha }}` so the release tag lands on the
artifact's commit, not the default branch. Updated `AGENTS.md` workflow
YAML conventions.
- **#62 — Rename Library project to NuGetLibrary.** Project + folder
renamed; `.slnx`, `.csproj`, build workflow, and references updated.
Disambiguates from the new Python sibling.
- **#63 — Add devcontainer + per-OS host and SSH signing docs.** New
`docs/host-setup.md`, `docs/ssh-signing.md`, `docs/devcontainer.md`.
Devcontainer bind-mounts SSH public key, allowed_signers, and `gh`
config so commits sign correctly inside the container.
- **#64 — Add PyPiLibrary Python sibling project.** New `PyPiLibrary/`
template under `src/`-layout: pyproject.toml + uv.lock +
ruff/pyright/pytest config + sample module + tests +
`build-pypilibrary-task.yml` workflow + `publish-pypi` job in
`publish-release.yml`.
- **#65 — Split Devcontainer and Workspace per Language and Drop
Husky.** `.devcontainer/dotnet/` + `DotNet.code-workspace` and
`.devcontainer/python/` + `Python.code-workspace`. Husky.Net removed (CI
is the lint backstop). Optional opt-in hooks documented in README. All
Husky references removed from workflows, AGENTS, CODESTYLE, and tasks.

## Notes

- Merge method: **merge-commit** (per [AGENTS.md branching
model](https://github.com/ptr727/ProjectTemplate/blob/develop/AGENTS.md#branching-model)).
Squash and rebase are blocked by the main ruleset.
- Main currently has 6 codegen-update commits that develop doesn't have
(#58, #59, #60 etc.). The merge-commit re-anchors develop on top of
those; the next develop cycle will start by merging main back into
develop to absorb them.

## Test plan

- [ ] CI passes on the merge commit (test-release-task workflow, all
build matrix legs).
- [ ] Confirm release tag lands on the merge commit (target_commitish
from #61).
- [ ] Spot-check the new `.devcontainer/dotnet/` and
`.devcontainer/python/` open and build cleanly.
- [ ] Spot-check `cd PyPiLibrary && uv sync && uv run pytest` passes.
- [ ] Confirm `.git/hooks/pre-commit` is absent in a fresh clone (Husky
removed).
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.

2 participants