Move project into content/; add description param

This commit is contained in:
2026-08-27 02:09:55 +02:00
parent ea7d7d3b6b
commit dd6325f2a1
8 changed files with 187 additions and 107 deletions
+2 -2
View File
@@ -7,8 +7,8 @@
"isVisible": "true"
},
{
"id": "SkipGit",
"name": { "text": "Skip Git" },
"id": "PluginDescription",
"name": { "text": "Plugin Description" },
"isVisible": "true"
}
]
+12 -14
View File
@@ -10,19 +10,26 @@
},
"sourceName": "LabApiPluginTemplate",
"preferNameDirectory": true,
"sources": [
{
"source": "./content",
"target": "./"
}
],
"symbols": {
"PluginAuthor": {
"type": "parameter",
"datatype": "string",
"defaultValue": "YourName",
"defaultValue": "Your Name",
"replaces": "YourName",
"description": "Name used as the plugin's author."
},
"SkipGit": {
"PluginDescription": {
"type": "parameter",
"datatype": "bool",
"description": "If set, skips printing the reminder to initialize a git repository.",
"defaultValue": "false"
"datatype": "string",
"defaultValue": "Your Description",
"replaces": "TODO: describe what this plugin does.",
"description": "Short description of what the plugin does."
}
},
"primaryOutputs": [
@@ -36,15 +43,6 @@
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
},
{
"condition": "(!SkipGit)",
"description": "This project is not a git repository yet.",
"manualInstructions": [
{ "text": "Run 'git init && git add -A && git commit -m \"Initial commit\"'" }
],
"actionId": "AC1156F7-BB77-4DB8-B28F-24EEBCCA1E5C",
"continueOnError": true
}
]
}
+88 -45
View File
@@ -13,10 +13,9 @@ are ordinary project files at that point, not template scaffolding.
## Repo is template *source* — read before touching any file
`.template.config/template.json` sets this folder's name as the template's
`sourceName` and defines one text-replacement `symbol`, `PluginAuthor` (plus
`SkipGit`, which only toggles the git-reminder post-generation action and replaces no
text). The dotnet templating engine does a literal, repo-wide **string replacement**
at generation time — not semantic renaming — driven by that config:
`sourceName` and defines one text-replacement `symbol`, `PluginAuthor`. The dotnet
templating engine does a literal, repo-wide **string replacement** at generation
time — not semantic renaming — driven by that config:
- The folder/project name (`sourceName` in `template.json`, currently this repo's own
folder name) → whatever `-n`/`--name` the consumer passes to `dotnet new`.
@@ -41,50 +40,86 @@ Consequences:
## Repo layout (only what affects decisions)
- `.template.config/template.json` — template identity, the symbols above plus
`SkipGit` (a display-only toggle, see below), `primaryOutputs` (points at the
project file using its pre-substitution literal name — that's correct and expected,
not a bug), and two `postActions`: the standard dotnet-templating "restore NuGet
packages" action (id `210D431B-...`; skipped in Visual Studio, which restores on
its own) and a "display manual instructions" action (id `AC1156F7-...`) that prints
a reminder to run `git init` — see "Git on generation" below for why it's just a
printed reminder and not an executed command.
- Repo root holds only template-source/maintenance files that must **never** ship in
a generated project: `.git/`, `.gitignore`, `.editorconfig`, `CLAUDE.md`, and
`.template.config/`. None of these are copied to generated output — see "content/
vs. repo root" below.
- `.template.config/template.json` — template identity, the `PluginAuthor` symbol,
a `sources` entry that maps `./content` → the output root (this is what keeps the
root-level files above out of every generated project), `primaryOutputs` (points at
the project file using its pre-substitution literal name, relative to `content/` —
that's correct and expected, not a bug), and one `postAction`: the standard
dotnet-templating "restore NuGet packages" action (id `210D431B-...`; skipped in
Visual Studio, which restores on its own).
- `.template.config/ide.host.json` — Visual Studio-only UI hints for the New Project
wizard. It does **not** rename any symbol (the CLI flags stay `PluginAuthor`/
`SkipGit` — a flag can't contain a space); it only maps each symbol's `id` to a
human-readable `name.text` label (e.g. `SkipGit` → "Skip Git") shown in VS's UI, via
`isVisible: "true"` per entry (VS hides a symbol from that UI unless explicitly
marked visible here). Ignored entirely by `dotnet new` on the CLI.
- The `.csproj` at the repo root — the project file every generated plugin ships with.
- `EntryPoint.cs` / `Config.cs` — the scaffolded plugin (`Plugin<Config>`) and its
YAML-backed settings class.
- `.editorconfig` — formatting/naming only (indentation, brace style). It does **not**
enforce the comment/language rules below — those aren't analyzer-checkable.
wizard. It does **not** rename the `PluginAuthor` symbol (a flag can't contain a
space); it only maps the symbol's `id` to a human-readable `name.text` label shown
in VS's UI, via `isVisible: "true"` (VS hides a symbol from that UI unless
explicitly marked visible here). Ignored entirely by `dotnet new` on the CLI.
- `content/` — the actual buildable project, and the only folder the `sources`
mapping in `template.json` copies into generated output: `LabApiPluginTemplate.csproj`,
`EntryPoint.cs`, `Config.cs`, and `content/CLAUDE.md`. Add any new file that should
ship with generated plugins in here, not at the repo root.
- `content/CLAUDE.md` is a **different file** from this one (the repo-root
`CLAUDE.md`, which is template-editing guidance and never ships). It's the project
memory a generated plugin ships with, so Claude Code has LabAPI/toolchain/style
context automatically in every plugin generated from this template, without
depending on any C#/.NET/LabAPI Skill existing in the catalog (none currently does —
re-check with `SearchSkills`/`ToolSearch` before assuming that's still true). Keep
its LabAPI-contract/toolchain/style sections in sync with this file's equivalent
sections when either changes; `content/CLAUDE.md` omits everything specific to
editing *this* template repo (symbols, `sources` mapping, `sourceName` traps, the
verification workflow below) since none of that applies to a generated plugin.
- No `.sln`, no test project, no CI, no packaging (`.nupkg`) project, no GitHub
Copilot instructions file currently exist in this folder. Don't assume any of them.
This repo itself does have its own git history (see `git log`) — that's unrelated
to whether a *generated* project gets one, which is what "Git on generation" below
covers.
## `content/` vs. repo root — why the project isn't at the repo root
Early versions of this template had the `.csproj`/`.cs` files sitting directly next
to `.gitignore`/`.editorconfig`/`CLAUDE.md`/`.template.config/` at the repo root, with
no `sources` entry in `template.json`. Because `dotnet new` copies everything under
the mounted template root except `.template.config/` by default, every one of those
maintenance files silently ended up inside every generated plugin too.
The fix: `content/` holds only what should be copied, and `template.json` declares
`"sources": [{ "source": "./content", "target": "./" }]`. Files outside `./content`
(root-level `.gitignore`, `.editorconfig`, `CLAUDE.md`) are never touched by
generation — no exclude-list to maintain.
Do **not** nest a folder named `LabApiPluginTemplate` inside `content/` (e.g.
`content/LabApiPluginTemplate/EntryPoint.cs`). `sourceName` substitution renames any
folder whose name matches it, so an inner folder named after the project would get
renamed too, producing a doubled-up output path like
`<output>/<ProjectName>/<ProjectName>/<ProjectName>.csproj` instead of the flat
`<output>/<ProjectName>/<ProjectName>.csproj` consumers expect. Keep the `.csproj`/
`.cs` files directly under `content/`.
## Git on generation — why there's no automated `git init`
A generated project is **not** a git repository by default; a `postAction` only
prints a reminder to run `git init && git add -A && git commit -m "Initial commit"`.
This was a deliberate choice after testing the alternative:
A generated project is **not** a git repository, and there is no `postAction` that
tries to make it one. This was verified, not assumed — re-verified against the
`dotnet` 10.0.400 SDK on this machine while working on this exact question:
- `dotnet new`'s only postAction capable of running an arbitrary process is the
"Run script" action (id `3A7C4B45-...`). Actually wiring it to run `git init`/
`git add`/`git commit` was tested and it **hangs forever** in any non-interactive
shell: the CLI's script-execution security gate demands an explicit `Y` typed at a
`Do you want to run this action [Y(yes)|N(no)]?` prompt, once per script action,
with no flag on this SDK to pre-approve it — unlike the NuGet-restore postAction,
which the CLI treats as trusted and never gates. Do not reintroduce "Run script"
postActions for this without re-verifying that gate no longer applies.
"Run script" action (id `3A7C4B45-...`). Wiring it to run `git init`/`git add`/
`git commit` hangs forever in any non-interactive shell: the CLI's script-execution
security gate demands an explicit `Y` typed at a
`Do you want to run this action [Y(yes)|N(no)]?` prompt, once per script action.
`dotnet new create --help` on this SDK exposes no `--allow-scripts` or equivalent
flag to pre-approve it (unlike the NuGet-restore postAction, which the CLI treats
as trusted and never gates). Do not reintroduce "Run script" postActions for this
without re-verifying that gate no longer applies on whatever SDK is current then.
- A previous revision compensated with a `SkipGit` symbol plus a "display manual
instructions" postAction that just printed the `git init` one-liner. That has been
removed — it wasn't automation, just a reminder to run a command any git user
already knows, so it wasn't worth the extra symbol/postAction/`ide.host.json` entry.
- Visual Studio's own "New Project" dialog already has a built-in "Create new Git
repository" checkbox that works for any template, including this one, with zero
template-side configuration — so VS users get this for free regardless.
- CLI users see the printed reminder above (governed by the `SkipGit` symbol) and run
the one-liner themselves.
template-side configuration — so VS users get real automatic git init for free.
CLI users just run `git init` themselves after generating.
## LabAPI contract (verified against northwood-studios/LabAPI source, not guessed)
@@ -121,7 +156,8 @@ assume one exists.
`NU1701` (via a per-`PackageReference` `<NoWarn>`, not a project-wide one) and
`MSB3270` (via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`). Don't
broaden either into a blanket suppression and don't remove them without
understanding why (comments sit next to both in the `.csproj`).
understanding why (both are explained here, not in the `.csproj` — this repo's
code style forbids comments, including in the `.csproj`, see "Code style" below).
- The `Northwood.LabAPI` `PackageReference` uses a floating `Version="*"` on purpose,
so every `dotnet restore` always pulls whatever is the latest stable release on
NuGet at that moment — there's no version to bump here. This is intentionally
@@ -136,10 +172,10 @@ assume one exists.
## Code style for anything you write here
- No `//`, `/* */`, or `///` comments unless the user explicitly asks for
comments/documentation in that request. No comments restating self-explanatory code.
`EntryPoint.cs`/`Config.cs` are already comment-free — keep them that way and follow
the same standard for any new file.
- No `//`, `/* */`, `///`, or XML (`<!-- -->`) comments unless the user explicitly
asks for comments/documentation in that request. No comments restating
self-explanatory code. `EntryPoint.cs`/`Config.cs`/the `.csproj` are already
comment-free — keep them that way and follow the same standard for any new file.
- All text embedded in code defaults to **English**: string/log/console messages,
exception messages, `[Description]`/config text, any user-facing string. Example:
`Logger.Info("Plugin initialized successfully")`, not a Spanish equivalent.
@@ -170,10 +206,17 @@ Notes:
- Re-run `dotnet new uninstall .` + `dotnet new install .` after **any** edit to
`.template.config/template.json` or the scaffolded files — the template cache
keys off the installed path and stale content otherwise gets regenerated.
- `dotnet build` also works directly inside `<repo-name>` (the placeholder project
is itself valid and compilable), but that only proves the *unsubstituted*
placeholder compiles — it does not exercise symbol substitution. Always additionally
verify through an actual `dotnet new` generation before calling a change done.
- `dotnet build` also works directly inside `content/` (the placeholder project is
itself valid and compilable), but that only proves the *unsubstituted* placeholder
compiles — it does not exercise symbol substitution or the `sources` mapping.
Always additionally verify through an actual `dotnet new` generation before calling
a change done.
- If `dotnet new install .` warns about another installed template sharing this
identity (`SCPSL.LabApi.PluginTemplate`) from a different path — e.g. this repo's
own parent folder having been installed by an earlier mistaken
`dotnet new install .` run one level up — that other install shadows this one and
your edits won't be exercised. Run `dotnet new uninstall <that other path>` first.
- On Windows, if cleaning up `../_verify` fails with "resource busy", run
`dotnet build-server shutdown` first, then retry.
`dotnet build-server shutdown` first, then retry; if it still won't delete, fall
back to PowerShell's `Remove-Item -Recurse -Force`.
- Delete the `../_verify` scratch output when done; it isn't part of the template.
-43
View File
@@ -1,43 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- .NET Standard 2.1: the most modern ABI that can still be loaded inside the
.NET Framework 4.8 / Mono process the SCP:SL dedicated server uses, letting the
project use recent C# syntax (see LangVersion) while still producing an
assembly compatible with the game's host runtime. -->
<TargetFramework>netstandard2.1</TargetFramework>
<!-- C# language version explicitly requested by the project. -->
<LangVersion>14.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<RootNamespace>LabApiPluginTemplate</RootNamespace>
<AssemblyName>LabApiPluginTemplate</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<!-- Northwood.LabAPI only ships net48/net481 binaries. Restoring it into a
netstandard2.1 project makes NuGet fall back to that asset group and raise
NU1701 to flag it. That's expected and safe here (the resulting assembly is
still loaded by the server's .NET Framework/Mono runtime either way), so this
suppresses just that one warning and only for this package. -->
<NoWarn>$(NoWarn);NU1701</NoWarn>
<!-- LabAPI's prebuilt assembly (and the game's own managed assemblies it depends
on) are built for .NET Framework, which on some SDK/platform combinations
triggers a processor-architecture mismatch warning (MSB3270) when resolving
references. It isn't an actual runtime problem, so this turns off just that
specific check instead of every MSBuild warning. -->
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<ItemGroup>
<!-- Floating version: restore always resolves to the latest stable Northwood.LabAPI
release published on NuGet at the time, instead of a version pinned here. -->
<PackageReference Include="Northwood.LabAPI" Version="*">
<NoWarn>NU1701</NoWarn>
</PackageReference>
</ItemGroup>
</Project>
+63
View File
@@ -0,0 +1,63 @@
# CLAUDE.md
This is an SCP: Secret Laboratory LabAPI plugin, generated from the
`labapi-plugin` `dotnet new` template. `EntryPoint.cs` is the plugin's entry
point (`Plugin<Config>`); `Config.cs` is its YAML-backed settings class.
## LabAPI contract (verified against northwood-studios/LabAPI source, not guessed)
`Plugin<TConfig>` (`LabApi.Loader.Features.Plugins`) requires implementing:
`Name`, `Description`, `Author`, `RequiredApiVersion` (all abstract get-only) plus
`Enable()`/`Disable()`. Overridable but optional: `Version` (defaults to the
assembly's version), `Priority` (`LoadPriority`, defaults to `Medium`),
`ConfigFileName` (defaults to `"config.yml"`). `Config` is a plain `class` with a
public parameterless constructor and public get/set properties — the base class
(de)serializes it via YamlDotNet automatically; don't hand-roll config I/O.
**Never invent a LabAPI class/namespace/method/property/attribute.** Before using one
you haven't already seen in this project, check the restored package (`LabApi.dll` +
`LabApi.xml` under `%userprofile%\.nuget\packages\northwood.labapi\<version>\lib\net48\`)
or https://github.com/northwood-studios/LabAPI. If you can't verify a member exists,
say so instead of guessing.
## Toolchain choices — don't "fix" these without being asked
- `TargetFramework` is `netstandard2.1` on purpose, even though LabAPI's own docs and
the `Northwood.LabAPI` NuGet package only target `net48`/`net481`. This is a
deliberate compatibility choice (compiled output still loads under the game's
Mono/.NET Framework 4.8 host) that trades away some netstandard2.1 BCL/language
surface .NET Framework can't actually run (e.g. default interface members). Don't
switch it to `net48`, don't add multi-targeting, unless asked.
- `LangVersion` is pinned explicitly in the `.csproj` — don't let it drift to `latest`.
- The `.csproj` suppresses exactly two warnings, both scoped as narrowly as MSBuild
allows, because of the netstandard2.1/net48-only-package combination above:
`NU1701` (via a per-`PackageReference` `<NoWarn>`, not a project-wide one) and
`MSB3270` (via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`). Don't
broaden either into a blanket suppression and don't remove them without
understanding why (both are explained here, not in the `.csproj` — this project's
code style forbids comments, including in the `.csproj`, see "Code style" below).
- `Northwood.LabAPI` uses a floating `Version="*"` on purpose, so every
`dotnet restore` pulls whatever is latest on NuGet at that moment. This is
intentionally **not** wired to `EntryPoint.cs`'s `RequiredApiVersion`, which is a
separate, manually-maintained literal for the minimum LabAPI version this code is
known to work with — bump it by hand if you verify compatibility with a newer
release, don't try to auto-sync it with the floating restore.
## Code style
- No `//`, `/* */`, `///`, or XML (`<!-- -->`) comments unless explicitly asked for.
No comments restating self-explanatory code.
- All text embedded in code defaults to **English**: string/log/console messages,
exception messages, `[Description]`/config text, any user-facing string.
- This never applies to identifiers picked deliberately (project name, namespace,
class/method/property/variable names) — keep those as requested.
## How to verify a change
There is no test project scaffolded. Verify with:
```bash
dotnet build
```
Expect `Build succeeded. 0 Warning(s) 0 Error(s)`.
View File
-3
View File
@@ -1,7 +1,6 @@
using System;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
using LabApi.Loader.Features.Plugins.Enums;
namespace LabApiPluginTemplate
{
@@ -15,8 +14,6 @@ namespace LabApiPluginTemplate
public override Version RequiredApiVersion { get; } = new Version("1.1.7");
public override LoadPriority Priority => LoadPriority.Medium;
public override void Enable()
{
Logger.Info($"{Name} has been enabled.");
+22
View File
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>14.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<RootNamespace>LabApiPluginTemplate</RootNamespace>
<AssemblyName>LabApiPluginTemplate</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<NoWarn>$(NoWarn);NU1701</NoWarn>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="*">
<NoWarn>NU1701</NoWarn>
</PackageReference>
</ItemGroup>
</Project>