Compare commits

...

2 Commits

Author SHA1 Message Date
dependabot[bot]
f54838709c chore(deps): bump the minor-patch group across 1 directory with 3 updates
Bumps the minor-patch group with 3 updates in the /frontend directory: [i18next](https://github.com/i18next/i18next), [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `i18next` from 25.8.7 to 25.8.10
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.8.7...v25.8.10)

Updates `lucide-react` from 0.564.0 to 0.568.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.568.0/packages/lucide-react)

Updates `typescript-eslint` from 8.55.0 to 8.56.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.56.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 25.8.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-patch
- dependency-name: lucide-react
  dependency-version: 0.568.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.56.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-18 08:18:19 +00:00
Stavros
e4c4e34d5c feat: add markdown generation to config generator (#652) 2026-02-17 15:46:38 +02:00
8 changed files with 252 additions and 254 deletions

View File

@@ -2,220 +2,171 @@
# The base URL where the app is hosted. # The base URL where the app is hosted.
TINYAUTH_APPURL= TINYAUTH_APPURL=
# The directory where resources are stored. # The directory where resources are stored.
TINYAUTH_RESOURCESDIR="./resources" TINYAUTH_RESOURCESDIR="./resources"
# The path to the database file. # The path to the database file.
TINYAUTH_DATABASEPATH="./tinyauth.db" TINYAUTH_DATABASEPATH="./tinyauth.db"
# Disable analytics. # Disable analytics.
TINYAUTH_DISABLEANALYTICS=false TINYAUTH_DISABLEANALYTICS=false
# Disable resources server. # Disable resources server.
TINYAUTH_DISABLERESOURCES=false TINYAUTH_DISABLERESOURCES=false
# server config
# The port on which the server listens. # The port on which the server listens.
TINYAUTH_SERVER_PORT=3000 TINYAUTH_SERVER_PORT=3000
# The address on which the server listens. # The address on which the server listens.
TINYAUTH_SERVER_ADDRESS="0.0.0.0" TINYAUTH_SERVER_ADDRESS="0.0.0.0"
# The path to the Unix socket. # The path to the Unix socket.
TINYAUTH_SERVER_SOCKETPATH= TINYAUTH_SERVER_SOCKETPATH=
# auth config
# List of allowed IPs or CIDR ranges. # List of allowed IPs or CIDR ranges.
TINYAUTH_AUTH_IP_ALLOW= TINYAUTH_AUTH_IP_ALLOW=
# List of blocked IPs or CIDR ranges. # List of blocked IPs or CIDR ranges.
TINYAUTH_AUTH_IP_BLOCK= TINYAUTH_AUTH_IP_BLOCK=
# Comma-separated list of users (username:hashed_password). # Comma-separated list of users (username:hashed_password).
TINYAUTH_AUTH_USERS= TINYAUTH_AUTH_USERS=
# Path to the users file. # Path to the users file.
TINYAUTH_AUTH_USERSFILE= TINYAUTH_AUTH_USERSFILE=
# Enable secure cookies. # Enable secure cookies.
TINYAUTH_AUTH_SECURECOOKIE=false TINYAUTH_AUTH_SECURECOOKIE=false
# Session expiry time in seconds. # Session expiry time in seconds.
TINYAUTH_AUTH_SESSIONEXPIRY=86400 TINYAUTH_AUTH_SESSIONEXPIRY=86400
# Maximum session lifetime in seconds. # Maximum session lifetime in seconds.
TINYAUTH_AUTH_SESSIONMAXLIFETIME=0 TINYAUTH_AUTH_SESSIONMAXLIFETIME=0
# Login timeout in seconds. # Login timeout in seconds.
TINYAUTH_AUTH_LOGINTIMEOUT=300 TINYAUTH_AUTH_LOGINTIMEOUT=300
# Maximum login retries. # Maximum login retries.
TINYAUTH_AUTH_LOGINMAXRETRIES=3 TINYAUTH_AUTH_LOGINMAXRETRIES=3
# Comma-separated list of trusted proxy addresses. # Comma-separated list of trusted proxy addresses.
TINYAUTH_AUTH_TRUSTEDPROXIES= TINYAUTH_AUTH_TRUSTEDPROXIES=
# apps config
# The domain of the app. # The domain of the app.
TINYAUTH_APPS_name_CONFIG_DOMAIN= TINYAUTH_APPS_name_CONFIG_DOMAIN=
# Comma-separated list of allowed users. # Comma-separated list of allowed users.
TINYAUTH_APPS_name_USERS_ALLOW= TINYAUTH_APPS_name_USERS_ALLOW=
# Comma-separated list of blocked users. # Comma-separated list of blocked users.
TINYAUTH_APPS_name_USERS_BLOCK= TINYAUTH_APPS_name_USERS_BLOCK=
# Comma-separated list of allowed OAuth groups. # Comma-separated list of allowed OAuth groups.
TINYAUTH_APPS_name_OAUTH_WHITELIST= TINYAUTH_APPS_name_OAUTH_WHITELIST=
# Comma-separated list of required OAuth groups. # Comma-separated list of required OAuth groups.
TINYAUTH_APPS_name_OAUTH_GROUPS= TINYAUTH_APPS_name_OAUTH_GROUPS=
# List of allowed IPs or CIDR ranges. # List of allowed IPs or CIDR ranges.
TINYAUTH_APPS_name_IP_ALLOW= TINYAUTH_APPS_name_IP_ALLOW=
# List of blocked IPs or CIDR ranges. # List of blocked IPs or CIDR ranges.
TINYAUTH_APPS_name_IP_BLOCK= TINYAUTH_APPS_name_IP_BLOCK=
# List of IPs or CIDR ranges that bypass authentication. # List of IPs or CIDR ranges that bypass authentication.
TINYAUTH_APPS_name_IP_BYPASS= TINYAUTH_APPS_name_IP_BYPASS=
# Custom headers to add to the response. # Custom headers to add to the response.
TINYAUTH_APPS_name_RESPONSE_HEADERS= TINYAUTH_APPS_name_RESPONSE_HEADERS=
# Basic auth username. # Basic auth username.
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_USERNAME= TINYAUTH_APPS_name_RESPONSE_BASICAUTH_USERNAME=
# Basic auth password. # Basic auth password.
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORD= TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORD=
# Path to the file containing the basic auth password. # Path to the file containing the basic auth password.
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORDFILE= TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORDFILE=
# Comma-separated list of allowed paths. # Comma-separated list of allowed paths.
TINYAUTH_APPS_name_PATH_ALLOW= TINYAUTH_APPS_name_PATH_ALLOW=
# Comma-separated list of blocked paths. # Comma-separated list of blocked paths.
TINYAUTH_APPS_name_PATH_BLOCK= TINYAUTH_APPS_name_PATH_BLOCK=
# Comma-separated list of required LDAP groups. # Comma-separated list of required LDAP groups.
TINYAUTH_APPS_name_LDAP_GROUPS= TINYAUTH_APPS_name_LDAP_GROUPS=
# oauth config
# Comma-separated list of allowed OAuth domains. # Comma-separated list of allowed OAuth domains.
TINYAUTH_OAUTH_WHITELIST= TINYAUTH_OAUTH_WHITELIST=
# The OAuth provider to use for automatic redirection. # The OAuth provider to use for automatic redirection.
TINYAUTH_OAUTH_AUTOREDIRECT= TINYAUTH_OAUTH_AUTOREDIRECT=
# OAuth client ID. # OAuth client ID.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID= TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
# OAuth client secret. # OAuth client secret.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET= TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
# Path to the file containing the OAuth client secret. # Path to the file containing the OAuth client secret.
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE= TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
# OAuth scopes. # OAuth scopes.
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES= TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
# OAuth redirect URL. # OAuth redirect URL.
TINYAUTH_OAUTH_PROVIDERS_name_REDIRECTURL= TINYAUTH_OAUTH_PROVIDERS_name_REDIRECTURL=
# OAuth authorization URL. # OAuth authorization URL.
TINYAUTH_OAUTH_PROVIDERS_name_AUTHURL= TINYAUTH_OAUTH_PROVIDERS_name_AUTHURL=
# OAuth token URL. # OAuth token URL.
TINYAUTH_OAUTH_PROVIDERS_name_TOKENURL= TINYAUTH_OAUTH_PROVIDERS_name_TOKENURL=
# OAuth userinfo URL. # OAuth userinfo URL.
TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL= TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL=
# Allow insecure OAuth connections. # Allow insecure OAuth connections.
TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false
# Provider name in UI. # Provider name in UI.
TINYAUTH_OAUTH_PROVIDERS_name_NAME= TINYAUTH_OAUTH_PROVIDERS_name_NAME=
# oidc config
# Path to the private key file. # Path to the private key file.
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key" TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
# Path to the public key file. # Path to the public key file.
TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub" TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub"
# OIDC client ID. # OIDC client ID.
TINYAUTH_OIDC_CLIENTS_name_CLIENTID= TINYAUTH_OIDC_CLIENTS_name_CLIENTID=
# OIDC client secret. # OIDC client secret.
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRET= TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRET=
# Path to the file containing the OIDC client secret. # Path to the file containing the OIDC client secret.
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRETFILE= TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRETFILE=
# List of trusted redirect URIs. # List of trusted redirect URIs.
TINYAUTH_OIDC_CLIENTS_name_TRUSTEDREDIRECTURIS= TINYAUTH_OIDC_CLIENTS_name_TRUSTEDREDIRECTURIS=
# Client name in UI. # Client name in UI.
TINYAUTH_OIDC_CLIENTS_name_NAME= TINYAUTH_OIDC_CLIENTS_name_NAME=
# ui config
# The title of the UI. # The title of the UI.
TINYAUTH_UI_TITLE="Tinyauth" TINYAUTH_UI_TITLE="Tinyauth"
# Message displayed on the forgot password page. # Message displayed on the forgot password page.
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration." TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration."
# Path to the background image. # Path to the background image.
TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg" TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg"
# Disable UI warnings. # Disable UI warnings.
TINYAUTH_UI_DISABLEWARNINGS=false TINYAUTH_UI_DISABLEWARNINGS=false
# ldap config
# LDAP server address. # LDAP server address.
TINYAUTH_LDAP_ADDRESS= TINYAUTH_LDAP_ADDRESS=
# Bind DN for LDAP authentication. # Bind DN for LDAP authentication.
TINYAUTH_LDAP_BINDDN= TINYAUTH_LDAP_BINDDN=
# Bind password for LDAP authentication. # Bind password for LDAP authentication.
TINYAUTH_LDAP_BINDPASSWORD= TINYAUTH_LDAP_BINDPASSWORD=
# Base DN for LDAP searches. # Base DN for LDAP searches.
TINYAUTH_LDAP_BASEDN= TINYAUTH_LDAP_BASEDN=
# Allow insecure LDAP connections. # Allow insecure LDAP connections.
TINYAUTH_LDAP_INSECURE=false TINYAUTH_LDAP_INSECURE=false
# LDAP search filter. # LDAP search filter.
TINYAUTH_LDAP_SEARCHFILTER="(uid=%s)" TINYAUTH_LDAP_SEARCHFILTER="(uid=%s)"
# Certificate for mTLS authentication. # Certificate for mTLS authentication.
TINYAUTH_LDAP_AUTHCERT= TINYAUTH_LDAP_AUTHCERT=
# Certificate key for mTLS authentication. # Certificate key for mTLS authentication.
TINYAUTH_LDAP_AUTHKEY= TINYAUTH_LDAP_AUTHKEY=
# Cache duration for LDAP group membership in seconds. # Cache duration for LDAP group membership in seconds.
TINYAUTH_LDAP_GROUPCACHETTL=900 TINYAUTH_LDAP_GROUPCACHETTL=900
# log config
# Log level (trace, debug, info, warn, error). # Log level (trace, debug, info, warn, error).
TINYAUTH_LOG_LEVEL="info" TINYAUTH_LOG_LEVEL="info"
# Enable JSON formatted logs. # Enable JSON formatted logs.
TINYAUTH_LOG_JSON=false TINYAUTH_LOG_JSON=false
# Enable this log stream. # Enable this log stream.
TINYAUTH_LOG_STREAMS_HTTP_ENABLED=true TINYAUTH_LOG_STREAMS_HTTP_ENABLED=true
# Log level for this stream. Use global if empty. # Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_HTTP_LEVEL= TINYAUTH_LOG_STREAMS_HTTP_LEVEL=
# Enable this log stream. # Enable this log stream.
TINYAUTH_LOG_STREAMS_APP_ENABLED=true TINYAUTH_LOG_STREAMS_APP_ENABLED=true
# Log level for this stream. Use global if empty. # Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_APP_LEVEL= TINYAUTH_LOG_STREAMS_APP_LEVEL=
# Enable this log stream. # Enable this log stream.
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
# Log level for this stream. Use global if empty. # Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL= TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=

3
.gitignore vendored
View File

@@ -42,3 +42,6 @@ __debug_*
# traefik data # traefik data
/traefik /traefik
# generated markdown (for docs)
/config.gen.md

View File

@@ -1,102 +0,0 @@
# Tinyauth Example Configuration
# The base URL where Tinyauth is accessible
appUrl: "https://auth.example.com"
# Directory for static resources
resourcesDir: "./resources"
# Path to SQLite database file
databasePath: "./tinyauth.db"
# Disable usage analytics
disableAnalytics: false
# Disable static resource serving
disableResources: false
# Disable UI warning messages
disableUIWarnings: false
# Logging Configuration
log:
# Log level: trace, debug, info, warn, error
level: "info"
json: false
streams:
app:
enabled: true
level: "warn"
http:
enabled: true
level: "debug"
audit:
enabled: false
level: "info"
# Server Configuration
server:
# Port to listen on
port: 3000
# Interface to bind to (0.0.0.0 for all interfaces)
address: "0.0.0.0"
# Unix socket path (optional, overrides port/address if set)
socketPath: ""
# Comma-separated list of trusted proxy IPs/CIDRs
trustedProxies: ""
# Authentication Configuration
auth:
# Format: username:bcrypt_hash (use bcrypt to generate hash)
users: "admin:$2a$10$example_bcrypt_hash_here"
# Path to external users file (optional)
usersFile: ""
# Enable secure cookies (requires HTTPS)
secureCookie: false
# Session expiry in seconds (3600 = 1 hour)
sessionExpiry: 3600
# Session maximum lifetime in seconds (0 = unlimited)
sessionMaxLifetime: 0
# Login timeout in seconds (300 = 5 minutes)
loginTimeout: 300
# Maximum login retries before lockout
loginMaxRetries: 3
# OAuth Configuration
oauth:
# Regex pattern for allowed email addresses (e.g., /@example\.com$/)
whitelist: ""
# Provider ID to auto-redirect to (skips login page)
autoRedirect: ""
# OAuth Provider Configuration (replace myprovider with your provider name)
providers:
myprovider:
clientId: "your_client_id_here"
clientSecret: "your_client_secret_here"
authUrl: "https://provider.example.com/oauth/authorize"
tokenUrl: "https://provider.example.com/oauth/token"
userInfoUrl: "https://provider.example.com/oauth/userinfo"
redirectUrl: "https://auth.example.com/api/oauth/callback/myprovider"
scopes: "openid email profile"
name: "My OAuth Provider"
# Allow insecure connections (self-signed certificates)
insecure: false
# UI Customization
ui:
# Custom title for login page
title: "Tinyauth"
# Message shown on forgot password page
forgotPasswordMessage: "Contact your administrator to reset your password"
# Background image URL for login page
backgroundImage: ""
# LDAP Configuration (optional)
ldap:
# LDAP server address
address: "ldap://ldap.example.com:389"
# DN for binding to LDAP server
bindDn: "cn=readonly,dc=example,dc=com"
# Password for bind DN
bindPassword: "your_bind_password"
# Base DN for user searches
baseDn: "dc=example,dc=com"
# Search filter (%s will be replaced with username)
searchFilter: "(&(uid=%s)(memberOf=cn=users,ou=groups,dc=example,dc=com))"
# Allow insecure LDAP connections
insecure: false

View File

@@ -16,11 +16,11 @@
"axios": "^1.13.5", "axios": "^1.13.5",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^25.8.7", "i18next": "^25.8.10",
"i18next-browser-languagedetector": "^8.2.1", "i18next-browser-languagedetector": "^8.2.1",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.564.0", "lucide-react": "^0.574.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
@@ -48,7 +48,7 @@
"rollup-plugin-visualizer": "^6.0.5", "rollup-plugin-visualizer": "^6.0.5",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.55.0", "typescript-eslint": "^8.56.0",
"vite": "^7.3.1", "vite": "^7.3.1",
}, },
}, },
@@ -372,25 +372,25 @@
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.55.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/type-utils": "8.55.0", "@typescript-eslint/utils": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/type-utils": "8.56.0", "@typescript-eslint/utils": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.55.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.55.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.55.0", "@typescript-eslint/types": "^8.55.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ=="], "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.0", "@typescript-eslint/types": "^8.56.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.55.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q=="], "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/utils": "8.55.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g=="], "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0", "@typescript-eslint/utils": "8.56.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.55.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.55.0", "@typescript-eslint/tsconfig-utils": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw=="], "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.0", "@typescript-eslint/tsconfig-utils": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA=="], "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@@ -580,7 +580,7 @@
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
"i18next": ["i18next@25.8.7", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-ttxxc5+67S/0hhoeVdEgc1lRklZhdfcUSEPp1//uUG2NB88X3667gRsDar+ZWQFdysnOsnb32bcoMsa4mtzhkQ=="], "i18next": ["i18next@25.8.10", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-CtPJLMAz1G8sxo+mIzfBjGgLxWs7d6WqIjlmmv9BTsOat4pJIfwZ8cm07n3kFS6bP9c6YwsYutYrwsEeJVBo2g=="],
"i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.1", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw=="], "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.1", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw=="],
@@ -664,7 +664,7 @@
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"lucide-react": ["lucide-react@0.564.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg=="], "lucide-react": ["lucide-react@0.574.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dJ8xb5juiZVIbdSn3HTyHsjjIwUwZ4FNwV0RtYDScOyySOeie1oXZTymST6YPJ4Qwt3Po8g4quhYl4OxtACiuQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -854,7 +854,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.55.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.55.0", "@typescript-eslint/parser": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0", "@typescript-eslint/utils": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw=="], "typescript-eslint": ["typescript-eslint@8.56.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.0", "@typescript-eslint/parser": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0", "@typescript-eslint/utils": "8.56.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
@@ -968,25 +968,25 @@
"@types/estree-jsx/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], "@types/estree-jsx/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0" } }, "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.55.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="], "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0" } }, "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w=="],
"@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
"@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.55.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow=="], "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ=="],
"@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -994,9 +994,7 @@
"@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="], "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="],
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"eslint-plugin-react-hooks/@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], "eslint-plugin-react-hooks/@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="],
@@ -1010,7 +1008,7 @@
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.55.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", "@typescript-eslint/typescript-estree": "8.55.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow=="], "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ=="],
"@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], "@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
@@ -1022,13 +1020,13 @@
"@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], "@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="], "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0" } }, "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w=="],
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="], "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="],
@@ -1058,9 +1056,9 @@
"eslint-plugin-react-hooks/@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], "eslint-plugin-react-hooks/@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "@typescript-eslint/visitor-keys": "8.55.0" } }, "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q=="], "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0" } }, "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w=="],
"typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.55.0", "", {}, "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w=="], "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], "@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],

