diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml
deleted file mode 100644
index 4ea72a911..000000000
--- a/.idea/copilot.data.migration.agent.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml
deleted file mode 100644
index 8648f9401..000000000
--- a/.idea/copilot.data.migration.edit.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
deleted file mode 100644
index a37f6ff4a..000000000
--- a/.idea/dictionaries/project.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- backchannel
-
-
-
\ No newline at end of file
diff --git a/.idea/docs.duendesoftware.com.iml b/.idea/docs.duendesoftware.com.iml
deleted file mode 100644
index b16af2e18..000000000
--- a/.idea/docs.duendesoftware.com.iml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Dockerfile.xml b/.idea/runConfigurations/Dockerfile.xml
deleted file mode 100644
index eb746248d..000000000
--- a/.idea/runConfigurations/Dockerfile.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.opencode/agent/docs-writer.md b/.opencode/agent/docs-writer.md
new file mode 100644
index 000000000..438f936fd
--- /dev/null
+++ b/.opencode/agent/docs-writer.md
@@ -0,0 +1,21 @@
+---
+description: Writes and maintains documentation for Duende Software products, including IdentityServer and Backend for Frontend.
+mode: all
+tools:
+ write: true
+ edit: true
+ bash: true
+ webfetch: true
+ question: true
+ lsp: true
+---
+
+You are a technical documentation writer, with expert-level background in ASP.NET Core, OpenID Connect, OAuth, and general .NET development.
+Create clear, comprehensive documentation. Ask clarifying questions if there is ambiguity or something isn't clear.
+
+Focus on:
+
+* Clear explanations
+* Proper structure
+* Code examples
+* User-friendly language
diff --git a/.opencode/rules/writing-guidelines.md b/.opencode/rules/writing-guidelines.md
new file mode 100644
index 000000000..a0244e0ca
--- /dev/null
+++ b/.opencode/rules/writing-guidelines.md
@@ -0,0 +1,11 @@
+* Strictly follow the authoring and style guides from `README.md`
+* Do not use conversational filler or "fluff" in the generated documentation. Get straight to the point.
+* Always check the file structure and existing content to ensure consistent links and hierarchy.
+* Never invent features or configuration options. If you are unsure, ask the user.
+* Code generation - Prioritize complete, runnable examples over snippets. Ensure standard formatting.
+
+When writing Markdown content Markdown:
+* Use `*` for lists. Do not use `-`.
+* Use `[link title](https://example.com)` for links, avoid reference-style links unless there are multiple occurences of the same link in one document.
+* For internal links, always include the extension (e.g. `.md` or `.mdx`)
+* Prefer `csharp` over `cs` to set the language for C# code blocks.
\ No newline at end of file
diff --git a/README.md b/README.md
index cc13ccb59..5a641db2b 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,8 @@ Static assets, like favicons, can be placed in the `astro/public/` directory.
## ✍️ Authoring
+The `astro/` folder has been configured as a VS Code and WebStorm project, which you can open from that location to work on content.
+
Content can be authored in Markdown, in a `.md` or `.mdx` file. The Starlight documentation has some guidance on Markdown syntax, components, and more:
* [Authoring Content in Markdown](https://starlight.astro.build/guides/authoring-content/)
diff --git a/.devcontainer.json b/astro/.devcontainer.json
similarity index 100%
rename from .devcontainer.json
rename to astro/.devcontainer.json
diff --git a/astro/.gitignore b/astro/.gitignore
new file mode 100644
index 000000000..2a1ea9920
--- /dev/null
+++ b/astro/.gitignore
@@ -0,0 +1,70 @@
+# Astro build output
+astro/dist/
+astro/root/
+server/src/Docs.Web/wwwroot/
+
+# generated types
+astro/.astro/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
+.vscode/settings.json
+*.iml
+
+# Agents and assistants
+.opencode/
+opencode.json
+.idea/**/copilot.data.migration.*.xml
+
+# .NET
+*.user
+*.suo
+*.userosscache
+*.sln.docstates
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+.vs/
+*.nupkg
+*.snupkg
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Rider
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# User-specific files
+*.rsuser
+
+# JetBrains Rider
+*.sln.iml
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# Windows
+Thumbs.db
+ehthumbs.db
+
+# Azure Functions
+local.settings.json
diff --git a/.idea/.gitignore b/astro/.idea/.gitignore
similarity index 100%
rename from .idea/.gitignore
rename to astro/.idea/.gitignore
diff --git a/.idea/codeStyles/Project.xml b/astro/.idea/codeStyles/Project.xml
similarity index 100%
rename from .idea/codeStyles/Project.xml
rename to astro/.idea/codeStyles/Project.xml
diff --git a/.idea/compiler.xml b/astro/.idea/compiler.xml
similarity index 100%
rename from .idea/compiler.xml
rename to astro/.idea/compiler.xml
diff --git a/.idea/externalDependencies.xml b/astro/.idea/externalDependencies.xml
similarity index 100%
rename from .idea/externalDependencies.xml
rename to astro/.idea/externalDependencies.xml
diff --git a/.idea/modules.xml b/astro/.idea/modules.xml
similarity index 100%
rename from .idea/modules.xml
rename to astro/.idea/modules.xml
diff --git a/.idea/prettier.xml b/astro/.idea/prettier.xml
similarity index 100%
rename from .idea/prettier.xml
rename to astro/.idea/prettier.xml
diff --git a/.idea/scopes/Content.xml b/astro/.idea/scopes/Content.xml
similarity index 100%
rename from .idea/scopes/Content.xml
rename to astro/.idea/scopes/Content.xml
diff --git a/.idea/vcs.xml b/astro/.idea/vcs.xml
similarity index 75%
rename from .idea/vcs.xml
rename to astro/.idea/vcs.xml
index 35eb1ddfb..62bd7a01e 100644
--- a/.idea/vcs.xml
+++ b/astro/.idea/vcs.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/.idea/webResources.xml b/astro/.idea/webResources.xml
similarity index 100%
rename from .idea/webResources.xml
rename to astro/.idea/webResources.xml
diff --git a/.vscode/extensions.json b/astro/.vscode/extensions.json
similarity index 100%
rename from .vscode/extensions.json
rename to astro/.vscode/extensions.json
diff --git a/.vscode/launch.json b/astro/.vscode/launch.json
similarity index 100%
rename from .vscode/launch.json
rename to astro/.vscode/launch.json
diff --git a/opencode.json b/opencode.json
new file mode 100644
index 000000000..52fc3b8be
--- /dev/null
+++ b/opencode.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://opencode.ai/config.json",
+ "instructions": ["README.md", ".opencode/rules/writing-guidelines.md"],
+ "plugin": ["@opencode_weave/weave"],
+ "watcher": {
+ "ignore": ["node_modules/**", "dist/**", ".git/**"]
+ }
+}
diff --git a/server/.idea/.idea.Docs/.idea/.gitignore b/server/.idea/.idea.Docs/.idea/.gitignore
new file mode 100644
index 000000000..a5361d753
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/.gitignore
@@ -0,0 +1,15 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/contentModel.xml
+/projectSettingsUpdater.xml
+/.idea.Docs.iml
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/server/.idea/.idea.Docs/.idea/.name b/server/.idea/.idea.Docs/.idea/.name
new file mode 100644
index 000000000..1157f6756
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/.name
@@ -0,0 +1 @@
+Docs
\ No newline at end of file
diff --git a/server/.idea/.idea.Docs/.idea/encodings.xml b/server/.idea/.idea.Docs/.idea/encodings.xml
new file mode 100644
index 000000000..df87cf951
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/server/.idea/.idea.Docs/.idea/externalDependencies.xml b/server/.idea/.idea.Docs/.idea/externalDependencies.xml
new file mode 100644
index 000000000..be4a9b9e6
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/externalDependencies.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/.idea/.idea.Docs/.idea/indexLayout.xml b/server/.idea/.idea.Docs/.idea/indexLayout.xml
new file mode 100644
index 000000000..7b08163ce
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/.idea/.idea.Docs/.idea/misc.xml b/server/.idea/.idea.Docs/.idea/misc.xml
new file mode 100644
index 000000000..7fcdf3bad
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+ {}
+
\ No newline at end of file
diff --git a/server/.idea/.idea.Docs/.idea/vcs.xml b/server/.idea/.idea.Docs/.idea/vcs.xml
new file mode 100644
index 000000000..6c0b86358
--- /dev/null
+++ b/server/.idea/.idea.Docs/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/src/Docs.AppHost/Program.cs b/server/src/Docs.AppHost/Program.cs
index cf71e5b3f..884ad2c96 100644
--- a/server/src/Docs.AppHost/Program.cs
+++ b/server/src/Docs.AppHost/Program.cs
@@ -1,7 +1,7 @@
var builder = DistributedApplication.CreateBuilder(args);
// Astro dev server (for local development only)
-_ = builder.AddJavaScriptApp("astro", "../../astro")
+_ = builder.AddJavaScriptApp("astro", "../../../astro")
.WithHttpEndpoint(port: 4321, env: "PORT")
.WithExternalHttpEndpoints();
diff --git a/server/src/Docs.AppHost/Properties/launchSettings.json b/server/src/Docs.AppHost/Properties/launchSettings.json
index d685085b2..34a4475f8 100644
--- a/server/src/Docs.AppHost/Properties/launchSettings.json
+++ b/server/src/Docs.AppHost/Properties/launchSettings.json
@@ -5,26 +5,13 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
- "applicationUrl": "https://localhost:17001;http://localhost:17000",
+ "applicationUrl": "https://localhost:17001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21001",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22001"
}
- },
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "applicationUrl": "http://localhost:17000",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DOTNET_ENVIRONMENT": "Development",
- "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19001",
- "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20001",
- "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
- }
}
}
}
diff --git a/server/src/Docs.Web/Program.cs b/server/src/Docs.Web/Program.cs
index 0500bb610..4a6ab61be 100644
--- a/server/src/Docs.Web/Program.cs
+++ b/server/src/Docs.Web/Program.cs
@@ -54,11 +54,11 @@
// Add trailing slash redirect middleware (replicate nginx behavior)
app.Use(async (context, next) =>
{
- var path = context.Request.Path.Value;
-
- // If path doesn't end with slash and doesn't have a file extension, redirect with trailing slash
- if (!string.IsNullOrEmpty(path) &&
- !path.EndsWith("/") &&
+ var path = context.Request.Path.Value;
+
+ // If path doesn't end with slash and doesn't have a file extension, redirect with trailing slash
+ if (!string.IsNullOrEmpty(path) &&
+ !path.EndsWith("/") &&
!Path.HasExtension(path) &&
!path.StartsWith("/health") &&
!path.StartsWith("/alive"))
@@ -67,8 +67,8 @@
context.Response.StatusCode = 301;
context.Response.Headers.Location = $"{path}/{queryString}";
return;
- }
-
+ }
+
await next();
});
@@ -90,59 +90,59 @@
OnPrepareResponse = ctx =>
{
var path = ctx.File.Name;
- var requestPath = ctx.Context.Request.Path.Value ?? "";
-
- // Astro assets (_astro folder) - cache for 1 year
+ var requestPath = ctx.Context.Request.Path.Value ?? "";
+
+ // Astro assets (_astro folder) - cache for 1 year
if (requestPath.Contains("/_astro/"))
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=31536000, immutable";
return;
- }
-
- // JavaScript and CSS - cache for 1 year
+ }
+
+ // JavaScript and CSS - cache for 1 year
if (path.EndsWith(".js") || path.EndsWith(".css"))
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=31536000, immutable";
return;
- }
-
- // Fonts - cache for 1 year
+ }
+
+ // Fonts - cache for 1 year
if (path.EndsWith(".woff") || path.EndsWith(".woff2"))
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=31536000, immutable";
return;
- }
-
- // SVG - cache for 1 month
+ }
+
+ // SVG - cache for 1 month
if (path.EndsWith(".svg"))
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=2592000";
return;
- }
-
- // Images - cache for 1 week
- if (path.EndsWith(".png") || path.EndsWith(".jpg") ||
+ }
+
+ // Images - cache for 1 week
+ if (path.EndsWith(".png") || path.EndsWith(".jpg") ||
path.EndsWith(".jpeg") || path.EndsWith(".webp") || path.EndsWith(".ico"))
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=604800";
return;
- }
-
- // HTML - cache for 1 minute (to allow quick updates)
+ }
+
+ // HTML - cache for 1 minute (to allow quick updates)
if (path.EndsWith(".html"))
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=60";
return;
- }
-
- // JSON - cache for 1 year (except redirects.json which is internal)
+ }
+
+ // JSON - cache for 1 year (except redirects.json which is internal)
if (path.EndsWith(".json") && path != "redirects.json")
{
ctx.Context.Response.Headers.CacheControl = "public, max-age=31536000";
return;
- }
-
- // Default: no caching
+ }
+
+ // Default: no caching
ctx.Context.Response.Headers.CacheControl = "no-cache";
}
});
@@ -150,13 +150,13 @@
// Handle 404 with custom page
app.Use(async (context, next) =>
{
- await next();
-
+ await next();
+
if (context.Response.StatusCode == 404 && !context.Response.HasStarted)
{
var webHostEnvironment = context.RequestServices.GetRequiredService();
- var notFoundPath = Path.Combine(webHostEnvironment.WebRootPath, "404.html");
-
+ var notFoundPath = Path.Combine(webHostEnvironment.WebRootPath, "404.html");
+
if (File.Exists(notFoundPath))
{
context.Response.ContentType = "text/html";
@@ -169,9 +169,9 @@
app.MapFallback(async context =>
{
var webHostEnvironment = context.RequestServices.GetRequiredService();
- var path = context.Request.Path.Value?.TrimEnd('/') ?? "";
-
- // Try to find index.html in the requested directory
+ var path = context.Request.Path.Value?.TrimEnd('/') ?? "";
+
+ // Try to find index.html in the requested directory
var indexPath = Path.Combine(webHostEnvironment.WebRootPath, path.TrimStart('/'), "index.html");
if (File.Exists(indexPath))
{
@@ -179,9 +179,9 @@
context.Response.Headers.CacheControl = "public, max-age=60";
await context.Response.SendFileAsync(indexPath);
return;
- }
-
- // Return 404
+ }
+
+ // Return 404
context.Response.StatusCode = 404;
var notFoundPath = Path.Combine(webHostEnvironment.WebRootPath, "404.html");
if (File.Exists(notFoundPath))
diff --git a/server/src/Docs.Web/Properties/launchSettings.json b/server/src/Docs.Web/Properties/launchSettings.json
index 900ff92f9..78eb76a9e 100644
--- a/server/src/Docs.Web/Properties/launchSettings.json
+++ b/server/src/Docs.Web/Properties/launchSettings.json
@@ -1,15 +1,6 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "applicationUrl": "http://localhost:5000",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
"https": {
"commandName": "Project",
"dotnetRunMessages": true,