Commit 94b898b
feat(ui): add CLI client and shared UI common library (#1789)
* feat(ui): scaffold ui/common and ui/cli workspaces
- Add ui/common workspace with shared protocol types, SRPC WebSocket client,
UUID utilities, config types, and Zod validation schemas (21 tests, 0 errors)
- Add ui/cli workspace scaffolding with all configuration files
- Register both workspaces in pnpm-workspace.yaml
- Update .prettierignore, release-please config and manifest
- Add build-common (library, single env) and build-cli (3x3 matrix) CI jobs
- Add formatting steps for new workspaces in autofix.yml
- Add sonar-project.properties for both workspaces
* feat(cli): implement CLI core infrastructure
- Commander.js entry point with 10 subcommand groups (simulator, station,
template, connection, connector, atg, transaction, ocpp, performance,
supervision)
- Config loading with lilconfig + Zod validation + merge precedence
(defaults < config file < CLI flags)
- Output formatters: JSON (--json flag) and table (human-readable)
- WS client lifecycle: executeCommand() connects, sends, receives,
disconnects; registerSignalHandlers() for SIGINT/SIGTERM
- Typed error classes: ConnectionError, AuthenticationError,
TimeoutError, ServerError
- esbuild bundle with version injection and shebang
- 23 unit tests passing, zero lint warnings
* feat(cli): implement all 35 UI protocol command groups
All 35 ProcedureName procedures exposed as CLI subcommands:
- simulator: state, start, stop (3)
- station: list, start, stop, add, delete (5)
- template: list (1)
- connection: open, close (2)
- connector: lock, unlock (2)
- atg: start, stop (2)
- transaction: start, stop (2)
- ocpp: authorize, boot-notification, data-transfer, heartbeat,
meter-values, status-notification, firmware-status-notification,
diagnostics-status-notification, security-event-notification,
sign-certificate, notify-report, notify-customer-information,
log-status-notification, get-15118-ev-certificate,
get-certificate-status, transaction-event (16)
- performance: stats (1)
- supervision: set-url (1)
Shared runAction() helper DRYs up all command action handlers.
* test(cli): add integration tests
- 8 integration tests covering --help, --version, subcommand help,
connection error handling, JSON mode, and missing required options
- Separate test:integration script targeting tests/integration/
- Unit tests narrowed to tests/*.test.ts (no integration overlap)
* docs(cli): add README for ui/cli and ui/common
- ui/cli/README.md: installation, configuration, all command groups
with examples, global options, exit codes, environment variables,
available scripts
- ui/common/README.md: exported API reference (types, WebSocketClient,
config validation, UUID utilities)
* [autofix.ci] apply automated fixes
* fix(cli): improve error handling for config and connection failures
- Catch config file ENOENT in loader.ts and throw clean error message
- Move loadConfig inside try/catch in runAction to prevent stack traces
- Use event.error in WebSocketClient onerror for better error propagation
- Separate connect errors from request errors in lifecycle.ts
- Include cause message in ConnectionError for descriptive output
* fix(cli): address PR review feedback
- Fix onerror stale after connect: replace with persistent error handler
that fails all pending sendRequest promises immediately on WS error
- Fix dead code: call registerSignalHandlers() in cli.ts for SIGINT/SIGTERM
- Fix JSON error output: write to stdout (not stderr) in --json mode to
match documented contract and enable scripting with 2>/dev/null
- Fix process.exit() in action.ts: use process.exitCode for proper async
cleanup and testability
- Fix Map iteration: use snapshot+clear pattern in clearHandlers/failAllPending
- Fix empty array edge case in table formatter: check .length === 0
- Fix README: merge exit codes 1+2 into single row (Commander uses 1)
- Fix CI: add needs: build-common to build-cli job
* fix(cli): address second round of PR review feedback
- Fix ServerFailureError: WebSocketClient.handleMessage now rejects
with a typed Error (carrying the ResponsePayload) instead of a raw
object, preventing [object Object] in CLI output
- Fix table formatter: FAILURE responses now display hashIdsFailed/
hashIdsSucceeded tables instead of early-returning with minimal info
- Fix auth schema: Zod refinement requires username+password when
authentication enabled with protocol-basic-auth
- Fix AuthenticationConfig.type: use AuthenticationType enum instead
of plain string for compile-time safety
- Fix signal handlers: use process.exitCode instead of process.exit()
so finally blocks in executeCommand can run cleanup
* fix(cli): restore process.exit in signal handlers, fix type mismatch, remove dead code
- Signal handlers: restore process.exit() — setting only process.exitCode
keeps the process alive since registering SIGINT listener removes Node's
default termination behavior
- BroadcastChannelResponsePayload: align field name with server wire format
(hashId: string | undefined, not id: string)
- Remove unused DEFAULT_TIMEOUT_MS export from defaults.ts
* refactor(ui-common): extract WS timeout to shared constant
Move UI_WEBSOCKET_REQUEST_TIMEOUT_MS from a private constant in
WebSocketClient.ts to ui/common/src/constants.ts as a single source
of truth, exported via barrel for consumers.
* refactor(cli): merge duplicate ui-common and commander imports
Consolidate split type/value imports from the same module into single
import statements using inline type syntax (`import { type X, Y }`).
Resolves SonarCloud 'imported multiple times' warning.
* refactor(cli): use Number.parseInt instead of parseInt
Resolves SonarCloud 'Prefer Number.parseInt over parseInt' warning.
* refactor(cli): remove unnecessary Command alias from commander import
Use import { Command } from 'commander' directly instead of aliasing
to Cmd. Single import serves both type and value usage.
* chore: reorder ui workspaces consistently (common, cli, web)
Apply dependency-first ordering across all config files:
pnpm-workspace.yaml, .prettierignore, release-please config/manifest,
ci.yml job order, and autofix.yml step order.
* refactor(ui-common): widen validateUUID to accept unknown
Align with ui/web pattern — move typeof string check into validateUUID
itself, removing redundant guard at call sites. Add tests for
non-string inputs (number, null, undefined, object, boolean).
* fix(cli): use Vercel CLI pattern for graceful signal shutdown
Replace AbortController with module-level activeClient/activeSpinner
refs + cleanupInProgress guard (Vercel CLI pattern). Signal handler
stops spinner, disconnects WS, then process.exit(130/143). Simpler,
battle-tested, correct for batch request-response CLI.
* chore: reorder linked-versions components (common, cli, web)
* style(ci): add blank line between all job definitions in ci.yml
* refactor(cli): rename parseIntList to parseCommaSeparatedInts
* fix(cli): validate connector IDs input and wrap config search errors
- parseCommaSeparatedInts now rejects NaN values with a clear error
message instead of silently sending garbage to the server
- lilconfig search() path now wrapped in try/catch like the explicit
config path, giving consistent error messages for malformed configs
* feat(cli): standalone build + XDG-only config + install script
- Bundle all dependencies into single 504KB dist/cli.js (no node_modules
needed at runtime). Only ws native addons (bufferutil, utf-8-validate)
are external — ws falls back to pure JS automatically.
- Replace lilconfig with direct XDG config file reading:
${XDG_CONFIG_HOME:-~/.config}/evse-cli/config.json
- Remove lilconfig dependency
- Add install.sh: builds CLI, copies to ~/.local/bin/evse-cli, creates
default XDG config, warns if ~/.local/bin not in PATH
- Isolate ALL config tests from host env via XDG_CONFIG_HOME in
beforeEach/afterEach + add XDG auto-discovery happy path test
- Guard against JSON array in config file
- Update README: standalone install instructions + XDG config location
* [autofix.ci] apply automated fixes
* fix(cli): address review feedback round 3
WebSocketClient:
- Validate responsePayload shape before casting (guard against [uuid, null])
- Reject connect() Promise if socket closes before onopen fires
- Add tests for both edge cases
CLI:
- Validate ws:/wss: URL scheme in parseServerUrl
- Output ServerFailureError.payload via formatter (show hashIdsFailed details)
- Extract shared parseInteger() validator — reject NaN with clear error
- Remove dead error types (AuthenticationError, ServerError, TimeoutError)
- Chain build in test:integration script
- Remove unreachable FAILURE branch in outputTable
Schema:
- Require password.length > 0 in auth refinement (reject empty string)
* ci: remove redundant lint:fix from autofix workflow
pnpm format already runs eslint --cache --fix, making the separate
pnpm lint:fix step redundant in all three ui workspaces.
* refactor(cli): remove unnecessary comment in outputTable
* fix(cli): don't override template defaults in station add, reject array config
- station add: only include autoStart, persistentConfiguration,
ocppStrictCompliance, deleteConfiguration in payload when explicitly
passed — lets server use template defaults otherwise
- config loader: reject uiServer array with clear error instead of
silently spreading array keys into object
* refactor(cli): remove unused terminal-link dep and dead exports
- Remove terminal-link from dependencies (never imported)
- Remove unused exports: printSuccess, printWarning, printInfo (human.ts),
outputTableList (table.ts)
- Remove corresponding test for printSuccess
* [autofix.ci] apply automated fixes
* fix(ui-common): reject malformed payloads, replace ReadyState with enum
- handleMessage: reject pending handler immediately when server sends
response with matching UUID but missing status field, instead of
silently dropping and waiting for 60s timeout
- Replace ReadyState type alias with WebSocketReadyState const enum
- Remove redundant ReadyState type (duplicated enum semantics)
* fix(cli): add connection timeout, remove dead defaultUIServerConfig
- Wrap client.connect() with Promise.race timeout to prevent infinite
hang when server accepts TCP but never completes WS handshake
- Remove unused defaultUIServerConfig export from defaults.ts
* fix(cli): clear connection timeout timer on success
* refactor(ui-common): expose url getter, remove duplicate ConfigurationType
- WebSocketClient: make buildUrl() a public url getter so consumers
use the canonical URL instead of reconstructing it
- lifecycle.ts: use client.url instead of building URL independently
- Remove ConfigurationType.ts: UIServerConfigurationSection is now
a type alias for the Zod-inferred UIServerConfig (single source)
* fix(cli): display failure status instead of misleading Success
displayGenericPayload now checks payload.status before printing — a
failure response without hashIds shows the red status line instead of
a green checkmark.
* fix: align ui/web ResponsePayload with server, reject non-object config
- ui/web ResponsePayload: replace incorrect hashIds with
hashIdsFailed/hashIdsSucceeded/responsesFailed matching server schema
- cli config loader: reject non-object uiServer values with clear error
instead of silently falling back to defaults
* fix: reject non-object config files, merge duplicate import
- Config loader: throw on primitive JSON config values (42, "hello")
instead of silently falling back to defaults
- Merge duplicate UIProtocol.ts import in ui/common/src/client/types.ts
* fix: suppress dangling connect rejection, remove dead Zod defaults
- Attach .catch() to connect() promise to prevent unhandled rejection
when the timeout wins Promise.race and disconnect triggers onclose
- Remove .default() calls from Zod schema — the CLI loader always
provides all fields via its canonical defaults map, making Zod
defaults dead code paths
* fix(cli): preserve error cause in config loader for debuggability
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>1 parent 624adeb commit 94b898b
65 files changed
Lines changed: 3138 additions & 8 deletions
File tree
- .github
- release-please
- workflows
- ui
- cli
- scripts
- src
- client
- commands
- config
- output
- tests
- integration
- common
- src
- client
- config
- types
- utils
- tests
- web/src/types
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
14 | 22 | | |
15 | 23 | | |
16 | 24 | | |
| |||
24 | 32 | | |
25 | 33 | | |
26 | 34 | | |
27 | | - | |
| 35 | + | |
28 | 36 | | |
29 | 37 | | |
30 | 38 | | |
| |||
34 | 42 | | |
35 | 43 | | |
36 | 44 | | |
37 | | - | |
38 | 45 | | |
39 | 46 | | |
40 | | - | |
41 | 47 | | |
42 | 48 | | |
43 | 49 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
3 | 5 | | |
4 | 6 | | |
5 | 7 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
35 | 41 | | |
36 | | - | |
37 | | - | |
38 | | - | |
| 42 | + | |
39 | 43 | | |
40 | 44 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
| 58 | + | |
57 | 59 | | |
58 | 60 | | |
59 | 61 | | |
| |||
106 | 108 | | |
107 | 109 | | |
108 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
109 | 209 | | |
110 | 210 | | |
111 | 211 | | |
| |||
163 | 263 | | |
164 | 264 | | |
165 | 265 | | |
| 266 | + | |
166 | 267 | | |
167 | 268 | | |
168 | 269 | | |
| |||
175 | 276 | | |
176 | 277 | | |
177 | 278 | | |
| 279 | + | |
178 | 280 | | |
179 | 281 | | |
180 | 282 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
| |||
0 commit comments