diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d95bfdd..c26a200a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,20 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). -## [Unreleased] +## [1.1.136](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.136) - 2026-07-02 ### Changed -- `socket manifest gradle --facts` no longer silently skips a Gradle - configuration it can't resolve. Such configurations are now reported and stop - the run unless you pass `--ignore-unresolved`, so an incomplete scan can't slip - by unnoticed. Benign variant-selection ambiguity stays a one-line notice. +- `socket manifest gradle --facts` now reports Gradle configurations it can't resolve instead of silently skipping them, failing the run unless you pass `--ignore-unresolved` so an incomplete scan can't slip by. Benign variant-selection ambiguity stays a one-line notice. +- Config filters (`--include-configs` / `--exclude-configs`) for `socket manifest gradle`, `kotlin`, `scala`, and `maven` are now case-sensitive, as documented, and support `[...]` character classes — e.g. `*[Tt]est*` matches both `testCompileClasspath` and `androidTestCompileClasspath`. ### Fixed -- `socket manifest auto` and `scan create --auto-manifest` now detect a Gradle - project by its build files (`build.gradle`/`.kts` or `settings.gradle`/`.kts`) - instead of requiring a `gradlew` wrapper, so a project that builds with - `gradle` on your PATH — including a settings-only multi-module root — is no - longer skipped. +- `socket manifest auto` and `socket scan create --auto-manifest` now detect a Gradle project by its build files (`build.gradle`/`.kts` or `settings.gradle`/`.kts`) instead of requiring a `gradlew` wrapper, so wrapper-less projects — including settings-only multi-module roots — are no longer skipped. ## [1.1.135](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.135) - 2026-07-01 diff --git a/package.json b/package.json index e2a27457b..80a67b77b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socket", - "version": "1.1.135", + "version": "1.1.136", "description": "CLI for Socket.dev", "homepage": "https://github.com/SocketDev/socket-cli", "license": "MIT", diff --git a/src/commands/manifest/cmd-manifest-gradle.mts b/src/commands/manifest/cmd-manifest-gradle.mts index 14e74afa4..416426b93 100644 --- a/src/commands/manifest/cmd-manifest-gradle.mts +++ b/src/commands/manifest/cmd-manifest-gradle.mts @@ -45,7 +45,7 @@ const config: CliCommandConfig = { includeConfigs: { type: 'string', description: - 'When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, `*` and `?` wildcards). Only configurations matching at least one pattern are resolved. e.g. `*CompileClasspath,*RuntimeClasspath`. Default: every resolvable configuration', + 'When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive; `*`, `?`, and `[...]` wildcards). Only configurations matching at least one pattern are resolved. e.g. `*CompileClasspath,*RuntimeClasspath`. Default: every resolvable configuration', }, excludeConfigs: { type: 'string', diff --git a/src/commands/manifest/cmd-manifest-gradle.test.mts b/src/commands/manifest/cmd-manifest-gradle.test.mts index 071ac0e78..08d859a44 100644 --- a/src/commands/manifest/cmd-manifest-gradle.test.mts +++ b/src/commands/manifest/cmd-manifest-gradle.test.mts @@ -28,7 +28,7 @@ describe('socket manifest gradle', async () => { --facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph. This is the default; pass \`--pom\` to generate \`pom.xml\` files instead --gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\` --ignore-unresolved When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file) - --include-configs When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, \`*\` and \`?\` wildcards). Only configurations matching at least one pattern are resolved. e.g. \`*CompileClasspath,*RuntimeClasspath\`. Default: every resolvable configuration + --include-configs When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive; \`*\`, \`?\`, and \`[...]\` wildcards). Only configurations matching at least one pattern are resolved. e.g. \`*CompileClasspath,*RuntimeClasspath\`. Default: every resolvable configuration --pom Generate \`pom.xml\` manifest file(s) instead of the default Socket facts file (\`.socket.facts.json\`) --verbose Print debug messages diff --git a/src/commands/manifest/cmd-manifest-kotlin.mts b/src/commands/manifest/cmd-manifest-kotlin.mts index 6977a2e1b..f858b4d5a 100644 --- a/src/commands/manifest/cmd-manifest-kotlin.mts +++ b/src/commands/manifest/cmd-manifest-kotlin.mts @@ -50,7 +50,7 @@ const config: CliCommandConfig = { includeConfigs: { type: 'string', description: - 'When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, `*` and `?` wildcards). Only configurations matching at least one pattern are resolved. e.g. `*CompileClasspath,*RuntimeClasspath`. Default: every resolvable configuration', + 'When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive; `*`, `?`, and `[...]` wildcards). Only configurations matching at least one pattern are resolved. e.g. `*CompileClasspath,*RuntimeClasspath`. Default: every resolvable configuration', }, excludeConfigs: { type: 'string', diff --git a/src/commands/manifest/cmd-manifest-kotlin.test.mts b/src/commands/manifest/cmd-manifest-kotlin.test.mts index 610637606..565d6342d 100644 --- a/src/commands/manifest/cmd-manifest-kotlin.test.mts +++ b/src/commands/manifest/cmd-manifest-kotlin.test.mts @@ -28,7 +28,7 @@ describe('socket manifest kotlin', async () => { --facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph. This is the default; pass \`--pom\` to generate \`pom.xml\` files instead --gradle-opts Additional options to pass on to ./gradlew, see \`./gradlew --help\` --ignore-unresolved When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file) - --include-configs When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive, \`*\` and \`?\` wildcards). Only configurations matching at least one pattern are resolved. e.g. \`*CompileClasspath,*RuntimeClasspath\`. Default: every resolvable configuration + --include-configs When generating facts: comma-separated glob patterns matched against Gradle configuration names (case-sensitive; \`*\`, \`?\`, and \`[...]\` wildcards). Only configurations matching at least one pattern are resolved. e.g. \`*CompileClasspath,*RuntimeClasspath\`. Default: every resolvable configuration --pom Generate \`pom.xml\` manifest file(s) instead of the default Socket facts file (\`.socket.facts.json\`) --verbose Print debug messages diff --git a/src/commands/manifest/cmd-manifest-maven.mts b/src/commands/manifest/cmd-manifest-maven.mts index f28510844..7d41faef1 100644 --- a/src/commands/manifest/cmd-manifest-maven.mts +++ b/src/commands/manifest/cmd-manifest-maven.mts @@ -34,7 +34,7 @@ const config: CliCommandConfig = { includeConfigs: { type: 'string', description: - 'Comma-separated glob patterns matched against Maven dependency scopes (case-sensitive, `*` and `?` wildcards). Only scopes matching at least one pattern are resolved. e.g. `compile,runtime`. Default: every scope', + 'Comma-separated glob patterns matched against Maven dependency scopes (case-sensitive; `*`, `?`, and `[...]` wildcards). Only scopes matching at least one pattern are resolved. e.g. `compile,runtime`. Default: every scope', }, excludeConfigs: { type: 'string', diff --git a/src/commands/manifest/cmd-manifest-maven.test.mts b/src/commands/manifest/cmd-manifest-maven.test.mts index 765abe9e5..18460c713 100644 --- a/src/commands/manifest/cmd-manifest-maven.test.mts +++ b/src/commands/manifest/cmd-manifest-maven.test.mts @@ -25,7 +25,7 @@ describe('socket manifest maven', async () => { --bin Location of the maven binary to use, default: ./mvnw if present, else mvn on PATH --exclude-configs Comma-separated glob patterns; Maven scopes matching any pattern are skipped (applied after --include-configs) --ignore-unresolved Warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file) - --include-configs Comma-separated glob patterns matched against Maven dependency scopes (case-sensitive, \`*\` and \`?\` wildcards). Only scopes matching at least one pattern are resolved. e.g. \`compile,runtime\`. Default: every scope + --include-configs Comma-separated glob patterns matched against Maven dependency scopes (case-sensitive; \`*\`, \`?\`, and \`[...]\` wildcards). Only scopes matching at least one pattern are resolved. e.g. \`compile,runtime\`. Default: every scope --maven-opts Additional options to pass on to maven, e.g. \`-P -s \` --verbose Print debug messages diff --git a/src/commands/manifest/cmd-manifest-scala.mts b/src/commands/manifest/cmd-manifest-scala.mts index 6d4861b7e..5f3ab20e0 100644 --- a/src/commands/manifest/cmd-manifest-scala.mts +++ b/src/commands/manifest/cmd-manifest-scala.mts @@ -43,7 +43,7 @@ const config: CliCommandConfig = { includeConfigs: { type: 'string', description: - 'When generating facts: comma-separated glob patterns matched against sbt configuration names (case-sensitive, `*` and `?` wildcards). Only configurations matching at least one pattern are resolved. e.g. `compile,test`. Default: compile,optional,provided,runtime,test', + 'When generating facts: comma-separated glob patterns matched against sbt configuration names (case-sensitive; `*`, `?`, and `[...]` wildcards). Only configurations matching at least one pattern are resolved. e.g. `compile,test`. Default: compile,optional,provided,runtime,test', }, excludeConfigs: { type: 'string', diff --git a/src/commands/manifest/cmd-manifest-scala.test.mts b/src/commands/manifest/cmd-manifest-scala.test.mts index cd5e6a6c9..893d16f4b 100644 --- a/src/commands/manifest/cmd-manifest-scala.test.mts +++ b/src/commands/manifest/cmd-manifest-scala.test.mts @@ -27,7 +27,7 @@ describe('socket manifest scala', async () => { --exclude-configs When generating facts: comma-separated glob patterns; sbt configurations matching any pattern are skipped (applied after --include-configs) --facts Emit a Socket facts JSON file (\`.socket.facts.json\`) describing the resolved dependency graph. This is the default; pass \`--pom\` to generate \`pom.xml\` files instead --ignore-unresolved When generating facts: warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts file) - --include-configs When generating facts: comma-separated glob patterns matched against sbt configuration names (case-sensitive, \`*\` and \`?\` wildcards). Only configurations matching at least one pattern are resolved. e.g. \`compile,test\`. Default: compile,optional,provided,runtime,test + --include-configs When generating facts: comma-separated glob patterns matched against sbt configuration names (case-sensitive; \`*\`, \`?\`, and \`[...]\` wildcards). Only configurations matching at least one pattern are resolved. e.g. \`compile,test\`. Default: compile,optional,provided,runtime,test --out Only with --pom: path of the output \`pom.xml\`, see also --stdout. Does not apply when generating Socket facts (always written to the project root as \`.socket.facts.json\`) --pom Generate \`pom.xml\` manifest file(s) instead of the default Socket facts file (\`.socket.facts.json\`) --sbt-opts Additional options to pass on to sbt, as per \`sbt --help\` diff --git a/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketSupport.java b/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketSupport.java index 5d9c670a0..edcb5fe8c 100644 --- a/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketSupport.java +++ b/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketSupport.java @@ -39,25 +39,44 @@ public static String bareId(String groupId, String artifactId, String version) { return coordId(groupId, artifactId, null, null, version); } - /** Translate a config-name glob ({@code *}/{@code ?}) to a case-insensitive regex. */ + /** + * Translate a config-name glob to a case-sensitive regex. Supports {@code *}, {@code ?}, and + * {@code [...]} character classes: enumerations ({@code [cC]}), ranges ({@code [a-z]}), and + * {@code [!..]}/{@code [^..]} negation. A malformed glob falls back to a literal match, never throws. + */ public static Pattern globToRegex(String glob) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < glob.length(); i++) { + int i = 0; + int n = glob.length(); + while (i < n) { char c = glob.charAt(i); - switch (c) { - case '*': sb.append(".*"); break; - case '?': sb.append('.'); break; - case '\\': case '.': case '^': case '$': case '|': - case '+': case '(': case ')': - case '[': case ']': case '{': case '}': - sb.append('\\').append(c); break; - default: sb.append(c); - } + if (c == '*') { sb.append(".*"); i++; } + else if (c == '?') { sb.append('.'); i++; } + else if (c == '[') { + int j = glob.indexOf(']', i + 1); + // Treat as a class only with a non-empty body; else a literal '['. + if (j <= i + 1) { sb.append("\\["); i++; } + else { + String body = glob.substring(i + 1, j); + boolean neg = body.startsWith("!"); + if (neg) body = body.substring(1); + // Only literal chars and '-' ranges are meaningful; neutralize regex-class tricks. + body = body.replace("\\", "\\\\").replace("[", "\\[").replace("&", "\\&"); + sb.append('[').append(neg ? "^" : "").append(body).append(']'); + i = j + 1; + } + } else if ("\\.^$|+(){}]".indexOf(c) >= 0) { + sb.append('\\').append(c); i++; + } else { sb.append(c); i++; } + } + try { + return Pattern.compile(sb.toString()); + } catch (java.util.regex.PatternSyntaxException e) { + return Pattern.compile(Pattern.quote(glob)); } - return Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE); } - /** Parse a comma-separated list of globs into case-insensitive patterns. */ + /** Parse a comma-separated list of globs into case-sensitive patterns. */ public static List parsePatterns(String csv) { List out = new ArrayList<>(); if (csv == null || csv.trim().isEmpty()) return out; diff --git a/src/commands/manifest/scripts/socket-facts.init.gradle b/src/commands/manifest/scripts/socket-facts.init.gradle index ceb0b84c2..bb9269d11 100644 --- a/src/commands/manifest/scripts/socket-facts.init.gradle +++ b/src/commands/manifest/scripts/socket-facts.init.gradle @@ -286,22 +286,39 @@ allprojects { project -> n.contains('classpath') || n == 'compile' || n == 'runtime' } - // Config-name glob (`*`/`?` only) to a case-INSENSITIVE regex — Gradle config names are - // camelCase, so a case-sensitive `*Classpath` would miss the base `compileClasspath`. + // Config-name glob to a case-SENSITIVE regex (matching is documented as case-sensitive). + // Supports `*`, `?`, and `[...]` character classes: enumerations (`[cC]`), ranges (`[a-z]`), + // and `[!..]`/`[^..]` negation. A malformed glob falls back to a literal match, never throws. def globToRegex = { String glob -> def sb = new StringBuilder() - glob.each { String ch -> - switch (ch) { - case '*': sb << '.*'; break - case '?': sb << '.'; break - case '.': case '\\': case '^': case '$': case '|': - case '+': case '(': case ')': - case '[': case ']': case '{': case '}': - sb << '\\' << ch; break - default: sb << ch + int i = 0 + int n = glob.length() + while (i < n) { + def ch = glob[i] + if (ch == '*') { sb << '.*'; i++ } + else if (ch == '?') { sb << '.'; i++ } + else if (ch == '[') { + int j = glob.indexOf(']', i + 1) + // Treat as a class only with a non-empty body; else a literal `[`. + if (j <= i + 1) { sb << '\\['; i++ } + else { + def body = glob.substring(i + 1, j) + boolean neg = body.startsWith('!') + if (neg) { body = body.substring(1) } + // Only literal chars and `-` ranges are meaningful; neutralize regex-class tricks. + body = body.replace('\\', '\\\\').replace('[', '\\[').replace('&', '\\&') + sb << '[' << (neg ? '^' : '') << body << ']' + i = j + 1 + } } + else if ('.\\^$|+(){}]'.contains(ch)) { sb << '\\' << ch; i++ } + else { sb << ch; i++ } + } + try { + java.util.regex.Pattern.compile(sb.toString()) + } catch (java.util.regex.PatternSyntaxException e) { + java.util.regex.Pattern.compile(java.util.regex.Pattern.quote(glob)) } - java.util.regex.Pattern.compile(sb.toString(), java.util.regex.Pattern.CASE_INSENSITIVE) } // `-Psocket.includeConfigs`/`-Psocket.excludeConfigs`: comma-separated config-name globs. A diff --git a/src/commands/manifest/scripts/socket-facts.plugin.scala b/src/commands/manifest/scripts/socket-facts.plugin.scala index b121c31bb..5d1f518ac 100644 --- a/src/commands/manifest/scripts/socket-facts.plugin.scala +++ b/src/commands/manifest/scripts/socket-facts.plugin.scala @@ -336,16 +336,39 @@ object SocketFactsPlugin extends AutoPlugin { } } + // Case-SENSITIVE (matching is documented as case-sensitive). Supports `*`, `?`, and `[...]` + // character classes: enumerations (`[cC]`), ranges (`[a-z]`), and `[!..]`/`[^..]` negation. A + // malformed glob falls back to a literal match, never throws. private def globToRegex(glob: String): java.util.regex.Pattern = { val sb = new StringBuilder - glob.foreach { - case '*' => sb.append(".*") - case '?' => sb.append('.') - case c if "\\.^$|+()[]{}".indexOf(c.toInt) >= 0 => - sb.append('\\').append(c) - case c => sb.append(c) + var i = 0 + val n = glob.length + while (i < n) { + val c = glob.charAt(i) + if (c == '*') { sb.append(".*"); i += 1 } + else if (c == '?') { sb.append('.'); i += 1 } + else if (c == '[') { + val j = glob.indexOf(']', i + 1) + // Treat as a class only with a non-empty body; else a literal `[`. + if (j <= i + 1) { sb.append("\\["); i += 1 } + else { + var body = glob.substring(i + 1, j) + val neg = body.startsWith("!") + if (neg) body = body.substring(1) + // Only literal chars and `-` ranges are meaningful; neutralize regex-class tricks. + body = body.replace("\\", "\\\\").replace("[", "\\[").replace("&", "\\&") + sb.append('[').append(if (neg) "^" else "").append(body).append(']') + i = j + 1 + } + } else if ("\\.^$|+(){}]".indexOf(c.toInt) >= 0) { + sb.append('\\').append(c); i += 1 + } else { sb.append(c); i += 1 } + } + try java.util.regex.Pattern.compile(sb.toString) + catch { + case _: java.util.regex.PatternSyntaxException => + java.util.regex.Pattern.compile(java.util.regex.Pattern.quote(glob)) } - java.util.regex.Pattern.compile(sb.toString, java.util.regex.Pattern.CASE_INSENSITIVE) } // ConfigurationReport.configuration is a String on sbt 0.13, a ConfigRef on 1.x: read `.name` reflectively.