├── .all-contributorsrc
├── .git-blame-ignore-revs
├── .github
├── dependabot.yml
└── workflows
│ ├── build.yml
│ ├── early-access.yml
│ ├── mutation-testing.yml
│ └── release.yml
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── .mvn
└── .gitkeep
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── jreleaser.yml
├── pom.xml
└── src
├── main
├── assembly
│ └── assembly.xml
├── graalvm-native
│ └── resource-config.json
├── java
│ └── it
│ │ └── mulders
│ │ └── mcs
│ │ ├── App.java
│ │ ├── cli
│ │ ├── ClassSearchCommand.java
│ │ ├── ClasspathVersionProvider.java
│ │ ├── Cli.java
│ │ ├── SearchCommand.java
│ │ └── SystemPropertyLoader.java
│ │ ├── common
│ │ ├── McsExecutionExceptionHandler.java
│ │ ├── McsRuntimeException.java
│ │ ├── Result.java
│ │ └── SearchResponseBodyHandler.java
│ │ ├── dagger
│ │ ├── Application.java
│ │ ├── CommandLineModule.java
│ │ ├── DaggerFactory.java
│ │ ├── OutputModule.java
│ │ └── SearchModule.java
│ │ └── search
│ │ ├── ClassnameQuery.java
│ │ ├── Constants.java
│ │ ├── CoordinateQuery.java
│ │ ├── FormatType.java
│ │ ├── SearchClient.java
│ │ ├── SearchCommandHandler.java
│ │ ├── SearchQuery.java
│ │ ├── SearchResponse.java
│ │ ├── UnsupportedFormatException.java
│ │ ├── WildcardSearchQuery.java
│ │ ├── printer
│ │ ├── BuildrOutput.java
│ │ ├── CoordinatePrinter.java
│ │ ├── DelegatingOutputPrinter.java
│ │ ├── GavOutput.java
│ │ ├── GradleGroovyOutput.java
│ │ ├── GradleGroovyShortOutput.java
│ │ ├── GradleKotlinOutput.java
│ │ ├── GrapeOutput.java
│ │ ├── IvyXmlOutput.java
│ │ ├── JBangOutput.java
│ │ ├── LeiningenOutput.java
│ │ ├── NoOutputPrinter.java
│ │ ├── OutputFactory.java
│ │ ├── OutputPrinter.java
│ │ ├── PomXmlOutput.java
│ │ ├── SbtOutput.java
│ │ └── TabularOutputPrinter.java
│ │ └── vulnerability
│ │ ├── ComponentReportClient.java
│ │ ├── ComponentReportResponse.java
│ │ ├── ComponentReportResponseBodyHandler.java
│ │ └── ComponentReportVulnerabilitySeverity.java
└── resources
│ └── mcs.properties
└── test
├── java
└── it
│ └── mulders
│ └── mcs
│ ├── AppIT.java
│ ├── AppTest.java
│ ├── cli
│ ├── ClassSearchCommandTest.java
│ ├── ClasspathVersionProviderTest.java
│ ├── CliTest.java
│ ├── DaggerFactoryTest.java
│ ├── MockitoFactory.java
│ ├── SearchCommandTest.java
│ └── SystemPropertyLoaderTest.java
│ ├── common
│ ├── McsExecutionExceptionHandlerTest.java
│ ├── ResultTest.java
│ └── SearchResponseBodyHandlerTest.java
│ └── search
│ ├── ClassnameQueryTest.java
│ ├── CoordinateQueryTest.java
│ ├── FormatTypeTest.java
│ ├── SearchClientIT.java
│ ├── SearchCommandHandlerTest.java
│ ├── SearchQueryTest.java
│ ├── WildcardSearchQueryTest.java
│ ├── printer
│ ├── CoordinatePrinterTest.java
│ ├── DelegatingOutputPrinterTest.java
│ ├── NoOutputPrinterTest.java
│ └── TabularOutputPrinterTest.java
│ └── vulnerability
│ ├── ComponentReportClientIT.java
│ ├── ComponentReportResponseBodyHandlerTest.java
│ └── ComponentReportVulnerabilitySeverityTest.java
└── resources
├── group-artifact-search.json
├── group-artifact-version-search.json
├── mcs.properties
├── no-vulnerabilities-component-report-response.json
├── sample-mcs.config
├── vulnerabilities-component-report-response.json
└── wildcard-search-response.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "mcs",
3 | "projectOwner": "mthmulders",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "CONTRIBUTORS.md"
8 | ],
9 | "imageSize": 100,
10 | "contributorsPerLine": 7,
11 | "skipCi": true,
12 | "commitType": "docs",
13 | "commitConvention": "angular",
14 | "contributors": [
15 | {
16 | "login": "willemvanlent",
17 | "name": "Willem van Lent",
18 | "avatar_url": "https://avatars.githubusercontent.com/u/4223994?v=4",
19 | "profile": "https://github.com/willemvanlent",
20 | "contributions": [
21 | "code"
22 | ]
23 | },
24 | {
25 | "login": "hannotify",
26 | "name": "Hanno Embregts",
27 | "avatar_url": "https://avatars.githubusercontent.com/u/11613148?v=4",
28 | "profile": "https://hanno.codes",
29 | "contributions": [
30 | "code",
31 | "ideas"
32 | ]
33 | },
34 | {
35 | "login": "BranislavBeno",
36 | "name": "Branislav Beňo",
37 | "avatar_url": "https://avatars.githubusercontent.com/u/57846939?v=4",
38 | "profile": "https://github.com/BranislavBeno",
39 | "contributions": [
40 | "code"
41 | ]
42 | },
43 | {
44 | "login": "Giovds",
45 | "name": "Giovanni van der Schelde",
46 | "avatar_url": "https://avatars.githubusercontent.com/u/27761321?v=4",
47 | "profile": "https://giovds.com/",
48 | "contributions": [
49 | "code"
50 | ]
51 | },
52 | {
53 | "login": "martinbonnin",
54 | "name": "Martin Bonnin",
55 | "avatar_url": "https://avatars.githubusercontent.com/u/3974977?v=4",
56 | "profile": "https://mbonnin.net",
57 | "contributions": [
58 | "ideas"
59 | ]
60 | },
61 | {
62 | "login": "BOTbkcd",
63 | "name": "bot_bkcd",
64 | "avatar_url": "https://avatars.githubusercontent.com/u/83156045?v=4",
65 | "profile": "https://github.com/BOTbkcd",
66 | "contributions": [
67 | "code"
68 | ]
69 | },
70 | {
71 | "login": "shaikhu",
72 | "name": "Usman Shaikh",
73 | "avatar_url": "https://avatars.githubusercontent.com/u/38332365?v=4",
74 | "profile": "https://github.com/shaikhu",
75 | "contributions": [
76 | "code"
77 | ]
78 | },
79 | {
80 | "login": "jwedel",
81 | "name": "Jan Wedel",
82 | "avatar_url": "https://avatars.githubusercontent.com/u/4849728?v=4",
83 | "profile": "http://return.co.de",
84 | "contributions": [
85 | "code"
86 | ]
87 | },
88 | {
89 | "login": "frimtec",
90 | "name": "Markus Friedli",
91 | "avatar_url": "https://avatars.githubusercontent.com/u/3511114?v=4",
92 | "profile": "https://github.com/frimtec",
93 | "contributions": [
94 | "bug"
95 | ]
96 | },
97 | {
98 | "login": "slovdahl",
99 | "name": "Sebastian Lövdahl",
100 | "avatar_url": "https://avatars.githubusercontent.com/u/1417619?v=4",
101 | "profile": "https://github.com/slovdahl",
102 | "contributions": [
103 | "bug"
104 | ]
105 | },
106 | {
107 | "login": "dhinojosa",
108 | "name": "Daniel Hinojosa",
109 | "avatar_url": "https://avatars.githubusercontent.com/u/410757?v=4",
110 | "profile": "http://www.evolutionnext.com",
111 | "contributions": [
112 | "bug"
113 | ]
114 | },
115 | {
116 | "login": "spencergibb",
117 | "name": "Spencer Gibb",
118 | "avatar_url": "https://avatars.githubusercontent.com/u/594085?v=4",
119 | "profile": "https://gibb.tech",
120 | "contributions": [
121 | "ideas"
122 | ]
123 | },
124 | {
125 | "login": "jeffmaury",
126 | "name": "Jeff MAURY",
127 | "avatar_url": "https://avatars.githubusercontent.com/u/695993?v=4",
128 | "profile": "http://riadiscuss.jeffmaury.com",
129 | "contributions": [
130 | "bug",
131 | "ideas"
132 | ]
133 | },
134 | {
135 | "login": "jludvice",
136 | "name": "Josef Ludvicek",
137 | "avatar_url": "https://avatars.githubusercontent.com/u/8707241?v=4",
138 | "profile": "https://github.com/jludvice",
139 | "contributions": [
140 | "bug"
141 | ]
142 | },
143 | {
144 | "login": "bmarwell",
145 | "name": "Benjamin Marwell",
146 | "avatar_url": "https://avatars.githubusercontent.com/u/1413391?v=4",
147 | "profile": "https://blog.bmarwell.de/",
148 | "contributions": [
149 | "ideas"
150 | ]
151 | },
152 | {
153 | "login": "AbdelHajou",
154 | "name": "Abdel Hajou",
155 | "avatar_url": "https://avatars.githubusercontent.com/u/62144407?v=4",
156 | "profile": "https://github.com/AbdelHajou",
157 | "contributions": [
158 | "ideas"
159 | ]
160 | },
161 | {
162 | "login": "barrantesgerman",
163 | "name": "Herman Barrantes",
164 | "avatar_url": "https://avatars.githubusercontent.com/u/1646195?v=4",
165 | "profile": "https://www.hermanbarrantes.dev/",
166 | "contributions": [
167 | "ideas"
168 | ]
169 | },
170 | {
171 | "login": "aalmiray",
172 | "name": "Andres Almiray",
173 | "avatar_url": "https://avatars.githubusercontent.com/u/13969?v=4",
174 | "profile": "https://andresalmiray.com/",
175 | "contributions": [
176 | "ideas"
177 | ]
178 | },
179 | {
180 | "login": "maddingo",
181 | "name": "Martin Goldhahn",
182 | "avatar_url": "https://avatars.githubusercontent.com/u/362120?v=4",
183 | "profile": "https://github.com/maddingo",
184 | "contributions": [
185 | "code"
186 | ]
187 | },
188 | {
189 | "login": "maxandersen",
190 | "name": "Max Rydahl Andersen",
191 | "avatar_url": "https://avatars.githubusercontent.com/u/54129?v=4",
192 | "profile": "https://xam.dk",
193 | "contributions": [
194 | "ideas",
195 | "code"
196 | ]
197 | },
198 | {
199 | "login": "jreleaser",
200 | "name": "JReleaser",
201 | "avatar_url": "https://avatars.githubusercontent.com/u/81020166?v=4",
202 | "profile": "https://jreleaser.org",
203 | "contributions": [
204 | "tool"
205 | ]
206 | }
207 | ]
208 | }
209 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | 794ddb1f7943af888bafb9dcc0930f775b6e0cbc
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: maven
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "04:00"
8 | open-pull-requests-limit: 10
9 | - package-ecosystem: github-actions
10 | directory: "/"
11 | schedule:
12 | interval: daily
13 | time: "04:00"
14 | open-pull-requests-limit: 10
15 |
16 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | if: contains(github.event.head_commit.message, 'Releasing version') != true && contains(github.event.head_commit.message, 'Prepare next version') != true
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Check out repository
16 | uses: actions/checkout@v4.2.2
17 | with:
18 | # Disabling shallow clone is recommended for improving relevancy of reporting
19 | fetch-depth: 0
20 | - name: Set up JDK
21 | uses: actions/setup-java@v4.7.1
22 | with:
23 | java-version: 21
24 | distribution: 'adopt'
25 | cache: maven
26 | - name: Cache Maven packages
27 | id: restore-maven-package-cache
28 | uses: actions/cache/restore@v4.2.3
29 | with:
30 | path: ~/.m2
31 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
32 | restore-keys: ${{ runner.os }}-m2
33 | - name: Build with Maven
34 | run: mvn -B verify
35 | - name: Save downloaded Maven packages
36 | uses: actions/cache/save@v4.2.3
37 | with:
38 | path: ~/.m2
39 | key: ${{ steps.restore-maven-package-cache.outputs.cache-primary-key }}
40 | if: github.event_name != 'pull_request'
--------------------------------------------------------------------------------
/.github/workflows/early-access.yml:
--------------------------------------------------------------------------------
1 | # Inspired by & copied from JReleaser sample:
2 | # https://github.com/jreleaser/jreleaser/blob/main/.github/workflows/trigger-early-access.yml
3 |
4 | name: Publish Early Access builds
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | jobs:
13 | # Build native executable per runner
14 | build:
15 | if: contains(github.event.head_commit.message, 'Releasing version') != true && contains(github.event.head_commit.message, 'Prepare next version') != true
16 | name: build-${{ matrix.os }}
17 | strategy:
18 | fail-fast: true
19 | matrix:
20 | os: [ ubuntu-latest, macos-13, macos-14, windows-latest ]
21 | gu-binary: [ gu, gu.cmd ]
22 | exclude:
23 | - os: ubuntu-latest
24 | gu-binary: gu.cmd
25 | - os: macos-13
26 | gu-binary: gu.cmd
27 | - os: macos-14
28 | gu-binary: gu.cmd
29 | - os: windows-latest
30 | gu-binary: gu
31 | runs-on: ${{ matrix.os }}
32 |
33 | steps:
34 | - name: Download all build artifacts
35 | uses: actions/download-artifact@v4.3.0
36 |
37 | - name: Check out repository
38 | uses: actions/checkout@v4.2.2
39 | with:
40 | ref: ${{ steps.head.outputs.content }}
41 |
42 | # This action supports Windows; it does nothing on Linux and macOS.
43 | - name: Add Developer Command Prompt for Microsoft Visual C++
44 | uses: ilammy/msvc-dev-cmd@v1.13.0
45 |
46 | - name: Set up JDK
47 | uses: actions/setup-java@v4.7.1
48 | with:
49 | distribution: 'graalvm'
50 | java-version: 21
51 |
52 | - name: Get musl toolchain and compile libz against it
53 | id: prepare-musl
54 | run: |
55 | TMP_DIR=$(mktemp -d)
56 | pushd $TMP_DIR
57 | curl -LOJ http://more.musl.cc/10/x86_64-linux-musl/x86_64-linux-musl-native.tgz
58 | tar -xvf x86_64-linux-musl-native.tgz
59 |
60 | curl -LOJ https://zlib.net/fossils/zlib-1.3.tar.gz
61 | tar -xzf zlib-1.3.tar.gz
62 | cd zlib-1.3
63 |
64 | TOOLCHAIN_DIR=$TMP_DIR/x86_64-linux-musl-native
65 | CC=$TOOLCHAIN_DIR/bin/gcc
66 |
67 | ./configure --prefix=$TOOLCHAIN_DIR --static
68 | make
69 | make install
70 |
71 | echo "TOOLCHAIN_DIR=$TOOLCHAIN_DIR" >> $GITHUB_OUTPUT
72 | if: matrix.os == 'ubuntu-latest'
73 |
74 | - name: Cache Maven packages
75 | id: restore-maven-package-cache
76 | uses: actions/cache/restore@v4.2.3
77 | with:
78 | path: ~/.m2
79 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
80 | restore-keys: ${{ runner.os }}-m2
81 |
82 | - name: Build static native image for Linux
83 | run: |
84 | PATH=${TOOLCHAIN_DIR}/bin:$PATH; mvn -B -Pnative package
85 | env:
86 | TOOLCHAIN_DIR: ${{ steps.prepare-musl.outputs.TOOLCHAIN_DIR }}
87 | if: matrix.os == 'ubuntu-latest'
88 |
89 | - name: Build static native image for Windows / macOS
90 | run: |
91 | mvn -B -Pnative package
92 | if: matrix.os != 'ubuntu-latest'
93 |
94 | - name: Create distribution
95 | run: mvn -B -Pdist package -DskipTests
96 |
97 | - name: Upload build artifacts
98 | uses: actions/upload-artifact@v4.6.2
99 | with:
100 | name: artifacts-${{ matrix.os }}
101 | path: |
102 | target/distributions/*.zip
103 | target/distributions/*.tar.gz
104 |
105 | - name: Save downloaded Maven packages
106 | uses: actions/cache/save@v4.2.3
107 | with:
108 | path: ~/.m2
109 | key: ${{ steps.restore-maven-package-cache.outputs.cache-primary-key }}
110 | if: github.event_name != 'pull_request'
111 |
112 | # Collect all executables and release
113 | release:
114 | needs: [ build ]
115 | runs-on: ubuntu-latest
116 | permissions: write-all
117 | if: github.event_name != 'pull_request'
118 |
119 | steps:
120 | - name: Check out repository
121 | uses: actions/checkout@v4.2.2
122 | with:
123 | fetch-depth: 0
124 |
125 | - name: Check out correct Git ref
126 | run: git checkout ${{ steps.head.outputs.content }}
127 |
128 | - name: Download all build artifacts
129 | uses: actions/download-artifact@v4.3.0
130 | with:
131 | path: /tmp/artifacts
132 |
133 | - name: Move build artifacts to correct folder
134 | run: |
135 | targets=("ubuntu-latest" "macos-13" "macos-14" "windows-latest")
136 |
137 | mkdir -p artifacts
138 |
139 | find /tmp/artifacts/ -name "mcs*" -exec mv -v {} artifacts/ \;
140 |
141 | - name: Cache Maven packages
142 | uses: actions/cache@v4.2.3
143 | with:
144 | path: ~/.m2
145 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
146 | restore-keys: ${{ runner.os }}-m2
147 |
148 | - name: Release with JReleaser
149 | run: mvn -B -Prelease -DartifactsDir=artifacts jreleaser:full-release
150 | env:
151 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
152 |
153 | - name: Capture JReleaser output
154 | if: always()
155 | uses: actions/upload-artifact@v4.6.2
156 | with:
157 | name: jreleaser-release-output
158 | retention-days: 7
159 | path: |
160 | target/jreleaser/trace.log
161 | target/jreleaser/output.properties
--------------------------------------------------------------------------------
/.github/workflows/mutation-testing.yml:
--------------------------------------------------------------------------------
1 | name: Mutation testing
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | mutation-testing:
11 | if: contains(github.event.head_commit.message, 'Releasing version') != true && contains(github.event.head_commit.message, 'Prepare next version') != true
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Check out repository
16 | uses: actions/checkout@v4.2.2
17 | with:
18 | # Disabling shallow clone is recommended for improving relevancy of reporting
19 | fetch-depth: 0
20 |
21 | - name: Cache Maven packages
22 | uses: actions/cache@v4.2.3
23 | with:
24 | path: ~/.m2
25 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
26 | restore-keys: ${{ runner.os }}-m2
27 |
28 | - name: Set up JDK
29 | uses: actions/setup-java@v4.7.1
30 | with:
31 | java-version: 21
32 | distribution: 'adopt'
33 | cache: maven
34 |
35 | - name: Run Pitest
36 | run: mvn -B test-compile org.pitest:pitest-maven:mutationCoverage
37 | env:
38 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
39 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # Inspired by & copied from JReleaser sample:
2 | # https://github.com/aalmiray/q-cli/blob/main/.github/workflows/release.yml
3 |
4 | name: Release
5 |
6 | on:
7 | workflow_dispatch:
8 | inputs:
9 | version:
10 | description: "Release version"
11 | required: true
12 | next:
13 | description: "Next version"
14 | required: false
15 |
16 | jobs:
17 | version:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - name: Check out repository
22 | uses: actions/checkout@v4.2.2
23 |
24 | - name: Set up Java
25 | uses: actions/setup-java@v4.7.1
26 | with:
27 | java-version: 21
28 | distribution: 'adopt'
29 |
30 | - name: Cache Maven packages
31 | uses: actions/cache/restore@v4.2.3
32 | with:
33 | path: ~/.m2
34 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
35 | restore-keys: ${{ runner.os }}-m2
36 |
37 | - name: Set release version
38 | id: version
39 | run: |
40 | RELEASE_VERSION=${{ github.event.inputs.version }}
41 | NEXT_VERSION=${{ github.event.inputs.next }}
42 | PLAIN_VERSION=`echo ${RELEASE_VERSION} | awk 'match($0, /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)/) { print substr($0, RSTART, RLENGTH); }'`
43 | COMPUTED_NEXT_VERSION="${PLAIN_VERSION}-SNAPSHOT"
44 | if [ -z $NEXT_VERSION ]
45 | then
46 | NEXT_VERSION=$COMPUTED_NEXT_VERSION
47 | fi
48 | mvn -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION
49 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
50 | git config --global user.name "GitHub Action"
51 | git commit -a -m "chore: Releasing version $RELEASE_VERSION"
52 | git push origin HEAD:main
53 | git rev-parse HEAD > HEAD
54 | echo $RELEASE_VERSION > RELEASE_VERSION
55 | echo $PLAIN_VERSION > PLAIN_VERSION
56 | echo $NEXT_VERSION > NEXT_VERSION
57 |
58 | - name: Upload version files
59 | uses: actions/upload-artifact@v4.6.2
60 | with:
61 | name: artifacts
62 | path: |
63 | HEAD
64 | *_VERSION
65 |
66 | # Build native executable per runner
67 | build:
68 | needs: [ version ]
69 | name: build-${{ matrix.os }}
70 | strategy:
71 | fail-fast: true
72 | matrix:
73 | os: [ ubuntu-latest, macos-13, macos-14, windows-latest ]
74 | gu-binary: [ gu, gu.cmd ]
75 | exclude:
76 | - os: ubuntu-latest
77 | gu-binary: gu.cmd
78 | - os: macos-13
79 | gu-binary: gu.cmd
80 | - os: macos-14
81 | gu-binary: gu.cmd
82 | - os: windows-latest
83 | gu-binary: gu
84 | runs-on: ${{ matrix.os }}
85 |
86 | steps:
87 | - name: Download all build artifacts
88 | uses: actions/download-artifact@v4.3.0
89 |
90 | - name: Read HEAD ref
91 | id: head
92 | uses: juliangruber/read-file-action@v1.1.7
93 | with:
94 | path: artifacts/HEAD
95 |
96 | - name: Check out repository
97 | uses: actions/checkout@v4.2.2
98 | with:
99 | ref: ${{ steps.head.outputs.content }}
100 |
101 | # This action supports Windows; it does nothing on Linux and macOS.
102 | - name: Add Developer Command Prompt for Microsoft Visual C++
103 | uses: ilammy/msvc-dev-cmd@v1.13.0
104 |
105 | - name: Set up JDK
106 | uses: actions/setup-java@v4.7.1
107 | with:
108 | distribution: 'graalvm'
109 | java-version: 21
110 |
111 | - name: Get musl toolchain and compile libz against it
112 | id: prepare-musl
113 | run: |
114 | TMP_DIR=$(mktemp -d)
115 | pushd $TMP_DIR
116 | curl -LOJ http://more.musl.cc/10/x86_64-linux-musl/x86_64-linux-musl-native.tgz
117 | tar -xvf x86_64-linux-musl-native.tgz
118 |
119 | curl -LOJ https://zlib.net/fossils/zlib-1.3.tar.gz
120 | tar -xzf zlib-1.3.tar.gz
121 | cd zlib-1.3
122 |
123 | TOOLCHAIN_DIR=$TMP_DIR/x86_64-linux-musl-native
124 | CC=$TOOLCHAIN_DIR/bin/gcc
125 |
126 | ./configure --prefix=$TOOLCHAIN_DIR --static
127 | make
128 | make install
129 |
130 | echo "TOOLCHAIN_DIR=$TOOLCHAIN_DIR" >> $GITHUB_OUTPUT
131 | if: matrix.os == 'ubuntu-latest'
132 |
133 | - name: Cache Maven packages
134 | uses: actions/cache@v4.2.3
135 | with:
136 | path: ~/.m2
137 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
138 | restore-keys: ${{ runner.os }}-m2
139 |
140 | - name: Build static native image for Linux
141 | run: |
142 | PATH=${TOOLCHAIN_DIR}/bin:$PATH; mvn -B -Pnative package
143 | env:
144 | TOOLCHAIN_DIR: ${{ steps.prepare-musl.outputs.TOOLCHAIN_DIR }}
145 | if: matrix.os == 'ubuntu-latest'
146 |
147 | - name: Build static native image for Windows / macOS
148 | run: |
149 | mvn -B -Pnative package
150 | if: matrix.os != 'ubuntu-latest'
151 |
152 | - name: Create distribution
153 | run: mvn -B -Pdist package -DskipTests
154 |
155 | - name: Upload build artifacts
156 | uses: actions/upload-artifact@v4.6.2
157 | with:
158 | name: artifacts-${{ matrix.os }}
159 | path: |
160 | target/distributions/*.zip
161 | target/distributions/*.tar.gz
162 |
163 | # Collect all executables and release
164 | release:
165 | needs: [ build ]
166 | runs-on: ubuntu-latest
167 | permissions: write-all
168 |
169 | steps:
170 | # must read HEAD before checkout
171 | - name: Download all build artifacts
172 | uses: actions/download-artifact@v4.3.0
173 |
174 | - name: Read HEAD ref
175 | id: head
176 | uses: juliangruber/read-file-action@v1.1.7
177 | with:
178 | path: artifacts/HEAD
179 |
180 | - name: Read versions
181 | id: version
182 | run: |
183 | RELEASE_VERSION=`cat artifacts/RELEASE_VERSION`
184 | PLAIN_VERSION=`cat artifacts/PLAIN_VERSION`
185 | NEXT_VERSION=`cat artifacts/NEXT_VERSION`
186 | echo "RELEASE_VERSION = $RELEASE_VERSION"
187 | echo "PLAIN_VERSION = $PLAIN_VERSION"
188 | echo "NEXT_VERSION = $NEXT_VERSION"
189 | echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
190 | echo "PLAIN_VERSION=$PLAIN_VERSION" >> $GITHUB_OUTPUT
191 | echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_OUTPUT
192 |
193 | - name: Check out repository
194 | uses: actions/checkout@v4.2.2
195 | with:
196 | fetch-depth: 0
197 |
198 | - name: Check out correct Git ref
199 | run: git checkout ${{ steps.head.outputs.content }}
200 |
201 | # checkout will clobber downloaded artifacts; we have to download them again
202 | - name: Download all build artifacts
203 | uses: actions/download-artifact@v4.3.0
204 | with:
205 | path: /tmp/artifacts
206 |
207 | - name: Move build artifacts to correct folder
208 | run: |
209 | targets=("ubuntu-latest" "macos-13" "macos-14" "windows-latest")
210 |
211 | mkdir -p artifacts
212 |
213 | find /tmp/artifacts/ -name "mcs*" -exec mv -v {} artifacts/ \;
214 |
215 | - name: Set up Java
216 | uses: actions/setup-java@v4.7.1
217 | with:
218 | java-version: 21
219 | distribution: 'adopt'
220 |
221 | - name: Cache Maven packages
222 | uses: actions/cache@v4.2.3
223 | with:
224 | path: ~/.m2
225 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
226 | restore-keys: ${{ runner.os }}-m2
227 |
228 | - name: Release with JReleaser
229 | run: mvn -B -Prelease -DartifactsDir=artifacts jreleaser:full-release
230 | env:
231 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
232 | JRELEASER_HOMEBREW_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
233 | JRELEASER_SNAP_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
234 | JRELEASER_CHOCOLATEY_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
235 | JRELEASER_SCOOP_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
236 | JRELEASER_SDKMAN_CONSUMER_KEY: ${{ secrets.JRELEASER_SDKMAN_CONSUMER_KEY }}
237 | JRELEASER_SDKMAN_CONSUMER_TOKEN: ${{ secrets.JRELEASER_SDKMAN_CONSUMER_TOKEN }}
238 | JRELEASER_TWITTER_CONSUMER_KEY: ${{ secrets.JRELEASER_TWITTER_CONSUMER_KEY }}
239 | JRELEASER_TWITTER_CONSUMER_SECRET: ${{ secrets.JRELEASER_TWITTER_CONSUMER_SECRET }}
240 | JRELEASER_TWITTER_ACCESS_TOKEN: ${{ secrets.JRELEASER_TWITTER_ACCESS_TOKEN }}
241 | JRELEASER_TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.JRELEASER_TWITTER_ACCESS_TOKEN_SECRET }}
242 | JRELEASER_MASTODON_ACCESS_TOKEN: ${{ secrets.JRELEASER_MASTODON_ACCESS_TOKEN }}
243 | JRELEASER_BLUESKY_PASSWORD: ${{ secrets.JRELEASER_BLUESKY_PASSWORD }}
244 |
245 | - name: Capture JReleaser output
246 | if: always()
247 | uses: actions/upload-artifact@v4.6.2
248 | with:
249 | name: jreleaser-release-output
250 | retention-days: 7
251 | path: |
252 | target/jreleaser/trace.log
253 | target/jreleaser/output.properties
254 |
255 | - name: Set next version
256 | env:
257 | NEXT_VERSION: ${{ steps.version.outputs.NEXT_VERSION }}
258 | run: |
259 | mvn -B versions:set versions:commit -DnewVersion=$NEXT_VERSION
260 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
261 | git config --global user.name "GitHub Action"
262 | git commit -a -m "chore: Prepare next version: $NEXT_VERSION"
263 | git push origin HEAD:main
264 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Maven
2 | target/
3 |
4 | # IntelliJ
5 | .idea/
6 | *.iml
7 |
8 | # Eclipse
9 | .classpath
10 | .project
--------------------------------------------------------------------------------
/.gitpod.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gitpod/workspace-full
2 |
3 | USER gitpod
4 |
5 | RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && \
6 | sdk install java 22.1.0.r17-grl && \
7 | sdk default java 22.1.0.r17-grl"
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | image:
2 | file: .gitpod.Dockerfile
3 | tasks:
4 | - init: mvn verify
5 |
--------------------------------------------------------------------------------
/.mvn/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mthmulders/mcs/742e33aa4d96220939ac4ca938ca6e3e0e494418/.mvn/.gitkeep
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 |
2 | [](#contributors-)
3 |
4 | ## Contributors ✨
5 |
6 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
7 |
8 |
9 |
10 |
11 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Maarten Mulders
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Maven Central Search
2 |
3 | [](https://github.com/mthmulders/mcs/actions/workflows/build.yml)
4 | [](https://dashboard.stryker-mutator.io/reports/github.com/mthmulders/mcs/main)
5 | [](https://snapcraft.io/maven-central-search)
6 |
7 | > Use [Maven Central Repository Search](https://search.maven.org/) from your command line!
8 |
9 | Use `mcs` to quickly lookup dependency coordinates in Maven Central, without having to switch to your browser.
10 |
11 | ## Usage
12 |
13 | This tool supports the following modes of searching:
14 |
15 | 1. **Wildcard search**
16 |
17 | ```console
18 | mcs search plexus-utils
19 | ```
20 |
21 | This will give you all artifacts in Maven Central that have "plexus-utils" in their name.
22 | The output is in a tabular form, showing the exact coordinate of each artifact and the moment when its latest version was deployed.
23 |
24 | 2. **Coordinate search**
25 |
26 | ```console
27 | mcs search org.codehaus.plexus:plexus-utils
28 | mcs search org.codehaus.plexus:plexus-utils:3.4.1
29 | ```
30 |
31 | If there are multiple hits, you will get the same table output as above.
32 | But if there's only one hit, this will give you by default a pom.xml snippet for the artifact you searched for.
33 | Ready for copy & paste in your favourite IDE!
34 | If you require snippet in different format, use `-f ` or `--format=`.
35 | Supported types are: `maven`, `gradle`, `gradle-short`, `gradle-kotlin`, `sbt`, `ivy`, `grape`, `leiningen`, `buildr`, `jbang`, `gav`.
36 |
37 | 3. **Class-name search**
38 |
39 | ```console
40 | mcs class-search CommandLine
41 | mcs class-search -f picocli.CommandLine
42 | ```
43 |
44 | This will give you all artifacts in Maven Central that contain a particular class.
45 | If you set the `-f` flag, the search term is considered a "fully classified" class name, so including the package name.
46 |
47 | ## Flags
48 |
49 | * All modes recognise the `-l ` switch, which lets you specify how many results you want to see _at most_.
50 | * In **Wildcard sarch** and **Coordinate search**, you can pass along the `-s` (or `--show-vulnerabilities`) flag.
51 | It will cause MCS to show a summary of reported security vulnerabilities against each result.
52 | If there is only one search result, it will display the CVE numbers reported against that result.
53 | **Note** that this feature will probably soon hit the API limits for the Sonatype OSS Index. See [their documentation](https://ossindex.sonatype.org) for details on how this may impact your usage.
54 | You can specify your credentials using the system properties `ossindex.username` and `ossindex.password`.
55 | See under "Configuring MCS" on how to do this in the most convenient way.
56 |
57 | ## Installation
58 |
59 | You can install mcs using the package manager of your choice:
60 |
61 | | Package manager | Platform | Installation | Remarks |
62 | |-----------------|----------|-------------------------------------|---------|
63 | | **Homebrew** | 🍎 🐧 | `brew install mthmulders/tap/mcs` | ⚠️ 1 |
64 | | **Snap** | 🐧 | `snap install maven-central-search` | |
65 | | **SDKMAN!** | 🍎 🐧 | `sdk install mcs` | |
66 | | **Chocolatey** | 🪟 | `choco install mcs` | |
67 | | **Scoop** | 🪟 | `scoop install mthmulders/mcs` | |
68 |
69 | 1. The Linux binaries only work on x86_64 CPU's.
70 | There Apple binaries for both x86_64 and Apple Silicon, so you don't need Rosetta.
71 |
72 | ### Usage with custom trust store
73 |
74 | In certain situations, such as when you work behind a TLS-intercepting (corporate) firewall, MCS may fail with
75 |
76 | > PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
77 |
78 | In layman's speak: the default, built-in trust store (the set of trusted X.509 certificates) does not contain anything that allows to trust the certificate(s) presented by the server.
79 | Maven Central uses a certificate that would've been trusted, but the culprit here is the TLS-intercepting (corporate) firewall that presents an internal certificate.
80 |
81 | The solution is to create a trust store that has the "highest" certificate in the certificate chain, e.g. that of the (internal) certificate authority.
82 | You can use a tool like [Portecle](https://portecle.sourceforge.net/) to create such a trust store.
83 | Next, point MCS to that trust store like so
84 |
85 | ```
86 | mcs -Djavax.net.ssl.trustStore=/path/to/keystore search something
87 | ```
88 |
89 | ### Usage Behind a Proxy
90 |
91 | If you are running behind a proxy, MCS will respect the `HTTP_PROXY` and `HTTPS_PROXY` environment variables.
92 |
93 | ## Configuring MCS
94 |
95 | Some configuration for MCS is passed through system properties.
96 | You can do this every time you invoke MCS by adding `-Dxxx=yyy`.
97 | To make it more conveniently, you can create a configuration file that will automatically be read by MCS and interpreted as configuration settings.
98 |
99 | To do so, create a directory **.mcs** in your user directory (typically **C:\Users\** on 🪟, **/home/** on 🐧 or **/Users/** on 🍎).
100 | Inside that folder, create a file **mcs.config** and write the following line in it:
101 |
102 | ```
103 | javax.net.ssl.trustStore=/path/to/keystore
104 | ossindex.username=xxx
105 | ossindex.password=yyy
106 | ```
107 |
108 | This way, you don't have to remember passing the `-D`.
109 |
110 | ## Contributing
111 |
112 | Probably the easiest way to get a working development environment is to use Gitpod:
113 |
114 | [](https://gitpod.io/#https://github.com/mthmulders/mcs)
115 |
116 | It will configure a workspace in your browser and show that everything works as expected by running `mvn verify`.
117 | This setup does not touch your computer - as soon as you close your browser tab, it's gone.
118 |
119 | Checkout the [issues](https://github.com/mthmulders/mcs/issues) if you're looking for something to work on.
120 | If you have a new idea, feel free to bring it up using the [discussions](https://github.com/mthmulders/mcs/discussions).
121 |
122 | ## Acknowledgements
123 |
124 | MCS would not have been possible without the contributions of wonderful people around the globe.
125 | The full list is in [CONTRIBUTORS.md](./CONTRIBUTORS.md).
126 |
--------------------------------------------------------------------------------
/jreleaser.yml:
--------------------------------------------------------------------------------
1 | project:
2 | name: mcs
3 | description: Maven Central Search
4 | longDescription: Search Maven Central from your command line!
5 | links:
6 | bugTracker: https://github.com/mthmulders/mcs/issues
7 | homepage: https://maarten.mulders.it/projects/mcs/
8 | vcsBrowser: https://github.com/mthmulders/mcs
9 | authors:
10 | - Maarten Mulders
11 | license: MIT
12 | extraProperties:
13 | inceptionYear: 2021
14 | stereotype: CLI
15 | tags:
16 | - 'maven'
17 | - 'development'
18 | - 'cli'
19 |
20 | release:
21 | github:
22 | enabled: true
23 | owner: mthmulders
24 | name: mcs
25 | username: mthmulders
26 | update:
27 | enabled: true
28 | changelog:
29 | formatted: ALWAYS
30 | preset: 'conventional-commits'
31 | links: true
32 | hide:
33 | contributors:
34 | - 'Maarten Mulders'
35 | - '[bot]'
36 | - 'GitHub'
37 |
38 | announce:
39 | mastodon:
40 | active: RELEASE
41 | host: https://mastodon.online/
42 | status: 🚀 {{projectName}} {{projectVersion}} has just been released! Check {{releaseNotesUrl}} for the details.
43 | bluesky:
44 | active: RELEASE
45 | host: https://bsky.social
46 | handle: mthmulders.bsky.social
47 | status: 🚀 {{projectName}} {{projectVersion}} has just been released! Check {{releaseNotesUrl}} for the details.
48 | twitter:
49 | active: RELEASE
50 | status: 🚀 {{projectName}} {{projectVersion}} has just been released! Check {{releaseNotesUrl}} for the details.
51 |
52 | distributions:
53 | mcs:
54 | stereotype: CLI
55 | type: BINARY
56 | brew:
57 | active: RELEASE
58 | multiPlatform: true
59 | repository:
60 | active: RELEASE
61 | chocolatey:
62 | active: RELEASE
63 | repository:
64 | active: RELEASE
65 | remoteBuild: true
66 | iconUrl: https://maarten.mulders.it/img/mcs-icon.png
67 | title: Maven Central Search
68 | sdkman:
69 | active: RELEASE
70 | snap:
71 | active: RELEASE
72 | base: core
73 | localPlugs:
74 | - network
75 | architectures:
76 | - buildOn: [amd64]
77 | runOn: [amd64]
78 | repository:
79 | active: RELEASE
80 | name: mcs-snap
81 | owner: mthmulders
82 | packageName: maven-central-search
83 | remoteBuild: true
84 | scoop:
85 | active: RELEASE
86 | repository:
87 | active: RELEASE
88 | name: scoop-mcs
89 | artifacts:
90 | - path: ./{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-linux-x86_64.zip
91 | platform: linux-x86_64
92 | extraProperties:
93 | graalVMNativeImage: true
94 | - path: ./{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-linux-x86_64.tar.gz
95 | platform: linux-x86_64
96 | extraProperties:
97 | graalVMNativeImage: true
98 | - path: ./{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-windows-x86_64.zip
99 | platform: windows-x86_64
100 | - path: ./{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-osx-x86_64.zip
101 | platform: osx-x86_64
102 | extraProperties:
103 | graalVMNativeImage: true
104 | - path: ./{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-osx-aarch_64.zip
105 | platform: osx-aarch_64
106 | extraProperties:
107 | graalVMNativeImage: true
108 |
--------------------------------------------------------------------------------
/src/main/assembly/assembly.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | dist
7 |
8 | tar.gz
9 | zip
10 | dir
11 |
12 |
13 |
14 | LICENSE
15 | ./
16 |
17 |
18 | ${project.build.directory}/${project.artifactId}-${project.version}${executable-suffix}
19 | ./bin
20 | ${project.artifactId}${executable-suffix}
21 | 0755
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/graalvm-native/resource-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "resources":{
3 | "includes":[{
4 | "pattern":"mcs.properties"
5 | }]},
6 | "bundles":[]
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/App.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs;
2 |
3 | import it.mulders.mcs.dagger.Application;
4 | import it.mulders.mcs.dagger.DaggerApplication;
5 | import java.net.URI;
6 | import java.net.URISyntaxException;
7 | import picocli.CommandLine;
8 |
9 | public class App {
10 | public static void main(final String... args) {
11 | System.exit(doMain(args));
12 | }
13 |
14 | // Visible for testing
15 | static int doMain(final String... originalArgs) {
16 | final Application components = DaggerApplication.create();
17 |
18 | var systemPropertyLoader = components.systemPropertyLoader();
19 | System.setProperties(systemPropertyLoader.getProperties());
20 | setUpProxy();
21 |
22 | var commandLine = components.commandLine();
23 |
24 | var args = isInvocationWithoutSearchCommand(commandLine, originalArgs)
25 | ? prependSearchCommandToArgs(originalArgs)
26 | : originalArgs;
27 |
28 | return commandLine.execute(args);
29 | }
30 |
31 | private static void setUpProxy() {
32 | var httpProxy = System.getenv("HTTP_PROXY");
33 | var httpsProxy = System.getenv("HTTPS_PROXY");
34 |
35 | try {
36 | if (httpProxy != null && !httpProxy.isEmpty()) {
37 | final URI uri = new URI(httpProxy);
38 |
39 | System.setProperty("http.proxyHost", uri.getHost());
40 | System.setProperty("http.proxyPort", Integer.toString(uri.getPort()));
41 | }
42 |
43 | if (httpsProxy != null && !httpsProxy.isEmpty()) {
44 | final URI uri = new URI(httpsProxy);
45 | System.setProperty("https.proxyHost", uri.getHost());
46 | System.setProperty("https.proxyPort", Integer.toString(uri.getPort()));
47 | }
48 | } catch (URISyntaxException e) {
49 | System.err.printf(
50 | "Error while setting up proxy from environment: HTTP_PROXY=[%s], HTTPS_PROXY=[%s]%n",
51 | httpProxy, httpsProxy);
52 | }
53 | }
54 |
55 | static boolean isInvocationWithoutSearchCommand(CommandLine program, String... args) {
56 | try {
57 | program.parseArgs(args);
58 | return false;
59 | } catch (CommandLine.ParameterException pe1) {
60 | try {
61 | program.parseArgs(prependSearchCommandToArgs(args));
62 | return true;
63 | } catch (CommandLine.ParameterException pe2) {
64 | return false;
65 | }
66 | }
67 | }
68 |
69 | static String[] prependSearchCommandToArgs(String... originalArgs) {
70 | var args = new String[originalArgs.length + 1];
71 | args[0] = "search";
72 | System.arraycopy(originalArgs, 0, args, 1, originalArgs.length);
73 |
74 | return args;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/cli/ClassSearchCommand.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.cli;
2 |
3 | import it.mulders.mcs.search.SearchCommandHandler;
4 | import it.mulders.mcs.search.SearchQuery;
5 | import jakarta.inject.Inject;
6 | import java.util.concurrent.Callable;
7 | import picocli.CommandLine;
8 |
9 | @CommandLine.Command(
10 | name = "class-search",
11 | description = "Search artifacts in Maven Central by class name",
12 | usageHelpAutoWidth = true)
13 | public class ClassSearchCommand implements Callable {
14 | @CommandLine.Parameters(
15 | arity = "1",
16 | description = {
17 | "The class name to search for.",
18 | })
19 | private String query;
20 |
21 | @CommandLine.Option(
22 | names = {"-f", "--full-name"},
23 | negatable = true,
24 | arity = "0",
25 | description = "Class name includes package")
26 | private boolean fullName;
27 |
28 | @CommandLine.Option(
29 | names = {"-l", "--limit"},
30 | description = "Show results",
31 | paramLabel = "")
32 | private Integer limit;
33 |
34 | private final SearchCommandHandler searchCommandHandler;
35 |
36 | @Inject
37 | public ClassSearchCommand(final SearchCommandHandler searchCommandHandler) {
38 | this.searchCommandHandler = searchCommandHandler;
39 | }
40 |
41 | // Visible for testing
42 | ClassSearchCommand(final SearchCommandHandler searchCommandHandler, String query, Integer limit, boolean fullName) {
43 | this(searchCommandHandler);
44 | this.fullName = fullName;
45 | this.limit = limit;
46 | this.query = query;
47 | }
48 |
49 | @Override
50 | public Integer call() {
51 | System.out.printf("Searching for artifacts containing %s...%n", query);
52 | var searchQuery = SearchQuery.classSearch(this.query)
53 | .isFullyQualified(this.fullName)
54 | .withLimit(limit)
55 | .build();
56 | searchCommandHandler.search(searchQuery, "maven", false);
57 | return 0;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/cli/ClasspathVersionProvider.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.cli;
2 |
3 | import java.util.Properties;
4 | import picocli.CommandLine;
5 |
6 | /**
7 | * {@link CommandLine.IVersionProvider} implementation that returns version information from a
8 | * {@code /mcs.properties} file in the classpath.
9 | */
10 | public class ClasspathVersionProvider implements CommandLine.IVersionProvider {
11 | @Override
12 | public String[] getVersion() throws Exception {
13 | var properties = new Properties();
14 | try (var stream = getClass().getResourceAsStream("/mcs.properties")) {
15 | properties.load(stream);
16 | var version = String.format("mcs v%s", properties.getProperty("mcs.version"));
17 | return new String[] {version};
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/cli/Cli.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.cli;
2 |
3 | import jakarta.inject.Inject;
4 | import picocli.CommandLine;
5 |
6 | @CommandLine.Command(
7 | name = "mcs",
8 | subcommands = {SearchCommand.class, ClassSearchCommand.class},
9 | usageHelpAutoWidth = true,
10 | versionProvider = ClasspathVersionProvider.class)
11 | public class Cli {
12 |
13 | @CommandLine.Option(
14 | names = {"-V", "--version"},
15 | description = "Show version number",
16 | versionHelp = true)
17 | private boolean showVersion;
18 |
19 | @CommandLine.Option(
20 | names = {"-h", "--help"},
21 | description = "Display this help message and exits",
22 | scope = CommandLine.ScopeType.INHERIT,
23 | usageHelp = true)
24 | private boolean usageHelpRequested;
25 |
26 | @Inject
27 | public Cli() {}
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/cli/SearchCommand.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.cli;
2 |
3 | import it.mulders.mcs.search.SearchCommandHandler;
4 | import it.mulders.mcs.search.SearchQuery;
5 | import jakarta.inject.Inject;
6 | import java.util.concurrent.Callable;
7 | import picocli.CommandLine;
8 |
9 | @CommandLine.Command(
10 | name = "search",
11 | description = "Search artifacts in Maven Central by coordinates",
12 | usageHelpAutoWidth = true)
13 | public class SearchCommand implements Callable {
14 | @CommandLine.Parameters(
15 | arity = "1..n",
16 | description = {
17 | "What to search for.",
18 | "If the search term contains a colon ( : ), it is considered a literal groupId and artifactId",
19 | "Otherwise, the search term is considered a wildcard search"
20 | })
21 | private String[] query;
22 |
23 | @CommandLine.Option(
24 | names = {"-l", "--limit"},
25 | description = "Show results",
26 | paramLabel = "")
27 | private Integer limit;
28 |
29 | @CommandLine.Option(
30 | names = {"-f", "--format"},
31 | description =
32 | """
33 | Show result in format
34 | Supported types are:
35 | maven, gradle, gradle-short, gradle-kotlin, sbt, ivy, grape, leiningen, buildr, jbang, gav
36 | """,
37 | paramLabel = "")
38 | private String responseFormat;
39 |
40 | @CommandLine.Option(
41 | names = {"-s", "--show-vulnerabilities"},
42 | description = "Show reported security vulnerabilities",
43 | paramLabel = "")
44 | private boolean showVulnerabilities;
45 |
46 | private final SearchCommandHandler searchCommandHandler;
47 |
48 | @Inject
49 | public SearchCommand(final SearchCommandHandler searchCommandHandler) {
50 | this.searchCommandHandler = searchCommandHandler;
51 | }
52 |
53 | // Visible for testing
54 | SearchCommand(
55 | SearchCommandHandler searchCommandHandler,
56 | String[] query,
57 | Integer limit,
58 | String responseFormat,
59 | boolean showVulnerabilities) {
60 | this(searchCommandHandler);
61 | this.limit = limit;
62 | this.query = query;
63 | this.responseFormat = responseFormat;
64 | this.showVulnerabilities = showVulnerabilities;
65 | }
66 |
67 | @Override
68 | public Integer call() {
69 | var combinedQuery = String.join(" ", query);
70 | System.out.printf("Searching for %s...%n", combinedQuery);
71 | var searchQuery =
72 | SearchQuery.search(combinedQuery).withLimit(this.limit).build();
73 |
74 | searchCommandHandler.search(searchQuery, responseFormat, showVulnerabilities);
75 | return 0;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/cli/SystemPropertyLoader.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.cli;
2 |
3 | import jakarta.inject.Inject;
4 | import java.io.IOException;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.nio.file.Paths;
8 | import java.util.Properties;
9 |
10 | /**
11 | * Loads additional system properties from a predefined file on disk.
12 | *
13 | * Copies those properties over the existing ones @{{@link System#getProperties()}} so the result
14 | * is a drop-in replacement that could be passed for {@link System#setProperties(Properties)}.
15 | * This class does not modify the System properties itself.
16 | */
17 | public class SystemPropertyLoader {
18 | private static final Path MCS_PROPERTIES_FILE = Paths.get(System.getProperty("user.home"), ".mcs", "mcs.config");
19 |
20 | private final Properties properties = new Properties();
21 |
22 | @Inject
23 | public SystemPropertyLoader() {
24 | this(MCS_PROPERTIES_FILE);
25 | }
26 |
27 | protected SystemPropertyLoader(final Path source) {
28 | properties.putAll(System.getProperties());
29 |
30 | if (Files.exists(source) && Files.isRegularFile(source)) {
31 | var input = new Properties();
32 | try (var reader = Files.newBufferedReader(source)) {
33 | input.load(reader);
34 | properties.putAll(input);
35 | } catch (IOException ioe) {
36 | System.err.printf("Failed to load %s: %s%n", source, ioe.getLocalizedMessage());
37 | }
38 | }
39 | }
40 |
41 | public Properties getProperties() {
42 | return properties;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/common/McsExecutionExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.common;
2 |
3 | import jakarta.inject.Inject;
4 | import picocli.CommandLine;
5 |
6 | public class McsExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler {
7 | @Inject
8 | public McsExecutionExceptionHandler() {}
9 |
10 | @Override
11 | public int handleExecutionException(Exception ex, CommandLine commandLine, CommandLine.ParseResult parseResult) {
12 | var message =
13 | ex instanceof McsRuntimeException ? ex.getCause().getLocalizedMessage() : ex.getLocalizedMessage();
14 | System.err.printf("MCS ran into an error: %s%n", message);
15 | System.err.printf("%n");
16 | System.err.printf(
17 | "If the error persist, please consider reporting the issue at https://github.com/mthmulders/mcs/issues/new%n");
18 | System.err.printf("%n");
19 | return -1;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/common/McsRuntimeException.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.common;
2 |
3 | public class McsRuntimeException extends RuntimeException {
4 | public McsRuntimeException(Throwable cause) {
5 | super(cause);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/common/Result.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.common;
2 |
3 | import java.util.NoSuchElementException;
4 | import java.util.function.Consumer;
5 | import java.util.function.Function;
6 |
7 | public sealed interface Result permits Result.Success, Result.Failure {
8 | record Success(T value) implements Result {
9 | @Override
10 | public Result map(final Function mapping) {
11 | try {
12 | return new Success<>(mapping.apply(this.value));
13 | } catch (final Throwable throwable) {
14 | return new Failure<>(throwable);
15 | }
16 | }
17 |
18 | @Override
19 | public void ifPresent(final Consumer consumer) {
20 | consumer.accept(this.value);
21 | }
22 |
23 | @Override
24 | public void ifPresentOrElse(Consumer successConsumer, Consumer failureConsumer) {
25 | successConsumer.accept(value);
26 | }
27 |
28 | @Override
29 | public Throwable cause() {
30 | throw new NoSuchElementException("success: " + this.value);
31 | }
32 | }
33 |
34 | record Failure(Throwable cause) implements Result {
35 | @Override
36 | public Result map(final Function mapping) {
37 | return (Failure) this;
38 | }
39 |
40 | @Override
41 | public void ifPresent(final Consumer consumer) {}
42 |
43 | @Override
44 | public void ifPresentOrElse(Consumer successConsumer, Consumer failureConsumer) {
45 | failureConsumer.accept(cause);
46 | }
47 |
48 | @Override
49 | public T value() {
50 | throw new NoSuchElementException("failure: " + this.cause.getLocalizedMessage());
51 | }
52 | }
53 |
54 | Result map(final Function mapping);
55 |
56 | void ifPresent(final Consumer consumer);
57 |
58 | void ifPresentOrElse(final Consumer successConsumer, final Consumer failureConsumer);
59 |
60 | T value();
61 |
62 | Throwable cause();
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/it/mulders/mcs/common/SearchResponseBodyHandler.java:
--------------------------------------------------------------------------------
1 | package it.mulders.mcs.common;
2 |
3 | import com.fasterxml.jackson.core.JsonParseException;
4 | import com.fasterxml.jackson.jr.ob.JSON;
5 | import com.fasterxml.jackson.jr.ob.JSONObjectException;
6 | import it.mulders.mcs.search.SearchResponse;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.net.http.HttpResponse;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | public class SearchResponseBodyHandler implements HttpResponse.BodyHandler> {
14 | @Override
15 | public HttpResponse.BodySubscriber> apply(final HttpResponse.ResponseInfo responseInfo) {
16 | return asObject();
17 | }
18 |
19 | static HttpResponse.BodySubscriber> asObject() {
20 | var upstream = HttpResponse.BodySubscribers.ofInputStream();
21 |
22 | return HttpResponse.BodySubscribers.mapping(upstream, SearchResponseBodyHandler::toSearchResponse);
23 | }
24 |
25 | static Result toSearchResponse(final InputStream inputStream) {
26 | try (final InputStream input = inputStream) {
27 | var map = JSON.std.mapFrom(input);
28 | return new Result.Success<>(constructSearchResponse(map));
29 | } catch (final JsonParseException | JSONObjectException joe) {
30 | return new Result.Failure<>(
31 | new IllegalStateException(
32 | """
33 | Error parsing the search result. This may be a temporary failure from search.maven.org.
34 | It can also be caused by requesting a large number of results. If that is the case, try lowering the -l/--limit parameter.
35 | If the problem persists, please open a conversation at
36 |
37 | https://github.com/mthmulders/mcs/discussions
38 |
39 | Make sure to at least provide your invocation of mcs and the version of mcs you're using.
40 | """));
41 | } catch (final IOException ioe) {
42 | return new Result.Failure<>(
43 | new IllegalStateException("Error processing response: %s%n".formatted(ioe.getLocalizedMessage())));
44 | }
45 | }
46 |
47 | static SearchResponse constructSearchResponse(final Map input) {
48 | return new SearchResponse(null, constructResponse((Map) input.get("response")));
49 | }
50 |
51 | private static SearchResponse.Response constructResponse(final Map input) {
52 | return new SearchResponse.Response(
53 | (int) input.get("numFound"), (int) input.get("start"), constructDocs((List