Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
274aeb6
Initial plan
Copilot Aug 25, 2025
734664e
Initial analysis and plan for implementing minimalist run option
Copilot Aug 25, 2025
20b9ce4
Implement minimalist run option with do/run commands and aliases
Copilot Aug 25, 2025
2d737db
Fix help text formatting for do command
Copilot Aug 25, 2025
99a247e
Address PR feedback: remove run command, rename scripts to actions, c…
Copilot Aug 25, 2025
bb07e68
chore: Add comprehensive GitHub Copilot instructions for jpm repository
Copilot Aug 25, 2025
fad7e80
Initial plan
Copilot Aug 25, 2025
f4533dd
Address PR feedback: fix formatting and change 'compile' to 'build' a…
Copilot Aug 25, 2025
e2028b5
Add --list/-l flag to 'do' command to list available actions
Copilot Aug 25, 2025
ef72e32
Optimize 'do' command to only get classpath when ${deps} variable is …
Copilot Aug 25, 2025
f2ca4ff
Add comprehensive JUnit tests for new action execution features
Copilot Aug 25, 2025
e1873c1
Fix AppInfo path resolution and most unit test failures
Copilot Aug 25, 2025
9d3be8b
Fix MainIntegrationTest hanging issues by replacing Main.main() calls…
Copilot Aug 25, 2025
b2604ed
Add Windows to CI workflow matrix to build and test on multiple platf…
Copilot Aug 25, 2025
1114385
Fix Windows test failures by handling cross-platform path differences
Copilot Aug 25, 2025
803171d
Remove code duplication by making isWindows() public and fix app.yml …
Copilot Aug 25, 2025
b48c27d
Change variable substitution from ${deps} to {{deps}} and update copi…
Copilot Aug 25, 2025
fcacf03
Merge branch 'main' into copilot/fix-43
quintesse Aug 26, 2025
9337631
Fix corrupted channel issue in Maven Surefire tests by removing unnec…
Copilot Aug 26, 2025
b99a5a3
Fix cross-platform compatibility for test commands by using platform-…
Copilot Aug 26, 2025
2f60ed5
Fixed hanging test
quintesse Aug 26, 2025
b675734
Refactor 'do' command to follow Main/Jpm separation pattern by moving…
Copilot Aug 26, 2025
0099220
Fixed stupid AI code
quintesse Aug 26, 2025
a9fd7ce
Added {:} and {/}
quintesse Aug 26, 2025
1f1da03
DOn't parse command, just pass it as-is to sh or cmd
quintesse Aug 26, 2025
cfa8bde
Allow executing multiple commands
quintesse Aug 27, 2025
6e29589
Fix alias commands to accept and pass through unknown options using @…
Copilot Aug 27, 2025
8958d74
updated test
quintesse Aug 27, 2025
ec07563
Fix argument ordering in alias commands by using only @Unmatched anno…
Copilot Aug 27, 2025
63c13a3
Replace JUnit assertions with AssertJ fluent assertions across all te…
Copilot Aug 27, 2025
1a2632a
minor refactor
quintesse Aug 28, 2025
c652724
Added complete path replacement in actions
quintesse Aug 28, 2025
3a298b8
Add comprehensive Actions section to README explaining do command and…
Copilot Aug 28, 2025
772c5da
minor README change
quintesse Aug 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Always manually validate jpm functionality after making changes:

### Important Files
- `pom.xml` - Maven configuration with Spotless formatting, Shade plugin, Appassembler
- `app.yml` - Example dependency configuration (also created by jpm install)
- `app.yml` - jpm's actual runtime dependencies and actions (NOT an example file). Dependencies should be kept up-to-date with the (non-test) dependencies in pom.xml
- `RELEASE.md` - Release process documentation
- `.gitignore` - Excludes target/, deps/, IDE files

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]

steps:
- uses: actions/checkout@v5
Expand Down
86 changes: 84 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,76 @@ to get the required dependencies to run the code.
_NB: We could have used `jpm copy` instead of `jpm install` to copy the dependencies but that would not have created
the `app.yml` file._

