@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 CHANGED
@@ -1,52 +1,226 @@
1
1
  # camel-lite-cli
2
2
 
3
- Command-line runtime for camel-lite. Load a route definition file and optionally inject a single message into the first `from:` endpoint.
3
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../../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
- ## Usage
13
+ ## Quick Start
12
14
 
13
15
  ```sh
14
- # Run a route file (daemon mode implied — exits when the route finishes)
16
+ # Run a route file silently
15
17
  camel-lite -r route.yaml
16
18
 
17
- # Inject a JSON body and exit
19
+ # Inject a JSON body and print any reply
18
20
  camel-lite -r route.yaml -i '{"name":"world"}'
19
21
 
20
- # Read the message body from stdin
21
- camel-lite -r route.yaml -i -
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
- # Read the route definition from stdin, inject a literal string body
24
- cat route.yaml | camel-lite -r - -i body
115
+ # Route as argument, body from stdin
116
+ echo '{"name":"world"}' | camel-lite -r route.yaml -i -
117
+ ```
25
118
 
26
- # Daemon mode — keep running after the initial message (timer/cron/seda routes)
27
- camel-lite -r route.yaml -d
119
+ ### Daemon mode
28
120
 
29
- # JSON-structured log output
30
- camel-lite -l json -r route.yaml
121
+ ```sh
122
+ camel-lite -r cron-route.yaml -d
31
123
  ```
32
124
 
33
- ## Options
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
- | Flag | Long form | Description | Default |
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
- ## Notes
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
- - All 13 camel-lite components are pre-loaded. Components whose external broker or service is unreachable at startup are skipped gracefully — the route still starts for the components that are available.
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|-> Route definition file (.yaml/.yml/.json) or - for stdin
11
- * -i, --input <body|-> Message body to inject, or - to read from stdin
12
- * -d, --daemon Keep context alive until SIGINT/SIGTERM (default: false)
13
- * -l, --log-mode <text|json> Log output format: text (default) or json
14
- * -v, --version Print version
15
- * -h, --help Print this help
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
- // Configure logging FIRST before any camel-lite module imports
140
+ // Compute log level from flags
141
+ // --debug takes precedence over --verbose; default is 'off'
115
142
  // ---------------------------------------------------------------------------
116
143
 
117
- await configureLogging(logMode);
144
+ const logLevel = opts.debug ? 'debug' : (opts.verbose ? 'info' : 'off');
118
145
 
119
146
  // ---------------------------------------------------------------------------
120
- // Main async executionimport camel-lite modules AFTER logging is configured
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 so the log formatter is set before any getLogger() calls
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 { CamelRuntime } = await import('../src/index.js');
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 CamelRuntime();
149
- const ctx = await runtime.createContext(routeBuilder);
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 exchange = await pt.sendBody(fromUri, inputBody);
164
- if (exchange.isFailed()) {
165
- process.stderr.write(`camel-lite: route error: ${exchange.exception.message}\n`);
166
- await runtime.stop();
167
- process.exit(1);
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
- // Print result body to stdout
170
- const resultBody = exchange.in.body;
171
- if (resultBody !== null && resultBody !== undefined) {
172
- const out = typeof resultBody === 'string' ? resultBody : JSON.stringify(resultBody);
173
- process.stdout.write(out + '\n');
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
- // 6. Daemon mode or shutdown
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.0.2",
4
- "description": "Command-line runtime for camel-lite load a route definition and send a message",
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.0.2",
20
- "@alt-javascript/camel-lite-component-amqp": "1.0.2",
21
- "@alt-javascript/camel-lite-component-direct": "1.0.2",
22
- "@alt-javascript/camel-lite-component-file": "1.0.2",
23
- "@alt-javascript/camel-lite-component-ftp": "1.0.2",
24
- "@alt-javascript/camel-lite-component-http": "1.0.2",
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
- * CamelRuntimetestable core of the CLI.
9
- * Separates context lifecycle and routing logic from the bin entry point.
2
+ * CdiCamelRuntimeCDI-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
- class CamelRuntime {
12
- #context = null;
24
+
25
+ class CdiCamelRuntime {
26
+ #routeBuilder = null;
27
+ #config = null;
28
+ #applicationContext = null;
29
+ #camelLiteContextBean = null;
13
30
 
14
31
  /**
15
- * Build a CamelContext with all bundled components registered and routes loaded.
16
- * @param {import('@alt-javascript/camel-lite-core').RouteBuilder} routeBuilder
17
- * @returns {Promise<CamelContext>}
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
- const ctx = new CamelContext();
21
- await registerAllComponents(ctx);
22
- ctx.addRoutes(routeBuilder);
23
- this.#context = ctx;
24
- return ctx;
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 context.
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.#context) throw new Error('CamelRuntime: call createContext() before start()');
33
- log.info('CamelRuntime: starting context');
34
- await this.#context.start();
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 context.
72
+ * Stop the underlying CamelContext and destroy the CDI ApplicationContext.
39
73
  * @returns {Promise<void>}
40
74
  */
41
75
  async stop() {
42
- if (!this.#context) return;
43
- log.info('CamelRuntime: stopping context');
44
- await this.#context.stop();
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.#context;
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
- export { CamelRuntime };
53
- export default CamelRuntime;
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 };