You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@grails.apache.org by "jamesfredley (via GitHub)" <gi...@apache.org> on 2026/02/21 00:28:28 UTC

[PR] feat: hybrid ConfigReportCommand with static metadata and runtime values [grails-core]

jamesfredley opened a new pull request, #15426:
URL: https://github.com/apache/grails-core/pull/15426

   ## Summary
   
   Combines the curated property metadata from #15407 with the runtime environment collection from the `configuration-command` branch into a single hybrid `ConfigReportCommand`.
   
   ### What it does
   
   - Loads property metadata from `config-properties.yml` (descriptions, defaults for ~160 known Grails properties across 21 categories)
   - Collects runtime property values from the Spring `ConfigurableEnvironment`
   - Outputs a **3-column AsciiDoc table** (`Property | Description | Default`) organized by functional category
   - Runtime values **override** static defaults when the property is set in the running application
   - Properties not found in the metadata appear in a separate **"Other Properties"** section
   
   ### Changes
   
   - **`ConfigReportCommand.groovy`** - Modified to load YAML metadata and merge with runtime values
   - **`config-properties.yml`** - New metadata file with ~160 properties across 21 categories (Core, Web & Controllers, CORS, Views & GSP, GORM, DataSource, Spring Security, MongoDB, etc.)
   - **`ConfigReportCommandSpec.groovy`** - Updated unit tests for 3-column hybrid format
   - **`ConfigReportCommandIntegrationSpec.groovy`** - Updated integration tests for category-based layout
   
   ### Based on
   
   - Cherry-picks from #15407 (static AsciiDoc documentation) 
   - Cherry-picks from `configuration-command` branch (jdaugherty's ConfigReportCommand proof-of-concept)
   
   ### Test results
   
   - Unit tests: 16/16 passed
   - Integration tests: 8/8 passed


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#issuecomment-4260613171

   @jamesfredley I approved #15574 , once it's merged can you please update this & adjust for the feedback that @matrei has provided? We can then approve.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#issuecomment-4158751647

   ## 🔎 No tests executed 🔎
   
   🏷️ Commit: 242b1d560223a4cbf88e7bf180000bafd3d918a7
   ▶️ Tests:  0 executed
   🟡 Checks: 0/25 completed
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley merged PR #15426:
URL: https://github.com/apache/grails-core/pull/15426


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat: hybrid ConfigReportCommand with static metadata and runtime values [grails-core]

Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#issuecomment-3937793446

   Screenshot: [The-Grails-Framework-7-0-8-SNAPSHOT-02-20-2026_07_40_PM.pdf](https://github.com/user-attachments/files/25454472/The-Grails-Framework-7-0-8-SNAPSHOT-02-20-2026_07_40_PM.pdf)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#discussion_r3093702510


##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgentsXhr",
+            "type": "java.lang.Boolean",
+            "description": "When true, XHR requests also respect the grails.mime.disable.accept.header.userAgents setting, while by default XHR requests ignore user agent filtering.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.converters.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for converter output (JSON or XML).",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.resources.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether serving static files from src/main/resources/public is enabled.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.resources.pattern",
+            "type": "java.lang.String",
+            "description": "The URL path pattern for serving static resources.",
+            "defaultValue": "/static/**"
+        },
+        {
+            "name": "grails.resources.cachePeriod",
+            "type": "java.lang.Integer",
+            "description": "The cache period (in seconds) for static resource HTTP responses.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.urlmapping.cache.maxsize",
+            "type": "java.lang.Integer",
+            "description": "The maximum size of the URL mapping cache.",
+            "defaultValue": 1000

Review Comment:
   We should change the default in the code.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on code in PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#discussion_r3138595088


##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgentsXhr",
+            "type": "java.lang.Boolean",
+            "description": "When true, XHR requests also respect the grails.mime.disable.accept.header.userAgents setting, while by default XHR requests ignore user agent filtering.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.converters.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for converter output (JSON or XML).",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.resources.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether serving static files from src/main/resources/public is enabled.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.resources.pattern",
+            "type": "java.lang.String",
+            "description": "The URL path pattern for serving static resources.",
+            "defaultValue": "/static/**"
+        },
+        {
+            "name": "grails.resources.cachePeriod",
+            "type": "java.lang.Integer",
+            "description": "The cache period (in seconds) for static resource HTTP responses.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.urlmapping.cache.maxsize",
+            "type": "java.lang.Integer",
+            "description": "The maximum size of the URL mapping cache.",
+            "defaultValue": 1000

Review Comment:
   Resolved in e768e20: changed `DEFAULT_MAX_WEIGHTED_CAPACITY` in `DefaultUrlMappingsHolder` from `5000` to `1000` so the code default now matches the documented value (and what the generated `application.yml` used to contain before #15574 removed it). The JSON metadata stays at `1000` and now reflects the true code default.



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"

Review Comment:
   Resolved in e768e20: changed `defaultValue` from `none` to `html` and updated the description to clarify that this is the fallback default used for `grails.views.gsp.codecs.expression` / `scriptlet` / `taglib` / `staticparts` when those are not set.



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"
+        },
+        {
+            "name": "grails.views.gsp.encoding",
+            "type": "java.lang.String",
+            "description": "The file encoding for GSP source files.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.views.gsp.htmlcodec",
+            "type": "java.lang.String",
+            "description": "The HTML codec for GSP output (xml or html).",
+            "defaultValue": "xml"

Review Comment:
   Resolved in e768e20: removed the misleading `xml` default and rewrote the description to match the actual behavior - when unset (or when the value does not start with `xml` or equal `xhtml` ignoring case), a legacy HTML4-compatible encoder is used; setting it to `xml` or `xhtml` selects the XML-safe encoder.



##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []

Review Comment:
   Resolved in e768e20: changed `defaultValue` from `[]` to `["Gecko", "WebKit", "Presto", "Trident"]` to match the regex pattern baked into `HttpServletResponseExtension.useDefaultConfig()`. Also tweaked the description to note the match is case-insensitive.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on code in PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#discussion_r3138589145


##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgentsXhr",
+            "type": "java.lang.Boolean",
+            "description": "When true, XHR requests also respect the grails.mime.disable.accept.header.userAgents setting, while by default XHR requests ignore user agent filtering.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.converters.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for converter output (JSON or XML).",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.resources.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether serving static files from src/main/resources/public is enabled.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.resources.pattern",
+            "type": "java.lang.String",
+            "description": "The URL path pattern for serving static resources.",
+            "defaultValue": "/static/**"
+        },
+        {
+            "name": "grails.resources.cachePeriod",
+            "type": "java.lang.Integer",
+            "description": "The cache period (in seconds) for static resource HTTP responses.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.urlmapping.cache.maxsize",
+            "type": "java.lang.Integer",
+            "description": "The maximum size of the URL mapping cache.",
+            "defaultValue": 1000

Review Comment:
   Resolved in e768e20: changed DEFAULT_MAX_WEIGHTED_CAPACITY in DefaultUrlMappingsHolder from 5000 to 1000 so the code default now matches the documented value (and what the generated pplication.yml used to contain before #15574 removed it). The JSON metadata stays at 1000 and now reflects the true code default.



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"

Review Comment:
   Resolved in e768e20: changed defaultValue from 
   one to html and updated the description to clarify that this is the fallback default used for grails.views.gsp.codecs.expression/scriptlet/	aglib/staticparts when those are not set.



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"
+        },
+        {
+            "name": "grails.views.gsp.encoding",
+            "type": "java.lang.String",
+            "description": "The file encoding for GSP source files.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.views.gsp.htmlcodec",
+            "type": "java.lang.String",
+            "description": "The HTML codec for GSP output (xml or html).",
+            "defaultValue": "xml"

Review Comment:
   Resolved in e768e20: removed the misleading xml default and rewrote the description to match the actual behavior - when unset (or when the value does not start with xml or equal xhtml ignoring case), a legacy HTML4-compatible encoder is used; setting it to xml or xhtml selects the XML-safe encoder.



##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []

Review Comment:
   Resolved in e768e20: changed defaultValue from [] to [\Gecko\, \WebKit\, \Presto\, \Trident\] to match the regex pattern baked into HttpServletResponseExtension.useDefaultConfig(). Also tweaked the description to note the match is case-insensitive.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#issuecomment-4165703849

   ## 🔎 No tests executed 🔎
   
   🏷️ Commit: 9a6dd39527bb3210e71c676c96cc5ed2c75490ed
   ▶️ Tests:  0 executed
   🟡 Checks: 0/25 completed
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on code in PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#discussion_r3138589145


##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgentsXhr",
+            "type": "java.lang.Boolean",
+            "description": "When true, XHR requests also respect the grails.mime.disable.accept.header.userAgents setting, while by default XHR requests ignore user agent filtering.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.converters.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for converter output (JSON or XML).",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.resources.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether serving static files from src/main/resources/public is enabled.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.resources.pattern",
+            "type": "java.lang.String",
+            "description": "The URL path pattern for serving static resources.",
+            "defaultValue": "/static/**"
+        },
+        {
+            "name": "grails.resources.cachePeriod",
+            "type": "java.lang.Integer",
+            "description": "The cache period (in seconds) for static resource HTTP responses.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.urlmapping.cache.maxsize",
+            "type": "java.lang.Integer",
+            "description": "The maximum size of the URL mapping cache.",
+            "defaultValue": 1000

Review Comment:
   Resolved in e768e20: changed DEFAULT_MAX_WEIGHTED_CAPACITY in DefaultUrlMappingsHolder from 5000 to 1000 so the code default now matches the documented value (and what the generated pplication.yml used to contain before #15574 removed it). The JSON metadata stays at 1000 and now reflects the true code default.



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"

Review Comment:
   Resolved in e768e20: changed defaultValue from 
   one to html and updated the description to clarify that this is the fallback default used for grails.views.gsp.codecs.expression/scriptlet/	aglib/staticparts when those are not set.



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"
+        },
+        {
+            "name": "grails.views.gsp.encoding",
+            "type": "java.lang.String",
+            "description": "The file encoding for GSP source files.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.views.gsp.htmlcodec",
+            "type": "java.lang.String",
+            "description": "The HTML codec for GSP output (xml or html).",
+            "defaultValue": "xml"

Review Comment:
   Resolved in e768e20: removed the misleading xml default and rewrote the description to match the actual behavior - when unset (or when the value does not start with xml or equal xhtml ignoring case), a legacy HTML4-compatible encoder is used; setting it to xml or xhtml selects the XML-safe encoder.



##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []

Review Comment:
   Resolved in e768e20: changed defaultValue from [] to [\Gecko\, \WebKit\, \Presto\, \Trident\] to match the regex pattern baked into HttpServletResponseExtension.useDefaultConfig(). Also tweaked the description to note the match is case-insensitive.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "jamesfredley (via GitHub)" <gi...@apache.org>.
jamesfredley commented on code in PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#discussion_r3089262018


##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgentsXhr",
+            "type": "java.lang.Boolean",
+            "description": "When true, XHR requests also respect the grails.mime.disable.accept.header.userAgents setting, while by default XHR requests ignore user agent filtering.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.converters.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for converter output (JSON or XML).",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.resources.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether serving static files from src/main/resources/public is enabled.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.resources.pattern",
+            "type": "java.lang.String",
+            "description": "The URL path pattern for serving static resources.",
+            "defaultValue": "/static/**"
+        },
+        {
+            "name": "grails.resources.cachePeriod",
+            "type": "java.lang.Integer",
+            "description": "The cache period (in seconds) for static resource HTTP responses.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.urlmapping.cache.maxsize",
+            "type": "java.lang.Integer",
+            "description": "The maximum size of the URL mapping cache.",
+            "defaultValue": 1000

Review Comment:
   @jdaugherty I think this came from the desire to pull them from a generated app vs the default in the code.  Similar for comments below.  Should it be default in code or in generated app, I lean towards code.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on code in PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#discussion_r3084504916


##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"

Review Comment:
   I think, this property is only used as fallback default value of `grails.views.gsp.codecs` if that is not set, and in that case defaults to `html`. (`GroovyPageParser`, `OutputEncodingSettings`)



##########
grails-gsp/plugin/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,100 @@
+{
+    "groups": [
+        {
+            "name": "grails.views",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.views.gsp.codecs",
+            "description": "Views & GSP"
+        },
+        {
+            "name": "grails.gsp",
+            "description": "Views & GSP"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.views.default.codec",
+            "type": "java.lang.String",
+            "description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
+            "defaultValue": "none"
+        },
+        {
+            "name": "grails.views.gsp.encoding",
+            "type": "java.lang.String",
+            "description": "The file encoding for GSP source files.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.views.gsp.htmlcodec",
+            "type": "java.lang.String",
+            "description": "The HTML codec for GSP output (xml or html).",
+            "defaultValue": "xml"

Review Comment:
   I think this has no default value, but if specified and the value does not start with `xml` or equal (ignoring case) `xhtml`, it uses legacy `HTML4Encoder` instead of `HTMLEncoder` (`HTMLCodec`)



##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []

Review Comment:
   I think default value is: `['Gecko', 'WebKit', 'Presto', 'Trident']` (`HttpServletResponseExtension`)



##########
grails-web-core/src/main/resources/META-INF/spring-configuration-metadata.json:
##########
@@ -0,0 +1,248 @@
+{
+    "groups": [
+        {
+            "name": "grails.controllers",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.web",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.filter",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.exceptionresolver",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.logging",
+            "description": "Web & Controllers"
+        },
+        {
+            "name": "grails.cors",
+            "description": "CORS"
+        },
+        {
+            "name": "grails.mime",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.converters",
+            "description": "Content Negotiation & MIME Types"
+        },
+        {
+            "name": "grails.resources",
+            "description": "Static Resources"
+        },
+        {
+            "name": "grails.urlmapping",
+            "description": "URL Mappings"
+        },
+        {
+            "name": "grails.scaffolding",
+            "description": "Scaffolding"
+        }
+    ],
+    "properties": [
+        {
+            "name": "grails.controllers.defaultScope",
+            "type": "java.lang.String",
+            "description": "The default scope for controllers (singleton, prototype, session).",
+            "defaultValue": "singleton"
+        },
+        {
+            "name": "grails.controllers.upload.location",
+            "type": "java.lang.String",
+            "description": "The directory for temporary file uploads.",
+            "defaultValue": "System.getProperty('java.io.tmpdir')"
+        },
+        {
+            "name": "grails.controllers.upload.maxFileSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum file size for uploads (in bytes).",
+            "defaultValue": 1048576
+        },
+        {
+            "name": "grails.controllers.upload.maxRequestSize",
+            "type": "java.lang.Integer",
+            "description": "Maximum request size for multipart uploads (in bytes).",
+            "defaultValue": 10485760
+        },
+        {
+            "name": "grails.controllers.upload.fileSizeThreshold",
+            "type": "java.lang.Integer",
+            "description": "File size threshold (in bytes) above which uploads are written to disk.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.web.url.converter",
+            "type": "java.lang.String",
+            "description": "The URL token converter strategy, use hyphenated for hyphen-separated URLs.",
+            "defaultValue": "camelCase"
+        },
+        {
+            "name": "grails.web.linkGenerator.useCache",
+            "type": "java.lang.Boolean",
+            "description": "Whether to cache links generated by the link generator.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.web.servlet.path",
+            "type": "java.lang.String",
+            "description": "The path the Grails dispatcher servlet is mapped to.",
+            "defaultValue": "/**"
+        },
+        {
+            "name": "grails.filter.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for the Grails character encoding filter.",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.filter.forceEncoding",
+            "type": "java.lang.Boolean",
+            "description": "Whether to force the encoding filter to set the encoding on the response.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.logRequestParameters",
+            "type": "java.lang.Boolean",
+            "description": "Whether to log request parameters in exception stack traces.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.exceptionresolver.params.exclude",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of parameter names to mask (replace with [*****]) in exception stack traces, typically used for password and creditCard.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.logging.stackTraceFiltererClass",
+            "type": "java.lang.String",
+            "description": "Fully qualified class name of a custom StackTraceFilterer implementation.",
+            "defaultValue": "org.grails.exceptions.reporting.DefaultStackTraceFilterer"
+        },
+        {
+            "name": "grails.cors.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS support is enabled.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.filter",
+            "type": "java.lang.Boolean",
+            "description": "Whether CORS is handled via a servlet filter (true) or an interceptor (false).",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.cors.allowedOrigins",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed origins (e.g., http://localhost:5000), only applies when grails.cors.enabled is true.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedMethods",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed HTTP methods.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.allowedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of allowed request headers.",
+            "defaultValue": ["*"]
+        },
+        {
+            "name": "grails.cors.exposedHeaders",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of response headers to expose to the client.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.cors.maxAge",
+            "type": "java.lang.Integer",
+            "description": "How long (in seconds) the preflight response can be cached.",
+            "defaultValue": 1800
+        },
+        {
+            "name": "grails.cors.allowCredentials",
+            "type": "java.lang.Boolean",
+            "description": "Whether credentials (cookies, authorization headers) are supported.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.cors.mappings",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of URL patterns to per-path CORS configuration where defining any mapping disables the global /** mapping.",
+            "defaultValue": {}
+        },
+        {
+            "name": "grails.mime.types",
+            "type": "java.util.Map<java.lang.String,java.lang.Object>",
+            "description": "Map of MIME type names to content type strings used for content negotiation.",
+            "defaultValue": "see web profile application.yml"
+        },
+        {
+            "name": "grails.mime.file.extensions",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the file extension to determine the MIME type in content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.use.accept.header",
+            "type": "java.lang.Boolean",
+            "description": "Whether to use the Accept header for content negotiation.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgents",
+            "type": "java.util.List<java.lang.String>",
+            "description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
+            "defaultValue": []
+        },
+        {
+            "name": "grails.mime.disable.accept.header.userAgentsXhr",
+            "type": "java.lang.Boolean",
+            "description": "When true, XHR requests also respect the grails.mime.disable.accept.header.userAgents setting, while by default XHR requests ignore user agent filtering.",
+            "defaultValue": false
+        },
+        {
+            "name": "grails.converters.encoding",
+            "type": "java.lang.String",
+            "description": "The character encoding for converter output (JSON or XML).",
+            "defaultValue": "UTF-8"
+        },
+        {
+            "name": "grails.resources.enabled",
+            "type": "java.lang.Boolean",
+            "description": "Whether serving static files from src/main/resources/public is enabled.",
+            "defaultValue": true
+        },
+        {
+            "name": "grails.resources.pattern",
+            "type": "java.lang.String",
+            "description": "The URL path pattern for serving static resources.",
+            "defaultValue": "/static/**"
+        },
+        {
+            "name": "grails.resources.cachePeriod",
+            "type": "java.lang.Integer",
+            "description": "The cache period (in seconds) for static resource HTTP responses.",
+            "defaultValue": 0
+        },
+        {
+            "name": "grails.urlmapping.cache.maxsize",
+            "type": "java.lang.Integer",
+            "description": "The maximum size of the URL mapping cache.",
+            "defaultValue": 1000

Review Comment:
   I think this is actually `5000` (`DefaultUrlMappingsHolder`). But in generated `application.yml` files it is set to `1000`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#issuecomment-4103273129

   ## ✅ All tests passed ✅
   
   🏷️ Commit: d9055f60720a007c7f713832ccb0977a730eb671
   ▶️ Tests:  110 executed
   🟡 Checks: 0/25 completed
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Replace config-properties.yml with standard spring-configuration-metadata.json [grails-core]

Posted by "testlens-app[bot] (via GitHub)" <gi...@apache.org>.
testlens-app[bot] commented on PR #15426:
URL: https://github.com/apache/grails-core/pull/15426#issuecomment-4102299623

   ## ✅ All tests passed ✅
   
   🏷️ Commit: a7a1e22ee5083ef8e389bc09f5ccc19bc01c3899
   ▶️ Tests:  9798 executed
   ⚪️ Checks: 30/30 completed
   
   ---
   _Learn more about TestLens at [testlens.app](https://testlens.app)._
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org