From 2aca3cf31ef2c7b375978eb54962bd7e742e5cbe Mon Sep 17 00:00:00 2001 From: Bernhard Windisch Date: Tue, 23 Jun 2026 11:44:32 +0200 Subject: [PATCH] chore: normalise line endings to LF per .gitattributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six files were committed with CRLF but .gitattributes declares `* text=auto eol=lf`, so git perpetually flagged them as modified on any checkout (noisy `git status`, blocked fast-forwards). `git add --renormalize` converts them to LF. Pure line-ending change — zero content edits (verified each file has 0 changes under `git diff -w`). Affected: .editorconfig, CODE_OF_CONDUCT.md, LICENSE, NOTICE, docs/scripts/export-md.mjs, docs/scripts/export-pdf.mjs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .editorconfig | 326 ++++++++--------- CODE_OF_CONDUCT.md | 256 ++++++------- LICENSE | 402 ++++++++++----------- NOTICE | 32 +- docs/scripts/export-md.mjs | 350 +++++++++--------- docs/scripts/export-pdf.mjs | 698 ++++++++++++++++++------------------ 6 files changed, 1032 insertions(+), 1032 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6235f134..58154565 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,163 +1,163 @@ -# EditorConfig is awesome: https://EditorConfig.org - -root = true - -[*] -charset = utf-8 -indent_style = tab -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true -end_of_line = lf - -[*.{cs,csx}] -indent_style = tab - -[*.{json,yml,yaml}] -indent_style = space -indent_size = 2 - -[*.{js,ts,jsx,tsx,html,css,scss}] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false - -# ───────────────────────────────────────────────────────────────────── -# Backend security analyzers (Roslyn / NetAnalyzers) -# ───────────────────────────────────────────────────────────────────── -# We elevate the security-relevant rule families to `error` so a -# regression breaks the build. Style/perf/naming rules are intentionally -# left at their defaults — see security-hardening.md for the campaign -# that closed the existing security findings; this configuration is the -# guardrail to keep new ones from landing silently. -# -# Coverage: -# CA21xx (general security: command/SQL injection, format-string) -# CA3xxx (data-flow taint analysis: XSS, XPath, LDAP, XML, ReDoS) -# CA53xx (cryptography & PKI: weak ciphers, weak hashes, weak random, -# cert validation, key length, padding) -# -# When a rule fires legitimately for a justified reason, suppress at -# the call site with a `#pragma` + comment explaining why — never -# globally relax the rule here. -[*.cs] -# Injection / format-string -dotnet_diagnostic.CA2100.severity = error # SQL queries built from user input -dotnet_diagnostic.CA2109.severity = error # event-handler exposed publicly -dotnet_diagnostic.CA2153.severity = error # corrupted-state exception handling -dotnet_diagnostic.CA2300.severity = error # BinaryFormatter without bind -dotnet_diagnostic.CA2301.severity = error # BinaryFormatter without bind -dotnet_diagnostic.CA2302.severity = error # BinaryFormatter without bind -dotnet_diagnostic.CA2305.severity = error # LosFormatter -dotnet_diagnostic.CA2310.severity = error # NetDataContractSerializer -dotnet_diagnostic.CA2311.severity = error # NetDataContractSerializer without bind -dotnet_diagnostic.CA2312.severity = error # NetDataContractSerializer without bind -dotnet_diagnostic.CA2315.severity = error # ObjectStateFormatter -dotnet_diagnostic.CA2321.severity = error # JavaScriptSerializer with SimpleTypeResolver -dotnet_diagnostic.CA2322.severity = error # JavaScriptSerializer ensure-not-Simple -dotnet_diagnostic.CA2326.severity = error # TypeNameHandling values other than None -dotnet_diagnostic.CA2327.severity = error # insecure JsonSerializerSettings -dotnet_diagnostic.CA2328.severity = error # insecure JsonSerializerSettings (taint) -dotnet_diagnostic.CA2329.severity = error # JsonSerializer w/ insecure settings -dotnet_diagnostic.CA2330.severity = error # JsonSerializer w/ insecure settings (taint) -dotnet_diagnostic.CA2350.severity = error # DataTable.ReadXml untrusted -dotnet_diagnostic.CA2351.severity = error # DataSet.ReadXml untrusted -dotnet_diagnostic.CA2352.severity = error # unsafe DataSet/Table in serialized type -dotnet_diagnostic.CA2353.severity = error # unsafe DataSet/Table in serializable type -dotnet_diagnostic.CA2354.severity = error # unsafe DataSet/Table in deserialized graph -dotnet_diagnostic.CA2355.severity = error # unsafe DataSet/Table in deserialized graph -dotnet_diagnostic.CA2356.severity = error # unsafe DataSet/Table in WCF type -dotnet_diagnostic.CA2361.severity = error # autogen class containing DataSet -dotnet_diagnostic.CA2362.severity = error # unsafe DataSet/Table in autogen class - -# Data-flow / taint analysis (CA3xxx) -dotnet_diagnostic.CA3001.severity = error # SQL injection -dotnet_diagnostic.CA3002.severity = error # XSS -dotnet_diagnostic.CA3003.severity = error # file-path injection -dotnet_diagnostic.CA3004.severity = error # information-exposure-through-exception -dotnet_diagnostic.CA3005.severity = error # LDAP injection -dotnet_diagnostic.CA3006.severity = error # process-command injection -dotnet_diagnostic.CA3007.severity = error # open-redirect -dotnet_diagnostic.CA3008.severity = error # XPath injection -dotnet_diagnostic.CA3009.severity = error # XML injection -dotnet_diagnostic.CA3010.severity = error # XAML injection -dotnet_diagnostic.CA3011.severity = error # DLL injection -dotnet_diagnostic.CA3012.severity = error # ReDoS (regex injection) -dotnet_diagnostic.CA3061.severity = error # XmlDocument insecure -dotnet_diagnostic.CA3075.severity = error # insecure DTD processing -dotnet_diagnostic.CA3076.severity = error # insecure XSLT script processing -dotnet_diagnostic.CA3077.severity = error # insecure XML processing in API -dotnet_diagnostic.CA3147.severity = error # missing ValidateAntiForgeryToken - -# Crypto / PKI (CA53xx) -dotnet_diagnostic.CA5350.severity = error # weak cryptographic algorithm -dotnet_diagnostic.CA5351.severity = error # broken cryptographic algorithm -dotnet_diagnostic.CA5358.severity = error # unsafe cipher mode -dotnet_diagnostic.CA5359.severity = error # certificate validation disabled -dotnet_diagnostic.CA5360.severity = error # dangerous deserialize call -dotnet_diagnostic.CA5361.severity = error # SChannel encryption disabled -dotnet_diagnostic.CA5362.severity = error # potential reference-cycle in deserialize -dotnet_diagnostic.CA5363.severity = error # request validation disabled -dotnet_diagnostic.CA5364.severity = error # deprecated security protocol -dotnet_diagnostic.CA5365.severity = error # disable HTTP-header checking -dotnet_diagnostic.CA5366.severity = error # use XmlReader for DataSet read -dotnet_diagnostic.CA5367.severity = error # mutable serializable types -dotnet_diagnostic.CA5368.severity = error # set ViewStateUserKey for AspNet -dotnet_diagnostic.CA5369.severity = error # use XmlReader for Deserialize -dotnet_diagnostic.CA5370.severity = error # use XmlReader for validating -dotnet_diagnostic.CA5371.severity = error # use XmlReader for schema-read -dotnet_diagnostic.CA5372.severity = error # use XmlReader for XPathDocument -dotnet_diagnostic.CA5373.severity = error # obsolete key derivation function -dotnet_diagnostic.CA5374.severity = error # XslTransform unsafe -dotnet_diagnostic.CA5375.severity = error # account SAS token -dotnet_diagnostic.CA5376.severity = error # SharedAccessProtocol HttpsOnly -dotnet_diagnostic.CA5377.severity = error # container-level access policy -dotnet_diagnostic.CA5378.severity = error # disable ServicePointManagerSecurityProtocols -dotnet_diagnostic.CA5379.severity = error # weak key-derivation algorithm -dotnet_diagnostic.CA5380.severity = error # do not add cert to root store -dotnet_diagnostic.CA5381.severity = error # ensure cert added to root store -dotnet_diagnostic.CA5382.severity = error # use secure cookies in ASP.NET Core -dotnet_diagnostic.CA5383.severity = error # ensure secure cookies in ASP.NET Core -dotnet_diagnostic.CA5384.severity = error # do not use DSA -dotnet_diagnostic.CA5385.severity = error # use RSA with sufficient key size -dotnet_diagnostic.CA5386.severity = error # avoid hardcoding SecurityProtocolType value -dotnet_diagnostic.CA5387.severity = error # weak iteration count for KDF -dotnet_diagnostic.CA5388.severity = error # ensure sufficient iteration count for KDF -dotnet_diagnostic.CA5389.severity = error # do not add archive item path to target -dotnet_diagnostic.CA5390.severity = error # do not hard-code encryption key -dotnet_diagnostic.CA5391.severity = error # use anti-forgery tokens in ASP.NET Core MVC -dotnet_diagnostic.CA5392.severity = error # use DefaultDllImportSearchPaths -dotnet_diagnostic.CA5393.severity = error # do not use unsafe DllImportSearchPath -dotnet_diagnostic.CA5394.severity = error # do not use insecure randomness -dotnet_diagnostic.CA5395.severity = error # missing HttpVerb attribute on action methods -dotnet_diagnostic.CA5396.severity = error # set HttpOnly to true for HttpCookie -dotnet_diagnostic.CA5397.severity = error # do not use deprecated SslProtocols values -dotnet_diagnostic.CA5398.severity = error # avoid hardcoded SslProtocols -dotnet_diagnostic.CA5399.severity = error # HttpClient with disabled cert revocation -dotnet_diagnostic.CA5400.severity = error # ensure cert revocation enabled on HttpClient -dotnet_diagnostic.CA5401.severity = error # SymmetricAlgorithm with non-default IV -dotnet_diagnostic.CA5402.severity = error # CreateEncryptor with non-default IV -dotnet_diagnostic.CA5403.severity = error # do not hard-code certificate - -# ───────────────────────────────────────────────────────────────────── -# Dead-code surfacing (Roslyn IDE analyzers) -# ───────────────────────────────────────────────────────────────────── -# These rules surface unused private members + unread fields + unused -# parameters as warnings. Not elevated to error to avoid CI failures -# on legitimate WIP — but the warnings make orphan code visible in -# review + during `dotnet build` runs (and via the `cleanup:check` -# pattern used in the frontend workspaces). -dotnet_diagnostic.IDE0051.severity = warning # unused private member -dotnet_diagnostic.IDE0052.severity = warning # read-but-never-used private member -dotnet_diagnostic.IDE0060.severity = suggestion # unused parameter (lots of public APIs satisfy interfaces, keep noisier) - -# Test projects must reproduce protocol-defined cryptographic primitives -# (TOTP via HMACSHA1 per RFC 6238, etc.) to exercise real auth flows. -# These projects don't ship in the runtime image, so the security-rule -# noise is more harmful than the rule itself. Production code paths are -# still covered by the rules above. -[{**/Modgud.Api.Tests/**.cs,**/Modgud.Tests.Unit/**.cs}] -dotnet_diagnostic.CA5350.severity = suggestion -dotnet_diagnostic.CA5351.severity = suggestion +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +[*.{cs,csx}] +indent_style = tab + +[*.{json,yml,yaml}] +indent_style = space +indent_size = 2 + +[*.{js,ts,jsx,tsx,html,css,scss}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +# ───────────────────────────────────────────────────────────────────── +# Backend security analyzers (Roslyn / NetAnalyzers) +# ───────────────────────────────────────────────────────────────────── +# We elevate the security-relevant rule families to `error` so a +# regression breaks the build. Style/perf/naming rules are intentionally +# left at their defaults — see security-hardening.md for the campaign +# that closed the existing security findings; this configuration is the +# guardrail to keep new ones from landing silently. +# +# Coverage: +# CA21xx (general security: command/SQL injection, format-string) +# CA3xxx (data-flow taint analysis: XSS, XPath, LDAP, XML, ReDoS) +# CA53xx (cryptography & PKI: weak ciphers, weak hashes, weak random, +# cert validation, key length, padding) +# +# When a rule fires legitimately for a justified reason, suppress at +# the call site with a `#pragma` + comment explaining why — never +# globally relax the rule here. +[*.cs] +# Injection / format-string +dotnet_diagnostic.CA2100.severity = error # SQL queries built from user input +dotnet_diagnostic.CA2109.severity = error # event-handler exposed publicly +dotnet_diagnostic.CA2153.severity = error # corrupted-state exception handling +dotnet_diagnostic.CA2300.severity = error # BinaryFormatter without bind +dotnet_diagnostic.CA2301.severity = error # BinaryFormatter without bind +dotnet_diagnostic.CA2302.severity = error # BinaryFormatter without bind +dotnet_diagnostic.CA2305.severity = error # LosFormatter +dotnet_diagnostic.CA2310.severity = error # NetDataContractSerializer +dotnet_diagnostic.CA2311.severity = error # NetDataContractSerializer without bind +dotnet_diagnostic.CA2312.severity = error # NetDataContractSerializer without bind +dotnet_diagnostic.CA2315.severity = error # ObjectStateFormatter +dotnet_diagnostic.CA2321.severity = error # JavaScriptSerializer with SimpleTypeResolver +dotnet_diagnostic.CA2322.severity = error # JavaScriptSerializer ensure-not-Simple +dotnet_diagnostic.CA2326.severity = error # TypeNameHandling values other than None +dotnet_diagnostic.CA2327.severity = error # insecure JsonSerializerSettings +dotnet_diagnostic.CA2328.severity = error # insecure JsonSerializerSettings (taint) +dotnet_diagnostic.CA2329.severity = error # JsonSerializer w/ insecure settings +dotnet_diagnostic.CA2330.severity = error # JsonSerializer w/ insecure settings (taint) +dotnet_diagnostic.CA2350.severity = error # DataTable.ReadXml untrusted +dotnet_diagnostic.CA2351.severity = error # DataSet.ReadXml untrusted +dotnet_diagnostic.CA2352.severity = error # unsafe DataSet/Table in serialized type +dotnet_diagnostic.CA2353.severity = error # unsafe DataSet/Table in serializable type +dotnet_diagnostic.CA2354.severity = error # unsafe DataSet/Table in deserialized graph +dotnet_diagnostic.CA2355.severity = error # unsafe DataSet/Table in deserialized graph +dotnet_diagnostic.CA2356.severity = error # unsafe DataSet/Table in WCF type +dotnet_diagnostic.CA2361.severity = error # autogen class containing DataSet +dotnet_diagnostic.CA2362.severity = error # unsafe DataSet/Table in autogen class + +# Data-flow / taint analysis (CA3xxx) +dotnet_diagnostic.CA3001.severity = error # SQL injection +dotnet_diagnostic.CA3002.severity = error # XSS +dotnet_diagnostic.CA3003.severity = error # file-path injection +dotnet_diagnostic.CA3004.severity = error # information-exposure-through-exception +dotnet_diagnostic.CA3005.severity = error # LDAP injection +dotnet_diagnostic.CA3006.severity = error # process-command injection +dotnet_diagnostic.CA3007.severity = error # open-redirect +dotnet_diagnostic.CA3008.severity = error # XPath injection +dotnet_diagnostic.CA3009.severity = error # XML injection +dotnet_diagnostic.CA3010.severity = error # XAML injection +dotnet_diagnostic.CA3011.severity = error # DLL injection +dotnet_diagnostic.CA3012.severity = error # ReDoS (regex injection) +dotnet_diagnostic.CA3061.severity = error # XmlDocument insecure +dotnet_diagnostic.CA3075.severity = error # insecure DTD processing +dotnet_diagnostic.CA3076.severity = error # insecure XSLT script processing +dotnet_diagnostic.CA3077.severity = error # insecure XML processing in API +dotnet_diagnostic.CA3147.severity = error # missing ValidateAntiForgeryToken + +# Crypto / PKI (CA53xx) +dotnet_diagnostic.CA5350.severity = error # weak cryptographic algorithm +dotnet_diagnostic.CA5351.severity = error # broken cryptographic algorithm +dotnet_diagnostic.CA5358.severity = error # unsafe cipher mode +dotnet_diagnostic.CA5359.severity = error # certificate validation disabled +dotnet_diagnostic.CA5360.severity = error # dangerous deserialize call +dotnet_diagnostic.CA5361.severity = error # SChannel encryption disabled +dotnet_diagnostic.CA5362.severity = error # potential reference-cycle in deserialize +dotnet_diagnostic.CA5363.severity = error # request validation disabled +dotnet_diagnostic.CA5364.severity = error # deprecated security protocol +dotnet_diagnostic.CA5365.severity = error # disable HTTP-header checking +dotnet_diagnostic.CA5366.severity = error # use XmlReader for DataSet read +dotnet_diagnostic.CA5367.severity = error # mutable serializable types +dotnet_diagnostic.CA5368.severity = error # set ViewStateUserKey for AspNet +dotnet_diagnostic.CA5369.severity = error # use XmlReader for Deserialize +dotnet_diagnostic.CA5370.severity = error # use XmlReader for validating +dotnet_diagnostic.CA5371.severity = error # use XmlReader for schema-read +dotnet_diagnostic.CA5372.severity = error # use XmlReader for XPathDocument +dotnet_diagnostic.CA5373.severity = error # obsolete key derivation function +dotnet_diagnostic.CA5374.severity = error # XslTransform unsafe +dotnet_diagnostic.CA5375.severity = error # account SAS token +dotnet_diagnostic.CA5376.severity = error # SharedAccessProtocol HttpsOnly +dotnet_diagnostic.CA5377.severity = error # container-level access policy +dotnet_diagnostic.CA5378.severity = error # disable ServicePointManagerSecurityProtocols +dotnet_diagnostic.CA5379.severity = error # weak key-derivation algorithm +dotnet_diagnostic.CA5380.severity = error # do not add cert to root store +dotnet_diagnostic.CA5381.severity = error # ensure cert added to root store +dotnet_diagnostic.CA5382.severity = error # use secure cookies in ASP.NET Core +dotnet_diagnostic.CA5383.severity = error # ensure secure cookies in ASP.NET Core +dotnet_diagnostic.CA5384.severity = error # do not use DSA +dotnet_diagnostic.CA5385.severity = error # use RSA with sufficient key size +dotnet_diagnostic.CA5386.severity = error # avoid hardcoding SecurityProtocolType value +dotnet_diagnostic.CA5387.severity = error # weak iteration count for KDF +dotnet_diagnostic.CA5388.severity = error # ensure sufficient iteration count for KDF +dotnet_diagnostic.CA5389.severity = error # do not add archive item path to target +dotnet_diagnostic.CA5390.severity = error # do not hard-code encryption key +dotnet_diagnostic.CA5391.severity = error # use anti-forgery tokens in ASP.NET Core MVC +dotnet_diagnostic.CA5392.severity = error # use DefaultDllImportSearchPaths +dotnet_diagnostic.CA5393.severity = error # do not use unsafe DllImportSearchPath +dotnet_diagnostic.CA5394.severity = error # do not use insecure randomness +dotnet_diagnostic.CA5395.severity = error # missing HttpVerb attribute on action methods +dotnet_diagnostic.CA5396.severity = error # set HttpOnly to true for HttpCookie +dotnet_diagnostic.CA5397.severity = error # do not use deprecated SslProtocols values +dotnet_diagnostic.CA5398.severity = error # avoid hardcoded SslProtocols +dotnet_diagnostic.CA5399.severity = error # HttpClient with disabled cert revocation +dotnet_diagnostic.CA5400.severity = error # ensure cert revocation enabled on HttpClient +dotnet_diagnostic.CA5401.severity = error # SymmetricAlgorithm with non-default IV +dotnet_diagnostic.CA5402.severity = error # CreateEncryptor with non-default IV +dotnet_diagnostic.CA5403.severity = error # do not hard-code certificate + +# ───────────────────────────────────────────────────────────────────── +# Dead-code surfacing (Roslyn IDE analyzers) +# ───────────────────────────────────────────────────────────────────── +# These rules surface unused private members + unread fields + unused +# parameters as warnings. Not elevated to error to avoid CI failures +# on legitimate WIP — but the warnings make orphan code visible in +# review + during `dotnet build` runs (and via the `cleanup:check` +# pattern used in the frontend workspaces). +dotnet_diagnostic.IDE0051.severity = warning # unused private member +dotnet_diagnostic.IDE0052.severity = warning # read-but-never-used private member +dotnet_diagnostic.IDE0060.severity = suggestion # unused parameter (lots of public APIs satisfy interfaces, keep noisier) + +# Test projects must reproduce protocol-defined cryptographic primitives +# (TOTP via HMACSHA1 per RFC 6238, etc.) to exercise real auth flows. +# These projects don't ship in the runtime image, so the security-rule +# noise is more harmful than the rule itself. Production code paths are +# still covered by the rules above. +[{**/Modgud.Api.Tests/**.cs,**/Modgud.Tests.Unit/**.cs}] +dotnet_diagnostic.CA5350.severity = suggestion +dotnet_diagnostic.CA5351.severity = suggestion diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a69814e4..521709db 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,128 +1,128 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -bwi@cocoar.dev. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +bwi@cocoar.dev. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE b/LICENSE index a7f77c8b..4997aea2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2025 COCOAR e.U. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 COCOAR e.U. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE index 29f89323..60cbc1cb 100644 --- a/NOTICE +++ b/NOTICE @@ -1,16 +1,16 @@ -Modgud -Copyright (c) 2025 COCOAR e.U. - -Authentication and authorization services for COCOAR applications -Backend API • Frontend UI • OAuth2/OIDC Support - -Licensed under the Apache License, Version 2.0 (the "License"); -You may not use this project except in compliance with the License. -You may obtain a copy at: http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -Third-Party Notices: -(None at this time) +Modgud +Copyright (c) 2025 COCOAR e.U. + +Authentication and authorization services for COCOAR applications +Backend API • Frontend UI • OAuth2/OIDC Support + +Licensed under the Apache License, Version 2.0 (the "License"); +You may not use this project except in compliance with the License. +You may obtain a copy at: http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +Third-Party Notices: +(None at this time) diff --git a/docs/scripts/export-md.mjs b/docs/scripts/export-md.mjs index d66807b0..88a017d8 100644 --- a/docs/scripts/export-md.mjs +++ b/docs/scripts/export-md.mjs @@ -1,175 +1,175 @@ -#!/usr/bin/env node - -/** - * Exports the VitePress documentation as a single Markdown file. - * - * Usage: node scripts/export-md.mjs [output-path] - * Default: ./dist/modgud-docs.md - */ - -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, '..'); -const OUTPUT = process.argv[2] || path.join(ROOT, 'dist', 'modgud-docs.md'); - -// ─── Document structure (matches VitePress sidebar) ───────────────────────── - -const structure = [ - { - part: 'Concepts', - pages: [ - { file: 'concepts/glossary.md', title: 'Glossary' }, - { file: 'concepts/realms.md', title: 'Realms' }, - { file: 'concepts/authentication.md', title: 'Authentication Model' }, - { file: 'concepts/oauth.md', title: 'OAuth & OIDC' }, - { file: 'concepts/tokens.md', title: 'Tokens & Sessions' }, - ], - }, - { - part: 'User Guide', - pages: [ - { file: 'user-guide/first-setup.md', title: 'First-Time Setup' }, - { file: 'user-guide/realms.md', title: 'Managing Realms' }, - { file: 'user-guide/realm-setup.md', title: 'Realm Setup Flow' }, - { file: 'user-guide/users.md', title: 'Managing Users' }, - { file: 'user-guide/roles.md', title: 'Managing Roles' }, - { file: 'user-guide/clients.md', title: 'Registering Clients' }, - { file: 'user-guide/scopes.md', title: 'Scopes & Permissions' }, - { file: 'user-guide/api-resources.md', title: 'APIs' }, - { file: 'user-guide/client-flows.md', title: 'Client Flows' }, - { file: 'user-guide/two-factor.md', title: 'Two-Factor Authentication' }, - { file: 'user-guide/sessions.md', title: 'Session Management' }, - ], - }, - { - part: 'Developer Guide', - pages: [ - { file: 'guide/developing-locally.md', title: 'Developing locally' }, - { file: 'guide/architecture.md', title: 'Clean Architecture' }, - { file: 'guide/cqrs-event-sourcing.md', title: 'CQRS & Event Sourcing' }, - { file: 'guide/realms.md', title: 'Multi-Tenancy / Realms' }, - { file: 'guide/auth-cookies.md', title: 'Cookie-Based Auth' }, - { file: 'guide/two-factor.md', title: 'Two-Factor Authentication' }, - { file: 'guide/oauth.md', title: 'OAuth / OpenID Connect' }, - { file: 'guide/frontend.md', title: 'Vue Frontend' }, - { file: 'guide/frontend-realms.md', title: 'Realm-Aware SPA' }, - { file: 'guide/deployment.md', title: 'Docker & Deployment' }, - { file: 'guide/database.md', title: 'Database & Migrations' }, - ], - }, - { - part: 'API Reference', - pages: [ - { file: 'reference/auth-api.md', title: 'Auth Endpoints' }, - { file: 'reference/admin-api.md', title: 'Admin Endpoints' }, - { file: 'reference/realm-api.md', title: 'Realm Endpoints' }, - { file: 'reference/oauth-api.md', title: 'OAuth Endpoints' }, - ], - }, -]; - -// ─── Pre-processing ───────────────────────────────────────────────────────── - -function stripFrontmatter(content) { - return content.replace(/^---[\s\S]*?---\n*/, ''); -} - -function convertContainers(content) { - const lines = content.split('\n'); - const result = []; - const stack = []; - - for (const line of lines) { - const openMatch = line.match(/^::: (\w+)\s*(.*)$/); - if (openMatch) { - const type = openMatch[1]; - const label = openMatch[2]?.trim() || - { tip: 'Tip', warning: 'Warning', info: 'Info', danger: 'Danger', details: 'Details' }[type] || - type.charAt(0).toUpperCase() + type.slice(1); - stack.push(type); - result.push(`> **${label}**`); - result.push('>'); - continue; - } - if (line.trim() === ':::' && stack.length > 0) { - stack.pop(); - result.push(''); - continue; - } - if (stack.length > 0) { - result.push(line ? `> ${line}` : '>'); - } else { - result.push(line); - } - } - - return result.join('\n'); -} - -function processPage(filePath) { - let content = fs.readFileSync(path.join(ROOT, filePath), 'utf-8'); - content = stripFrontmatter(content); - content = convertContainers(content); - return content.trim(); -} - -// ─── Document assembly ────────────────────────────────────────────────────── - -function buildDocument() { - const parts = []; - - // Header - parts.push(`# Modgud Documentation`); - parts.push(''); - parts.push(`*Multi-tenant Identity Provider — ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' })}*`); - parts.push(''); - parts.push('---'); - parts.push(''); - - // Table of contents - parts.push('## Table of Contents'); - parts.push(''); - for (const section of structure) { - parts.push(`**${section.part}**`); - for (const page of section.pages) { - parts.push(`- ${page.title}`); - } - parts.push(''); - } - parts.push('---'); - parts.push(''); - - // Content - for (const section of structure) { - parts.push(`# ${section.part}`); - parts.push(''); - - for (const page of section.pages) { - const content = processPage(page.file); - parts.push(content); - parts.push(''); - parts.push('---'); - parts.push(''); - } - } - - return parts.join('\n'); -} - -// ─── Main ─────────────────────────────────────────────────────────────────── - -const pageCount = structure.reduce((sum, s) => sum + s.pages.length, 0); -console.log(`Exporting Modgud documentation to Markdown...\n`); -console.log(` Sections: ${structure.length}`); -console.log(` Pages: ${pageCount}`); - -const doc = buildDocument(); - -fs.mkdirSync(path.dirname(OUTPUT), { recursive: true }); -fs.writeFileSync(OUTPUT, doc, 'utf-8'); - -const sizeKb = (Buffer.byteLength(doc, 'utf-8') / 1024).toFixed(0); -console.log(`\n Output: ${OUTPUT} (${sizeKb} KB)\n`); +#!/usr/bin/env node + +/** + * Exports the VitePress documentation as a single Markdown file. + * + * Usage: node scripts/export-md.mjs [output-path] + * Default: ./dist/modgud-docs.md + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const OUTPUT = process.argv[2] || path.join(ROOT, 'dist', 'modgud-docs.md'); + +// ─── Document structure (matches VitePress sidebar) ───────────────────────── + +const structure = [ + { + part: 'Concepts', + pages: [ + { file: 'concepts/glossary.md', title: 'Glossary' }, + { file: 'concepts/realms.md', title: 'Realms' }, + { file: 'concepts/authentication.md', title: 'Authentication Model' }, + { file: 'concepts/oauth.md', title: 'OAuth & OIDC' }, + { file: 'concepts/tokens.md', title: 'Tokens & Sessions' }, + ], + }, + { + part: 'User Guide', + pages: [ + { file: 'user-guide/first-setup.md', title: 'First-Time Setup' }, + { file: 'user-guide/realms.md', title: 'Managing Realms' }, + { file: 'user-guide/realm-setup.md', title: 'Realm Setup Flow' }, + { file: 'user-guide/users.md', title: 'Managing Users' }, + { file: 'user-guide/roles.md', title: 'Managing Roles' }, + { file: 'user-guide/clients.md', title: 'Registering Clients' }, + { file: 'user-guide/scopes.md', title: 'Scopes & Permissions' }, + { file: 'user-guide/api-resources.md', title: 'APIs' }, + { file: 'user-guide/client-flows.md', title: 'Client Flows' }, + { file: 'user-guide/two-factor.md', title: 'Two-Factor Authentication' }, + { file: 'user-guide/sessions.md', title: 'Session Management' }, + ], + }, + { + part: 'Developer Guide', + pages: [ + { file: 'guide/developing-locally.md', title: 'Developing locally' }, + { file: 'guide/architecture.md', title: 'Clean Architecture' }, + { file: 'guide/cqrs-event-sourcing.md', title: 'CQRS & Event Sourcing' }, + { file: 'guide/realms.md', title: 'Multi-Tenancy / Realms' }, + { file: 'guide/auth-cookies.md', title: 'Cookie-Based Auth' }, + { file: 'guide/two-factor.md', title: 'Two-Factor Authentication' }, + { file: 'guide/oauth.md', title: 'OAuth / OpenID Connect' }, + { file: 'guide/frontend.md', title: 'Vue Frontend' }, + { file: 'guide/frontend-realms.md', title: 'Realm-Aware SPA' }, + { file: 'guide/deployment.md', title: 'Docker & Deployment' }, + { file: 'guide/database.md', title: 'Database & Migrations' }, + ], + }, + { + part: 'API Reference', + pages: [ + { file: 'reference/auth-api.md', title: 'Auth Endpoints' }, + { file: 'reference/admin-api.md', title: 'Admin Endpoints' }, + { file: 'reference/realm-api.md', title: 'Realm Endpoints' }, + { file: 'reference/oauth-api.md', title: 'OAuth Endpoints' }, + ], + }, +]; + +// ─── Pre-processing ───────────────────────────────────────────────────────── + +function stripFrontmatter(content) { + return content.replace(/^---[\s\S]*?---\n*/, ''); +} + +function convertContainers(content) { + const lines = content.split('\n'); + const result = []; + const stack = []; + + for (const line of lines) { + const openMatch = line.match(/^::: (\w+)\s*(.*)$/); + if (openMatch) { + const type = openMatch[1]; + const label = openMatch[2]?.trim() || + { tip: 'Tip', warning: 'Warning', info: 'Info', danger: 'Danger', details: 'Details' }[type] || + type.charAt(0).toUpperCase() + type.slice(1); + stack.push(type); + result.push(`> **${label}**`); + result.push('>'); + continue; + } + if (line.trim() === ':::' && stack.length > 0) { + stack.pop(); + result.push(''); + continue; + } + if (stack.length > 0) { + result.push(line ? `> ${line}` : '>'); + } else { + result.push(line); + } + } + + return result.join('\n'); +} + +function processPage(filePath) { + let content = fs.readFileSync(path.join(ROOT, filePath), 'utf-8'); + content = stripFrontmatter(content); + content = convertContainers(content); + return content.trim(); +} + +// ─── Document assembly ────────────────────────────────────────────────────── + +function buildDocument() { + const parts = []; + + // Header + parts.push(`# Modgud Documentation`); + parts.push(''); + parts.push(`*Multi-tenant Identity Provider — ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' })}*`); + parts.push(''); + parts.push('---'); + parts.push(''); + + // Table of contents + parts.push('## Table of Contents'); + parts.push(''); + for (const section of structure) { + parts.push(`**${section.part}**`); + for (const page of section.pages) { + parts.push(`- ${page.title}`); + } + parts.push(''); + } + parts.push('---'); + parts.push(''); + + // Content + for (const section of structure) { + parts.push(`# ${section.part}`); + parts.push(''); + + for (const page of section.pages) { + const content = processPage(page.file); + parts.push(content); + parts.push(''); + parts.push('---'); + parts.push(''); + } + } + + return parts.join('\n'); +} + +// ─── Main ─────────────────────────────────────────────────────────────────── + +const pageCount = structure.reduce((sum, s) => sum + s.pages.length, 0); +console.log(`Exporting Modgud documentation to Markdown...\n`); +console.log(` Sections: ${structure.length}`); +console.log(` Pages: ${pageCount}`); + +const doc = buildDocument(); + +fs.mkdirSync(path.dirname(OUTPUT), { recursive: true }); +fs.writeFileSync(OUTPUT, doc, 'utf-8'); + +const sizeKb = (Buffer.byteLength(doc, 'utf-8') / 1024).toFixed(0); +console.log(`\n Output: ${OUTPUT} (${sizeKb} KB)\n`); diff --git a/docs/scripts/export-pdf.mjs b/docs/scripts/export-pdf.mjs index 751e5e7e..bb0df6e9 100644 --- a/docs/scripts/export-pdf.mjs +++ b/docs/scripts/export-pdf.mjs @@ -1,349 +1,349 @@ -#!/usr/bin/env node - -/** - * Exports the VitePress documentation as a single, professionally-formatted PDF. - * - * Usage: node scripts/export-pdf.mjs [output-path] - * Default: ./dist/modgud-docs.pdf - */ - -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import MarkdownIt from 'markdown-it'; -import hljs from 'highlight.js'; -import puppeteer from 'puppeteer-core'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(__dirname, '..'); -const OUTPUT = process.argv[2] || path.join(ROOT, 'dist', 'modgud-docs.pdf'); - -// ─── Document structure (matches VitePress sidebar) ───────────────────────── - -const structure = [ - { - part: 'Concepts', - pages: [ - { file: 'concepts/glossary.md', title: 'Glossary' }, - { file: 'concepts/realms.md', title: 'Realms' }, - { file: 'concepts/authentication.md', title: 'Authentication Model' }, - { file: 'concepts/oauth.md', title: 'OAuth & OIDC' }, - { file: 'concepts/tokens.md', title: 'Tokens & Sessions' }, - ], - }, - { - part: 'User Guide', - pages: [ - { file: 'user-guide/first-setup.md', title: 'First-Time Setup' }, - { file: 'user-guide/realms.md', title: 'Managing Realms' }, - { file: 'user-guide/realm-setup.md', title: 'Realm Setup Flow' }, - { file: 'user-guide/users.md', title: 'Managing Users' }, - { file: 'user-guide/roles.md', title: 'Managing Roles' }, - { file: 'user-guide/clients.md', title: 'Registering Clients' }, - { file: 'user-guide/scopes.md', title: 'Scopes & Permissions' }, - { file: 'user-guide/api-resources.md', title: 'APIs' }, - { file: 'user-guide/client-flows.md', title: 'Client Flows' }, - { file: 'user-guide/two-factor.md', title: 'Two-Factor Authentication' }, - { file: 'user-guide/sessions.md', title: 'Session Management' }, - ], - }, - { - part: 'Developer Guide', - pages: [ - { file: 'guide/developing-locally.md', title: 'Developing locally' }, - { file: 'guide/architecture.md', title: 'Clean Architecture' }, - { file: 'guide/cqrs-event-sourcing.md', title: 'CQRS & Event Sourcing' }, - { file: 'guide/realms.md', title: 'Multi-Tenancy / Realms' }, - { file: 'guide/auth-cookies.md', title: 'Cookie-Based Auth' }, - { file: 'guide/two-factor.md', title: 'Two-Factor Authentication' }, - { file: 'guide/oauth.md', title: 'OAuth / OpenID Connect' }, - { file: 'guide/frontend.md', title: 'Vue Frontend' }, - { file: 'guide/frontend-realms.md', title: 'Realm-Aware SPA' }, - { file: 'guide/deployment.md', title: 'Docker & Deployment' }, - { file: 'guide/database.md', title: 'Database & Migrations' }, - ], - }, - { - part: 'API Reference', - pages: [ - { file: 'reference/auth-api.md', title: 'Auth Endpoints' }, - { file: 'reference/admin-api.md', title: 'Admin Endpoints' }, - { file: 'reference/realm-api.md', title: 'Realm Endpoints' }, - { file: 'reference/oauth-api.md', title: 'OAuth Endpoints' }, - ], - }, -]; - -// ─── Markdown setup ───────────────────────────────────────────────────────── - -const md = new MarkdownIt({ - html: true, - linkify: true, - typographer: true, - highlight(str, lang) { - if (lang && hljs.getLanguage(lang)) { - try { - return `
${hljs.highlight(str, { language: lang }).value}
`; - } catch (_) { /* fall through */ } - } - return `
${md.utils.escapeHtml(str)}
`; - }, -}); - -// ─── Markdown pre-processing ──────────────────────────────────────────────── - -function slugify(text) { - return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); -} - -function stripFrontmatter(content) { - return content.replace(/^---[\s\S]*?---\n*/, ''); -} - -function convertContainers(content) { - const lines = content.split('\n'); - const result = []; - const stack = []; - - for (const line of lines) { - const openMatch = line.match(/^::: (\w+)\s*(.*)$/); - if (openMatch) { - const type = openMatch[1]; - const title = openMatch[2]?.trim() || - { tip: 'Tip', warning: 'Warning', info: 'Info', danger: 'Danger', details: 'Details' }[type] || - type.charAt(0).toUpperCase() + type.slice(1); - stack.push(type); - result.push(`

