@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 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;