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" "isVisible": "true"
}, },
{ {
"id": "SkipGit", "id": "PluginDescription",
"name": { "text": "Skip Git" }, "name": { "text": "Plugin Description" },
"isVisible": "true" "isVisible": "true"
} }
] ]
+11 -13
View File
@@ -10,6 +10,12 @@
}, },
"sourceName": "LabApiPluginTemplate", "sourceName": "LabApiPluginTemplate",
"preferNameDirectory": true, "preferNameDirectory": true,
"sources": [
{
"source": "./content",
"target": "./"
}
],
"symbols": { "symbols": {
"PluginAuthor": { "PluginAuthor": {
"type": "parameter", "type": "parameter",
@@ -18,11 +24,12 @@
"replaces": "YourName", "replaces": "YourName",
"description": "Name used as the plugin's author." "description": "Name used as the plugin's author."
}, },
"SkipGit": { "PluginDescription": {
"type": "parameter", "type": "parameter",
"datatype": "bool", "datatype": "string",
"description": "If set, skips printing the reminder to initialize a git repository.", "defaultValue": "Your Description",
"defaultValue": "false" "replaces": "TODO: describe what this plugin does.",
"description": "Short description of what the plugin does."
} }
}, },
"primaryOutputs": [ "primaryOutputs": [
@@ -36,15 +43,6 @@
], ],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true "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 ## Repo is template *source* — read before touching any file
`.template.config/template.json` sets this folder's name as the template's `.template.config/template.json` sets this folder's name as the template's
`sourceName` and defines one text-replacement `symbol`, `PluginAuthor` (plus `sourceName` and defines one text-replacement `symbol`, `PluginAuthor`. The dotnet
`SkipGit`, which only toggles the git-reminder post-generation action and replaces no templating engine does a literal, repo-wide **string replacement** at generation
text). The dotnet templating engine does a literal, repo-wide **string replacement** time — not semantic renaming — driven by that config:
at generation time — not semantic renaming — driven by that config:
- The folder/project name (`sourceName` in `template.json`, currently this repo's own - The folder/project name (`sourceName` in `template.json`, currently this repo's own
folder name) → whatever `-n`/`--name` the consumer passes to `dotnet new`. folder name) → whatever `-n`/`--name` the consumer passes to `dotnet new`.
@@ -41,50 +40,86 @@ Consequences:
## Repo layout (only what affects decisions) ## Repo layout (only what affects decisions)
- `.template.config/template.json` — template identity, the symbols above plus - Repo root holds only template-source/maintenance files that must **never** ship in
`SkipGit` (a display-only toggle, see below), `primaryOutputs` (points at the a generated project: `.git/`, `.gitignore`, `.editorconfig`, `CLAUDE.md`, and
project file using its pre-substitution literal name — that's correct and expected, `.template.config/`. None of these are copied to generated output — see "content/
not a bug), and two `postActions`: the standard dotnet-templating "restore NuGet vs. repo root" below.
packages" action (id `210D431B-...`; skipped in Visual Studio, which restores on - `.template.config/template.json` — template identity, the `PluginAuthor` symbol,
its own) and a "display manual instructions" action (id `AC1156F7-...`) that prints a `sources` entry that maps `./content` → the output root (this is what keeps the
a reminder to run `git init` — see "Git on generation" below for why it's just a root-level files above out of every generated project), `primaryOutputs` (points at
printed reminder and not an executed command. 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 - `.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`/ wizard. It does **not** rename the `PluginAuthor` symbol (a flag can't contain a
`SkipGit` — a flag can't contain a space); it only maps each symbol's `id` to a space); it only maps the symbol's `id` to a human-readable `name.text` label shown
human-readable `name.text` label (e.g. `SkipGit` → "Skip Git") shown in VS's UI, via in VS's UI, via `isVisible: "true"` (VS hides a symbol from that UI unless
`isVisible: "true"` per entry (VS hides a symbol from that UI unless explicitly explicitly marked visible here). Ignored entirely by `dotnet new` on the CLI.
marked visible here). Ignored entirely by `dotnet new` on the CLI. - `content/` — the actual buildable project, and the only folder the `sources`
- The `.csproj` at the repo root — the project file every generated plugin ships with. mapping in `template.json` copies into generated output: `LabApiPluginTemplate.csproj`,
- `EntryPoint.cs` / `Config.cs` — the scaffolded plugin (`Plugin<Config>`) and its `EntryPoint.cs`, `Config.cs`, and `content/CLAUDE.md`. Add any new file that should
YAML-backed settings class. ship with generated plugins in here, not at the repo root.
- `.editorconfig` — formatting/naming only (indentation, brace style). It does **not** - `content/CLAUDE.md` is a **different file** from this one (the repo-root
enforce the comment/language rules below — those aren't analyzer-checkable. `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 - 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. 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 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 to whether a *generated* project gets one, which is what "Git on generation" below
covers. 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` ## Git on generation — why there's no automated `git init`
A generated project is **not** a git repository by default; a `postAction` only A generated project is **not** a git repository, and there is no `postAction` that
prints a reminder to run `git init && git add -A && git commit -m "Initial commit"`. tries to make it one. This was verified, not assumed — re-verified against the
This was a deliberate choice after testing the alternative: `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 - `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`/ "Run script" action (id `3A7C4B45-...`). Wiring it to run `git init`/`git add`/
`git add`/`git commit` was tested and it **hangs forever** in any non-interactive `git commit` hangs forever in any non-interactive shell: the CLI's script-execution
shell: the CLI's script-execution security gate demands an explicit `Y` typed at a 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, `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, `dotnet new create --help` on this SDK exposes no `--allow-scripts` or equivalent
which the CLI treats as trusted and never gates. Do not reintroduce "Run script" flag to pre-approve it (unlike the NuGet-restore postAction, which the CLI treats
postActions for this without re-verifying that gate no longer applies. 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 - 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 repository" checkbox that works for any template, including this one, with zero
template-side configuration — so VS users get this for free regardless. template-side configuration — so VS users get real automatic git init for free.
- CLI users see the printed reminder above (governed by the `SkipGit` symbol) and run CLI users just run `git init` themselves after generating.
the one-liner themselves.
## LabAPI contract (verified against northwood-studios/LabAPI source, not guessed) ## 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 `NU1701` (via a per-`PackageReference` `<NoWarn>`, not a project-wide one) and
`MSB3270` (via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`). Don't `MSB3270` (via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`). Don't
broaden either into a blanket suppression and don't remove them without 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, - The `Northwood.LabAPI` `PackageReference` uses a floating `Version="*"` on purpose,
so every `dotnet restore` always pulls whatever is the latest stable release on 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 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 ## Code style for anything you write here
- No `//`, `/* */`, or `///` comments unless the user explicitly asks for - No `//`, `/* */`, `///`, or XML (`<!-- -->`) comments unless the user explicitly
comments/documentation in that request. No comments restating self-explanatory code. asks for comments/documentation in that request. No comments restating
`EntryPoint.cs`/`Config.cs` are already comment-free — keep them that way and follow self-explanatory code. `EntryPoint.cs`/`Config.cs`/the `.csproj` are already
the same standard for any new file. 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, - All text embedded in code defaults to **English**: string/log/console messages,
exception messages, `[Description]`/config text, any user-facing string. Example: exception messages, `[Description]`/config text, any user-facing string. Example:
`Logger.Info("Plugin initialized successfully")`, not a Spanish equivalent. `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 - Re-run `dotnet new uninstall .` + `dotnet new install .` after **any** edit to
`.template.config/template.json` or the scaffolded files — the template cache `.template.config/template.json` or the scaffolded files — the template cache
keys off the installed path and stale content otherwise gets regenerated. keys off the installed path and stale content otherwise gets regenerated.
- `dotnet build` also works directly inside `<repo-name>` (the placeholder project - `dotnet build` also works directly inside `content/` (the placeholder project is
is itself valid and compilable), but that only proves the *unsubstituted* itself valid and compilable), but that only proves the *unsubstituted* placeholder
placeholder compiles — it does not exercise symbol substitution. Always additionally compiles — it does not exercise symbol substitution or the `sources` mapping.
verify through an actual `dotnet new` generation before calling a change done. 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 - 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. - 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 System;
using LabApi.Features.Console; using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins; using LabApi.Loader.Features.Plugins;
using LabApi.Loader.Features.Plugins.Enums;
namespace LabApiPluginTemplate namespace LabApiPluginTemplate
{ {
@@ -15,8 +14,6 @@ namespace LabApiPluginTemplate
public override Version RequiredApiVersion { get; } = new Version("1.1.7"); public override Version RequiredApiVersion { get; } = new Version("1.1.7");
public override LoadPriority Priority => LoadPriority.Medium;
public override void Enable() public override void Enable()
{ {
Logger.Info($"{Name} has been enabled."); 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>