${title}

`); - result.push(''); - continue; - } - if (line.trim() === ':::' && stack.length > 0) { - stack.pop(); - result.push(''); - result.push('
'); - continue; - } - result.push(line); - } - - return result.join('\n'); -} - -function rewriteInternalLinks(content) { - return content.replace(/\[([^\]]+)\]\(\/([^)#]+)(#[^)]+)?\)/g, (_, text, linkPath, anchor) => { - const slug = slugify(linkPath.replace(/\//g, '-').replace(/\.md$/, '')); - return `[${text}](#${slug}${anchor || ''})`; - }); -} - -function processMarkdown(filePath) { - let content = fs.readFileSync(path.join(ROOT, filePath), 'utf-8'); - content = stripFrontmatter(content); - content = convertContainers(content); - content = rewriteInternalLinks(content); - return content; -} - -// ─── HTML generation ──────────────────────────────────────────────────────── - -function generateCoverPage() { - return ` -
-
-
Documentation
-

Modgud

-

Multi-tenant Identity Provider

-
- ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' })} -
-
- -
`; -} - -function generateToc() { - let html = '

Table of Contents

'; - for (const section of structure) { - html += `
${section.part}
`; - html += '
    '; - for (const page of section.pages) { - const slug = slugify(page.file.replace(/\//g, '-').replace(/\.md$/, '')); - html += `
  • ${page.title}
  • `; - } - html += '
'; - } - html += '
'; - return html; -} - -function generateBody() { - let html = ''; - for (const section of structure) { - html += `
${section.part}
`; - for (const page of section.pages) { - const slug = slugify(page.file.replace(/\//g, '-').replace(/\.md$/, '')); - const content = processMarkdown(page.file); - const rendered = md.render(content); - html += `
${rendered}
`; - } - } - return html; -} - -// ─── CSS ──────────────────────────────────────────────────────────────────── - -const CSS = ` -@page { size: A4; margin: 22mm 18mm 22mm 18mm; } -* { box-sizing: border-box; } -html { -webkit-print-color-adjust: exact; print-color-adjust: exact; } -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - font-size: 9.5pt; line-height: 1.65; color: #1a1a2e; margin: 0; padding: 0; -} -.cover { page-break-after: always; display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 100vh; text-align: center; position: relative; } -.cover-content { margin-top: -80px; } -.cover-badge { display: inline-block; font-size: 10pt; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: #5672cd; border: 2px solid #5672cd; border-radius: 4px; padding: 4px 16px; margin-bottom: 24px; } -.cover-title { font-size: 36pt; font-weight: 700; color: #1a1a2e; margin: 0 0 12px 0; letter-spacing: -0.5px; } -.cover-subtitle { font-size: 14pt; color: #64748b; font-weight: 400; margin: 0 0 32px 0; } -.cover-meta { font-size: 11pt; color: #94a3b8; } -.cover-footer { position: absolute; bottom: 0; font-size: 8.5pt; color: #94a3b8; } -.toc { page-break-after: always; } -.toc-title { font-size: 22pt; font-weight: 700; color: #1a1a2e; margin: 0 0 28px 0; padding-bottom: 12px; border-bottom: 2px solid #e2e8f0; } -.toc-part { font-size: 10.5pt; font-weight: 700; color: #5672cd; text-transform: uppercase; letter-spacing: 1px; margin: 20px 0 6px 0; } -.toc-list { list-style: none; padding: 0; margin: 0; } -.toc-list li { margin: 0; padding: 4px 0 4px 16px; border-bottom: 1px dotted #e2e8f0; } -.toc-list a { color: #1a1a2e; text-decoration: none; font-size: 9.5pt; } -.part-divider { page-break-before: always; display: flex; align-items: center; justify-content: center; min-height: 35vh; text-align: center; } -.part-divider span { font-size: 28pt; font-weight: 700; color: #1a1a2e; letter-spacing: -0.3px; position: relative; } -.part-divider span::after { content: ''; display: block; width: 60px; height: 3px; background: #5672cd; margin: 16px auto 0; border-radius: 2px; } -.chapter { page-break-before: always; } -h1 { font-size: 20pt; font-weight: 700; color: #1a1a2e; margin: 0 0 16px 0; padding-bottom: 8px; border-bottom: 2px solid #e2e8f0; } -h2 { font-size: 14pt; font-weight: 700; color: #1a1a2e; margin: 28px 0 10px 0; padding-bottom: 5px; border-bottom: 1px solid #f1f5f9; } -h3 { font-size: 11.5pt; font-weight: 600; color: #334155; margin: 22px 0 8px 0; } -h4 { font-size: 10pt; font-weight: 600; color: #475569; margin: 16px 0 6px 0; } -p { margin: 8px 0; orphans: 3; widows: 3; } -a { color: #5672cd; text-decoration: none; } -code { font-family: 'Cascadia Code', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 8.5pt; background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 3px; padding: 1px 4px; } -pre.hljs { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 14px 16px; margin: 12px 0; overflow-x: auto; break-inside: avoid; } -pre.hljs code { background: none; border: none; padding: 0; font-size: 8pt; line-height: 1.55; color: #1e293b; } -.hljs-keyword { color: #8250df; font-weight: 600; } .hljs-built_in { color: #8250df; } .hljs-type { color: #0550ae; } .hljs-title { color: #0550ae; } .hljs-title.class_ { color: #0550ae; } .hljs-title.function_ { color: #6639ba; } .hljs-string { color: #0a3069; } .hljs-number { color: #0550ae; } .hljs-literal { color: #0550ae; } .hljs-comment { color: #6e7781; font-style: italic; } .hljs-attr { color: #0550ae; } .hljs-attribute { color: #0550ae; } .hljs-meta { color: #6e7781; } .hljs-params { color: #24292f; } .hljs-property { color: #0550ae; } .hljs-variable { color: #953800; } -table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 8.5pt; break-inside: avoid; } -th { background: #f1f5f9; font-weight: 600; text-align: left; padding: 8px 10px; border: 1px solid #e2e8f0; } -td { padding: 7px 10px; border: 1px solid #e2e8f0; vertical-align: top; } -tr:nth-child(even) td { background: #f8fafc; } -ul, ol { margin: 8px 0; padding-left: 24px; } li { margin: 3px 0; } li > p { margin: 2px 0; } -blockquote { border-left: 3px solid #e2e8f0; margin: 12px 0; padding: 4px 16px; color: #64748b; } -hr { border: none; border-top: 1px solid #e2e8f0; margin: 24px 0; } -.callout { border-left: 4px solid; border-radius: 0 6px 6px 0; padding: 12px 16px; margin: 14px 0; break-inside: avoid; } -.callout p { margin: 4px 0; } -.callout-title { font-weight: 700; font-size: 9pt; text-transform: uppercase; letter-spacing: 0.5px; margin: 0 0 6px 0 !important; } -.callout-tip { border-color: #10b981; background: #ecfdf5; } .callout-tip .callout-title { color: #059669; } -.callout-warning { border-color: #f59e0b; background: #fffbeb; } .callout-warning .callout-title { color: #d97706; } -.callout-info { border-color: #3b82f6; background: #eff6ff; } .callout-info .callout-title { color: #2563eb; } -.callout-danger { border-color: #ef4444; background: #fef2f2; } .callout-danger .callout-title { color: #dc2626; } -img { max-width: 100%; height: auto; } -strong { font-weight: 600; } -h1, h2, h3, h4 { page-break-after: avoid; } -pre, table, .callout { page-break-inside: avoid; } -`; - -// ─── HTML assembly ────────────────────────────────────────────────────────── - -function buildHtml() { - const cover = generateCoverPage(); - const toc = generateToc(); - const body = generateBody(); - - return ` - - - - Modgud Documentation - - - - ${cover} - ${toc} - ${body} - -`; -} - -// ─── PDF generation ───────────────────────────────────────────────────────── - -function findChrome() { - const candidates = [ - process.env.CHROME_PATH, - 'C:/Program Files/Google/Chrome/Application/chrome.exe', - 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe', - '/usr/bin/google-chrome', - '/usr/bin/chromium-browser', - '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', - ].filter(Boolean); - - for (const p of candidates) { - if (fs.existsSync(p)) return p; - } - throw new Error('Chrome not found. Set CHROME_PATH environment variable.'); -} - -async function generatePdf(html) { - const chromePath = findChrome(); - console.log(` Chrome: ${chromePath}`); - - const browser = await puppeteer.launch({ - executablePath: chromePath, - headless: true, - args: ['--no-sandbox', '--disable-setuid-sandbox'], - }); - - const page = await browser.newPage(); - await page.setContent(html, { waitUntil: 'networkidle0' }); - - fs.mkdirSync(path.dirname(OUTPUT), { recursive: true }); - - await page.pdf({ - path: OUTPUT, - format: 'A4', - margin: { top: '22mm', right: '18mm', bottom: '22mm', left: '18mm' }, - printBackground: true, - displayHeaderFooter: true, - headerTemplate: '', - footerTemplate: ` -
- Modgud - · - / -
`, - }); - - await browser.close(); - return OUTPUT; -} - -// ─── Main ─────────────────────────────────────────────────────────────────── - -async function main() { - console.log('Exporting Modgud documentation to PDF...\n'); - - const pageCount = structure.reduce((sum, s) => sum + s.pages.length, 0); - console.log(` Sections: ${structure.length}`); - console.log(` Pages: ${pageCount}`); - - console.log('\n Building HTML...'); - const html = buildHtml(); - - console.log(' Generating PDF...'); - const outputPath = await generatePdf(html); - - const stats = fs.statSync(outputPath); - const sizeMb = (stats.size / 1024 / 1024).toFixed(1); - console.log(`\n Output: ${outputPath} (${sizeMb} MB)\n`); -} - -main().catch((err) => { - console.error('\nExport failed:', err.message); - process.exit(1); -}); +#!/usr/bin/env node + +/** + * Exports the VitePress documentation as a single, professionally-formatted PDF. + * + * Usage: node scripts/export-pdf.mjs [output-path] + * Default: ./dist/modgud-docs.pdf + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import MarkdownIt from 'markdown-it'; +import hljs from 'highlight.js'; +import puppeteer from 'puppeteer-core'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const OUTPUT = process.argv[2] || path.join(ROOT, 'dist', 'modgud-docs.pdf'); + +// ─── Document structure (matches VitePress sidebar) ───────────────────────── + +const structure = [ + { + part: 'Concepts', + pages: [ + { file: 'concepts/glossary.md', title: 'Glossary' }, + { file: 'concepts/realms.md', title: 'Realms' }, + { file: 'concepts/authentication.md', title: 'Authentication Model' }, + { file: 'concepts/oauth.md', title: 'OAuth & OIDC' }, + { file: 'concepts/tokens.md', title: 'Tokens & Sessions' }, + ], + }, + { + part: 'User Guide', + pages: [ + { file: 'user-guide/first-setup.md', title: 'First-Time Setup' }, + { file: 'user-guide/realms.md', title: 'Managing Realms' }, + { file: 'user-guide/realm-setup.md', title: 'Realm Setup Flow' }, + { file: 'user-guide/users.md', title: 'Managing Users' }, + { file: 'user-guide/roles.md', title: 'Managing Roles' }, + { file: 'user-guide/clients.md', title: 'Registering Clients' }, + { file: 'user-guide/scopes.md', title: 'Scopes & Permissions' }, + { file: 'user-guide/api-resources.md', title: 'APIs' }, + { file: 'user-guide/client-flows.md', title: 'Client Flows' }, + { file: 'user-guide/two-factor.md', title: 'Two-Factor Authentication' }, + { file: 'user-guide/sessions.md', title: 'Session Management' }, + ], + }, + { + part: 'Developer Guide', + pages: [ + { file: 'guide/developing-locally.md', title: 'Developing locally' }, + { file: 'guide/architecture.md', title: 'Clean Architecture' }, + { file: 'guide/cqrs-event-sourcing.md', title: 'CQRS & Event Sourcing' }, + { file: 'guide/realms.md', title: 'Multi-Tenancy / Realms' }, + { file: 'guide/auth-cookies.md', title: 'Cookie-Based Auth' }, + { file: 'guide/two-factor.md', title: 'Two-Factor Authentication' }, + { file: 'guide/oauth.md', title: 'OAuth / OpenID Connect' }, + { file: 'guide/frontend.md', title: 'Vue Frontend' }, + { file: 'guide/frontend-realms.md', title: 'Realm-Aware SPA' }, + { file: 'guide/deployment.md', title: 'Docker & Deployment' }, + { file: 'guide/database.md', title: 'Database & Migrations' }, + ], + }, + { + part: 'API Reference', + pages: [ + { file: 'reference/auth-api.md', title: 'Auth Endpoints' }, + { file: 'reference/admin-api.md', title: 'Admin Endpoints' }, + { file: 'reference/realm-api.md', title: 'Realm Endpoints' }, + { file: 'reference/oauth-api.md', title: 'OAuth Endpoints' }, + ], + }, +]; + +// ─── Markdown setup ───────────────────────────────────────────────────────── + +const md = new MarkdownIt({ + html: true, + linkify: true, + typographer: true, + highlight(str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return `
${hljs.highlight(str, { language: lang }).value}
`; + } catch (_) { /* fall through */ } + } + return `
${md.utils.escapeHtml(str)}
`; + }, +}); + +// ─── Markdown pre-processing ──────────────────────────────────────────────── + +function slugify(text) { + return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); +} + +function stripFrontmatter(content) { + return content.replace(/^---[\s\S]*?---\n*/, ''); +} + +function convertContainers(content) { + const lines = content.split('\n'); + const result = []; + const stack = []; + + for (const line of lines) { + const openMatch = line.match(/^::: (\w+)\s*(.*)$/); + if (openMatch) { + const type = openMatch[1]; + const title = openMatch[2]?.trim() || + { tip: 'Tip', warning: 'Warning', info: 'Info', danger: 'Danger', details: 'Details' }[type] || + type.charAt(0).toUpperCase() + type.slice(1); + stack.push(type); + result.push(`

${title}

`); + result.push(''); + continue; + } + if (line.trim() === ':::' && stack.length > 0) { + stack.pop(); + result.push(''); + result.push('
'); + continue; + } + result.push(line); + } + + return result.join('\n'); +} + +function rewriteInternalLinks(content) { + return content.replace(/\[([^\]]+)\]\(\/([^)#]+)(#[^)]+)?\)/g, (_, text, linkPath, anchor) => { + const slug = slugify(linkPath.replace(/\//g, '-').replace(/\.md$/, '')); + return `[${text}](#${slug}${anchor || ''})`; + }); +} + +function processMarkdown(filePath) { + let content = fs.readFileSync(path.join(ROOT, filePath), 'utf-8'); + content = stripFrontmatter(content); + content = convertContainers(content); + content = rewriteInternalLinks(content); + return content; +} + +// ─── HTML generation ──────────────────────────────────────────────────────── + +function generateCoverPage() { + return ` +
+
+
Documentation
+

Modgud

+

Multi-tenant Identity Provider

+
+ ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long' })} +
+
+ +
`; +} + +function generateToc() { + let html = '

Table of Contents

'; + for (const section of structure) { + html += `
${section.part}
`; + html += '
    '; + for (const page of section.pages) { + const slug = slugify(page.file.replace(/\//g, '-').replace(/\.md$/, '')); + html += `
  • ${page.title}
  • `; + } + html += '
'; + } + html += '
'; + return html; +} + +function generateBody() { + let html = ''; + for (const section of structure) { + html += `
${section.part}
`; + for (const page of section.pages) { + const slug = slugify(page.file.replace(/\//g, '-').replace(/\.md$/, '')); + const content = processMarkdown(page.file); + const rendered = md.render(content); + html += `
${rendered}
`; + } + } + return html; +} + +// ─── CSS ──────────────────────────────────────────────────────────────────── + +const CSS = ` +@page { size: A4; margin: 22mm 18mm 22mm 18mm; } +* { box-sizing: border-box; } +html { -webkit-print-color-adjust: exact; print-color-adjust: exact; } +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + font-size: 9.5pt; line-height: 1.65; color: #1a1a2e; margin: 0; padding: 0; +} +.cover { page-break-after: always; display: flex; flex-direction: column; justify-content: center; align-items: center; min-height: 100vh; text-align: center; position: relative; } +.cover-content { margin-top: -80px; } +.cover-badge { display: inline-block; font-size: 10pt; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: #5672cd; border: 2px solid #5672cd; border-radius: 4px; padding: 4px 16px; margin-bottom: 24px; } +.cover-title { font-size: 36pt; font-weight: 700; color: #1a1a2e; margin: 0 0 12px 0; letter-spacing: -0.5px; } +.cover-subtitle { font-size: 14pt; color: #64748b; font-weight: 400; margin: 0 0 32px 0; } +.cover-meta { font-size: 11pt; color: #94a3b8; } +.cover-footer { position: absolute; bottom: 0; font-size: 8.5pt; color: #94a3b8; } +.toc { page-break-after: always; } +.toc-title { font-size: 22pt; font-weight: 700; color: #1a1a2e; margin: 0 0 28px 0; padding-bottom: 12px; border-bottom: 2px solid #e2e8f0; } +.toc-part { font-size: 10.5pt; font-weight: 700; color: #5672cd; text-transform: uppercase; letter-spacing: 1px; margin: 20px 0 6px 0; } +.toc-list { list-style: none; padding: 0; margin: 0; } +.toc-list li { margin: 0; padding: 4px 0 4px 16px; border-bottom: 1px dotted #e2e8f0; } +.toc-list a { color: #1a1a2e; text-decoration: none; font-size: 9.5pt; } +.part-divider { page-break-before: always; display: flex; align-items: center; justify-content: center; min-height: 35vh; text-align: center; } +.part-divider span { font-size: 28pt; font-weight: 700; color: #1a1a2e; letter-spacing: -0.3px; position: relative; } +.part-divider span::after { content: ''; display: block; width: 60px; height: 3px; background: #5672cd; margin: 16px auto 0; border-radius: 2px; } +.chapter { page-break-before: always; } +h1 { font-size: 20pt; font-weight: 700; color: #1a1a2e; margin: 0 0 16px 0; padding-bottom: 8px; border-bottom: 2px solid #e2e8f0; } +h2 { font-size: 14pt; font-weight: 700; color: #1a1a2e; margin: 28px 0 10px 0; padding-bottom: 5px; border-bottom: 1px solid #f1f5f9; } +h3 { font-size: 11.5pt; font-weight: 600; color: #334155; margin: 22px 0 8px 0; } +h4 { font-size: 10pt; font-weight: 600; color: #475569; margin: 16px 0 6px 0; } +p { margin: 8px 0; orphans: 3; widows: 3; } +a { color: #5672cd; text-decoration: none; } +code { font-family: 'Cascadia Code', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 8.5pt; background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 3px; padding: 1px 4px; } +pre.hljs { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 14px 16px; margin: 12px 0; overflow-x: auto; break-inside: avoid; } +pre.hljs code { background: none; border: none; padding: 0; font-size: 8pt; line-height: 1.55; color: #1e293b; } +.hljs-keyword { color: #8250df; font-weight: 600; } .hljs-built_in { color: #8250df; } .hljs-type { color: #0550ae; } .hljs-title { color: #0550ae; } .hljs-title.class_ { color: #0550ae; } .hljs-title.function_ { color: #6639ba; } .hljs-string { color: #0a3069; } .hljs-number { color: #0550ae; } .hljs-literal { color: #0550ae; } .hljs-comment { color: #6e7781; font-style: italic; } .hljs-attr { color: #0550ae; } .hljs-attribute { color: #0550ae; } .hljs-meta { color: #6e7781; } .hljs-params { color: #24292f; } .hljs-property { color: #0550ae; } .hljs-variable { color: #953800; } +table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 8.5pt; break-inside: avoid; } +th { background: #f1f5f9; font-weight: 600; text-align: left; padding: 8px 10px; border: 1px solid #e2e8f0; } +td { padding: 7px 10px; border: 1px solid #e2e8f0; vertical-align: top; } +tr:nth-child(even) td { background: #f8fafc; } +ul, ol { margin: 8px 0; padding-left: 24px; } li { margin: 3px 0; } li > p { margin: 2px 0; } +blockquote { border-left: 3px solid #e2e8f0; margin: 12px 0; padding: 4px 16px; color: #64748b; } +hr { border: none; border-top: 1px solid #e2e8f0; margin: 24px 0; } +.callout { border-left: 4px solid; border-radius: 0 6px 6px 0; padding: 12px 16px; margin: 14px 0; break-inside: avoid; } +.callout p { margin: 4px 0; } +.callout-title { font-weight: 700; font-size: 9pt; text-transform: uppercase; letter-spacing: 0.5px; margin: 0 0 6px 0 !important; } +.callout-tip { border-color: #10b981; background: #ecfdf5; } .callout-tip .callout-title { color: #059669; } +.callout-warning { border-color: #f59e0b; background: #fffbeb; } .callout-warning .callout-title { color: #d97706; } +.callout-info { border-color: #3b82f6; background: #eff6ff; } .callout-info .callout-title { color: #2563eb; } +.callout-danger { border-color: #ef4444; background: #fef2f2; } .callout-danger .callout-title { color: #dc2626; } +img { max-width: 100%; height: auto; } +strong { font-weight: 600; } +h1, h2, h3, h4 { page-break-after: avoid; } +pre, table, .callout { page-break-inside: avoid; } +`; + +// ─── HTML assembly ────────────────────────────────────────────────────────── + +function buildHtml() { + const cover = generateCoverPage(); + const toc = generateToc(); + const body = generateBody(); + + return ` + + + + Modgud Documentation + + + + ${cover} + ${toc} + ${body} + +`; +} + +// ─── PDF generation ───────────────────────────────────────────────────────── + +function findChrome() { + const candidates = [ + process.env.CHROME_PATH, + 'C:/Program Files/Google/Chrome/Application/chrome.exe', + 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe', + '/usr/bin/google-chrome', + '/usr/bin/chromium-browser', + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + ].filter(Boolean); + + for (const p of candidates) { + if (fs.existsSync(p)) return p; + } + throw new Error('Chrome not found. Set CHROME_PATH environment variable.'); +} + +async function generatePdf(html) { + const chromePath = findChrome(); + console.log(` Chrome: ${chromePath}`); + + const browser = await puppeteer.launch({ + executablePath: chromePath, + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + + const page = await browser.newPage(); + await page.setContent(html, { waitUntil: 'networkidle0' }); + + fs.mkdirSync(path.dirname(OUTPUT), { recursive: true }); + + await page.pdf({ + path: OUTPUT, + format: 'A4', + margin: { top: '22mm', right: '18mm', bottom: '22mm', left: '18mm' }, + printBackground: true, + displayHeaderFooter: true, + headerTemplate: '', + footerTemplate: ` +
+ Modgud + · + / +
`, + }); + + await browser.close(); + return OUTPUT; +} + +// ─── Main ─────────────────────────────────────────────────────────────────── + +async function main() { + console.log('Exporting Modgud documentation to PDF...\n'); + + const pageCount = structure.reduce((sum, s) => sum + s.pages.length, 0); + console.log(` Sections: ${structure.length}`); + console.log(` Pages: ${pageCount}`); + + console.log('\n Building HTML...'); + const html = buildHtml(); + + console.log(' Generating PDF...'); + const outputPath = await generatePdf(html); + + const stats = fs.statSync(outputPath); + const sizeMb = (stats.size / 1024 / 1024).toFixed(1); + console.log(`\n Output: ${outputPath} (${sizeMb} MB)\n`); +} + +main().catch((err) => { + console.error('\nExport failed:', err.message); + process.exit(1); +});