Skip to content

Commit 596c41f

Browse files
NetdocsCopilot
andcommitted
feat(cli): add 'netdocs new' to scaffold an annotated config
etdocs new [path] [--force] writes a ready-to-edit appsettings.json with every common option, sane defaults, and inline links back to the docs site. The file is JSONC — // comments and trailing commas are preserved and parsed at build time (JsonConfigLoader already skips comments). - New NewCommand.cs with the template; wired into CLI dispatch + help text. - Refuses to overwrite unless --force. - Documented in reference/cli.md (with a dedicated section) and referenced from the quickstart. Verified end-to-end: the generated config parses and builds a working site. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 599c1cd commit 596c41f

4 files changed

Lines changed: 214 additions & 3 deletions

File tree

docs-site/docs/getting-started/quickstart.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ my-site/
1717

1818
## 2. Write `appsettings.json`
1919

20+
!!! tip "Scaffold it instead"
21+
Run **`netdocs new`** to generate a fully-annotated `appsettings.json` with every common
22+
option, sane defaults, and links back to these docs — then edit to taste. See the
23+
[`netdocs new`](../reference/cli.md#netdocs-new) reference.
24+
2025
The site is configured under the `Netdocs` section:
2126

2227
```json

docs-site/docs/reference/cli.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: CLI reference
44

55
# CLI reference
66

7-
The `netdocs` executable exposes four commands: `build`, `serve`, `watch`, and `import`.
7+
The `netdocs` executable exposes these commands: `build`, `serve`, `watch`, `new`, and `import`.
88

99
```text
1010
netdocs - static site generator
@@ -13,6 +13,7 @@ Usage:
1313
netdocs build [options] Build the site to the output directory
1414
netdocs serve [options] Serve with live reload
1515
netdocs watch [options] Publish daemon: poll a git remote and rebuild on push
16+
netdocs new [path] Scaffold an annotated appsettings.json
1617
netdocs import [mkdocs.yml] Convert an mkdocs.yml to appsettings.json
1718
```
1819

@@ -23,9 +24,31 @@ Usage:
2324
| `netdocs build` | Build the site to the configured output dir (`siteDir`). |
2425
| `netdocs serve` | Kestrel dev server with file-watch rebuilds + WebSocket live reload. |
2526
| `netdocs watch` | Long-running publish daemon: polls a git remote and rebuilds when the tracked branch advances. |
27+
| `netdocs new` | Scaffold a fully-annotated `appsettings.json` (all common options + doc links). |
2628
| `netdocs import` | Convert an existing `mkdocs.yml` into a Netdocs `appsettings.json`. |
2729
| `netdocs --help` | Print usage. |
2830

31+
### `netdocs new`
32+
33+
Writes a ready-to-edit `appsettings.json` with every common setting, sane defaults, and inline
34+
links back to this documentation. The file is JSONC — `//` comments and trailing commas are kept
35+
and parsed at build time, so you can leave the guidance in place.
36+
37+
```bash
38+
netdocs new # writes ./appsettings.json
39+
netdocs new docs/appsettings.json
40+
netdocs new --force # overwrite an existing file
41+
```
42+
43+
Starting from scratch:
44+
45+
```bash
46+
netdocs new
47+
mkdir docs && echo "# Home" > docs/index.md
48+
netdocs serve
49+
```
50+
51+
2952
If no command is given, `build` is assumed.
3053

3154
## Options

src/Netdocs.Cli/CliApp.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public static async Task<int> RunAsync(string[] args)
2121
{
2222
case "import":
2323
return await ImportAsync(args);
24+
case "new":
25+
return await NewCommand.RunAsync(args);
2426
case "--help" or "-h" or "help":
2527
return PrintHelp();
2628
case "--version" or "-V" or "version":
@@ -277,11 +279,12 @@ netdocs build [options] Build the site to the output directory
277279
netdocs deploy [options] Build, then publish to the configured deploy target
278280
netdocs serve [options] Serve with live reload
279281
netdocs watch [options] Publish daemon: poll a git remote and rebuild on push
282+
netdocs new [path] Scaffold an annotated appsettings.json
280283
netdocs import [mkdocs.yml] Convert an mkdocs.yml to appsettings.json
281284
netdocs --version Print the Netdocs version and exit
282285
283-
Import options:
284-
--out <path> Output appsettings.json path (default next to mkdocs.yml)
286+
New / import options:
287+
--out <path> Output appsettings.json path (import; default next to mkdocs.yml)
285288
--force Overwrite an existing appsettings.json
286289
287290
Options:

src/Netdocs.Cli/NewCommand.cs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
namespace Netdocs.Cli;
2+
3+
/// <summary>
4+
/// <c>netdocs new</c> — scaffolds an annotated <c>appsettings.json</c> with every common option,
5+
/// sane defaults, and inline links back to the docs site. Comments and trailing commas are legal
6+
/// because <see cref="Netdocs.Core.Configuration.JsonConfigLoader"/> parses with
7+
/// <c>JsonCommentHandling.Skip</c> / <c>AllowTrailingCommas</c>.
8+
/// </summary>
9+
internal static class NewCommand
10+
{
11+
private const string DocsBase = "https://xtremeownage.github.io/Netdocs";
12+
13+
public static async Task<int> RunAsync(string[] args)
14+
{
15+
// netdocs new [path/to/appsettings.json] [--force]
16+
var target = args.Skip(1).FirstOrDefault(a => !a.StartsWith('-'))
17+
?? Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
18+
target = Path.GetFullPath(target);
19+
var force = args.Contains("--force");
20+
21+
if (File.Exists(target) && !force)
22+
{
23+
Console.Error.WriteLine($"Refusing to overwrite existing {target}. Pass --force to replace it.");
24+
return 1;
25+
}
26+
27+
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
28+
await File.WriteAllTextAsync(target, Template);
29+
Console.WriteLine($"Wrote annotated config -> {target}");
30+
Console.WriteLine("Edit the values (siteName, siteUrl, repoUrl…), add your docs/ pages, then run: netdocs build");
31+
return 0;
32+
}
33+
34+
private static readonly string Template = $$"""
35+
{
36+
// Netdocs configuration. This file is JSONC: // comments and trailing commas are allowed.
37+
// Full reference: {{DocsBase}}/reference/configuration/
38+
"Logging": {
39+
"LogLevel": { "Default": "Information" }
40+
},
41+
42+
"Netdocs": {
43+
// --- Site identity ---------------------------------------------------------------
44+
"siteName": "My Project",
45+
"siteUrl": "https://example.github.io/my-project/", // used for absolute URLs, sitemap, feeds
46+
"siteAuthor": "Your Name",
47+
"siteDescription": "Documentation for My Project.",
48+
"copyright": "© 2025 Your Name",
49+
50+
// Repo links power the header icon and the per-page Edit/View buttons.
51+
"repoUrl": "https://github.com/you/my-project",
52+
"repoName": "you/my-project",
53+
"editUri": "edit/main/docs/", // appended to repoUrl for the "Edit this page" button
54+
55+
"docsDir": "docs", // source markdown folder
56+
"siteDir": "site", // build output folder
57+
58+
// --- Theme -----------------------------------------------------------------------
59+
// Reference: {{DocsBase}}/reference/theme/
60+
"theme": {
61+
"name": "material",
62+
"language": "en",
63+
// "logo": "assets/logo.svg",
64+
// "favicon": "assets/favicon.png",
65+
// "customDir": "overrides", // drop-in template/partial overrides (Scriban)
66+
"highlight": "highlightjs", // "highlightjs" | "none" | custom (bring your own)
67+
"features": [
68+
"navigation.instant",
69+
"navigation.tabs",
70+
"navigation.sections",
71+
"navigation.top",
72+
"navigation.footer",
73+
"navigation.indexes",
74+
// "navigation.path", // breadcrumbs
75+
"toc.follow",
76+
"content.code.copy",
77+
"content.code.annotate",
78+
"content.tooltips",
79+
"content.footnote.tooltips",
80+
"search.highlight",
81+
"search.suggest",
82+
"content.action.edit", // requires editUri above
83+
"content.action.view"
84+
],
85+
// Light/dark palette with a header toggle.
86+
"palette": [
87+
{ "media": "(prefers-color-scheme: light)", "scheme": "default", "primary": "indigo", "accent": "indigo",
88+
"toggleIcon": "material/brightness-7", "toggleName": "Switch to dark mode" },
89+
{ "media": "(prefers-color-scheme: dark)", "scheme": "slate", "primary": "indigo", "accent": "indigo",
90+
"toggleIcon": "material/brightness-4", "toggleName": "Switch to light mode" }
91+
]
92+
},
93+
94+
// --- Navigation ------------------------------------------------------------------
95+
// Omit `nav` to auto-generate from the docs folder structure.
96+
"nav": [
97+
{ "path": "index.md" },
98+
{
99+
"title": "Getting started",
100+
"children": [
101+
{ "path": "getting-started/installation.md" },
102+
{ "path": "getting-started/quickstart.md" }
103+
]
104+
}
105+
],
106+
107+
// --- Markdown extensions ---------------------------------------------------------
108+
// Reference: {{DocsBase}}/reference/markdown-extensions/
109+
"markdownExtensions": [
110+
{ "name": "admonition" },
111+
{ "name": "attr_list" },
112+
{ "name": "md_in_html" },
113+
{ "name": "footnotes" },
114+
{ "name": "toc", "options": { "permalink": true } },
115+
{ "name": "pymdownx.details" },
116+
{ "name": "pymdownx.superfences" },
117+
{ "name": "pymdownx.tabbed", "options": { "alternate_style": true } },
118+
{ "name": "pymdownx.highlight", "options": { "line_spans": "__span" } },
119+
{ "name": "pymdownx.tasklist", "options": { "custom_checkbox": true } },
120+
{ "name": "pymdownx.emoji" },
121+
{ "name": "pymdownx.keys" },
122+
{ "name": "pymdownx.critic" }
123+
],
124+
125+
// --- Plugins ---------------------------------------------------------------------
126+
// Reference: {{DocsBase}}/plugins/ — remove any you don't need.
127+
"plugins": [
128+
{ "name": "search", "options": { "lang": "en" } },
129+
{ "name": "meta" },
130+
{ "name": "tags", "options": { "export": true } },
131+
// { "name": "blog" }, // enable if you have a docs/blog/ folder
132+
// { "name": "rss", "options": { "atom": true, "social_icon": true } },
133+
// { "name": "git-revision-date-localized" },
134+
// { "name": "redirects" },
135+
// { "name": "glightbox" },
136+
// { "name": "macros" }, // fileuri()/button()/download() macros
137+
// { "name": "table-reader" }, // read_csv()/read_table()
138+
// { "name": "arithmatex" }, // LaTeX math
139+
// { "name": "social" } // OG cards; best behind `--prod`
140+
],
141+
142+
// --- Extra: social links, custom vars --------------------------------------------
143+
"extra": {
144+
"social": [
145+
{ "icon": "fontawesome/brands/github", "link": "https://github.com/you/my-project" }
146+
]
147+
},
148+
149+
// --- URL slugs -------------------------------------------------------------------
150+
"slugify": { "case": "lower", "separator": "-", "ascii": false },
151+
152+
// --- Output optimization ---------------------------------------------------------
153+
"optimize": {
154+
"minifyHtml": false,
155+
"minifyCss": false,
156+
"minifyJs": false,
157+
"convertImagesToWebp": false,
158+
"webpQuality": 80
159+
},
160+
161+
// --- Build-time validation -------------------------------------------------------
162+
// Reference: {{DocsBase}}/reference/validation/ — pair with `netdocs build --strict`.
163+
"validation": {
164+
"links": false, // broken internal links
165+
"anchors": false, // broken #fragment anchors (requires links)
166+
"unusedImages": false,
167+
"orphanPages": false
168+
},
169+
170+
// --- Deploy (optional) -----------------------------------------------------------
171+
// target: "none" | "filesystem" | "git" | "s3" — run `netdocs deploy`.
172+
"deploy": {
173+
"target": "none"
174+
// "target": "git", "branch": "gh-pages", "remote": "origin"
175+
}
176+
}
177+
}
178+
179+
""";
180+
}

0 commit comments

Comments
 (0)