View File

@@ -22,11 +22,11 @@
"axios": "^1.13.5", "axios": "^1.13.5",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^25.8.7", "i18next": "^25.8.10",
"i18next-browser-languagedetector": "^8.2.1", "i18next-browser-languagedetector": "^8.2.1",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.564.0", "lucide-react": "^0.574.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
@@ -54,7 +54,7 @@
"rollup-plugin-visualizer": "^6.0.5", "rollup-plugin-visualizer": "^6.0.5",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.55.0", "typescript-eslint": "^8.56.0",
"vite": "^7.3.1" "vite": "^7.3.1"
} }
} }

View File

@@ -2,10 +2,37 @@ package main
import ( import (
"log/slog" "log/slog"
"reflect"
) )
func main() { func main() {
slog.Info("generating example env file") slog.Info("generating example env file")
generateExampleEnv() generateExampleEnv()
slog.Info("generating config reference markdown file")
generateMarkdown()
}
func walkAndBuild[T any](parent reflect.Type, parentValue reflect.Value,
parentPath string, entries *[]T,
buildEntry func(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]T),
buildMap func(child reflect.StructField, parentPath string, entries *[]T),
buildChildPath func(parentPath string, childName string) string,
) {
for i := 0; i < parent.NumField(); i++ {
field := parent.Field(i)
fieldType := field.Type
fieldValue := parentValue.Field(i)
switch fieldType.Kind() {
case reflect.Struct:
childPath := buildChildPath(parentPath, field.Name)
walkAndBuild[T](fieldType, fieldValue, childPath, entries, buildEntry, buildMap, buildChildPath)
case reflect.Map:
buildMap(field, parentPath, entries)
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int:
buildEntry(field, fieldValue, parentPath, entries)
default:
slog.Info("unknown type", "type", fieldType.Kind())
}
}
} }

