diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5947b56..40dfd06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: - master paths: - 'src/**' + - 'fixtures/**' - 'build.gradle*' - 'settings.gradle*' - 'gradle/**' @@ -16,6 +17,7 @@ on: pull_request: paths: - 'src/**' + - 'fixtures/**' - 'build.gradle*' - 'settings.gradle*' - 'gradle/**' @@ -31,11 +33,13 @@ concurrency: jobs: test: - name: Download Fixtures And Run Tests + name: Test (${{ matrix.profile }}) runs-on: ubuntu-latest - timeout-minutes: 45 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + profile: [26x, legacy] steps: - name: Check out repository @@ -52,42 +56,19 @@ jobs: with: gradle-version: "9.4.1" - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Sync addon source repos - run: python tools/download_latest_release_jars.py --update - - - name: Download latest release fixture jars - run: gradle downloadLatestReleaseJars --no-daemon + - name: Run test suite + run: gradle test --no-daemon -PautoGenerateStubs -PparserProfile=${{ matrix.profile }} - - name: Run 26x test suite - run: gradle test --no-daemon -PautoGenerateStubs -PparserProfile=26x - - - name: Generate 26x JSON output + - name: Generate JSON output run: > - gradle run --no-daemon -PparserProfile=26x - --args="--input fixtures/addons/jars --output output/poc-scan --summary output/poc-scan/summary.json --profile 26x" - - - name: Upload release summaries - if: always() - uses: actions/upload-artifact@v4 - with: - name: release-summaries - if-no-files-found: warn - path: | - ai_reference/addons/clone-summary.json - fixtures/addons/jars/release-summary.json - fixtures/addons/jars/release-summary.csv - fixtures/addons/jars/release-summary.txt + gradle run --no-daemon -PparserProfile=${{ matrix.profile }} + --args="--input fixtures/addons/jars/${{ matrix.profile }} --output output/poc-scan --summary output/poc-scan/summary.json --profile ${{ matrix.profile }}" - name: Upload test reports if: failure() uses: actions/upload-artifact@v4 with: - name: test-reports + name: test-reports-${{ matrix.profile }} if-no-files-found: warn path: | build/reports/tests/test/** diff --git a/.gitignore b/.gitignore index ac2512d..f526900 100644 --- a/.gitignore +++ b/.gitignore @@ -6,15 +6,12 @@ output/ # Temporary files tmp/ -# AI reference materials +# AI reference materials (kept local; not part of project sources) ai_reference/ # Mappings mappings/ -# Local fixture jars -fixtures/addons/jars/ - # Generated source src/generated/ diff --git a/build.gradle b/build.gradle index 2fe1b66..a179530 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'application' - id 'com.diffplug.spotless' version '7.0.2' + id 'com.diffplug.spotless' version '8.4.0' } group = 'com.cope.addonparser' @@ -20,6 +20,7 @@ if (parserProfile != 'legacy' && parserProfile != '26x') { throw new GradleException("Unknown parserProfile: ${parserProfile} (expected 'legacy' or '26x')") } def profileSourceDir = parserProfile == 'legacy' ? 'src/profile-legacy/java' : 'src/profile-26x/java' +def profileFixtureJarsDir = "fixtures/addons/jars/${parserProfile}" repositories { mavenCentral() @@ -46,6 +47,7 @@ tasks.withType(Test).configureEach { useJUnitPlatform() jvmArgs '-noverify' systemProperty 'addonparser.profile', parserProfile + systemProperty 'addonparser.fixtureJarsDir', profileFixtureJarsDir } application { @@ -80,14 +82,14 @@ dependencies { tasks.register('generateStubs', JavaExec) { classpath = sourceSets.stubgen.runtimeClasspath mainClass = 'com.cope.addonparser.tools.StubGenerator' - args '--input-dir', 'fixtures/addons/jars', + args '--input-dir', profileFixtureJarsDir, '--output-dir', 'src/generated/java', '--manual-class-list', 'tools/manual_classes.txt', '--manual-source-dirs', files('src/main/java', profileSourceDir).asPath, '--profile', parserProfile // Incremental support: declare inputs and outputs - inputs.dir('fixtures/addons/jars') + inputs.dir(profileFixtureJarsDir) inputs.file('tools/manual_classes.txt') inputs.dir('src/main/java') inputs.dir(profileSourceDir) @@ -95,20 +97,6 @@ tasks.register('generateStubs', JavaExec) { outputs.dir('src/generated/java') } -tasks.register('downloadLatestReleaseJars', JavaExec) { - group = 'build setup' - description = 'Downloads latest GitHub release jar assets for repos under ai_reference/addons.' - classpath = sourceSets.stubgen.runtimeClasspath - mainClass = 'com.cope.addonparser.tools.ReleaseJarDownloader' - args '--addons-dir', 'ai_reference/addons', - '--jars-dir', 'fixtures/addons/jars', - '--fail-on-error' - - inputs.dir('ai_reference/addons') - outputs.dir('fixtures/addons/jars') - outputs.upToDateWhen { false } -} - tasks.named('compileJava') { mustRunAfter tasks.named('generateStubs') } @@ -153,11 +141,13 @@ tasks.named('clean') { // AP-009: Spotless configuration for Google Java Style on manual sources spotless { + ratchetFrom 'master' + java { target 'src/main/java/**/*.java', 'src/test/java/**/*.java', 'src/stubgen/java/**/*.java', 'src/profile-legacy/java/**/*.java', 'src/profile-26x/java/**/*.java' targetExclude 'src/generated/java/**' - googleJavaFormat() + googleJavaFormat('1.35.0') removeUnusedImports() trimTrailingWhitespace() endWithNewline() diff --git a/fixtures/addons/jars/26x/MeteorAdditions--MeteorAdditions-1.2.0.jar b/fixtures/addons/jars/26x/MeteorAdditions--MeteorAdditions-1.2.0.jar new file mode 100644 index 0000000..32b5ad7 Binary files /dev/null and b/fixtures/addons/jars/26x/MeteorAdditions--MeteorAdditions-1.2.0.jar differ diff --git a/fixtures/addons/jars/26x/Nora-Tweaks--nora-tweaks-26.1.2-build-10.jar b/fixtures/addons/jars/26x/Nora-Tweaks--nora-tweaks-26.1.2-build-10.jar new file mode 100644 index 0000000..118a815 Binary files /dev/null and b/fixtures/addons/jars/26x/Nora-Tweaks--nora-tweaks-26.1.2-build-10.jar differ diff --git a/fixtures/addons/jars/26x/PowHax--powhax-1.6.8.jar b/fixtures/addons/jars/26x/PowHax--powhax-1.6.8.jar new file mode 100644 index 0000000..2197397 Binary files /dev/null and b/fixtures/addons/jars/26x/PowHax--powhax-1.6.8.jar differ diff --git a/fixtures/addons/jars/26x/Seija-Printer--Seija-Printer-1.5.6.jar b/fixtures/addons/jars/26x/Seija-Printer--Seija-Printer-1.5.6.jar new file mode 100644 index 0000000..13f9ecc Binary files /dev/null and b/fixtures/addons/jars/26x/Seija-Printer--Seija-Printer-1.5.6.jar differ diff --git a/fixtures/addons/jars/26x/meteor-addon-template--addon-template-0.1.0.jar b/fixtures/addons/jars/26x/meteor-addon-template--addon-template-0.1.0.jar new file mode 100644 index 0000000..9436d72 Binary files /dev/null and b/fixtures/addons/jars/26x/meteor-addon-template--addon-template-0.1.0.jar differ diff --git a/fixtures/addons/jars/26x/meteor-addons-addon--meteor-addons-0.3.0.jar b/fixtures/addons/jars/26x/meteor-addons-addon--meteor-addons-0.3.0.jar new file mode 100644 index 0000000..958e3c5 Binary files /dev/null and b/fixtures/addons/jars/26x/meteor-addons-addon--meteor-addons-0.3.0.jar differ diff --git a/fixtures/addons/jars/26x/meteor-client-webgui--meteor-webgui-0.4.0.jar b/fixtures/addons/jars/26x/meteor-client-webgui--meteor-webgui-0.4.0.jar new file mode 100644 index 0000000..0fee12c Binary files /dev/null and b/fixtures/addons/jars/26x/meteor-client-webgui--meteor-webgui-0.4.0.jar differ diff --git a/fixtures/addons/jars/26x/meteor-mcp-addon--meteor-mcp-0.3.0.jar b/fixtures/addons/jars/26x/meteor-mcp-addon--meteor-mcp-0.3.0.jar new file mode 100644 index 0000000..65aa83f Binary files /dev/null and b/fixtures/addons/jars/26x/meteor-mcp-addon--meteor-mcp-0.3.0.jar differ diff --git a/fixtures/addons/jars/26x/meteor-translation-addon--meteor-translation-addon-0.7.4.jar b/fixtures/addons/jars/26x/meteor-translation-addon--meteor-translation-addon-0.7.4.jar new file mode 100644 index 0000000..cf3a2de Binary files /dev/null and b/fixtures/addons/jars/26x/meteor-translation-addon--meteor-translation-addon-0.7.4.jar differ diff --git a/fixtures/addons/jars/legacy/Baritone-Controller--baritone-controller-0.1.8.jar b/fixtures/addons/jars/legacy/Baritone-Controller--baritone-controller-0.1.8.jar new file mode 100644 index 0000000..0e35d13 Binary files /dev/null and b/fixtures/addons/jars/legacy/Baritone-Controller--baritone-controller-0.1.8.jar differ diff --git a/fixtures/addons/jars/legacy/MeteorPlus--meteor-plus-1.21.4_1.0.9.4.jar b/fixtures/addons/jars/legacy/MeteorPlus--meteor-plus-1.21.4_1.0.9.4.jar new file mode 100644 index 0000000..7905789 Binary files /dev/null and b/fixtures/addons/jars/legacy/MeteorPlus--meteor-plus-1.21.4_1.0.9.4.jar differ diff --git a/fixtures/addons/jars/legacy/Meteorist--meteorist-1.21.11-2.jar b/fixtures/addons/jars/legacy/Meteorist--meteorist-1.21.11-2.jar new file mode 100644 index 0000000..6774775 Binary files /dev/null and b/fixtures/addons/jars/legacy/Meteorist--meteorist-1.21.11-2.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.10-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.10-build-7.jar new file mode 100644 index 0000000..a099e8d Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.10-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.11-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.11-build-7.jar new file mode 100644 index 0000000..b5486f2 Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.11-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.4-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.4-build-7.jar new file mode 100644 index 0000000..9b9c1b9 Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.4-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.5-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.5-build-7.jar new file mode 100644 index 0000000..2e5e70f Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.5-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.6-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.6-build-7.jar new file mode 100644 index 0000000..49c69e4 Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.6-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.7-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.7-build-7.jar new file mode 100644 index 0000000..5608eb8 Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.7-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.8-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.8-build-7.jar new file mode 100644 index 0000000..220a0d5 Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.8-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.9-build-7.jar b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.9-build-7.jar new file mode 100644 index 0000000..07b6d4c Binary files /dev/null and b/fixtures/addons/jars/legacy/Nora-Tweaks--nora-tweaks-1.21.9-build-7.jar differ diff --git a/fixtures/addons/jars/legacy/Trouser-Streak--1trouser-streak-1.5.8-1.21.11.jar b/fixtures/addons/jars/legacy/Trouser-Streak--1trouser-streak-1.5.8-1.21.11.jar new file mode 100644 index 0000000..95986c1 Binary files /dev/null and b/fixtures/addons/jars/legacy/Trouser-Streak--1trouser-streak-1.5.8-1.21.11.jar differ diff --git a/fixtures/addons/jars/legacy/Trouser-Streak--2trouser-streak-1.5.8-1.21.10.jar b/fixtures/addons/jars/legacy/Trouser-Streak--2trouser-streak-1.5.8-1.21.10.jar new file mode 100644 index 0000000..d661104 Binary files /dev/null and b/fixtures/addons/jars/legacy/Trouser-Streak--2trouser-streak-1.5.8-1.21.10.jar differ diff --git a/fixtures/addons/jars/legacy/Trouser-Streak--3trouser-streak-1.5.8-1.21.4.jar b/fixtures/addons/jars/legacy/Trouser-Streak--3trouser-streak-1.5.8-1.21.4.jar new file mode 100644 index 0000000..9a3a181 Binary files /dev/null and b/fixtures/addons/jars/legacy/Trouser-Streak--3trouser-streak-1.5.8-1.21.4.jar differ diff --git a/fixtures/addons/jars/legacy/Trouser-Streak--4trouser-streak-1.5.8-1.20.4.jar b/fixtures/addons/jars/legacy/Trouser-Streak--4trouser-streak-1.5.8-1.20.4.jar new file mode 100644 index 0000000..ba205a9 Binary files /dev/null and b/fixtures/addons/jars/legacy/Trouser-Streak--4trouser-streak-1.5.8-1.20.4.jar differ diff --git a/fixtures/addons/jars/legacy/mc-games--mc-games-0.1.7.jar b/fixtures/addons/jars/legacy/mc-games--mc-games-0.1.7.jar new file mode 100644 index 0000000..8edc9be Binary files /dev/null and b/fixtures/addons/jars/legacy/mc-games--mc-games-0.1.7.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-addon-template--addon-template-0.1.0.jar b/fixtures/addons/jars/legacy/meteor-addon-template--addon-template-0.1.0.jar new file mode 100644 index 0000000..6fa7474 Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-addon-template--addon-template-0.1.0.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-addons-addon--meteor-addons-0.2.1.jar b/fixtures/addons/jars/legacy/meteor-addons-addon--meteor-addons-0.2.1.jar new file mode 100644 index 0000000..bacf38c Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-addons-addon--meteor-addons-0.2.1.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-client-webgui--meteor-webgui-0.3.0.jar b/fixtures/addons/jars/legacy/meteor-client-webgui--meteor-webgui-0.3.0.jar new file mode 100644 index 0000000..6940d62 Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-client-webgui--meteor-webgui-0.3.0.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-mcp-addon--meteor-mcp-0.2.0.jar b/fixtures/addons/jars/legacy/meteor-mcp-addon--meteor-mcp-0.2.0.jar new file mode 100644 index 0000000..73487b2 Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-mcp-addon--meteor-mcp-0.2.0.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-satellite-addon--meteor-satellite-addon-1.21.11-2.jar b/fixtures/addons/jars/legacy/meteor-satellite-addon--meteor-satellite-addon-1.21.11-2.jar new file mode 100644 index 0000000..e9565d4 Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-satellite-addon--meteor-satellite-addon-1.21.11-2.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-translation-addon--meteor-translation-addon-0.7.3.jar b/fixtures/addons/jars/legacy/meteor-translation-addon--meteor-translation-addon-0.7.3.jar new file mode 100644 index 0000000..c70f0da Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-translation-addon--meteor-translation-addon-0.7.3.jar differ diff --git a/fixtures/addons/jars/legacy/meteor-villager-roller--villager-roller-1.4.18+mc1.21.11-rev.82b8feb.jar b/fixtures/addons/jars/legacy/meteor-villager-roller--villager-roller-1.4.18+mc1.21.11-rev.82b8feb.jar new file mode 100644 index 0000000..7d28a17 Binary files /dev/null and b/fixtures/addons/jars/legacy/meteor-villager-roller--villager-roller-1.4.18+mc1.21.11-rev.82b8feb.jar differ diff --git a/fixtures/addons/jars/legacy/nerv-printer-addon--nerv-printer-1.21.11.jar b/fixtures/addons/jars/legacy/nerv-printer-addon--nerv-printer-1.21.11.jar new file mode 100644 index 0000000..254d77e Binary files /dev/null and b/fixtures/addons/jars/legacy/nerv-printer-addon--nerv-printer-1.21.11.jar differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d997cfc Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c61a118 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..739907d --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/BlockListSetting.java b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/BlockListSetting.java index b00d4f7..1fd09b8 100644 --- a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/BlockListSetting.java +++ b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/BlockListSetting.java @@ -57,10 +57,6 @@ public Builder filter(java.util.function.Predicate fil return this; } - public Builder defaultValue(net.minecraft.world.level.block.Block... blocks) { - return this; - } - @Override public BlockListSetting build() { return new BlockListSetting( diff --git a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/EntityTypeListSetting.java b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/EntityTypeListSetting.java index b50f503..276828d 100644 --- a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/EntityTypeListSetting.java +++ b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/EntityTypeListSetting.java @@ -66,10 +66,6 @@ public Builder filter(java.util.function.Predicate fil return this; } - public Builder defaultValue(net.minecraft.world.entity.EntityType... values) { - return this; - } - @Override public EntityTypeListSetting build() { return new EntityTypeListSetting( diff --git a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/ItemListSetting.java b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/ItemListSetting.java index e72a449..954d170 100644 --- a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/ItemListSetting.java +++ b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/ItemListSetting.java @@ -57,10 +57,6 @@ public Builder filter(java.util.function.Predicate fil return this; } - public Builder defaultValue(net.minecraft.world.item.Item... items) { - return this; - } - @Override public ItemListSetting build() { return new ItemListSetting( diff --git a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/SettingGroup.java b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/SettingGroup.java index b307a31..eff6370 100644 --- a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/SettingGroup.java +++ b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/SettingGroup.java @@ -53,16 +53,7 @@ public net.minecraft.class_2487 toTag() { return new net.minecraft.class_2487(); } - public net.minecraft.nbt.CompoundTag toTag(net.minecraft.nbt.CompoundTag tag) { - return tag; - } - - public SettingGroup fromTag(net.minecraft.class_2487 tag) { return this; } - - public SettingGroup fromTag(net.minecraft.nbt.CompoundTag tag) { - return this; - } } diff --git a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/Settings.java b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/Settings.java index afaffd0..0e5d298 100644 --- a/src/profile-legacy/java/meteordevelopment/meteorclient/settings/Settings.java +++ b/src/profile-legacy/java/meteordevelopment/meteorclient/settings/Settings.java @@ -119,16 +119,7 @@ public net.minecraft.class_2487 toTag() { return new net.minecraft.class_2487(); } - public net.minecraft.nbt.CompoundTag toTag(net.minecraft.nbt.CompoundTag tag) { - return tag; - } - - public Settings fromTag(net.minecraft.class_2487 tag) { return this; } - - public Settings fromTag(net.minecraft.nbt.CompoundTag tag) { - return this; - } } diff --git a/src/profile-legacy/java/net/minecraft/class_1291.java b/src/profile-legacy/java/net/minecraft/class_1291.java index 4a3262a..5300d77 100644 --- a/src/profile-legacy/java/net/minecraft/class_1291.java +++ b/src/profile-legacy/java/net/minecraft/class_1291.java @@ -1,6 +1,6 @@ package net.minecraft; @SuppressWarnings("all") -public class class_1291 { +public class class_1291 implements class_6880 { public class_1291() {} } diff --git a/src/profile-legacy/java/net/minecraft/nbt/CompoundTag.java b/src/profile-legacy/java/net/minecraft/nbt/CompoundTag.java deleted file mode 100644 index 5c6c288..0000000 --- a/src/profile-legacy/java/net/minecraft/nbt/CompoundTag.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.minecraft.nbt; - -@SuppressWarnings("all") -public class CompoundTag { - public CompoundTag() {} -} diff --git a/src/profile-legacy/java/net/minecraft/sounds/SoundEvent.java b/src/profile-legacy/java/net/minecraft/sounds/SoundEvent.java deleted file mode 100644 index 716b5e9..0000000 --- a/src/profile-legacy/java/net/minecraft/sounds/SoundEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.minecraft.sounds; - -@SuppressWarnings("all") -public class SoundEvent { - public SoundEvent() {} -} diff --git a/src/profile-legacy/java/net/minecraft/world/entity/EntityType.java b/src/profile-legacy/java/net/minecraft/world/entity/EntityType.java deleted file mode 100644 index 8bb669b..0000000 --- a/src/profile-legacy/java/net/minecraft/world/entity/EntityType.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.minecraft.world.entity; - -@SuppressWarnings("all") -public class EntityType { - public EntityType() {} -} diff --git a/src/profile-legacy/java/net/minecraft/world/item/Item.java b/src/profile-legacy/java/net/minecraft/world/item/Item.java deleted file mode 100644 index fd16bf7..0000000 --- a/src/profile-legacy/java/net/minecraft/world/item/Item.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.minecraft.world.item; - -@SuppressWarnings("all") -public class Item { - public Item() {} -} diff --git a/src/profile-legacy/java/net/minecraft/world/level/block/Block.java b/src/profile-legacy/java/net/minecraft/world/level/block/Block.java deleted file mode 100644 index c10542f..0000000 --- a/src/profile-legacy/java/net/minecraft/world/level/block/Block.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.minecraft.world.level.block; - -@SuppressWarnings("all") -public class Block { - public Block() {} -} diff --git a/src/profile-legacy/java/net/minecraft/world/level/block/state/properties/Property.java b/src/profile-legacy/java/net/minecraft/world/level/block/state/properties/Property.java deleted file mode 100644 index 2445f31..0000000 --- a/src/profile-legacy/java/net/minecraft/world/level/block/state/properties/Property.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.minecraft.world.level.block.state.properties; - -@SuppressWarnings("all") -public class Property> { - public String getName() { - return ""; - } -} diff --git a/src/profile-legacy/java/org/meteordev/starscript/utils/SFunction.java b/src/profile-legacy/java/org/meteordev/starscript/utils/SFunction.java new file mode 100644 index 0000000..9be237b --- /dev/null +++ b/src/profile-legacy/java/org/meteordev/starscript/utils/SFunction.java @@ -0,0 +1,7 @@ +package org.meteordev.starscript.utils; + +@FunctionalInterface +public interface SFunction { + org.meteordev.starscript.value.Value run( + org.meteordev.starscript.Starscript starscript, int argCount); +} diff --git a/src/stubgen/java/com/cope/addonparser/tools/ReleaseJarDownloader.java b/src/stubgen/java/com/cope/addonparser/tools/ReleaseJarDownloader.java deleted file mode 100644 index 93570de..0000000 --- a/src/stubgen/java/com/cope/addonparser/tools/ReleaseJarDownloader.java +++ /dev/null @@ -1,559 +0,0 @@ -package com.cope.addonparser.tools; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import java.io.BufferedWriter; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.time.Duration; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -public final class ReleaseJarDownloader { - private static final String GITHUB_API = "https://api.github.com"; - private static final String USER_AGENT = "addon-parser-release-fetcher/1.0"; - private static final Path DEFAULT_ADDONS_DIR = Paths.get("ai_reference", "addons"); - private static final Path DEFAULT_JARS_DIR = Paths.get("fixtures", "addons", "jars"); - private static final Set FAILURE_STATUSES = - Set.of( - "not_a_git_repo", - "missing_origin", - "non_github_or_unparsed_remote", - "no_releases_found", - "release_found_no_jar_assets", - "failed_download"); - private static final List SUMMARY_FIELDS = - List.of( - "addon_folder", - "repo", - "release_tag", - "release_name", - "release_url", - "published_at", - "releases_url", - "asset_name", - "asset_url", - "downloaded_to", - "status", - "note"); - private static final List GITHUB_SLUG_PATTERNS = - List.of( - Pattern.compile("github\\.com[:/](?[^/]+/[^/.]+?)(?:\\.git)?$"), - Pattern.compile("^https?://github\\.com/(?[^/]+/[^/.]+?)(?:\\.git)?/?$"), - Pattern.compile("^git@github\\.com:(?[^/]+/[^/.]+?)(?:\\.git)?$")); - private static final Gson GSON = - new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - - private record Args(Path addonsDir, Path jarsDir, boolean failOnError) {} - - private record ApiJsonResult(JsonElement body, String error, int statusCode) {} - - private record DownloadResult(boolean ok, String error) {} - - private record ReleaseLookupResult(JsonObject release, String source, String error) {} - - private record SummaryEntry( - String addonFolder, - String repo, - String releaseTag, - String releaseName, - String releaseUrl, - String publishedAt, - String releasesUrl, - String assetName, - String assetUrl, - String downloadedTo, - String status, - String note) { - Map asMap() { - LinkedHashMap map = new LinkedHashMap<>(); - map.put("addon_folder", addonFolder); - map.put("repo", repo); - map.put("release_tag", releaseTag); - map.put("release_name", releaseName); - map.put("release_url", releaseUrl); - map.put("published_at", publishedAt); - map.put("releases_url", releasesUrl); - map.put("asset_name", assetName); - map.put("asset_url", assetUrl); - map.put("downloaded_to", downloadedTo); - map.put("status", status); - map.put("note", note); - return map; - } - } - - private ReleaseJarDownloader() {} - - public static void main(String[] argv) throws Exception { - Args args = parseArgs(argv); - if (args == null) { - printUsage(); - System.exit(2); - return; - } - - System.exit(run(args)); - } - - private static void printUsage() { - System.err.println( - "Usage: ReleaseJarDownloader [--addons-dir ] [--jars-dir ] [--fail-on-error]"); - } - - private static Args parseArgs(String[] argv) { - Path addonsDir = DEFAULT_ADDONS_DIR; - Path jarsDir = DEFAULT_JARS_DIR; - boolean failOnError = false; - - for (int i = 0; i < argv.length; i++) { - String arg = argv[i]; - switch (arg) { - case "--addons-dir": - if (i + 1 >= argv.length) return null; - addonsDir = Paths.get(argv[++i]); - break; - case "--jars-dir": - if (i + 1 >= argv.length) return null; - jarsDir = Paths.get(argv[++i]); - break; - case "--fail-on-error": - failOnError = true; - break; - default: - return null; - } - } - - return new Args( - addonsDir.toAbsolutePath().normalize(), jarsDir.toAbsolutePath().normalize(), failOnError); - } - - private static int run(Args args) throws IOException { - if (!Files.isDirectory(args.addonsDir())) { - System.err.println("addons directory not found: " + args.addonsDir()); - return 2; - } - Files.createDirectories(args.jarsDir()); - - String token = resolveToken(); - HttpClient client = - HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(20)) - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); - - List entries = new ArrayList<>(); - List repoDirs = listAddonDirs(args.addonsDir()); - for (Path addonDir : repoDirs) { - String addonFolder = addonDir.getFileName().toString(); - Path gitDir = addonDir.resolve(".git"); - if (!Files.exists(gitDir)) { - entries.add(entry(addonFolder, "", "", "", "", "", "", "", "", "", "not_a_git_repo", "")); - continue; - } - - String origin = getOriginUrl(addonDir); - if (origin == null || origin.isBlank()) { - entries.add(entry(addonFolder, "", "", "", "", "", "", "", "", "", "missing_origin", "")); - continue; - } - - String slug = parseGitHubSlug(origin); - if (slug == null) { - entries.add( - entry( - addonFolder, - origin, - "", - "", - "", - "", - "", - "", - "", - "", - "non_github_or_unparsed_remote", - "")); - continue; - } - - String releasesUrl = "https://github.com/" + slug + "/releases"; - ReleaseLookupResult releaseLookup = getLatestRelease(client, slug, token); - JsonObject release = releaseLookup.release(); - if (release == null) { - entries.add( - entry( - addonFolder, - slug, - "", - "", - "", - "", - releasesUrl, - "", - "", - "", - "no_releases_found", - releaseLookup.error())); - continue; - } - - String releaseTag = stringValue(release, "tag_name"); - String releaseName = stringValue(release, "name"); - String releaseUrl = stringValue(release, "html_url"); - String publishedAt = stringValue(release, "published_at"); - - JsonArray assets = - release.has("assets") && release.get("assets").isJsonArray() - ? release.getAsJsonArray("assets") - : new JsonArray(); - List jarAssets = new ArrayList<>(); - for (JsonElement assetElement : assets) { - if (!assetElement.isJsonObject()) continue; - JsonObject asset = assetElement.getAsJsonObject(); - String assetName = stringValue(asset, "name"); - if (assetName.toLowerCase().endsWith(".jar")) { - jarAssets.add(asset); - } - } - - if (jarAssets.isEmpty()) { - entries.add( - entry( - addonFolder, - slug, - releaseTag, - releaseName, - releaseUrl, - publishedAt, - releasesUrl, - "", - "", - "", - "release_found_no_jar_assets", - "source=" + releaseLookup.source())); - continue; - } - - for (JsonObject asset : jarAssets) { - String assetName = stringValue(asset, "name").trim(); - String assetUrl = stringValue(asset, "browser_download_url").trim(); - if (assetName.isEmpty() || assetUrl.isEmpty()) { - entries.add( - entry( - addonFolder, - slug, - releaseTag, - releaseName, - releaseUrl, - publishedAt, - releasesUrl, - assetName, - assetUrl, - "", - "failed_download", - "asset missing name/url")); - continue; - } - - String outName = sanitizeFileName(addonFolder + "--" + assetName); - Path outPath = args.jarsDir().resolve(outName); - DownloadResult download = downloadFile(client, assetUrl, outPath, token); - String note = - download.ok() - ? "source=" + releaseLookup.source() - : "source=" + releaseLookup.source() + ";error=" + download.error(); - entries.add( - entry( - addonFolder, - slug, - releaseTag, - releaseName, - releaseUrl, - publishedAt, - releasesUrl, - assetName, - assetUrl, - download.ok() ? outPath.toString() : "", - download.ok() ? "downloaded" : "failed_download", - note)); - } - } - - SummaryPaths summaryPaths = writeSummaryFiles(entries, args); - List summaryLines = buildSummaryLines(entries, args, summaryPaths); - Files.writeString( - summaryPaths.summaryTxt(), String.join("\n", summaryLines) + "\n", StandardCharsets.UTF_8); - System.out.println(String.join("\n", summaryLines)); - - boolean hasFailures = - entries.stream().anyMatch(entry -> FAILURE_STATUSES.contains(entry.status())); - if (args.failOnError() && hasFailures) return 1; - return 0; - } - - private static List listAddonDirs(Path addonsDir) throws IOException { - try (Stream stream = Files.list(addonsDir)) { - return stream - .filter(Files::isDirectory) - .filter(path -> !"jars".equalsIgnoreCase(path.getFileName().toString())) - .sorted(Comparator.comparing(path -> path.getFileName().toString().toLowerCase())) - .toList(); - } - } - - private static String resolveToken() { - String githubToken = System.getenv("GITHUB_TOKEN"); - if (githubToken != null && !githubToken.isBlank()) return githubToken; - String ghToken = System.getenv("GH_TOKEN"); - if (ghToken != null && !ghToken.isBlank()) return ghToken; - return null; - } - - private static ApiJsonResult apiGetJson(HttpClient client, String path, String token) { - HttpRequest.Builder requestBuilder = - HttpRequest.newBuilder(URI.create(GITHUB_API + path)) - .timeout(Duration.ofSeconds(60)) - .header("Accept", "application/vnd.github+json") - .header("User-Agent", USER_AGENT) - .GET(); - if (token != null) requestBuilder.header("Authorization", "Bearer " + token); - - try { - HttpResponse response = - client.send( - requestBuilder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); - int status = response.statusCode(); - String body = Optional.ofNullable(response.body()).orElse(""); - if (status >= 200 && status < 300) { - JsonElement parsed = JsonParser.parseString(body); - return new ApiJsonResult(parsed, null, status); - } - return new ApiJsonResult(null, "HTTP " + status + ": " + body, status); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return new ApiJsonResult(null, "interrupted", -1); - } catch (Exception e) { - return new ApiJsonResult(null, e.getMessage(), -1); - } - } - - private static DownloadResult downloadFile( - HttpClient client, String url, Path outPath, String token) { - HttpRequest.Builder requestBuilder = - HttpRequest.newBuilder(URI.create(url)) - .timeout(Duration.ofSeconds(120)) - .header("User-Agent", USER_AGENT) - .GET(); - if (token != null) requestBuilder.header("Authorization", "Bearer " + token); - - try { - HttpResponse response = - client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); - int status = response.statusCode(); - if (status < 200 || status >= 300) { - return new DownloadResult(false, "HTTP " + status); - } - - byte[] bytes = response.body(); - if (bytes == null || bytes.length == 0) { - return new DownloadResult(false, "downloaded file was empty"); - } - - Files.createDirectories(outPath.getParent()); - Path tmpPath = outPath.resolveSibling(outPath.getFileName().toString() + ".tmp"); - Files.write(tmpPath, bytes); - Files.move( - tmpPath, outPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - return new DownloadResult(true, ""); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return new DownloadResult(false, "interrupted"); - } catch (Exception e) { - return new DownloadResult(false, e.getMessage()); - } - } - - private static String getOriginUrl(Path repoDir) { - ProcessBuilder builder = - new ProcessBuilder("git", "-C", repoDir.toString(), "remote", "get-url", "origin") - .redirectErrorStream(true); - try { - Process process = builder.start(); - String output = - new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim(); - int code = process.waitFor(); - if (code != 0 || output.isBlank()) return null; - return output; - } catch (Exception e) { - return null; - } - } - - private static String parseGitHubSlug(String origin) { - for (Pattern pattern : GITHUB_SLUG_PATTERNS) { - Matcher matcher = pattern.matcher(origin); - if (matcher.find()) { - return matcher.group("slug"); - } - } - return null; - } - - private static ReleaseLookupResult getLatestRelease( - HttpClient client, String slug, String token) { - ApiJsonResult latest = apiGetJson(client, "/repos/" + slug + "/releases/latest", token); - if (latest.body() != null && latest.body().isJsonObject()) { - return new ReleaseLookupResult(latest.body().getAsJsonObject(), "latest", ""); - } - - ApiJsonResult releases = apiGetJson(client, "/repos/" + slug + "/releases?per_page=1", token); - if (releases.body() != null && releases.body().isJsonArray()) { - JsonArray array = releases.body().getAsJsonArray(); - if (!array.isEmpty() && array.get(0).isJsonObject()) { - return new ReleaseLookupResult(array.get(0).getAsJsonObject(), "releases_list_first", ""); - } - } - - if (releases.error() != null && !releases.error().isBlank()) { - return new ReleaseLookupResult(null, "", releases.error()); - } - if (latest.error() != null && !latest.error().isBlank()) { - return new ReleaseLookupResult(null, "", latest.error()); - } - if (latest.statusCode() == 404) { - return new ReleaseLookupResult(null, "", "no releases found"); - } - return new ReleaseLookupResult(null, "", "unable to resolve latest release"); - } - - private static String sanitizeFileName(String value) { - return value.replaceAll("[<>:\"/\\\\|?*]+", "_"); - } - - private static String stringValue(JsonObject object, String key) { - if (!object.has(key) || object.get(key).isJsonNull()) return ""; - return object.get(key).getAsString(); - } - - private static SummaryEntry entry( - String addonFolder, - String repo, - String releaseTag, - String releaseName, - String releaseUrl, - String publishedAt, - String releasesUrl, - String assetName, - String assetUrl, - String downloadedTo, - String status, - String note) { - return new SummaryEntry( - addonFolder, - repo, - releaseTag, - releaseName, - releaseUrl, - publishedAt, - releasesUrl, - assetName, - assetUrl, - downloadedTo, - status, - note); - } - - private record SummaryPaths(Path summaryJson, Path summaryCsv, Path summaryTxt) {} - - private static SummaryPaths writeSummaryFiles(List entries, Args args) - throws IOException { - Path summaryJson = args.jarsDir().resolve("release-summary.json"); - Path summaryCsv = args.jarsDir().resolve("release-summary.csv"); - Path summaryTxt = args.jarsDir().resolve("release-summary.txt"); - - List> rows = entries.stream().map(SummaryEntry::asMap).toList(); - Files.writeString(summaryJson, GSON.toJson(rows), StandardCharsets.UTF_8); - writeSummaryCsv(summaryCsv, rows); - return new SummaryPaths(summaryJson, summaryCsv, summaryTxt); - } - - private static void writeSummaryCsv(Path csvPath, List> rows) - throws IOException { - try (BufferedWriter writer = Files.newBufferedWriter(csvPath, StandardCharsets.UTF_8)) { - writer.write(String.join(",", SUMMARY_FIELDS)); - writer.newLine(); - for (Map row : rows) { - List cells = new ArrayList<>(SUMMARY_FIELDS.size()); - for (String field : SUMMARY_FIELDS) { - cells.add(escapeCsvCell(row.getOrDefault(field, ""))); - } - writer.write(String.join(",", cells)); - writer.newLine(); - } - } - } - - private static String escapeCsvCell(String value) { - boolean needsQuotes = - value.contains(",") || value.contains("\"") || value.contains("\n") || value.contains("\r"); - if (!needsQuotes) return value; - return "\"" + value.replace("\"", "\"\"") + "\""; - } - - private static List buildSummaryLines( - List entries, Args args, SummaryPaths summaryPaths) { - int downloadedCount = 0; - int noReleases = 0; - int noJarAssets = 0; - int failedDownloads = 0; - int failureCount = 0; - Set repoFolders = new LinkedHashSet<>(); - for (SummaryEntry entry : entries) { - repoFolders.add(entry.addonFolder()); - if ("downloaded".equals(entry.status())) downloadedCount++; - if ("no_releases_found".equals(entry.status())) noReleases++; - if ("release_found_no_jar_assets".equals(entry.status())) noJarAssets++; - if ("failed_download".equals(entry.status())) failedDownloads++; - if (FAILURE_STATUSES.contains(entry.status())) failureCount++; - } - - List lines = new ArrayList<>(); - lines.add("timestamp_utc=" + OffsetDateTime.now(ZoneOffset.UTC)); - lines.add("addons_dir=" + args.addonsDir()); - lines.add("jars_dir=" + args.jarsDir()); - lines.add("repos_seen=" + repoFolders.size()); - lines.add("downloaded_jars=" + downloadedCount); - lines.add("no_releases=" + noReleases); - lines.add("releases_without_jars=" + noJarAssets); - lines.add("failed_downloads=" + failedDownloads); - lines.add("failure_entries=" + failureCount); - lines.add("summary_json=" + summaryPaths.summaryJson()); - lines.add("summary_csv=" + summaryPaths.summaryCsv()); - return lines; - } -} diff --git a/src/stubgen/java/com/cope/addonparser/tools/StubGenerator.java b/src/stubgen/java/com/cope/addonparser/tools/StubGenerator.java index 647bb51..7d38ca0 100644 --- a/src/stubgen/java/com/cope/addonparser/tools/StubGenerator.java +++ b/src/stubgen/java/com/cope/addonparser/tools/StubGenerator.java @@ -58,6 +58,7 @@ private record Args( String profile) {} private static Set currentManualClasses = Set.of(); + private static Set currentNestedGeneratedClasses = Set.of(); private record ClassEntry(String internalName, byte[] bytes) {} @@ -119,6 +120,17 @@ private static final class StubModel { } } + private static final class StubTree { + final String internalName; + StubModel model; + final Map children = new TreeMap<>(); + + StubTree(String internalName, StubModel model) { + this.internalName = internalName; + this.model = model; + } + } + private static final class ManualClassConfig { final Set exactClasses; final Set prefixes; @@ -219,7 +231,10 @@ private static void regenerateStubs(Path inputDir, Path outputDir, ManualClassCo filtered.put(internal, entry.getValue()); } - for (Map.Entry entry : filtered.entrySet()) { + Map trees = buildStubTrees(filtered); + currentNestedGeneratedClasses = collectNestedGeneratedClasses(trees); + + for (Map.Entry entry : trees.entrySet()) { String source = renderStub(entry.getValue()); Path target = outputDir.resolve(entry.getKey() + ".java"); if (target.getParent() != null) Files.createDirectories(target.getParent()); @@ -456,7 +471,8 @@ private static void addSourceClasses(Path sourceDir, Set exact) { if (sourceDir == null || !Files.isDirectory(sourceDir)) return; try (var files = Files.walk(sourceDir)) { files - .filter(path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java")) + .filter( + path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java")) .forEach( path -> { Path relative = sourceDir.relativize(path); @@ -468,44 +484,69 @@ private static void addSourceClasses(Path sourceDir, Set exact) { } } - private static String renderStub(StubModel model) { - String javaName = internalToJavaType(model.internalName); - String pkg = ""; - String simple = javaName; - int lastDot = javaName.lastIndexOf('.'); - if (lastDot > 0) { - pkg = javaName.substring(0, lastDot); - simple = javaName.substring(lastDot + 1); + private static Map buildStubTrees(Map models) { + Map trees = new TreeMap<>(); + for (StubModel model : models.values()) { + String topLevel = topLevelInternalName(model.internalName); + StubTree root = trees.computeIfAbsent(topLevel, key -> new StubTree(key, new StubModel(key))); + addModelToTree(root, model); + } + return trees; + } + + private static void addModelToTree(StubTree root, StubModel model) { + if (root.internalName.equals(model.internalName)) { + root.model = model; + return; + } + + StubTree current = root; + String[] parts = model.internalName.substring(root.internalName.length() + 1).split("\\$"); + String currentInternal = root.internalName; + for (String part : parts) { + currentInternal = currentInternal + "$" + part; + String childInternal = currentInternal; + current = + current.children.computeIfAbsent( + part, key -> new StubTree(childInternal, new StubModel(childInternal))); + } + current.model = model; + } + + private static Set collectNestedGeneratedClasses(Map trees) { + Set classes = new HashSet<>(); + for (StubTree tree : trees.values()) collectNestedGeneratedClasses(tree, false, classes); + return classes; + } + + private static void collectNestedGeneratedClasses( + StubTree tree, boolean nested, Set classes) { + if (nested) classes.add(tree.internalName); + for (StubTree child : tree.children.values()) { + collectNestedGeneratedClasses(child, true, classes); } + } + private static String topLevelInternalName(String internalName) { + int dollar = internalName.indexOf('$'); + return dollar > 0 ? internalName.substring(0, dollar) : internalName; + } + + private static String renderStub(StubTree tree) { + String pkg = packageName(tree.internalName); StringBuilder sb = new StringBuilder(); sb.append("// AUTO-GENERATED FILE. DO NOT EDIT.\n"); if (!pkg.isEmpty()) sb.append("package ").append(pkg).append(";\n\n"); + renderType(sb, tree, simpleSourceName(tree.internalName), 0, false); + return sb.toString(); + } - sb.append("@SuppressWarnings({\"all\", \"unchecked\"})\n"); - if (model.isInterface) { - sb.append("public interface ").append(simple).append(" {\n"); - } else { - String superName = SUPER_OVERRIDES.get(model.internalName); - if (superName != null) { - String superJava = internalToJavaType(superName); - if (INTERFACE_OVERRIDES.contains(superName)) { - sb.append("public class ") - .append(simple) - .append(" implements ") - .append(superJava) - .append(" {\n"); - } else { - sb.append("public class ") - .append(simple) - .append(" extends ") - .append(superJava) - .append(" {\n"); - } - } else { - sb.append("public class ").append(simple).append(" {\n"); - } - } + private static void renderType( + StringBuilder sb, StubTree tree, String simpleName, int indentLevel, boolean nested) { + StubModel model = tree.model; + String indent = indent(indentLevel); + sb.append(indent).append("@SuppressWarnings({\"all\", \"unchecked\"})\n"); + sb.append(indent).append(typeDeclaration(model, simpleName, nested)).append(" {\n"); Set seenFieldNames = new HashSet<>(); for (StubField field : model.fields.values()) { @@ -514,42 +555,65 @@ private static String renderStub(StubModel model) { String mods = field.isStatic ? "public static" : "public"; if (model.isInterface && !field.isStatic) mods = "public static"; + sb.append(indent(indentLevel + 1)) + .append(mods) + .append(" ") + .append(javaType) + .append(" ") + .append(field.name); if (field.isStatic || model.isInterface) { - sb.append(" ") - .append(mods) - .append(" ") - .append(javaType) - .append(" ") - .append(field.name) - .append(" = ") - .append(fieldInitExpr(javaType)) - .append(";\n"); - } else { - sb.append(" ") - .append(mods) - .append(" ") - .append(javaType) - .append(" ") - .append(field.name) - .append(";\n"); + sb.append(" = ").append(fieldInitExpr(javaType)); } + sb.append(";\n"); } if (!model.fields.isEmpty()) sb.append('\n'); - if (!model.isInterface) { - Set ctorDescs = new LinkedHashSet<>(model.ctors); - ctorDescs.add("()V"); - List sortedCtors = new ArrayList<>(ctorDescs); - sortedCtors.sort(String::compareTo); - for (String desc : sortedCtors) { - Type methodType = Type.getMethodType(desc); - String params = renderParams(methodType.getArgumentTypes()); - sb.append(" public ").append(simple).append("(").append(params).append(") {\n"); - sb.append(" }\n\n"); - } + if (!model.isInterface) renderConstructors(sb, model, simpleName, indentLevel + 1); + renderMethods(sb, model, indentLevel + 1); + + for (StubTree child : tree.children.values()) { + sb.append('\n'); + renderType(sb, child, childSimpleName(child.internalName), indentLevel + 1, true); + } + + sb.append(indent).append("}\n"); + } + + private static String typeDeclaration(StubModel model, String simpleName, boolean nested) { + String prefix = nested ? "public static " : "public "; + if (model.isInterface) return prefix + "interface " + simpleName; + + String superName = SUPER_OVERRIDES.get(model.internalName); + if (superName == null) return prefix + "class " + simpleName; + + String superJava = internalToJavaType(superName); + if (INTERFACE_OVERRIDES.contains(superName)) { + return prefix + "class " + simpleName + " implements " + superJava; + } + return prefix + "class " + simpleName + " extends " + superJava; + } + + private static void renderConstructors( + StringBuilder sb, StubModel model, String simpleName, int indentLevel) { + Set ctorDescs = new LinkedHashSet<>(model.ctors); + ctorDescs.add("()V"); + List sortedCtors = new ArrayList<>(ctorDescs); + sortedCtors.sort(String::compareTo); + for (String desc : sortedCtors) { + Type methodType = Type.getMethodType(desc); + String params = renderParams(methodType.getArgumentTypes()); + sb.append(indent(indentLevel)) + .append("public ") + .append(simpleName) + .append("(") + .append(params) + .append(") {\n"); + sb.append(indent(indentLevel)).append("}\n\n"); } + } + private static void renderMethods(StringBuilder sb, StubModel model, int indentLevel) { Set seenMethodSigs = new HashSet<>(); for (StubMethod method : model.methods.values()) { if ("".equals(method.name) || "".equals(method.name)) continue; @@ -568,43 +632,21 @@ private static String renderStub(StubModel model) { String ret = typeToJava(mt.getReturnType()); String params = renderParams(args); - if (model.isInterface) { - if (method.isStatic) - sb.append(" static ") - .append(ret) - .append(" ") - .append(method.name) - .append("(") - .append(params) - .append(") {\n"); - else - sb.append(" default ") - .append(ret) - .append(" ") - .append(method.name) - .append("(") - .append(params) - .append(") {\n"); + sb.append(indent(indentLevel)); + if (method.isStatic) sb.append("static "); + else sb.append("default "); } else { - sb.append(" public"); + sb.append(indent(indentLevel)).append("public"); if (method.isStatic) sb.append(" static"); - sb.append(" ") - .append(ret) - .append(" ") - .append(method.name) - .append("(") - .append(params) - .append(") {\n"); + sb.append(" "); } + sb.append(ret).append(" ").append(method.name).append("(").append(params).append(") {\n"); String body = defaultReturnExpr(ret); - if (!body.isEmpty()) sb.append(" ").append(body).append("\n"); - sb.append(" }\n\n"); + if (!body.isEmpty()) sb.append(indent(indentLevel + 1)).append(body).append("\n"); + sb.append(indent(indentLevel)).append("}\n\n"); } - - sb.append("}\n"); - return sb.toString(); } private static String renderArgSig(Type[] args) { @@ -625,11 +667,33 @@ private static String renderParams(Type[] args) { return sb.toString(); } + private static String packageName(String internalName) { + int slash = internalName.lastIndexOf('/'); + if (slash < 0) return ""; + return internalName.substring(0, slash).replace('/', '.'); + } + + private static String simpleSourceName(String internalName) { + int slash = internalName.lastIndexOf('/'); + String simple = slash < 0 ? internalName : internalName.substring(slash + 1); + return simple.replace('$', '.'); + } + + private static String childSimpleName(String internalName) { + int dollar = internalName.lastIndexOf('$'); + return dollar < 0 ? simpleSourceName(internalName) : internalName.substring(dollar + 1); + } + + private static String indent(int level) { + return " ".repeat(level); + } + private static String internalToJavaType(String internalName) { int dollarIdx = internalName.indexOf('$'); if (dollarIdx > 0) { String outer = internalName.substring(0, dollarIdx); - if (currentManualClasses.contains(outer)) { + if (currentNestedGeneratedClasses.contains(internalName) + || currentManualClasses.contains(outer)) { return internalName.replace('/', '.').replace('$', '.'); } } diff --git a/src/test/java/com/cope/addonparser/FixtureLayout.java b/src/test/java/com/cope/addonparser/FixtureLayout.java new file mode 100644 index 0000000..ade1b21 --- /dev/null +++ b/src/test/java/com/cope/addonparser/FixtureLayout.java @@ -0,0 +1,35 @@ +package com.cope.addonparser; + +import java.util.List; + +/** + * Resolves the fixture jar directory and the set of jars expected to exist for the active profile. + */ +final class FixtureLayout { + private FixtureLayout() {} + + static String profile() { + return System.getProperty("addonparser.profile", "26x").toLowerCase(); + } + + static String defaultJarDir() { + return "fixtures/addons/jars/" + profile(); + } + + static List expectedJarPrefixes() { + if ("legacy".equals(profile())) { + return List.of( + "Baritone-Controller--", + "MeteorPlus--", + "Trouser-Streak--", + "meteor-villager-roller--", + "nerv-printer-addon--"); + } + return List.of( + "MeteorAdditions--", + "Nora-Tweaks--", + "meteor-addon-template--", + "meteor-translation-addon--", + "Seija-Printer--"); + } +} diff --git a/src/test/java/com/cope/addonparser/FixtureScanTest.java b/src/test/java/com/cope/addonparser/FixtureScanTest.java index 611282c..0469450 100644 --- a/src/test/java/com/cope/addonparser/FixtureScanTest.java +++ b/src/test/java/com/cope/addonparser/FixtureScanTest.java @@ -19,7 +19,8 @@ public class FixtureScanTest { @Test void scansAllFixtureJars() throws Exception { - Path jarDir = Path.of(System.getProperty("addonparser.fixtureJarsDir", "fixtures/addons/jars")); + Path jarDir = + Path.of(System.getProperty("addonparser.fixtureJarsDir", FixtureLayout.defaultJarDir())); assertTrue( Files.isDirectory(jarDir), "Fixture jar directory missing: " + jarDir.toAbsolutePath()); @@ -51,21 +52,14 @@ void scansAllFixtureJars() throws Exception { "addonparser.runtimeTmpDir", Path.of("tmp", "addon-parser-runtime").toString())); boolean tmpRootMissingAtStart = !Files.exists(runtimeTmpRoot); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("MeteorAdditions--")), - "Missing fixture jar for MeteorAdditions"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("Nora-Tweaks--")), - "Missing fixture jar for Nora-Tweaks"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("meteor-addon-template--")), - "Missing fixture jar for meteor-addon-template"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("meteor-translation-addon--")), - "Missing fixture jar for meteor-translation-addon"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("Seija-Printer--")), - "Missing fixture jar for Seija-Printer"); + for (String prefix : FixtureLayout.expectedJarPrefixes()) { + assertTrue( + jarNames.stream().anyMatch(name -> name.startsWith(prefix)), + "Missing fixture jar with prefix '" + + prefix + + "' for profile " + + FixtureLayout.profile()); + } List failures = new ArrayList<>(); try (AddonScanner scanner = new AddonScanner()) { diff --git a/src/test/java/com/cope/addonparser/IsolatedScannerTest.java b/src/test/java/com/cope/addonparser/IsolatedScannerTest.java index a90e579..12b5a6a 100644 --- a/src/test/java/com/cope/addonparser/IsolatedScannerTest.java +++ b/src/test/java/com/cope/addonparser/IsolatedScannerTest.java @@ -1,6 +1,5 @@ package com.cope.addonparser; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -29,7 +28,8 @@ public class IsolatedScannerTest { @Test @Timeout(value = 300, unit = java.util.concurrent.TimeUnit.SECONDS) void scansAllFixtureJars() throws Exception { - Path jarDir = Path.of(System.getProperty("addonparser.fixtureJarsDir", "fixtures/addons/jars")); + Path jarDir = + Path.of(System.getProperty("addonparser.fixtureJarsDir", FixtureLayout.defaultJarDir())); assertTrue( Files.isDirectory(jarDir), "Fixture jar directory missing: " + jarDir.toAbsolutePath()); @@ -44,22 +44,14 @@ void scansAllFixtureJars() throws Exception { Set jarNames = jars.stream().map(path -> path.getFileName().toString()).collect(Collectors.toSet()); - // Verify expected fixture jars exist - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("MeteorAdditions--")), - "Missing fixture jar for MeteorAdditions"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("Nora-Tweaks--")), - "Missing fixture jar for Nora-Tweaks"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("meteor-addon-template--")), - "Missing fixture jar for meteor-addon-template"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("meteor-translation-addon--")), - "Missing fixture jar for meteor-translation-addon"); - assertTrue( - jarNames.stream().anyMatch(name -> name.startsWith("Seija-Printer--")), - "Missing fixture jar for Seija-Printer"); + for (String prefix : FixtureLayout.expectedJarPrefixes()) { + assertTrue( + jarNames.stream().anyMatch(name -> name.startsWith(prefix)), + "Missing fixture jar with prefix '" + + prefix + + "' for profile " + + FixtureLayout.profile()); + } // Known addon side-effect artifacts that may be created during scanning. List sideEffectPaths = @@ -118,7 +110,8 @@ void scanInvalidFileReturnsFailure() throws Exception { @Test void scanResultContainsExpectedMetadata() throws Exception { - Path jarDir = Path.of(System.getProperty("addonparser.fixtureJarsDir", "fixtures/addons/jars")); + Path jarDir = + Path.of(System.getProperty("addonparser.fixtureJarsDir", FixtureLayout.defaultJarDir())); if (!Files.isDirectory(jarDir)) { // Skip if fixtures not available return; @@ -140,7 +133,9 @@ void scanResultContainsExpectedMetadata() throws Exception { assertTrue(result.success, "Scan should succeed: " + String.join(", ", result.errors)); assertNotNull(result.jarName, "jarName should be set"); assertNotNull(result.jarPath, "jarPath should be set"); - assertTrue(result.jarName.startsWith("meteor-translation-addon--"), "Unexpected jar name: " + result.jarName); + assertTrue( + result.jarName.startsWith("meteor-translation-addon--"), + "Unexpected jar name: " + result.jarName); // Verify addon metadata is populated assertFalse(result.addons.isEmpty(), "Should have at least one addon"); diff --git a/tools/addon_repos.csv b/tools/addon_repos.csv deleted file mode 100644 index 6b62fa7..0000000 --- a/tools/addon_repos.csv +++ /dev/null @@ -1,10 +0,0 @@ -folder,repo_url -meteor-addon-template,https://github.com/MeteorDevelopment/meteor-addon-template -meteor-addons-addon,https://github.com/MCDxAI/meteor-addons-addon -meteor-client-webgui,https://github.com/MCDxAI/meteor-client-webgui -meteor-mcp-addon,https://github.com/MCDxAI/meteor-mcp-addon -meteor-translation-addon,https://github.com/Nippaku-Zanmu/meteor-translation-addon -MeteorAdditions,https://github.com/JFronny/MeteorAdditions -Nora-Tweaks,https://github.com/noramibu/Nora-Tweaks -PowHax,https://github.com/Powie69/PowHax -Seija-Printer,https://github.com/Nippaku-Zanmu/Seija-Printer diff --git a/tools/download_latest_release_jars.py b/tools/download_latest_release_jars.py deleted file mode 100644 index 9898755..0000000 --- a/tools/download_latest_release_jars.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python -import argparse -import csv -import json -import subprocess -import sys -from datetime import datetime, timezone -from pathlib import Path - - -SCRIPT_DIR = Path(__file__).resolve().parent -PROJECT_ROOT = SCRIPT_DIR.parent -DEFAULT_ADDONS_DIR = PROJECT_ROOT / "ai_reference" / "addons" -DEFAULT_MANIFEST = SCRIPT_DIR / "addon_repos.csv" - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description=( - "Sync addon source repositories for ai_reference/addons. " - "Release jar downloading now lives in Java via Gradle task " - "'downloadLatestReleaseJars'." - ) - ) - parser.add_argument( - "--addons-dir", - default=str(DEFAULT_ADDONS_DIR), - help=f"Directory where addon repos are cloned (default: {DEFAULT_ADDONS_DIR})", - ) - parser.add_argument( - "--manifest", - default=str(DEFAULT_MANIFEST), - help=f"CSV manifest with columns folder,repo_url (default: {DEFAULT_MANIFEST})", - ) - parser.add_argument( - "--update", - action="store_true", - help="If set, also fetch and fast-forward pull repos that already exist.", - ) - parser.add_argument( - "--depth", - type=int, - default=1, - help="Depth to use for new clones (default: 1; use <=0 for full history).", - ) - parser.add_argument( - "--summary-json", - default="", - help="Optional output file for clone summary JSON (default: /clone-summary.json).", - ) - return parser.parse_args() - - -def run_git(args: list[str], cwd: Path | None = None) -> tuple[int, str]: - proc = subprocess.run( - args, - cwd=str(cwd) if cwd else None, - capture_output=True, - text=True, - check=False, - ) - combined = "\n".join(x for x in [proc.stdout.strip(), proc.stderr.strip()] if x).strip() - return proc.returncode, combined - - -def normalize_remote(url: str) -> str: - trimmed = url.strip() - if trimmed.endswith(".git"): - trimmed = trimmed[: -len(".git")] - return trimmed.rstrip("/") - - -def load_manifest(manifest_path: Path) -> list[dict]: - if not manifest_path.is_file(): - raise FileNotFoundError(f"manifest not found: {manifest_path}") - - with manifest_path.open("r", encoding="utf-8", newline="") as f: - reader = csv.DictReader(f) - required = {"folder", "repo_url"} - if reader.fieldnames is None or not required.issubset(set(reader.fieldnames)): - raise ValueError("manifest must include header columns: folder,repo_url") - - rows = [] - seen = set() - for row in reader: - folder = (row.get("folder") or "").strip() - repo_url = (row.get("repo_url") or "").strip() - if not folder or not repo_url: - continue - key = folder.lower() - if key in seen: - raise ValueError(f"duplicate folder in manifest: {folder}") - seen.add(key) - rows.append({"folder": folder, "repo_url": repo_url}) - return rows - - -def clone_repo(repo_url: str, target_dir: Path, depth: int) -> tuple[bool, str]: - clone_cmd = ["git", "clone"] - if depth > 0: - clone_cmd += ["--depth", str(depth)] - clone_cmd += [repo_url, str(target_dir)] - code, out = run_git(clone_cmd) - return code == 0, out - - -def update_repo(repo_dir: Path) -> tuple[bool, str]: - fetch_code, fetch_out = run_git( - ["git", "-C", str(repo_dir), "fetch", "--all", "--tags", "--prune"] - ) - if fetch_code != 0: - return False, fetch_out - - pull_code, pull_out = run_git(["git", "-C", str(repo_dir), "pull", "--ff-only"]) - if pull_code != 0: - return False, pull_out - return True, "\n".join(x for x in [fetch_out, pull_out] if x).strip() - - -def get_origin(repo_dir: Path) -> str: - code, out = run_git(["git", "-C", str(repo_dir), "remote", "get-url", "origin"]) - if code != 0: - return "" - return out.strip() - - -def main() -> int: - args = parse_args() - addons_dir = Path(args.addons_dir).resolve() - manifest_path = Path(args.manifest).resolve() - summary_json = ( - Path(args.summary_json).resolve() - if args.summary_json.strip() - else addons_dir / "clone-summary.json" - ) - - try: - manifest_rows = load_manifest(manifest_path) - except Exception as e: - print(str(e), file=sys.stderr) - return 2 - - addons_dir.mkdir(parents=True, exist_ok=True) - - entries: list[dict] = [] - failure_statuses = { - "path_exists_not_git", - "origin_mismatch", - "clone_failed", - "update_failed", - } - - for row in manifest_rows: - folder = row["folder"] - repo_url = row["repo_url"] - target = addons_dir / folder - status = "unknown" - note = "" - - if target.exists() and not (target / ".git").exists(): - status = "path_exists_not_git" - note = "target path exists but is not a git repository" - elif (target / ".git").exists(): - current_origin = get_origin(target) - expected = normalize_remote(repo_url) - actual = normalize_remote(current_origin) - if not current_origin: - status = "origin_mismatch" - note = "unable to read origin remote" - elif expected != actual: - status = "origin_mismatch" - note = f"expected_origin={repo_url};actual_origin={current_origin}" - elif args.update: - ok, output = update_repo(target) - status = "updated" if ok else "update_failed" - note = output - else: - status = "exists" - else: - ok, output = clone_repo(repo_url, target, args.depth) - status = "cloned" if ok else "clone_failed" - note = output - - entries.append( - { - "folder": folder, - "repo_url": repo_url, - "target_dir": str(target), - "status": status, - "note": note, - } - ) - - summary_json.parent.mkdir(parents=True, exist_ok=True) - summary_json.write_text(json.dumps(entries, indent=2), encoding="utf-8") - - cloned = sum(1 for e in entries if e["status"] == "cloned") - exists = sum(1 for e in entries if e["status"] == "exists") - updated = sum(1 for e in entries if e["status"] == "updated") - failed = sum(1 for e in entries if e["status"] in failure_statuses) - - lines = [ - f"timestamp_utc={datetime.now(timezone.utc).isoformat()}", - f"addons_dir={addons_dir}", - f"manifest={manifest_path}", - f"repos_total={len(entries)}", - f"cloned={cloned}", - f"exists={exists}", - f"updated={updated}", - f"failures={failed}", - f"summary_json={summary_json}", - "next_step=Run `gradle downloadLatestReleaseJars --no-daemon` to fetch release jars.", - ] - print("\n".join(lines)) - - return 1 if failed > 0 else 0 - - -if __name__ == "__main__": - raise SystemExit(main())