@alt-javascript/camel-lite-cli 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +200 -26
- package/bin/camel-lite.js +136 -43
- package/package.json +9 -17
- package/src/index.js +108 -29
- package/src/components.js +0 -42
package/README.md
CHANGED
|
@@ -1,52 +1,226 @@
|
|
|
1
1
|
# camel-lite-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](../../LICENSE)
|
|
4
|
+
|
|
5
|
+
Command-line runtime for camel-lite. Load a route definition and run it — inject a single message, receive messages from a URI, or keep the context alive in daemon mode.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```sh
|
|
8
|
-
npm install -g camel-lite-cli
|
|
10
|
+
npm install -g @alt-javascript/camel-lite-cli
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
15
|
```sh
|
|
14
|
-
# Run a route file
|
|
16
|
+
# Run a route file silently
|
|
15
17
|
camel-lite -r route.yaml
|
|
16
18
|
|
|
17
|
-
# Inject a JSON body and
|
|
19
|
+
# Inject a JSON body and print any reply
|
|
18
20
|
camel-lite -r route.yaml -i '{"name":"world"}'
|
|
19
21
|
|
|
20
|
-
#
|
|
21
|
-
camel-lite -r route.yaml -
|
|
22
|
+
# Keep a timer route running until Ctrl-C
|
|
23
|
+
camel-lite -r timer-route.yaml -d
|
|
24
|
+
|
|
25
|
+
# Consume messages from a URI and print each body to stdout
|
|
26
|
+
camel-lite -r route.yaml -c seda:orders
|
|
27
|
+
|
|
28
|
+
# Show framework logs while debugging a route
|
|
29
|
+
camel-lite --debug -r route.yaml -i '{"name":"world"}'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
camel-lite -r <file|-> [options]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Required
|
|
39
|
+
|
|
40
|
+
| Flag | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `-r`, `--routes <file\|->` | Route definition file (`.yaml`, `.yml`, `.json`) or `-` to read from stdin |
|
|
43
|
+
|
|
44
|
+
### Input / Output
|
|
45
|
+
|
|
46
|
+
| Flag | Description | Default |
|
|
47
|
+
|------|-------------|---------|
|
|
48
|
+
| `-i`, `--input <body\|->` | Message body to inject into the first `from:` endpoint. Accepts a JSON string, a plain string, or `-` to read from stdin | *(none — no message sent)* |
|
|
49
|
+
| `-p`, `--producer-uri <uri>` | Override the target endpoint URI for `-i`. Requires `-i`. Default: the first route's `from:` URI | *(first route `from:` URI)* |
|
|
50
|
+
| `--exchange-pattern <pattern>` | `InOnly` or `i` — fire-and-forget (exit after send). `InOut` or `io` — request-reply (prints reply body to stdout). Case-insensitive. | `InOnly` |
|
|
51
|
+
| `-c`, `--consumer-uri <uri>` | Poll a consumer URI in a daemon loop, printing each message body to stdout. Implies daemon mode. Mutually exclusive with `-i` and `-p` | *(none)* |
|
|
52
|
+
|
|
53
|
+
### Runtime Behaviour
|
|
54
|
+
|
|
55
|
+
| Flag | Description | Default |
|
|
56
|
+
|------|-------------|---------|
|
|
57
|
+
| `-d`, `--daemon` | Keep the context alive until `SIGINT`/`SIGTERM`. Use for timer, cron, and seda-driven routes | `false` |
|
|
58
|
+
| `-l`, `--log-mode <text\|json>` | Log output format: `text` (human-readable) or `json` (structured JSON objects) | `text` |
|
|
59
|
+
|
|
60
|
+
### Logging
|
|
61
|
+
|
|
62
|
+
By default, all camel-lite and boot framework log output is suppressed — the CLI is pipe-friendly and produces zero stderr output unless a flag is set.
|
|
63
|
+
|
|
64
|
+
| Flag | Description |
|
|
65
|
+
|------|-------------|
|
|
66
|
+
| `--verbose` | Enable info-level framework logging on stderr |
|
|
67
|
+
| `--debug` | Enable debug-level framework logging on stderr. Takes precedence over `--verbose` |
|
|
68
|
+
|
|
69
|
+
### Other
|
|
70
|
+
|
|
71
|
+
| Flag | Description |
|
|
72
|
+
|------|-------------|
|
|
73
|
+
| `-v`, `--version` | Print the CLI version and exit |
|
|
74
|
+
| `-h`, `--help` | Print help and exit |
|
|
75
|
+
|
|
76
|
+
## Flag Constraints
|
|
77
|
+
|
|
78
|
+
- `-r -` and `-i -` are mutually exclusive: only one argument can read from stdin.
|
|
79
|
+
- `-p` requires `-i`. Specifying a producer URI without a body to send is an error.
|
|
80
|
+
- `-c` is mutually exclusive with `-i` and `-p`. Consumer mode and producer mode are separate operations.
|
|
81
|
+
- `--exchange-pattern` accepts `InOnly`, `i`, `InOut`, or `io` (case-insensitive). Any other value is an error.
|
|
82
|
+
|
|
83
|
+
## Examples
|
|
84
|
+
|
|
85
|
+
### Fire-and-forget message
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
camel-lite -r route.yaml -i '{"order":"ABC-1","qty":5}'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Sends the JSON body to the first route's `from:` URI. Exits after the route completes. No output unless the route writes to stdout.
|
|
92
|
+
|
|
93
|
+
### Request-reply (InOut)
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
camel-lite -r transform-route.yaml -i 'hello' --exchange-pattern InOut
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Sends `hello`, waits for the reply, and prints the reply body to stdout.
|
|
100
|
+
|
|
101
|
+
### Send to a specific endpoint
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
camel-lite -r route.yaml -i '{"event":"login"}' -p seda:audit
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Sends the body to `seda:audit` instead of the route's default `from:` URI.
|
|
108
|
+
|
|
109
|
+
### Read route and body from stdin
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
# Route from stdin, body as argument
|
|
113
|
+
cat route.yaml | camel-lite -r - -i '{"name":"world"}'
|
|
22
114
|
|
|
23
|
-
#
|
|
24
|
-
|
|
115
|
+
# Route as argument, body from stdin
|
|
116
|
+
echo '{"name":"world"}' | camel-lite -r route.yaml -i -
|
|
117
|
+
```
|
|
25
118
|
|
|
26
|
-
|
|
27
|
-
camel-lite -r route.yaml -d
|
|
119
|
+
### Daemon mode
|
|
28
120
|
|
|
29
|
-
|
|
30
|
-
camel-lite -
|
|
121
|
+
```sh
|
|
122
|
+
camel-lite -r cron-route.yaml -d
|
|
31
123
|
```
|
|
32
124
|
|
|
33
|
-
|
|
125
|
+
Keeps the context running. Press Ctrl-C or send `SIGTERM` to stop.
|
|
126
|
+
|
|
127
|
+
### Consumer daemon
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
camel-lite -r route.yaml -c timer:tick?period=1000
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Polls `timer:tick` in a loop. Each message body is printed to stdout on a new line. Runs until `SIGINT` or `SIGTERM`.
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
camel-lite -r route.yaml -c seda:results | tee results.log
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Pipe consumer output to another tool — the CLI writes one body per line to stdout.
|
|
140
|
+
|
|
141
|
+
### Logging
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
# Show info-level framework logs (route registration, context start/stop)
|
|
145
|
+
camel-lite --verbose -r route.yaml -i body
|
|
146
|
+
|
|
147
|
+
# Show debug-level logs (per-message dispatch, timer fires, lock renewal)
|
|
148
|
+
camel-lite --debug -r route.yaml -i body
|
|
149
|
+
|
|
150
|
+
# --debug wins when both are present
|
|
151
|
+
camel-lite --debug --verbose -r route.yaml -i body
|
|
152
|
+
|
|
153
|
+
# JSON-structured log lines (combine with --verbose or --debug)
|
|
154
|
+
camel-lite --verbose -l json -r route.yaml -i body
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Missing home config directory
|
|
158
|
+
|
|
159
|
+
If `~/.camel-lite/` does not exist, the CLI starts silently with no user config. No error is thrown.
|
|
160
|
+
|
|
161
|
+
## User Configuration
|
|
162
|
+
|
|
163
|
+
The CLI loads `application.yaml` (or `application.json`) from `~/.camel-lite/` on every invocation. Place component connection strings, default log levels, or any `boot.camel-lite.*` properties there.
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
~/.camel-lite/
|
|
167
|
+
application.yaml
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Example `~/.camel-lite/application.yaml`:
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
boot:
|
|
174
|
+
camel-lite:
|
|
175
|
+
amqp:
|
|
176
|
+
url: amqp://localhost:5672
|
|
177
|
+
username: guest
|
|
178
|
+
password: guest
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Values in `~/.camel-lite/application.yaml` are overridden by the CLI's own `EphemeralConfig` overlay (logging level, banner suppression), and can themselves be overridden by environment variables loaded through the same config chain.
|
|
182
|
+
|
|
183
|
+
## Exit Codes
|
|
184
|
+
|
|
185
|
+
| Code | Meaning |
|
|
186
|
+
|------|---------|
|
|
187
|
+
| `0` | Success |
|
|
188
|
+
| `1` | Validation error (bad flags), route error, or unhandled exception |
|
|
189
|
+
|
|
190
|
+
Error messages are written to stderr in the format `camel-lite: error: <message>`.
|
|
191
|
+
|
|
192
|
+
## Troubleshooting
|
|
193
|
+
|
|
194
|
+
**No output in silent mode is expected.** All framework log lines are suppressed by default. Use `--verbose` or `--debug` to see what the route is doing.
|
|
195
|
+
|
|
196
|
+
**`camel-lite: error: -r / --routes is required`** — The `-r` flag is mandatory. Every invocation must specify a route definition file.
|
|
197
|
+
|
|
198
|
+
**`camel-lite: error: -p / --producer-uri requires -i / --input`** — You specified a target URI with `-p` but did not provide a body to send. Add `-i <body>`.
|
|
199
|
+
|
|
200
|
+
**`camel-lite: error: -c / --consumer-uri is mutually exclusive with -i / --input`** — Consumer mode (`-c`) and producer mode (`-i`) cannot be combined. Choose one.
|
|
201
|
+
|
|
202
|
+
**`camel-lite: fatal: <message>`** — An unhandled exception occurred during CDI boot or route execution. Run with `--debug` to see the full log trace leading up to the error.
|
|
203
|
+
|
|
204
|
+
**Route exits immediately without processing** — Without `-d` or `-c`, the context stops as soon as the initial message (if any) completes. Add `-d` for timer, cron, or seda-driven routes that need to stay alive.
|
|
205
|
+
|
|
206
|
+
## Runtime Architecture
|
|
34
207
|
|
|
35
|
-
|
|
36
|
-
|------|-----------|-------------|---------|
|
|
37
|
-
| `-r` | `--routes` | Path to route definition file (`.yaml`, `.yml`, `.json`), or `-` to read from stdin | *(required)* |
|
|
38
|
-
| `-i` | `--input` | Message body to inject into the first `from:` endpoint. Accepts a JSON string, a plain string, or `-` to read from stdin | *(none)* |
|
|
39
|
-
| `-d` | `--daemon` | Keep the process running after the initial dispatch. Use for timer/cron/seda-driven routes | `false` |
|
|
40
|
-
| `-l` | `--log-mode` | Log output format: `pretty` (human-readable) or `json` (structured) | `pretty` |
|
|
41
|
-
| `-v` | `--version` | Print the CLI version and exit | — |
|
|
208
|
+
The CLI uses a full CDI boot stack for every invocation:
|
|
42
209
|
|
|
43
|
-
|
|
210
|
+
1. `EphemeralConfig` overlay is constructed with the computed log level and banner suppression.
|
|
211
|
+
2. User config is loaded synchronously from `~/.camel-lite/` via `ProfileConfigLoader`.
|
|
212
|
+
3. A `PropertySourceChain` is composed: overlay (highest priority) → user config.
|
|
213
|
+
4. `Boot.boot({ config })` initialises the CDI container with the chained config.
|
|
214
|
+
5. `camelLiteExtrasStarter` registers all 12 component schemes into the CDI context.
|
|
215
|
+
6. `CdiCamelRuntime` loads the route definition and starts the `CamelContext`.
|
|
216
|
+
7. `ProducerTemplate` or `ConsumerTemplate` performs the requested operation.
|
|
217
|
+
8. The context is stopped (unless `-d` or `-c` keeps it alive).
|
|
44
218
|
|
|
45
|
-
|
|
46
|
-
- `-r` and `-i` cannot both be `-` simultaneously. Only one of the two can read from stdin at a time.
|
|
47
|
-
- Route files are loaded via `RouteLoader.loadFile()`. `.yaml` / `.yml` / `.json` extensions are recognised; other extensions trigger content-sniffing.
|
|
48
|
-
- Without `-d`, the process exits after the first message completes its route (or immediately if no `-i` is provided and the route has no self-triggering source).
|
|
219
|
+
All 12 camel-lite component schemes are pre-loaded. Components whose external broker or service is unavailable at startup fail gracefully — the route starts with the available components.
|
|
49
220
|
|
|
50
221
|
## See Also
|
|
51
222
|
|
|
52
223
|
- [camel-lite root README](../../README.md)
|
|
224
|
+
- [camel-lite-core](../camel-lite-core/README.md) — `CamelContext`, `RouteBuilder`, `ProducerTemplate`, `ConsumerTemplate`
|
|
225
|
+
- [boot-camel-lite-starter](../boot-camel-lite-starter/README.md) — CDI auto-configuration for core + 8 components
|
|
226
|
+
- [boot-camel-lite-extras-starter](../boot-camel-lite-extras-starter/README.md) — CDI auto-configuration for amqp/sql/nosql/master
|
package/bin/camel-lite.js
CHANGED
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* camel-lite -r <route-file|-> [-i <input|->] [-d] [-l text|json]
|
|
8
|
+
* [--verbose] [--debug]
|
|
9
|
+
* [-p <uri>] [--exchange-pattern <i|InOnly|io|InOut>]
|
|
10
|
+
* [-c <uri>]
|
|
8
11
|
*
|
|
9
12
|
* Options:
|
|
10
|
-
* -r, --routes <file|->
|
|
11
|
-
* -i, --input <body|->
|
|
12
|
-
* -d, --daemon
|
|
13
|
-
* -l, --log-mode <text|json>
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* -r, --routes <file|-> Route definition file (.yaml/.yml/.json) or - for stdin
|
|
14
|
+
* -i, --input <body|-> Message body to inject, or - to read from stdin
|
|
15
|
+
* -d, --daemon Keep context alive until SIGINT/SIGTERM (default: false)
|
|
16
|
+
* -l, --log-mode <text|json> Log output format: text (default) or json
|
|
17
|
+
* --verbose Enable info-level logging (default: logging suppressed)
|
|
18
|
+
* --debug Enable debug-level logging (takes precedence over --verbose)
|
|
19
|
+
* -p, --producer-uri <uri> Override producer URI (requires -i; default: first route from URI)
|
|
20
|
+
* --exchange-pattern <i|InOnly|io|InOut> Exchange pattern: InOnly (fire-and-forget) or InOut (request-reply)
|
|
21
|
+
* -c, --consumer-uri <uri> Poll consumer URI; implies daemon mode; mutually exclusive with -i and -p
|
|
22
|
+
* -v, --version Print version
|
|
23
|
+
* -h, --help Print this help
|
|
16
24
|
*
|
|
17
25
|
* Exactly one of -r or -i may use stdin (-).
|
|
18
26
|
*
|
|
@@ -23,11 +31,17 @@
|
|
|
23
31
|
* cat route.yaml | camel-lite -r - -i '{"name":"world"}'
|
|
24
32
|
* camel-lite -r route.yaml -d # daemon mode — Ctrl-C to stop
|
|
25
33
|
* camel-lite -l json -r route.yaml # JSON log output
|
|
34
|
+
* camel-lite --verbose -r route.yaml # info-level logging
|
|
35
|
+
* camel-lite --debug -r route.yaml # debug-level logging
|
|
36
|
+
* camel-lite -r route.yaml -i 'hello' -p direct:ep
|
|
37
|
+
* camel-lite -r route.yaml -i 'hello' --exchange-pattern InOut
|
|
38
|
+
* camel-lite -r route.yaml -c timer:tick?period=1000 # poll consumer daemon
|
|
26
39
|
*/
|
|
27
40
|
|
|
28
41
|
import { createRequire } from 'node:module';
|
|
29
42
|
import { fileURLToPath } from 'node:url';
|
|
30
|
-
import { dirname } from 'node:path';
|
|
43
|
+
import { dirname, join } from 'node:path';
|
|
44
|
+
import { homedir } from 'node:os';
|
|
31
45
|
import { program } from 'commander';
|
|
32
46
|
|
|
33
47
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -51,23 +65,6 @@ async function readStdin() {
|
|
|
51
65
|
return chunks.join('');
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
/**
|
|
55
|
-
* Configure @alt-javascript/logger for the requested output mode.
|
|
56
|
-
* Must be called before any camel-lite module imports to take effect,
|
|
57
|
-
* since loggers are constructed with their formatter at getLogger() call time.
|
|
58
|
-
* @param {'text'|'json'} mode
|
|
59
|
-
*/
|
|
60
|
-
async function configureLogging(mode) {
|
|
61
|
-
const { Boot } = await import('@alt-javascript/boot');
|
|
62
|
-
const { EphemeralConfig } = await import('@alt-javascript/config');
|
|
63
|
-
Boot.boot({
|
|
64
|
-
printBanner: false,
|
|
65
|
-
config: new EphemeralConfig({
|
|
66
|
-
logging: { format: mode === 'json' ? 'json' : 'text' }
|
|
67
|
-
})
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
68
|
// ---------------------------------------------------------------------------
|
|
72
69
|
// CLI definition
|
|
73
70
|
// ---------------------------------------------------------------------------
|
|
@@ -80,6 +77,11 @@ program
|
|
|
80
77
|
.option('-i, --input <body|->', 'Message body to inject, or - to read from stdin')
|
|
81
78
|
.option('-d, --daemon', 'Keep context alive until SIGINT/SIGTERM', false)
|
|
82
79
|
.option('-l, --log-mode <text|json>', 'Log output format: text (default) or json', 'text')
|
|
80
|
+
.option('--verbose', 'Enable info-level framework logging (default: suppressed)', false)
|
|
81
|
+
.option('--debug', 'Enable debug-level framework logging (overrides --verbose)', false)
|
|
82
|
+
.option('-p, --producer-uri <uri>', 'Override producer URI (requires -i; default: first route from URI)')
|
|
83
|
+
.option('--exchange-pattern <i|InOnly|io|InOut>', 'Exchange pattern: InOnly (fire-and-forget) or InOut (request-reply)', 'InOnly')
|
|
84
|
+
.option('-c, --consumer-uri <uri>', 'Poll consumer URI; implies daemon mode; mutually exclusive with -i and -p')
|
|
83
85
|
.addHelpText('after', `
|
|
84
86
|
Examples:
|
|
85
87
|
$ camel-lite -r route.yaml
|
|
@@ -88,6 +90,8 @@ Examples:
|
|
|
88
90
|
$ cat route.yaml | camel-lite -r - -i '{"name":"world"}'
|
|
89
91
|
$ camel-lite -r route.yaml -d
|
|
90
92
|
$ camel-lite -l json -r route.yaml
|
|
93
|
+
$ camel-lite --verbose -r route.yaml
|
|
94
|
+
$ camel-lite --debug -r route.yaml
|
|
91
95
|
`);
|
|
92
96
|
|
|
93
97
|
program.parse(process.argv);
|
|
@@ -110,20 +114,70 @@ if (logMode !== 'text' && logMode !== 'json') {
|
|
|
110
114
|
fatal(`-l / --log-mode must be 'text' or 'json', got: '${logMode}'`);
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
// -c is mutually exclusive with -i and -p (check before -p requires -i)
|
|
118
|
+
if (opts.consumerUri && opts.input !== undefined) {
|
|
119
|
+
fatal('-c / --consumer-uri is mutually exclusive with -i / --input');
|
|
120
|
+
}
|
|
121
|
+
if (opts.consumerUri && opts.producerUri) {
|
|
122
|
+
fatal('-c / --consumer-uri is mutually exclusive with -p / --producer-uri');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// -p requires -i
|
|
126
|
+
if (opts.producerUri && opts.input === undefined) {
|
|
127
|
+
fatal('-p / --producer-uri requires -i / --input');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --exchange-pattern normalization and validation
|
|
131
|
+
const rawPattern = (opts.exchangePattern ?? 'InOnly').toLowerCase();
|
|
132
|
+
const exchangePattern = (rawPattern === 'i' || rawPattern === 'inonly') ? 'InOnly'
|
|
133
|
+
: (rawPattern === 'io' || rawPattern === 'inout') ? 'InOut'
|
|
134
|
+
: null;
|
|
135
|
+
if (!exchangePattern) {
|
|
136
|
+
fatal(`--exchange-pattern must be 'InOnly', 'i', 'InOut', or 'io', got: '${opts.exchangePattern}'`);
|
|
137
|
+
}
|
|
138
|
+
|
|
113
139
|
// ---------------------------------------------------------------------------
|
|
114
|
-
//
|
|
140
|
+
// Compute log level from flags
|
|
141
|
+
// --debug takes precedence over --verbose; default is 'off'
|
|
115
142
|
// ---------------------------------------------------------------------------
|
|
116
143
|
|
|
117
|
-
|
|
144
|
+
const logLevel = opts.debug ? 'debug' : (opts.verbose ? 'info' : 'off');
|
|
118
145
|
|
|
119
146
|
// ---------------------------------------------------------------------------
|
|
120
|
-
//
|
|
147
|
+
// Boot CDI layer FIRST — before any other dynamic imports
|
|
148
|
+
// All config/boot imports are static since they must be available before CDI modules load
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
const { Boot } = await import('@alt-javascript/boot');
|
|
152
|
+
const { EphemeralConfig, PropertySourceChain, ProfileConfigLoader } = await import('@alt-javascript/config');
|
|
153
|
+
|
|
154
|
+
// Build the logging+banner overlay (highest priority)
|
|
155
|
+
// EphemeralConfig direct key lookup: 'logging.level./' is found via object['logging.level./']
|
|
156
|
+
const loggingOverlay = new EphemeralConfig({
|
|
157
|
+
'logging.level./': logLevel,
|
|
158
|
+
'logging.format': logMode === 'json' ? 'json' : 'text',
|
|
159
|
+
boot: { 'banner-mode': 'off' },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Load user config from ~/.camel-lite (synchronous — no await needed)
|
|
163
|
+
const userConfig = ProfileConfigLoader.load({
|
|
164
|
+
basePath: join(homedir(), '.camel-lite'),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Compose: overlay wins over user config (index 0 = highest priority)
|
|
168
|
+
const chainedConfig = new PropertySourceChain([loggingOverlay, userConfig]);
|
|
169
|
+
|
|
170
|
+
// Boot the framework — must happen before any CDI/starter dynamic imports
|
|
171
|
+
await Boot.boot({ config: chainedConfig });
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Main async execution — import camel-lite modules AFTER Boot.boot()
|
|
121
175
|
// ---------------------------------------------------------------------------
|
|
122
176
|
|
|
123
177
|
(async () => {
|
|
124
|
-
// Deferred imports
|
|
178
|
+
// Deferred imports — must come after Boot.boot() so LoggerFactory is configured
|
|
125
179
|
const { RouteLoader, ProducerTemplate } = await import('@alt-javascript/camel-lite-core');
|
|
126
|
-
const {
|
|
180
|
+
const { CdiCamelRuntime } = await import('../src/index.js');
|
|
127
181
|
|
|
128
182
|
let routeBuilder;
|
|
129
183
|
let inputBody;
|
|
@@ -144,9 +198,15 @@ await configureLogging(logMode);
|
|
|
144
198
|
inputBody = opts.input;
|
|
145
199
|
}
|
|
146
200
|
|
|
147
|
-
// 3. Build context and start
|
|
148
|
-
const runtime = new
|
|
149
|
-
const ctx = await runtime.createContext(
|
|
201
|
+
// 3. Build context and start — pass chainedConfig so CdiCamelRuntime forwards it to the starter
|
|
202
|
+
const runtime = new CdiCamelRuntime(routeBuilder, chainedConfig);
|
|
203
|
+
const ctx = await runtime.createContext();
|
|
204
|
+
|
|
205
|
+
// Wire polling URIs before start so CamelContext wraps them with PollingConsumerAdapter
|
|
206
|
+
if (opts.consumerUri) {
|
|
207
|
+
ctx.pollingUris = new Set([opts.consumerUri]);
|
|
208
|
+
}
|
|
209
|
+
|
|
150
210
|
await runtime.start();
|
|
151
211
|
|
|
152
212
|
// 4. Extract the from URI of the first route (used for ProducerTemplate injection)
|
|
@@ -160,21 +220,54 @@ await configureLogging(logMode);
|
|
|
160
220
|
// 5. Inject input if provided
|
|
161
221
|
if (inputBody !== undefined) {
|
|
162
222
|
const pt = new ProducerTemplate(ctx);
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
await
|
|
167
|
-
|
|
223
|
+
const targetUri = opts.producerUri ?? fromUri;
|
|
224
|
+
if (exchangePattern === 'InOut') {
|
|
225
|
+
// requestBody returns the reply body value directly (not an Exchange)
|
|
226
|
+
const replyBody = await pt.requestBody(targetUri, inputBody);
|
|
227
|
+
if (replyBody !== null && replyBody !== undefined) {
|
|
228
|
+
const out = typeof replyBody === 'string' ? replyBody : JSON.stringify(replyBody);
|
|
229
|
+
process.stdout.write(out + '\n');
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// InOnly: sendBody returns an Exchange
|
|
233
|
+
const exchange = await pt.sendBody(targetUri, inputBody);
|
|
234
|
+
if (exchange.isFailed()) {
|
|
235
|
+
process.stderr.write(`camel-lite: route error: ${exchange.exception.message}\n`);
|
|
236
|
+
await runtime.stop();
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
const resultBody = exchange.in.body;
|
|
240
|
+
if (resultBody !== null && resultBody !== undefined) {
|
|
241
|
+
const out = typeof resultBody === 'string' ? resultBody : JSON.stringify(resultBody);
|
|
242
|
+
process.stdout.write(out + '\n');
|
|
243
|
+
}
|
|
168
244
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 6. Consumer daemon loop (-c flag)
|
|
248
|
+
if (opts.consumerUri) {
|
|
249
|
+
const { ConsumerTemplate } = await import('@alt-javascript/camel-lite-core');
|
|
250
|
+
const ct = new ConsumerTemplate(ctx);
|
|
251
|
+
let stopping = false;
|
|
252
|
+
const shutdown = async (signal) => {
|
|
253
|
+
stopping = true;
|
|
254
|
+
process.stderr.write(`\ncamel-lite: received ${signal}, stopping...\n`);
|
|
255
|
+
await runtime.stop();
|
|
256
|
+
process.exit(0);
|
|
257
|
+
};
|
|
258
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
259
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
260
|
+
while (!stopping) {
|
|
261
|
+
const body = await ct.receiveBody(opts.consumerUri, 5000);
|
|
262
|
+
if (body !== null && body !== undefined) {
|
|
263
|
+
const out = typeof body === 'string' ? body : JSON.stringify(body);
|
|
264
|
+
process.stdout.write(out + '\n');
|
|
265
|
+
}
|
|
174
266
|
}
|
|
267
|
+
return; // skip the normal shutdown below
|
|
175
268
|
}
|
|
176
269
|
|
|
177
|
-
//
|
|
270
|
+
// 7. Daemon mode or shutdown
|
|
178
271
|
if (opts.daemon) {
|
|
179
272
|
process.stderr.write(`camel-lite: context running (daemon mode) — press Ctrl-C to stop\n`);
|
|
180
273
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alt-javascript/camel-lite-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Command-line runtime for camel-lite
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Command-line runtime for camel-lite \u2014 load a route definition and send a message",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -16,19 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"commander": "^12.0.0",
|
|
19
|
-
"@alt-javascript/camel-lite-core": "1.
|
|
20
|
-
"@alt-javascript/
|
|
21
|
-
"@alt-javascript/
|
|
22
|
-
"@alt-javascript/
|
|
23
|
-
"@alt-javascript/camel-lite-
|
|
24
|
-
"@alt-javascript/camel-lite-
|
|
25
|
-
"@alt-javascript/camel-lite-component-log": "1.0.2",
|
|
26
|
-
"@alt-javascript/camel-lite-component-nosql": "1.0.2",
|
|
27
|
-
"@alt-javascript/camel-lite-component-seda": "1.0.2",
|
|
28
|
-
"@alt-javascript/camel-lite-component-sql": "1.0.2",
|
|
29
|
-
"@alt-javascript/camel-lite-component-timer": "1.0.2",
|
|
30
|
-
"@alt-javascript/camel-lite-component-cron": "1.0.2",
|
|
31
|
-
"@alt-javascript/camel-lite-component-master": "1.0.2"
|
|
19
|
+
"@alt-javascript/camel-lite-core": "1.1.1",
|
|
20
|
+
"@alt-javascript/boot": "^3.0.7",
|
|
21
|
+
"@alt-javascript/cdi": "^3.0.7",
|
|
22
|
+
"@alt-javascript/config": "^3.0.7",
|
|
23
|
+
"@alt-javascript/boot-camel-lite-starter": "1.1.1",
|
|
24
|
+
"@alt-javascript/boot-camel-lite-extras-starter": "1.1.1"
|
|
32
25
|
},
|
|
33
26
|
"keywords": [
|
|
34
27
|
"alt-javascript",
|
|
@@ -48,8 +41,7 @@
|
|
|
48
41
|
"url": "https://github.com/alt-javascript/camel-lite"
|
|
49
42
|
},
|
|
50
43
|
"contributors": [
|
|
51
|
-
"Claude (Anthropic)"
|
|
52
|
-
"Apache Camel — design inspiration and pattern source"
|
|
44
|
+
"Claude (Anthropic)"
|
|
53
45
|
],
|
|
54
46
|
"publishConfig": {
|
|
55
47
|
"registry": "https://registry.npmjs.org/",
|
package/src/index.js
CHANGED
|
@@ -1,53 +1,132 @@
|
|
|
1
|
-
import { LoggerFactory } from '@alt-javascript/logger';
|
|
2
|
-
import { CamelContext } from '@alt-javascript/camel-lite-core';
|
|
3
|
-
import { registerAllComponents } from './components.js';
|
|
4
|
-
|
|
5
|
-
const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/cli/CamelRuntime');
|
|
6
|
-
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* CdiCamelRuntime — CDI-backed camel-lite runtime for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the old CamelRuntime that manually registered components via
|
|
5
|
+
* components.js. Now uses camelLiteExtrasStarter (which bundles
|
|
6
|
+
* boot-camel-lite-starter + boot-camel-lite-extras-starter) to boot a full
|
|
7
|
+
* CDI ApplicationContext with all components wired automatically.
|
|
8
|
+
*
|
|
9
|
+
* ALL imports of CDI / starter packages are performed dynamically inside
|
|
10
|
+
* boot() so that module-level LoggerFactory.getLogger() calls in those
|
|
11
|
+
* packages do not fire before the boot layer has set the log level.
|
|
12
|
+
*
|
|
13
|
+
* Public API (matches the old CamelRuntime contract expected by tests):
|
|
14
|
+
*
|
|
15
|
+
* const runtime = new CdiCamelRuntime(routeBuilder?, config?);
|
|
16
|
+
* // OR
|
|
17
|
+
* const ctx = await runtime.createContext(routeBuilder); // accepts builder at call-time too
|
|
18
|
+
* await runtime.start();
|
|
19
|
+
* await runtime.stop();
|
|
20
|
+
* runtime.context // → underlying CamelContext
|
|
21
|
+
*
|
|
22
|
+
* Named export CamelRuntime is kept as an alias for backward test compatibility.
|
|
10
23
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
|
|
25
|
+
class CdiCamelRuntime {
|
|
26
|
+
#routeBuilder = null;
|
|
27
|
+
#config = null;
|
|
28
|
+
#applicationContext = null;
|
|
29
|
+
#camelLiteContextBean = null;
|
|
13
30
|
|
|
14
31
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param {
|
|
17
|
-
|
|
32
|
+
* @param {object} [routeBuilder] - optional at construction; may also be supplied to createContext()
|
|
33
|
+
* @param {object} [config] - optional config POJO or IConfig instance
|
|
34
|
+
*/
|
|
35
|
+
constructor(routeBuilder, config) {
|
|
36
|
+
this.#routeBuilder = routeBuilder ?? null;
|
|
37
|
+
this.#config = config ?? null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Boot the CDI ApplicationContext and wire the CamelContext.
|
|
42
|
+
* Accepts routeBuilder at call-time (overrides constructor arg) for
|
|
43
|
+
* backward compatibility with tests that call createContext(builder).
|
|
44
|
+
*
|
|
45
|
+
* @param {object} [routeBuilder] - optional override
|
|
46
|
+
* @returns {Promise<import('@alt-javascript/camel-lite-core').CamelContext>}
|
|
18
47
|
*/
|
|
19
48
|
async createContext(routeBuilder) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
if (routeBuilder) this.#routeBuilder = routeBuilder;
|
|
50
|
+
if (!this.#routeBuilder) {
|
|
51
|
+
throw new Error('CdiCamelRuntime: a routeBuilder must be supplied either to the constructor or to createContext()');
|
|
52
|
+
}
|
|
53
|
+
await this.#boot();
|
|
54
|
+
return this.context;
|
|
25
55
|
}
|
|
26
56
|
|
|
27
57
|
/**
|
|
28
|
-
* Start the
|
|
58
|
+
* Start the underlying CamelContext (idempotent if createContext already started it).
|
|
29
59
|
* @returns {Promise<void>}
|
|
30
60
|
*/
|
|
31
61
|
async start() {
|
|
32
|
-
if (!this.#
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
if (!this.#applicationContext) {
|
|
63
|
+
throw new Error('CdiCamelRuntime: call createContext() before start()');
|
|
64
|
+
}
|
|
65
|
+
// The CDI ApplicationContext.start() already started CamelLiteContext.init()
|
|
66
|
+
// which fires CamelContext.start() asynchronously. We just wait for it to
|
|
67
|
+
// be fully ready here.
|
|
68
|
+
await this.#camelLiteContextBean.ready();
|
|
35
69
|
}
|
|
36
70
|
|
|
37
71
|
/**
|
|
38
|
-
* Stop the
|
|
72
|
+
* Stop the underlying CamelContext and destroy the CDI ApplicationContext.
|
|
39
73
|
* @returns {Promise<void>}
|
|
40
74
|
*/
|
|
41
75
|
async stop() {
|
|
42
|
-
if (!this.#
|
|
43
|
-
|
|
44
|
-
|
|
76
|
+
if (!this.#applicationContext) return;
|
|
77
|
+
await this.#camelLiteContextBean.destroy();
|
|
78
|
+
// ApplicationContext does not expose a stop(); destroy() on the Camel bean is sufficient.
|
|
79
|
+
this.#applicationContext = null;
|
|
80
|
+
this.#camelLiteContextBean = null;
|
|
45
81
|
}
|
|
46
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The underlying CamelContext instance (available after createContext()).
|
|
85
|
+
* @returns {import('@alt-javascript/camel-lite-core').CamelContext | null}
|
|
86
|
+
*/
|
|
47
87
|
get context() {
|
|
48
|
-
return this.#
|
|
88
|
+
return this.#camelLiteContextBean?.camelContext ?? null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Private boot method — ALL CDI/starter imports are dynamic here so that
|
|
93
|
+
// module-level LoggerFactory calls in those packages do not fire before the
|
|
94
|
+
// boot layer sets the log level.
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
async #boot() {
|
|
98
|
+
// Dynamic imports — must stay inside this async method
|
|
99
|
+
const { camelLiteExtrasStarter } = await import('@alt-javascript/boot-camel-lite-extras-starter');
|
|
100
|
+
const { Context, Singleton } = await import('@alt-javascript/cdi');
|
|
101
|
+
|
|
102
|
+
// Wrap the routeBuilder as a CDI singleton so RouteRegistry.applyRoutes()
|
|
103
|
+
// discovers it via the bean.configure() check.
|
|
104
|
+
const builder = this.#routeBuilder;
|
|
105
|
+
class CliRouteBuilder {
|
|
106
|
+
configure(camelContext) {
|
|
107
|
+
if (typeof builder.configure === 'function') builder.configure(camelContext);
|
|
108
|
+
}
|
|
109
|
+
getRoutes() {
|
|
110
|
+
return builder.getRoutes();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const userContext = new Context([
|
|
115
|
+
new Singleton({ Reference: CliRouteBuilder, name: 'cliRouteBuilder' }),
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
const { applicationContext } = await camelLiteExtrasStarter({
|
|
119
|
+
contexts: [userContext],
|
|
120
|
+
config: this.#config ?? {},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.#applicationContext = applicationContext;
|
|
124
|
+
this.#camelLiteContextBean = applicationContext.get('camelLiteContext');
|
|
49
125
|
}
|
|
50
126
|
}
|
|
51
127
|
|
|
52
|
-
|
|
53
|
-
|
|
128
|
+
// Backward-compat alias — tests import { CamelRuntime }
|
|
129
|
+
const CamelRuntime = CdiCamelRuntime;
|
|
130
|
+
|
|
131
|
+
export { CdiCamelRuntime, CamelRuntime };
|
|
132
|
+
export default CdiCamelRuntime;
|
package/src/components.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { LoggerFactory } from '@alt-javascript/logger';
|
|
2
|
-
|
|
3
|
-
const log = LoggerFactory.getLogger('@alt-javascript/camel-lite/cli/components');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Register all bundled camel-lite components into a CamelContext.
|
|
7
|
-
*
|
|
8
|
-
* Each component is wrapped in a try/catch so that a missing native dependency
|
|
9
|
-
* (e.g. AMQP without a broker, FTP without basic-ftp installed) skips that
|
|
10
|
-
* component with a warning rather than crashing the CLI for routes that don't
|
|
11
|
-
* use it.
|
|
12
|
-
*
|
|
13
|
-
* @param {import('@alt-javascript/camel-lite-core').CamelContext} ctx
|
|
14
|
-
*/
|
|
15
|
-
async function registerAllComponents(ctx) {
|
|
16
|
-
const registrations = [
|
|
17
|
-
{ scheme: 'direct', loader: () => import('@alt-javascript/camel-lite-component-direct').then(m => new m.DirectComponent()) },
|
|
18
|
-
{ scheme: 'seda', loader: () => import('@alt-javascript/camel-lite-component-seda').then(m => new m.SedaComponent()) },
|
|
19
|
-
{ scheme: 'log', loader: () => import('@alt-javascript/camel-lite-component-log').then(m => new m.LogComponent()) },
|
|
20
|
-
{ scheme: 'file', loader: () => import('@alt-javascript/camel-lite-component-file').then(m => new m.FileComponent()) },
|
|
21
|
-
{ scheme: 'http', loader: () => import('@alt-javascript/camel-lite-component-http').then(m => new m.HttpComponent()) },
|
|
22
|
-
{ scheme: 'ftp', loader: () => import('@alt-javascript/camel-lite-component-ftp').then(m => new m.FtpComponent()) },
|
|
23
|
-
{ scheme: 'amqp', loader: () => import('@alt-javascript/camel-lite-component-amqp').then(m => new m.AmqpComponent()) },
|
|
24
|
-
{ scheme: 'sql', loader: () => import('@alt-javascript/camel-lite-component-sql').then(m => new m.SqlComponent()) },
|
|
25
|
-
{ scheme: 'nosql', loader: () => import('@alt-javascript/camel-lite-component-nosql').then(m => new m.NosqlComponent()) },
|
|
26
|
-
{ scheme: 'timer', loader: () => import('@alt-javascript/camel-lite-component-timer').then(m => new m.TimerComponent()) },
|
|
27
|
-
{ scheme: 'cron', loader: () => import('@alt-javascript/camel-lite-component-cron').then(m => new m.CronComponent()) },
|
|
28
|
-
{ scheme: 'master', loader: () => import('@alt-javascript/camel-lite-component-master').then(m => new m.MasterComponent()) },
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
for (const { scheme, loader } of registrations) {
|
|
32
|
-
try {
|
|
33
|
-
const component = await loader();
|
|
34
|
-
ctx.addComponent(scheme, component);
|
|
35
|
-
log.info(`Registered component: ${scheme}`);
|
|
36
|
-
} catch (err) {
|
|
37
|
-
log.warn(`Skipping component '${scheme}': ${err.message}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export { registerAllComponents };
|