View File

@@ -2,7 +2,9 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io/fs"
"log/slog" "log/slog"
"os" "os"
"reflect" "reflect"
@@ -11,7 +13,7 @@ import (
"github.com/steveiliop56/tinyauth/internal/config" "github.com/steveiliop56/tinyauth/internal/config"
) )
type Path struct { type EnvEntry struct {
Name string Name string
Description string Description string
Value any Value any
@@ -19,17 +21,17 @@ type Path struct {
func generateExampleEnv() { func generateExampleEnv() {
cfg := config.NewDefaultConfiguration() cfg := config.NewDefaultConfiguration()
paths := make([]Path, 0) entries := make([]EnvEntry, 0)
root := reflect.TypeOf(cfg).Elem() root := reflect.TypeOf(cfg).Elem()
rootValue := reflect.ValueOf(cfg).Elem() rootValue := reflect.ValueOf(cfg).Elem()
rootPath := "TINYAUTH_" rootPath := "TINYAUTH_"
buildPaths(root, rootValue, rootPath, &paths) walkAndBuild(root, rootValue, rootPath, &entries, buildEnvEntry, buildEnvMapEntry, buildEnvChildPath)
compiled := compileEnv(paths) compiled := compileEnv(entries)
err := os.Remove(".env.example") err := os.Remove(".env.example")
if err != nil { if err != nil && !errors.Is(err, fs.ErrNotExist) {
slog.Error("failed to remove example env file", "error", err) slog.Error("failed to remove example env file", "error", err)
os.Exit(1) os.Exit(1)
} }
@@ -41,96 +43,88 @@ func generateExampleEnv() {
} }
} }
func buildPaths(parent reflect.Type, parentValue reflect.Value, parentPath string, paths *[]Path) { func buildEnvEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]EnvEntry) {
for i := 0; i < parent.NumField(); i++ { desc := child.Tag.Get("description")
field := parent.Field(i) tag := child.Tag.Get("yaml")
fieldType := field.Type
fieldValue := parentValue.Field(i)
switch fieldType.Kind() {
case reflect.Struct:
childPath := parentPath + strings.ToUpper(field.Name) + "_"
buildPaths(fieldType, fieldValue, childPath, paths)
case reflect.Map:
buildMapPaths(field, parentPath, paths)
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int:
buildPath(field, fieldValue, parentPath, paths)
default:
slog.Info("unknown type", "type", fieldType.Kind())
}
}
}
func buildPath(field reflect.StructField, fieldValue reflect.Value, parent string, paths *[]Path) { if tag == "-" {
desc := field.Tag.Get("description")
yamlTag := field.Tag.Get("yaml")
// probably internal logic, should be skipped
if yamlTag == "-" {
return return
} }
defaultValue := fieldValue.Interface() value := childValue.Interface()
path := Path{ entry := EnvEntry{
Name: parent + strings.ToUpper(field.Name), Name: parentPath + strings.ToUpper(child.Name),
Description: desc, Description: desc,
} }
switch fieldValue.Kind() { switch childValue.Kind() {
case reflect.Slice: case reflect.Slice:
sl, ok := defaultValue.([]string) sl, ok := value.([]string)
if !ok { if !ok {
slog.Error("invalid default value", "value", defaultValue) slog.Error("invalid default value", "value", value)
return return
} }
path.Value = strings.Join(sl, ",") entry.Value = strings.Join(sl, ",")
case reflect.String: case reflect.String:
st, ok := defaultValue.(string) st, ok := value.(string)
if !ok { if !ok {
slog.Error("invalid default value", "value", defaultValue) slog.Error("invalid default value", "value", value)
return return
} }
// good idea to escape strings probably
if st != "" { if st != "" {
path.Value = fmt.Sprintf(`"%s"`, st) entry.Value = fmt.Sprintf(`"%s"`, st)
} else { } else {
path.Value = "" entry.Value = ""
} }
default: default:
path.Value = defaultValue entry.Value = value
} }
*paths = append(*paths, path) *entries = append(*entries, entry)
} }
func buildMapPaths(field reflect.StructField, parentPath string, paths *[]Path) { func buildEnvMapEntry(child reflect.StructField, parentPath string, entries *[]EnvEntry) {
fieldType := field.Type fieldType := child.Type
if fieldType.Key().Kind() != reflect.String { if fieldType.Key().Kind() != reflect.String {
slog.Info("unsupported map key type", "type", fieldType.Key().Kind()) slog.Info("unsupported map key type", "type", fieldType.Key().Kind())
return return
} }
mapPath := parentPath + strings.ToUpper(field.Name) + "_name_" mapPath := parentPath + strings.ToUpper(child.Name) + "_name_"
valueType := fieldType.Elem() valueType := fieldType.Elem()
if valueType.Kind() == reflect.Struct { if valueType.Kind() == reflect.Struct {
zeroValue := reflect.New(valueType).Elem() zeroValue := reflect.New(valueType).Elem()
buildPaths(valueType, zeroValue, mapPath, paths) walkAndBuild(valueType, zeroValue, mapPath, entries, buildEnvEntry, buildEnvMapEntry, buildEnvChildPath)
} }
} }
func compileEnv(paths []Path) []byte { func buildEnvChildPath(parent string, child string) string {
return parent + strings.ToUpper(child) + "_"
}
func compileEnv(entries []EnvEntry) []byte {
buffer := bytes.Buffer{} buffer := bytes.Buffer{}
buffer.WriteString("# Tinyauth example configuration\n\n") buffer.WriteString("# Tinyauth example configuration\n\n")
for _, path := range paths { previousSection := ""
for _, entry := range entries {
if strings.Count(entry.Name, "_") > 1 {
section := strings.Split(strings.TrimPrefix(entry.Name, "TINYAUTH_"), "_")[0]
if section != previousSection {
buffer.WriteString("\n# " + strings.ToLower(section) + " config\n\n")
previousSection = section
}
}
buffer.WriteString("# ") buffer.WriteString("# ")
buffer.WriteString(path.Description) buffer.WriteString(entry.Description)
buffer.WriteString("\n") buffer.WriteString("\n")
buffer.WriteString(path.Name) buffer.WriteString(entry.Name)
buffer.WriteString("=") buffer.WriteString("=")
fmt.Fprintf(&buffer, "%v", path.Value) fmt.Fprintf(&buffer, "%v", entry.Value)
buffer.WriteString("\n\n") buffer.WriteString("\n")
} }
return buffer.Bytes() return buffer.Bytes()

127
gen/gen_md.go Normal file
View File

@@ -0,0 +1,127 @@
package main
import (
"bytes"
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"reflect"
"strings"
"github.com/steveiliop56/tinyauth/internal/config"
)
type MarkdownEntry struct {
Env string
Flag string
Description string
Default any
}
func generateMarkdown() {
cfg := config.NewDefaultConfiguration()
entries := make([]MarkdownEntry, 0)
root := reflect.TypeOf(cfg).Elem()
rootValue := reflect.ValueOf(cfg).Elem()
rootPath := "tinyauth."
walkAndBuild(root, rootValue, rootPath, &entries, buildMdEntry, buildMdMapEntry, buildMdChildPath)
compiled := compileMd(entries)
err := os.Remove("config.gen.md")
if err != nil && !errors.Is(err, fs.ErrNotExist) {
slog.Error("failed to remove example env file", "error", err)
os.Exit(1)
}
err = os.WriteFile("config.gen.md", compiled, 0644)
if err != nil {
slog.Error("failed to write example env file", "error", err)
os.Exit(1)
}
}
func buildMdEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]MarkdownEntry) {
desc := child.Tag.Get("description")
tag := child.Tag.Get("yaml")
if tag == "-" {
return
}
value := childValue.Interface()
entry := MarkdownEntry{
Env: strings.ToUpper(strings.ReplaceAll(parentPath, ".", "_")) + strings.ToUpper(child.Name),
Flag: fmt.Sprintf("--%s%s", strings.TrimPrefix(parentPath, "tinyauth."), tag),
Description: desc,
}
switch childValue.Kind() {
case reflect.Slice:
sl, ok := value.([]string)
if !ok {
slog.Error("invalid default value", "value", value)
return
}
entry.Default = fmt.Sprintf("`%s`", strings.Join(sl, ","))
default:
entry.Default = fmt.Sprintf("`%v`", value)
}
*entries = append(*entries, entry)
}
func buildMdMapEntry(child reflect.StructField, parentPath string, entries *[]MarkdownEntry) {
fieldType := child.Type
if fieldType.Key().Kind() != reflect.String {
slog.Info("unsupported map key type", "type", fieldType.Key().Kind())
return
}
tag := child.Tag.Get("yaml")
if tag == "-" {
return
}
mapPath := parentPath + tag + ".[name]."
valueType := fieldType.Elem()
if valueType.Kind() == reflect.Struct {
zeroValue := reflect.New(valueType).Elem()
walkAndBuild(valueType, zeroValue, mapPath, entries, buildMdEntry, buildMdMapEntry, buildMdChildPath)
}
}
func buildMdChildPath(parent string, child string) string {
return parent + strings.ToLower(child) + "."
}
func compileMd(entries []MarkdownEntry) []byte {
buffer := bytes.Buffer{}
buffer.WriteString("# Tinyauth configuration reference\n\n")
buffer.WriteString("| Environment | Flag | Description | Default |\n")
buffer.WriteString("| - | - | - | - |\n")
previousSection := ""
for _, entry := range entries {
if strings.Count(entry.Env, "_") > 1 {
section := strings.Split(strings.TrimPrefix(entry.Env, "TINYAUTH_"), "_")[0]
if section != previousSection {
buffer.WriteString("\n## " + strings.ToLower(section) + "\n\n")
buffer.WriteString("| Environment | Flag | Description | Default |\n")
buffer.WriteString("| - | - | - | - |\n")
previousSection = section
}
}
fmt.Fprintf(&buffer, "| `%s` | `%s` | %s | %s |\n", entry.Env, entry.Flag, entry.Description, entry.Default)
}
return buffer.Bytes()
}