## Actions

The `app.yml` file doesn't just track dependencies - it can also define custom actions that can be executed with the `jpm do` command or through convenient alias commands.

### Defining Actions

Actions are defined in the `actions` section of your `app.yml` file:

```yaml
dependencies:
com.github.lalyos:jfiglet:0.0.9

actions:
build: "javac -cp {{deps}} *.java"
run: "java -cp {{deps}} HelloWorld"
test: "java -cp {{deps}} TestRunner"
clean: "rm -f *.class"
```

### Executing Actions

You can execute actions using the `jpm do` command:

```shell
$ jpm do build
$ jpm do run
$ jpm do --list # Lists all available actions
```

Or use the convenient alias commands:

```shell
$ jpm build # Executes the 'build' action
$ jpm run # Executes the 'run' action
$ jpm test # Executes the 'test' action
$ jpm clean # Executes the 'clean' action
```

Alias commands can accept additional arguments that will be passed through to the underlying action:

```shell
$ jpm run --verbose debug # Passes '--verbose debug' to the run action
```

### Variable Substitution

Actions support several variable substitution features for cross-platform compatibility:

- **`{{deps}}`** - Replaced with the full classpath of all dependencies
- **`{/}`** - Replaced with the file separator (`\` on Windows, `/` on Linux/Mac)
- **`{:}`** - Replaced with the path separator (`;` on Windows, `:` on Linux/Mac)
- **`{~}`** - Replaced with the user's home directory (The actual path on Windows, `~` on Linux/Mac)
- **`{./path/to/file}`** - Converts relative paths to platform-specific format
- **`{./libs:./ext:~/usrlibs}`** - Converts entire class paths to platform-specific format

Example with cross-platform compatibility:

```yaml
actions:
build: "javac -cp {{deps}} -d {./target/classes} src{/}*.java"
run: "java -cp {{deps}}{:}{./target/classes} Main"
test: "java -cp {{deps}}{:}{./target/classes} org.junit.runner.JUnitCore TestSuite"
```

NB: The `{{deps}}` variable substitution is only performed when needed - if your action doesn't contain `{{deps}}`, jpm won't resolve the classpath, making execution faster for simple actions that don't require dependencies.

NB2: These actions are just a very simple convenience feature. For a much more full-featured cross-platform action runner I recommend taking a look at:

- [Just](https://github.com/casey/just) - Just a command runner

## Installation

For now the simplest way to install `jpm` is to use [JBang](https://www.jbang.dev/download/):
Expand Down Expand Up @@ -132,8 +202,8 @@ Commands:
dependencies to the target directory while at the same time
removing any artifacts that are no longer needed (ie the ones
that are not mentioned in the app.yml file). If no artifacts
are passed the app.yml file will be left untouched and only
the existing dependencies in the file will be copied.
are passed the app.yml file will be left untouched and only the
existing dependencies in the file will be copied.

Example:
jpm install org.apache.httpcomponents:httpclient:4.5.14
Expand All @@ -145,6 +215,18 @@ Commands:

Example:
jpm path org.apache.httpcomponents:httpclient:4.5.14

do Executes an action command defined in the app.yml file. Actions
can use variable substitution for classpath.

Example:
jpm do build
jpm do test

clean Executes the 'clean' action as defined in the app.yml file.
build Executes the 'build' action as defined in the app.yml file.
run Executes the 'run' action as defined in the app.yml file.
test Executes the 'test' action as defined in the app.yml file.
```

## Development
Expand Down
23 changes: 14 additions & 9 deletions app.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
dependencies:
eu.maveniverse.maven.mima:context: "2.4.15"
eu.maveniverse.maven.mima.runtime:standalone-static: "2.4.15"
info.picocli:picocli: "4.7.6"
org.yaml:snakeyaml: "2.3"
org.jline:jline-console-ui: "3.29.0"
org.jline:jline-terminal-jni: "3.29.0"
org.slf4j:slf4j-api: "2.0.13"
org.slf4j:slf4j-log4j12: "2.0.13"
org.slf4j:slf4j-simple: "2.0.13"
eu.maveniverse.maven.mima:context: "2.4.33"
eu.maveniverse.maven.mima.runtime:standalone-static: "2.4.33"
info.picocli:picocli: "4.7.7"
org.yaml:snakeyaml: "2.4"
org.jline:jline-console-ui: "3.30.5"
org.jline:jline-terminal-jni: "3.30.5"
org.slf4j:slf4j-api: "2.0.17"
org.slf4j:slf4j-simple: "2.0.17"

actions:
clean: ".{/}mvnw clean"
build: ".{/}mvnw spotless:apply package -DskipTests"
run: "{./target/binary/bin/jpm}"
test: ".{/}mvnw test"
34 changes: 34 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<version.slf4j>2.0.17</version.slf4j>
<version.spotless>2.46.1</version.spotless>
<version.google-java-format>1.22.0</version.google-java-format>
<version.junit>5.11.4</version.junit>
<version.mockito>5.15.2</version.mockito>
<version.assertj>3.26.3</version.assertj>
</properties>

<dependencies>
Expand Down Expand Up @@ -82,6 +85,32 @@
<artifactId>slf4j-simple</artifactId>
<version>${version.slf4j}</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${version.mockito}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${version.mockito}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${version.assertj}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -97,6 +126,11 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
Expand Down
61 changes: 59 additions & 2 deletions src/main/java/org/codejive/jpm/Jpm.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
public class Jpm {
private final Path directory;
private final boolean noLinks;
private final boolean verbose;

private Jpm(Path directory, boolean noLinks) {
private Jpm(Path directory, boolean noLinks, boolean verbose) {
this.directory = directory;
this.noLinks = noLinks;
this.verbose = verbose;
}

/**
Expand All @@ -31,6 +33,7 @@ public static Builder builder() {
public static class Builder {
private Path directory;
private boolean noLinks;
private boolean verbose;

private Builder() {}

Expand All @@ -56,13 +59,24 @@ public Builder noLinks(boolean noLinks) {
return this;
}

/**
* Set whether to enable verbose output or not.
*
* @param verbose Whether to enable verbose output or not.
* @return The builder instance for chaining.
*/
public Builder verbose(boolean verbose) {
this.verbose = verbose;
return this;
}

/**
* Builds the {@link Jpm} instance.
*
* @return A {@link Jpm} instance.
*/
public Jpm build() {
return new Jpm(directory, noLinks);
return new Jpm(directory, noLinks, verbose);
}
}

Expand Down Expand Up @@ -167,6 +181,49 @@ private static String[] getArtifacts(String[] artifactNames, AppInfo appInfo) {
return deps;
}

/**
* Executes an action defined in app.yml file.
*
* @param actionName The name of the action to execute (null to list actions)
* @return An integer containing the exit result of the action
* @throws IllegalArgumentException If the action name is not provided or not found
* @throws IOException If an error occurred during the operation
* @throws DependencyResolutionException If an error occurred during dependency resolution
* @throws InterruptedException If the action execution was interrupted
*/
public int executeAction(String actionName, List<String> args)
throws IOException, DependencyResolutionException, InterruptedException {
AppInfo appInfo = AppInfo.read();

// Get the action command
String command = appInfo.getAction(actionName);
if (command == null) {
throw new IllegalArgumentException(
"Action '"
+ actionName
+ "' not found in app.yml. Use --list to see available actions.");
}

// Get the classpath for variable substitution only if needed
List<Path> classpath = Collections.emptyList();
if (command.contains("{{deps}}")) {
classpath = this.path(new String[0]); // Empty array means use dependencies from app.yml
}

return ScriptUtils.executeScript(command, args, classpath, true);
}

/**
* Returns a list of available action names defined in the app.yml file.
*
* @return A list of available action names
* @throws IOException If an error occurred during the operation
*/
public List<String> listActions() throws IOException {
AppInfo appInfo = AppInfo.read();
return new ArrayList<>(appInfo.getActionNames());
}

private static boolean isWindows() {
String os =
System.getProperty("os.name")
Expand Down
Loading
Loading