@alt-javascript/camel-lite-cli 1.0.2
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 +52 -0
- package/bin/camel-lite.js +196 -0
- package/package.json +58 -0
- package/src/components.js +42 -0
- package/src/index.js +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# camel-lite-cli
|
|
2
|
+
|
|
3
|
+
Command-line runtime for camel-lite. Load a route definition file and optionally inject a single message into the first `from:` endpoint.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g camel-lite-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
# Run a route file (daemon mode implied — exits when the route finishes)
|
|
15
|
+
camel-lite -r route.yaml
|
|
16
|
+
|
|
17
|
+
# Inject a JSON body and exit
|
|
18
|
+
camel-lite -r route.yaml -i '{"name":"world"}'
|
|
19
|
+
|
|
20
|
+
# Read the message body from stdin
|
|
21
|
+
camel-lite -r route.yaml -i -
|
|
22
|
+
|
|
23
|
+
# Read the route definition from stdin, inject a literal string body
|
|
24
|
+
cat route.yaml | camel-lite -r - -i body
|
|
25
|
+
|
|
26
|
+
# Daemon mode — keep running after the initial message (timer/cron/seda routes)
|
|
27
|
+
camel-lite -r route.yaml -d
|
|
28
|
+
|
|
29
|
+
# JSON-structured log output
|
|
30
|
+
camel-lite -l json -r route.yaml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
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 | — |
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
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).
|
|
49
|
+
|
|
50
|
+
## See Also
|
|
51
|
+
|
|
52
|
+
- [camel-lite root README](../../README.md)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* camel-lite CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* camel-lite -r <route-file|-> [-i <input|->] [-d] [-l text|json]
|
|
8
|
+
*
|
|
9
|
+
* 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
|
|
16
|
+
*
|
|
17
|
+
* Exactly one of -r or -i may use stdin (-).
|
|
18
|
+
*
|
|
19
|
+
* Examples:
|
|
20
|
+
* camel-lite -r route.yaml
|
|
21
|
+
* camel-lite -r route.yaml -i '{"name":"world"}'
|
|
22
|
+
* camel-lite -r route.yaml -i - # read body from stdin
|
|
23
|
+
* cat route.yaml | camel-lite -r - -i '{"name":"world"}'
|
|
24
|
+
* camel-lite -r route.yaml -d # daemon mode — Ctrl-C to stop
|
|
25
|
+
* camel-lite -l json -r route.yaml # JSON log output
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { createRequire } from 'node:module';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
import { dirname } from 'node:path';
|
|
31
|
+
import { program } from 'commander';
|
|
32
|
+
|
|
33
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const require = createRequire(import.meta.url);
|
|
35
|
+
const pkg = require('../package.json');
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function fatal(msg) {
|
|
42
|
+
process.stderr.write(`camel-lite: error: ${msg}\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readStdin() {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
for await (const chunk of process.stdin) {
|
|
49
|
+
chunks.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
|
|
50
|
+
}
|
|
51
|
+
return chunks.join('');
|
|
52
|
+
}
|
|
53
|
+
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// CLI definition
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
program
|
|
76
|
+
.name('camel-lite')
|
|
77
|
+
.version(pkg.version, '-v, --version')
|
|
78
|
+
.description('Load a camel-lite route definition and optionally inject a message')
|
|
79
|
+
.option('-r, --routes <file|->', 'Route definition file (.yaml/.yml/.json) or - for stdin')
|
|
80
|
+
.option('-i, --input <body|->', 'Message body to inject, or - to read from stdin')
|
|
81
|
+
.option('-d, --daemon', 'Keep context alive until SIGINT/SIGTERM', false)
|
|
82
|
+
.option('-l, --log-mode <text|json>', 'Log output format: text (default) or json', 'text')
|
|
83
|
+
.addHelpText('after', `
|
|
84
|
+
Examples:
|
|
85
|
+
$ camel-lite -r route.yaml
|
|
86
|
+
$ camel-lite -r route.yaml -i '{"name":"world"}'
|
|
87
|
+
$ camel-lite -r route.yaml -i -
|
|
88
|
+
$ cat route.yaml | camel-lite -r - -i '{"name":"world"}'
|
|
89
|
+
$ camel-lite -r route.yaml -d
|
|
90
|
+
$ camel-lite -l json -r route.yaml
|
|
91
|
+
`);
|
|
92
|
+
|
|
93
|
+
program.parse(process.argv);
|
|
94
|
+
const opts = program.opts();
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Validation
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
if (!opts.routes) {
|
|
101
|
+
fatal('-r / --routes is required');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (opts.routes === '-' && opts.input === '-') {
|
|
105
|
+
fatal('-r - and -i - are mutually exclusive: only one argument can read from stdin');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const logMode = (opts.logMode ?? 'text').toLowerCase();
|
|
109
|
+
if (logMode !== 'text' && logMode !== 'json') {
|
|
110
|
+
fatal(`-l / --log-mode must be 'text' or 'json', got: '${logMode}'`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Configure logging FIRST — before any camel-lite module imports
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
await configureLogging(logMode);
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Main async execution — import camel-lite modules AFTER logging is configured
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
(async () => {
|
|
124
|
+
// Deferred imports so the log formatter is set before any getLogger() calls
|
|
125
|
+
const { RouteLoader, ProducerTemplate } = await import('@alt-javascript/camel-lite-core');
|
|
126
|
+
const { CamelRuntime } = await import('../src/index.js');
|
|
127
|
+
|
|
128
|
+
let routeBuilder;
|
|
129
|
+
let inputBody;
|
|
130
|
+
|
|
131
|
+
// 1. Load route definition
|
|
132
|
+
if (opts.routes === '-') {
|
|
133
|
+
process.stderr.write('camel-lite: reading route definition from stdin...\n');
|
|
134
|
+
routeBuilder = await RouteLoader.loadStream(process.stdin);
|
|
135
|
+
} else {
|
|
136
|
+
routeBuilder = await RouteLoader.loadFile(opts.routes);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. Read input body if -i - (stdin, only reachable when -r was not -)
|
|
140
|
+
if (opts.input === '-') {
|
|
141
|
+
process.stderr.write('camel-lite: reading input body from stdin...\n');
|
|
142
|
+
inputBody = await readStdin();
|
|
143
|
+
} else if (opts.input !== undefined) {
|
|
144
|
+
inputBody = opts.input;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. Build context and start
|
|
148
|
+
const runtime = new CamelRuntime();
|
|
149
|
+
const ctx = await runtime.createContext(routeBuilder);
|
|
150
|
+
await runtime.start();
|
|
151
|
+
|
|
152
|
+
// 4. Extract the from URI of the first route (used for ProducerTemplate injection)
|
|
153
|
+
const routes = routeBuilder.getRoutes();
|
|
154
|
+
if (routes.length === 0) {
|
|
155
|
+
await runtime.stop();
|
|
156
|
+
fatal('No routes found in the route definition');
|
|
157
|
+
}
|
|
158
|
+
const fromUri = routes[0].fromUri;
|
|
159
|
+
|
|
160
|
+
// 5. Inject input if provided
|
|
161
|
+
if (inputBody !== undefined) {
|
|
162
|
+
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);
|
|
168
|
+
}
|
|
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');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 6. Daemon mode or shutdown
|
|
178
|
+
if (opts.daemon) {
|
|
179
|
+
process.stderr.write(`camel-lite: context running (daemon mode) — press Ctrl-C to stop\n`);
|
|
180
|
+
|
|
181
|
+
const shutdown = async (signal) => {
|
|
182
|
+
process.stderr.write(`\ncamel-lite: received ${signal}, stopping...\n`);
|
|
183
|
+
await runtime.stop();
|
|
184
|
+
process.exit(0);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
188
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
189
|
+
} else {
|
|
190
|
+
await runtime.stop();
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
})().catch(err => {
|
|
194
|
+
process.stderr.write(`camel-lite: fatal: ${err.message}\n`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
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",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"camel-lite": "./bin/camel-lite.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test test/*.test.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
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"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"alt-javascript",
|
|
35
|
+
"camel",
|
|
36
|
+
"camel-lite",
|
|
37
|
+
"eai",
|
|
38
|
+
"eip",
|
|
39
|
+
"integration",
|
|
40
|
+
"cli",
|
|
41
|
+
"routing",
|
|
42
|
+
"yaml"
|
|
43
|
+
],
|
|
44
|
+
"author": "Craig Parravicini",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/alt-javascript/camel-lite"
|
|
49
|
+
},
|
|
50
|
+
"contributors": [
|
|
51
|
+
"Claude (Anthropic)",
|
|
52
|
+
"Apache Camel — design inspiration and pattern source"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"registry": "https://registry.npmjs.org/",
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* CamelRuntime — testable core of the CLI.
|
|
9
|
+
* Separates context lifecycle and routing logic from the bin entry point.
|
|
10
|
+
*/
|
|
11
|
+
class CamelRuntime {
|
|
12
|
+
#context = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
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>}
|
|
18
|
+
*/
|
|
19
|
+
async createContext(routeBuilder) {
|
|
20
|
+
const ctx = new CamelContext();
|
|
21
|
+
await registerAllComponents(ctx);
|
|
22
|
+
ctx.addRoutes(routeBuilder);
|
|
23
|
+
this.#context = ctx;
|
|
24
|
+
return ctx;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Start the context.
|
|
29
|
+
* @returns {Promise<void>}
|
|
30
|
+
*/
|
|
31
|
+
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();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Stop the context.
|
|
39
|
+
* @returns {Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
async stop() {
|
|
42
|
+
if (!this.#context) return;
|
|
43
|
+
log.info('CamelRuntime: stopping context');
|
|
44
|
+
await this.#context.stop();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get context() {
|
|
48
|
+
return this.#context;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { CamelRuntime };
|
|
53
|
+
export default CamelRuntime;
|