Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Hymod Agent Instructions

## Command policy
- Use `hymod` commands for mod workflows.
- Do not run Gradle commands directly for normal build/dev/deploy tasks.
- Prefer project-root execution unless a command explicitly needs `--path`.

## Use these `hymod` commands instead of Gradle
- Build mod artifact: `hymod build`
- Build release artifact: `hymod build --release`
- Local link to a configured local server: `hymod link [server_name]`
- Dev loop (build + link + restart): `hymod dev [target] [--path <mod_dir>]`
- Remote deploy (build + upload): `hymod deploy [server_name] [--transport rsync|scp] [--path <mod_dir>]`
- Preview deploy plan without executing: `hymod deploy [server_name] --dry-run`

## Server/config helpers
- List servers: `hymod server list`
- Add server: `hymod server add <kind> <name> <uri>`
- Set default server: `hymod server default <kind> <name>`
- Inspect server: `hymod server get <name>`
- Remove server: `hymod server remove <name>`
- Set global config: `hymod config set <key> <value>`
- Get global config value: `hymod config get <key>`
- List global config: `hymod config list`

## Project bootstrap
- Create a new mod project: `hymod new <name> [--path <dir>] [--group <group>] [--package <package>]`

## Notes
- Gradle wrappers/scripts may exist internally, but agent-driven workflows should go through `hymod`.
- If a task cannot be expressed through current `hymod` commands, call that out explicitly before falling back to direct Gradle usage.
31 changes: 31 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Hymod Agent Instructions

## Command policy
- Use `hymod` commands for mod workflows.
- Do not run Gradle commands directly for normal build/dev/deploy tasks.
- Prefer project-root execution unless a command explicitly needs `--path`.

## Use these `hymod` commands instead of Gradle
- Build mod artifact: `hymod build`
- Build release artifact: `hymod build --release`
- Local link to a configured local server: `hymod link [server_name]`
- Dev loop (build + link + restart): `hymod dev [target] [--path <mod_dir>]`
- Remote deploy (build + upload): `hymod deploy [server_name] [--transport rsync|scp] [--path <mod_dir>]`
- Preview deploy plan without executing: `hymod deploy [server_name] --dry-run`

## Server/config helpers
- List servers: `hymod server list`
- Add server: `hymod server add <kind> <name> <uri>`
- Set default server: `hymod server default <kind> <name>`
- Inspect server: `hymod server get <name>`
- Remove server: `hymod server remove <name>`
- Set global config: `hymod config set <key> <value>`
- Get global config value: `hymod config get <key>`
- List global config: `hymod config list`

## Project bootstrap
- Create a new mod project: `hymod new <name> [--path <dir>] [--group <group>] [--package <package>]`

## Notes
- Gradle wrappers/scripts may exist internally, but agent-driven workflows should go through `hymod`.
- If a task cannot be expressed through current `hymod` commands, call that out explicitly before falling back to direct Gradle usage.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hymod"
version = "0.0.8"
version = "0.3.6"
edition = "2021"

[[bin]]
Expand Down
31 changes: 31 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Hymod Agent Instructions

## Command policy
- Use `hymod` commands for mod workflows.
- Do not run Gradle commands directly for normal build/dev/deploy tasks.
- Prefer project-root execution unless a command explicitly needs `--path`.

## Use these `hymod` commands instead of Gradle
- Build mod artifact: `hymod build`
- Build release artifact: `hymod build --release`
- Local link to a configured local server: `hymod link [server_name]`
- Dev loop (build + link + restart): `hymod dev [target] [--path <mod_dir>]`
- Remote deploy (build + upload): `hymod deploy [server_name] [--transport rsync|scp] [--path <mod_dir>]`
- Preview deploy plan without executing: `hymod deploy [server_name] --dry-run`

## Server/config helpers
- List servers: `hymod server list`
- Add server: `hymod server add <kind> <name> <uri>`
- Set default server: `hymod server default <kind> <name>`
- Inspect server: `hymod server get <name>`
- Remove server: `hymod server remove <name>`
- Set global config: `hymod config set <key> <value>`
- Get global config value: `hymod config get <key>`
- List global config: `hymod config list`

## Project bootstrap
- Create a new mod project: `hymod new <name> [--path <dir>] [--group <group>] [--package <package>]`

