EasyPostman is a Java 17 + Swing desktop API testing app. Entry point: com.laker.postman.App. Build tool: Maven multi-module.
easy-postman-parent (root pom.xml, revision = host version)
├── easy-postman-plugin-api # Stable plugin SPI: EasyPostmanPlugin, PluginContext, PluginDescriptor
├── easy-postman-plugin-bridge # Shared bridge contracts, models, utils (ConfigPathConstants, AppConstants, I18nUtil, MessageKeys, SystemUtil, UserSettingsUtil, JsonUtil, AppRuntimeLayout); bridge service interfaces (GitPluginService, ClientCertificatePluginService); shared models (Workspace, WorkspaceType, GitAuthType, GitCommitInfo, GitOperationResult, etc.)
├── easy-postman-plugin-ui # Shared Swing UI base components, FontsUtil, IconUtil, NotificationUtil, EditorThemeUtil, ModernColors, ModernButtonFactory
├── easy-postman-plugin-runtime # Plugin scan/load/lifecycle: PluginRuntime, PluginScanner, PluginLoader, PluginRegistry
├── easy-postman-plugins/ # Official plugins (each builds an independent JAR)
│ ├── plugin-manager # Catalog parsing, online/offline install facade
│ ├── plugin-client-cert
│ ├── plugin-capture
│ ├── plugin-redis
│ ├── plugin-kafka
│ └── plugin-decompiler
└── easy-postman-app # Host application; consumes plugin-registered capabilities
When adding shared non-UI logic accessible by both host and plugins, put it in easy-postman-plugin-bridge. When adding shared UI utilities, put them in easy-postman-plugin-ui. Do not put bridge/SPI code directly into easy-postman-app.
# Full build (all modules + all plugins), skip tests
mvn clean package -DskipTests
# Build only the host app (fastest iteration)
mvn -pl easy-postman-app -am -DskipTests clean package
# Build host app + one plugin
mvn -pl easy-postman-app,easy-postman-plugins/plugin-redis -am clean package -DskipTests
# Quick compile check (no jar, fast)
mvn -q -pl easy-postman-app -am -DskipTests compile
# Run tests for a specific class in headless mode
mvn -q -pl easy-postman-app -am -Dtest=<TestClass> -Dsurefire.failIfNoSpecifiedTests=false -Djava.awt.headless=true testOutput: easy-postman-app/target/easy-postman-${revision}.jar
Native installers are produced by build/mac.sh, build/win-exe.bat, build/linux-deb.sh, build/linux-rpm.sh — these call jpackage and reference a fixed filename easy-postman.jar (not the versioned one).
App.main()
-> configurePlatformSpecificSettings() // Linux: FlatLaf window decorations
-> SwingUtilities.invokeLater()
-> SimpleThemeManager.initTheme() // reads easy_postman_settings.properties
-> FontManager.applyFontSettings()
-> SplashWindow or direct SwingWorker
-> StartupCoordinator.prepareMainFrame()
-> BeanFactory.init("com.laker.postman") // scans @Component beans
-> PluginRuntime.initialize() // scan, load, lifecycle
-> MainFrame (EDT)
-> registerShutdownHook()
-> PluginRuntime.shutdown() + BeanFactory.destroy()
The project uses its own lightweight IOC container (com.laker.postman.ioc), not Spring. Do not import Spring annotations.
| Annotation | Purpose |
|---|---|
@Component |
Marks a class as a managed bean (auto-scanned from com.laker.postman) |
@Autowired |
Field/constructor/method injection |
@PostConstruct |
Called after all fields are injected |
@PreDestroy |
Called on BeanFactory.destroy() |
Retrieve beans outside of injection: BeanFactory.getBean(MyService.class).
Three-level circular dependency cache is implemented in ApplicationContext — if you see a circular dependency crash, check bean design rather than patching the cache.
All UI panels that are logically singletons must:
- Extend
SingletonBasePanel - Be obtained via
SingletonFactory.getInstance(MyPanel.class)— nevernew MyPanel() - Implement
initUI()for component creation andregisterListeners()for event wiring - Call
safeInit()after obtaining the instance (this callsinitUI()thenregisterListeners())
SingletonBaseMenuBar follows the same pattern for menu bars.
AppConstants—APP_NAME,BASE_PACKAGEConfigPathConstants— all data file paths (EASY_POSTMAN_SETTINGS,COLLECTIONS,ENVIRONMENTS,DEFAULT_WORKSPACE_DIR, etc.)JsonUtil— Jackson-based JSON serialization/deserialization (supports JSON5/comments); use this instead of raw Jackson callsAppRuntimeLayout— resolves portable mode (isPortableMode(Class<?>)) and key directory paths (applicationRootDirectory,codeSourceDirectory); portable mode is triggered by a.portablemarker file or theeasyPostman.portablesystem property
Data root: SystemUtil.getEasyPostmanPath() — returns <user.home>/EasyPostman/ in normal mode, or <app-dir>/data/ in portable mode.
The app supports multiple named workspaces. Each workspace is an isolated directory (collections, environments, settings). A workspace can optionally be backed by a Git repository.
- Models:
Workspace,WorkspaceTypeineasy-postman-plugin-bridge - Workspace UI:
WorkspacePaneland its components incom.laker.postman.panel.workspace - Git operations are declared in
GitPluginService(bridge module) and implemented byGitWorkspacePluginServiceincom.laker.postman.plugin.git - Host-side accessor:
GitPluginServices(incom.laker.postman.plugin.bridge, app module) — callsPluginAccess.getService(GitPluginService.class) WorkspaceStorageUtil(app util) handles workspace list persistence
Pre/post scripts run through ScriptExecutionPipeline (com.laker.postman.service.js), which merges collection/folder/request-level scripts, pools Rhino contexts (JsContextPool), and injects polyfills.
Built-in JS libraries bundled at easy-postman-app/src/main/resources/js-libs/:
crypto-js.min.js,lodash.min.js,moment.min.js
Plugin scripts are injected via registerScriptApi (alias → object factory); they become accessible as pm.<alias> inside scripts.
All user-visible strings must use I18nUtil.getMessage(MessageKeys.SOME_KEY). Keys are defined as constants in MessageKeys (bridge module). Translations live in easy-postman-app/src/main/resources/messages_en.properties and messages_zh.properties. Never hard-code UI strings directly.
- Theme is managed by
SimpleThemeManager(light/dark via FlatLaf, animated transitions). - User settings are persisted to
easy_postman_settings.propertiesviaSettingManager(static Properties file) andUserSettingsUtil(bridge module). - Font size setting key:
ui_font_sizein that properties file. - Custom FlatLaf token overrides:
easy-postman-app/src/main/resources/com/laker/postman/common/themes/EasyLightLaf.propertiesandEasyDarkLaf.properties - RSyntaxTextArea editor theme XMLs:
easy-postman-app/src/main/resources/themes/easypostman-light.xmlandeasypostman-dark.xml - Shared semantic colors for both themes:
ModernColorsineasy-postman-plugin-ui(com.laker.postman.common.constants.ModernColors)
- Each plugin is a standalone JAR with a descriptor at
META-INF/easy-postman/*.properties(generated from the plugin'spom.xml). - Plugin entry class implements
EasyPostmanPlugin;onLoad(PluginContext)registers all capabilities. - Extension points:
registerScriptApi,registerService,registerToolboxContribution,registerScriptCompletionContributor,registerSnippet. - Host consumes registered capabilities from
PluginRegistry. - Bridge service interfaces (
GitPluginService,ClientCertificatePluginService) live ineasy-postman-plugin-bridge. Plugins register implementations viacontext.registerService(GitPluginService.class, impl). Host code retrieves them throughPluginAccess.getService(Type.class)or the typed accessors (GitPluginServices,ClientCertificatePluginServices) incom.laker.postman.plugin.bridge(app module). - Version model:
revision= host release version;plugin.platform.version= SPI compatibility boundary. Only bumpplugin.platform.versionwhen plugin SPI/runtime changes are breaking. - Catalog source of truth:
pom.xml → descriptor → release asset → catalog. Do not hand-editplugin-catalog/or the bundled fallback inplugin-manager/src/main/resources/plugin-catalog/independently — update both together. - Reference runtime architecture:
docs/PLUGIN_RUNTIME_ARCHITECTURE_zh.md.
| Workflow | Trigger | Purpose |
|---|---|---|
pr-check.yml |
PR to main/master/develop | Maven build + tests + PR validation |
release.yml |
Push tag | Multi-platform native installer build |
plugin-release.yml |
Plugin tag | Build plugin JARs, validate consistency, publish, update catalog |
codeql-analysis.yml |
Schedule/push | Security analysis |
auto-label.yml |
Issue/PR opened or edited | Auto-apply labels based on title/body keywords |
sync-labels.yml |
Push to main (.github/labels.yml changed) or manual |
Sync label definitions to the repository |
welcome.yml |
Issue/PR opened | Post a welcome comment for first-time contributors |
- swing-flatlaf-miglayout-principles: Use when modifying EasyPostman Swing forms that use FlatLaf and MigLayout, especially when layout refactors introduce clipped focus rings, dense spacing, border conflicts, or inconsistent form structure. (file: .codex/skills/swing-flatlaf-miglayout-principles/SKILL.md)
- fontsutil-font-usage: Use when modifying EasyPostman Swing UI fonts, especially when dialogs, labels, tables, tabs, or renderers look too large or too small, or when a change must follow the user's configured UI font size. Prefer FontsUtil.getDefaultFontWithOffset(...). (file: .codex/skills/fontsutil-font-usage/SKILL.md)
- swing-ui-test-headless-guard: Use when adding or updating EasyPostman Swing/TestNG UI tests that may run in headless CI. Reuse
AbstractSwingUiTestinstead of duplicating headless or no-display skip logic. (file: .codex/skills/swing-ui-test-headless-guard/SKILL.md)
- If the request names a skill or clearly matches the description above, read the skill and follow it.
- Keep the skill body concise and use it only for repo-specific knowledge that is hard to infer from the code alone.