ADR-064: Generator Output Directory Configuration
Status
Accepted
Date
2025-12-19
Deciders
- Norbert Bede
Context and Problem Statement
Code generators (plugin, model, process, callout) need a consistent way to determine where to write generated files. Currently, when outputDir is not provided, generators default to the current working directory (.), which causes problems:
- MCP Server context: Files are created where the MCP server was started, not where the developer expects
- n8n automation: Batch jobs create files in the workflow execution directory
- Multi-project setups: Developers work on multiple plugins in a structured workspace
We need a configurable default output directory that works across all interfaces (CLI, MCP, Chat API, n8n).
Decision Drivers
- Predictable output location: Developers should know where generated files will appear
- Workspace organization: Support standard GitHub/GitLab workspace layouts
- Automation friendly: n8n and CI/CD should work without hardcoded paths
- Backward compatible: Existing CLI usage should continue to work
Decision Outcome
Implement a hierarchical output directory resolution with configurable defaults:
1. Explicit outputDir parameter (highest priority)
↓
2. GITHUB_ROOT/{artifactId} for plugin generator
↓
3. GENERATOR_ROOT_DIR default directory
↓
4. Current working directory (.)
Configuration
# application.properties
idempiere.hub.generator.root-dir=${GENERATOR_ROOT_DIR:}
idempiere.hub.generator.github-root=${GITHUB_ROOT:}
Environment Variables
| Variable | Purpose | Example |
|---|---|---|
GITHUB_ROOT |
Parent directory for plugin repositories | /Users/dev/github |
GENERATOR_ROOT_DIR |
Default output for all generators | /Users/dev/generated |
Best Practices
Developer Workstation Setup
# ~/.zshrc or ~/.bashrc
export GITHUB_ROOT="$HOME/github"
export GENERATOR_ROOT_DIR="$HOME/github"
MCP Server Configuration (Claude Desktop)
{
"mcpServers": {
"idempiere": {
"command": "java",
"args": ["-jar", "/path/to/idempiere-hub-runner.jar", "server", "mcp"],
"env": {
"GITHUB_ROOT": "/Users/dev/github",
"GENERATOR_ROOT_DIR": "/Users/dev/github",
"IDEMPIERE_DB_HOST": "localhost",
"IDEMPIERE_DB_PORT": "5433"
}
}
}
}
n8n Workflow Configuration
{
"nodes": [
{
"name": "Generate Plugin",
"type": "n8n-nodes-base.executeCommand",
"parameters": {
"command": "java -jar idempiere-hub-runner.jar generate plugin --artifactId=my-plugin",
"env": {
"GITHUB_ROOT": "/opt/n8n/workspaces/plugins",
"GENERATOR_ROOT_DIR": "/opt/n8n/workspaces/generated"
}
}
}
]
}
CI/CD Pipeline (GitHub Actions)
jobs:
generate:
runs-on: ubuntu-latest
env:
GITHUB_ROOT: ${{ github.workspace }}
GENERATOR_ROOT_DIR: ${{ github.workspace }}/generated
steps:
- uses: actions/checkout@v4
- name: Generate Plugin
run: |
java -jar idempiere-hub-runner.jar generate plugin \
--artifactId=org.idempiere.myplugin \
--groupId=org.idempiere
Architecture
Resolution Flow
┌─────────────────────────────────────────────────────────────────────────┐
│ GeneratorToolLogic.generate(generatorName, options, outputDir, dryRun) │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ GeneratorConfig.resolveOutputDir(outputDir, artifactId) │
│ │
│ 1. if (outputDir != null) → return outputDir │
│ 2. if (artifactId != null && GITHUB_ROOT set) │
│ → return GITHUB_ROOT/artifactId │
│ 3. if (GENERATOR_ROOT_DIR set) → return GENERATOR_ROOT_DIR │
│ 4. return "." (current directory) │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ GeneratorTree(resolvedPath) │
│ → Write files to resolved directory │
└─────────────────────────────────────────────────────────────────────────┘
Example Scenarios
Scenario 1: Plugin Generation with GITHUB_ROOT
GITHUB_ROOT=/Users/dev/github
MCP Tool: generatePlugin(artifactId="org.idempiere.rating", ...)
Resolved: /Users/dev/github/org.idempiere.rating/
Scenario 2: Model Generation with GENERATOR_ROOT_DIR
GENERATOR_ROOT_DIR=/Users/dev/generated
MCP Tool: generateModel(tableName="XX_Rating", ...)
Resolved: /Users/dev/generated/
Scenario 3: Explicit outputDir (Always Wins)
CLI: idempiere-hub generate plugin --outputDir=/tmp/test
Resolved: /tmp/test/
Scenario 4: No Config (Backward Compatible)
Resolved: . (current directory)
Implementation
GeneratorConfig Interface
@ConfigMapping(prefix = "idempiere.hub.generator")
public interface GeneratorConfig {
Optional<String> rootDir();
Optional<String> githubRoot();
default String resolveOutputDir(String explicitOutputDir, String artifactId) {
if (explicitOutputDir != null && !explicitOutputDir.isBlank()) {
return explicitOutputDir;
}
if (artifactId != null && githubRoot().isPresent()) {
return githubRoot().get() + "/" + artifactId;
}
if (rootDir().isPresent() && !rootDir().get().isBlank()) {
return rootDir().get();
}
return ".";
}
}
GeneratorToolLogic Update
@Inject GeneratorConfig generatorConfig;
public ToolResult generate(String generatorName, Map<String, Object> options,
String outputDir, boolean dryRun) {
String artifactId = options != null ? (String) options.get("artifactId") : null;
String resolvedOutputDir = generatorConfig.resolveOutputDir(outputDir, artifactId);
Path basePath = Path.of(resolvedOutputDir);
// ...
}
Workspace Layout Best Practice
$GITHUB_ROOT/
├── org.idempiere.rating/ # Generated plugin
│ ├── pom.xml
│ ├── src/
│ └── META-INF/
├── org.idempiere.shipping/ # Another plugin
│ ├── pom.xml
│ └── src/
├── idempiere/ # iDempiere core (for reference)
│ └── ...
└── idempiere-hub/ # This project
└── ...
Security Considerations
- Path validation: Generators should validate resolved paths are within expected boundaries
- No path traversal: Reject
../in artifactId or outputDir - Permissions: Ensure write permissions before generating
Note: Credentials for VCS access (GitHub tokens, etc.) are out of scope for this ADR. See ADR-065: Credentials and Secrets Management for secure credential handling with AWS Secrets Manager, HashiCorp Vault, etc.
Related ADRs
- ADR-003: Generator Architecture - Generator framework
- ADR-010: MCP Server Architecture - MCP tool integration
- ADR-006: CLI as M2M API - Automation integration
References
Note: This configuration is optional. Without it, generators behave as before (current directory). Setting GITHUB_ROOT enables the workspace-aware plugin generation pattern.