## Notes
- Gradle wrappers/scripts may exist internally, but agent-driven workflows should go through `hymod`.
- If a task cannot be expressed through current `hymod` commands, call that out explicitly before falling back to direct Gradle usage.
68 changes: 68 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
SHELL := /bin/bash

BIN ?= hymod
RUN := cargo run -q --bin $(BIN) --
BUILD_CMD := cargo build -q --bin $(BIN)
BIN_PATH := ./target/debug/$(BIN)
ARGS ?=

.PHONY: make-help run run-built noargs help new build link dev deploy server config install-local

make-help:
@echo "Hymod CLI Make targets"
@echo ""
@echo "Usage:"
@echo " make noargs"
@echo " make help"
@echo " make <target> ARGS='...'"
@echo ""
@echo "Targets:"
@echo " run - run arbitrary args, e.g. make run ARGS='server list'"
@echo " run-built - build then run compiled binary, e.g. make run-built ARGS='server list'"
@echo " noargs - run hymod with no args (logo + help)"
@echo " help - run hymod --help"
@echo " new - run hymod new <args>"
@echo " build - run hymod build <args>"
@echo " link - run hymod link <args>"
@echo " dev - run hymod dev <args>"
@echo " deploy - run hymod deploy <args>"
@echo " server - run hymod server <args>"
@echo " config - run hymod config <args>"
@echo " install-local - build hymod and install to /usr/local/bin (uses sudo)"

run:
$(RUN) $(ARGS)

run-built:
$(BUILD_CMD)
$(BIN_PATH) $(ARGS)

noargs:
$(RUN)

help:
$(RUN) --help

new:
$(RUN) new $(ARGS)

build:
$(RUN) build $(ARGS)

link:
$(RUN) link $(ARGS)

dev:
$(RUN) dev $(ARGS)

deploy:
$(RUN) deploy $(ARGS)

server:
$(RUN) server $(ARGS)

config:
$(RUN) config $(ARGS)

install-local:
./scripts/local_deploy.sh
8 changes: 8 additions & 0 deletions cli/deploy/deploy_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ pub struct DeployCommand {
pub server_name: Option<String>,
#[arg(long)]
pub transport: Option<String>,
/// Restart server after upload (disabled by default)
#[arg(long)]
pub restart: bool,
#[arg(long)]
pub dry_run: bool,
/// Path to the mod directory (default: current directory)
#[arg(long, short)]
pub path: Option<std::path::PathBuf>,
}

use crate::command::CliCommand;
Expand All @@ -18,7 +24,9 @@ impl CliCommand for DeployCommand {
let args = features_deploy::DeployArgs {
server_name: self.server_name.clone(),
transport: self.transport.clone(),
restart: self.restart,
dry_run: self.dry_run,
path: self.path.clone(),
};
let plan = features_deploy::generate_plan(args);

Expand Down
53 changes: 52 additions & 1 deletion cli/deploy/tests/test_deploy_default_rsync.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,55 @@
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;

#[cfg(unix)]
fn make_fake_rsync(bin_dir: &std::path::Path) {
use std::os::unix::fs::PermissionsExt;

let rsync_path = bin_dir.join("rsync");
fs::write(&rsync_path, "#!/usr/bin/env sh\nexit 0\n").expect("failed to write fake rsync");
let mut perms = fs::metadata(&rsync_path)
.expect("failed to stat fake rsync")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&rsync_path, perms).expect("failed to chmod fake rsync");
}

#[cfg(unix)]
#[test]
fn test_deploy_default_rsync() {
panic!("Plan must contain UploadRsync(cache -> user@host:/mods/mod.zip) and SshRun(systemctl restart).");
let temp_dir = TempDir::new().expect("failed to create temp dir");
let home_dir = TempDir::new().expect("failed to create home dir");
let bin_dir = TempDir::new().expect("failed to create bin dir");

#[cfg(unix)]
make_fake_rsync(bin_dir.path());

Command::cargo_bin("hymod")
.expect("binary not found")
.current_dir(temp_dir.path())
.env("HOME", home_dir.path())
.args(["new", "testmod"])
.assert()
.success();

let project_dir = temp_dir.path().join("testmod");

Command::cargo_bin("hymod")
.expect("binary not found")
.env("HOME", home_dir.path())
.args(["server", "add", "remote", "prod", "user@host:/path"])
.assert()
.success();

Command::cargo_bin("hymod")
.expect("binary not found")
.current_dir(&project_dir)
.env("HOME", home_dir.path())
.env("PATH", bin_dir.path())
.args(["deploy", "prod", "--dry-run"])
.assert()
.success()
.stdout(predicate::str::contains("UPLOAD(RSYNC)"));
}
4 changes: 2 additions & 2 deletions cli/deploy/tests/test_deploy_dry_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ fn test_deploy_dry_run() {
.stdout(predicate::str::contains("PLAN"))
.stdout(predicate::str::contains("RUN ./gradlew build"))
.stdout(predicate::str::contains(
"COPY build/libs/mod-1.0.0.jar -> /tmp/server/mods/mod-1.0.0.jar",
"build/libs/mod-1.0.0.jar -> /tmp/server/mods/mod-1.0.0.jar",
));
// Ideally checking that it DIDN'T do something, but hard to prove negative without checking filesystem, which dry-run shouldn't touch.
// But verifying the plan output is printed is a good start.
// But verifying the plan output is printed is a good start.
}
3 changes: 2 additions & 1 deletion cli/deploy/tests/test_deploy_dry_run_with_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ fn test_deploy_dry_run_with_transport() {
.success()
.stdout(predicate::str::contains("PLAN"))
.stdout(predicate::str::contains("RUN ./gradlew build"))
.stdout(predicate::str::contains("UPLOAD(RSYNC)"))
.stdout(predicate::str::contains(
"UPLOAD build/libs/mod-1.0.0.jar -> mods/mod-1.0.0.jar",
"build/libs/mod-1.0.0.jar -> mods/mod-1.0.0.jar",
));
}
37 changes: 36 additions & 1 deletion cli/deploy/tests/test_deploy_force_scp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::TempDir;

#[test]
fn test_deploy_force_scp() {
panic!("Plan must contain UploadScp(...) instead of Rsync.");
let temp_dir = TempDir::new().expect("failed to create temp dir");
let home_dir = TempDir::new().expect("failed to create home dir");

// Create a new project
Command::cargo_bin("hymod")
.expect("binary not found")
.current_dir(temp_dir.path())
.env("HOME", home_dir.path())
.args(["new", "testmod"])
.assert()
.success();

let project_dir = temp_dir.path().join("testmod");

// Add a REMOTE server (dummy)
Command::cargo_bin("hymod")
.expect("binary not found")
.env("HOME", home_dir.path())
.args(["server", "add", "remote", "prod", "user@host:/path"])
.assert()
.success();

// Run deploy with no rsync in PATH: default should fall back to SCP.
Command::cargo_bin("hymod")
.expect("binary not found")
.current_dir(&project_dir)
.env("HOME", home_dir.path())
.env("PATH", "")
.args(["deploy", "prod", "--dry-run"])
.assert()
.success()
.stdout(predicate::str::contains("UPLOAD(SCP)"));
}
50 changes: 50 additions & 0 deletions cli/deploy/tests/test_deploy_path_arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::TempDir;

#[test]
fn test_deploy_path_arg() {
let temp_dir = TempDir::new().expect("failed to create temp dir");
let home_dir = TempDir::new().expect("failed to create home dir");

// Create a new project "testmod"
Command::cargo_bin("hymod")
.expect("binary not found")
.current_dir(temp_dir.path())
.env("HOME", home_dir.path())
.args(["new", "testmod"])
.assert()
.success();

let project_dir = temp_dir.path().join("testmod");

// Create another directory "outside"
let outside_dir = temp_dir.path().join("outside");
std::fs::create_dir(&outside_dir).unwrap();

// Add a server
Command::cargo_bin("hymod")
.expect("binary not found")
.env("HOME", home_dir.path())
.args(["server", "add", "local", "prod", "/tmp/server"])
.assert()
.success();

// Run deploy FROM OUTSIDE, pointing to project_dir via --path
Command::cargo_bin("hymod")
.expect("binary not found")
.current_dir(&outside_dir) // cwd is outside
.env("HOME", home_dir.path())
.args([
"deploy",
"prod",
"--path",
project_dir.to_str().unwrap(),
"--dry-run",
])
.assert()
.success()
.stdout(predicate::str::contains("PLAN"))
.stdout(predicate::str::contains("RUN ./gradlew build"))
.stdout(predicate::str::contains("mod-1.0.0.jar")); // Artifact identification should work
}
Loading
Loading