@modelriver/cli 1.1.0 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelriver/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "ModelRiver CLI for testing webhooks and WebSockets from production",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -52,6 +52,7 @@ async function forwardCommand(options) {
52
52
  apiKey,
53
53
  apiUrl: config.getApiUrl(),
54
54
  port: targetPort,
55
+ forwardUrl: forwardUrl,
55
56
  forward: true,
56
57
  print: true,
57
58
  verbose
@@ -4,9 +4,10 @@ const CLIWebSocketClient = require('../lib/cli-websocket-client');
4
4
  const WebhookVerifier = require('../lib/webhook-verifier');
5
5
  const Logger = require('../utils/logger');
6
6
  const Formatter = require('../utils/formatter');
7
+ const { parseForwardUrl } = require('../utils/url-helpers');
7
8
 
8
9
  async function listenCommand(options) {
9
- let { apiKey, apiUrl, print, port, verbose, forward } = options;
10
+ let { apiKey, apiUrl, print, port, verbose, forward, forwardUrl } = options;
10
11
  const forwardOnly = forward === true;
11
12
 
12
13
  const spinner = Logger.spinner('Setting up webhook forwarding...');
@@ -206,16 +207,25 @@ async function listenCommand(options) {
206
207
 
207
208
  // Forward to local server
208
209
  const http = require('http');
210
+ const https = require('https');
209
211
  const postData = JSON.stringify({
210
212
  channel_id,
211
213
  timestamp,
212
214
  data
213
215
  });
214
216
 
215
- const options = {
217
+ // Parse the forward URL or use localhost defaults
218
+ const parsedUrl = parseForwardUrl(forwardUrl) || {
216
219
  hostname: 'localhost',
217
220
  port: localPort,
218
- path: '/webhook',
221
+ path: '/webhook/modelriver',
222
+ protocol: 'http'
223
+ };
224
+
225
+ const options = {
226
+ hostname: parsedUrl.hostname,
227
+ port: parsedUrl.port,
228
+ path: parsedUrl.path,
219
229
  method: 'POST',
220
230
  headers: {
221
231
  'Content-Type': 'application/json',
@@ -226,15 +236,19 @@ async function listenCommand(options) {
226
236
  }
227
237
  };
228
238
 
229
- const req = http.request(options, (res) => {
239
+ // Use https or http based on protocol
240
+ const requestModule = parsedUrl.protocol === 'https' ? https : http;
241
+ const targetUrl = `${parsedUrl.protocol}://${parsedUrl.hostname}:${parsedUrl.port}${parsedUrl.path}`;
242
+
243
+ const req = requestModule.request(options, (res) => {
230
244
  // Webhook forwarded successfully
231
245
  if (print) {
232
- Logger.info(` → Forwarded to local server (status: ${res.statusCode})`);
246
+ Logger.info(` → Forwarded to ${targetUrl} (status: ${res.statusCode})`);
233
247
  }
234
248
  });
235
249
 
236
250
  req.on('error', (error) => {
237
- Logger.error(`Failed to forward webhook to local server: ${error.message}`);
251
+ Logger.error(`Failed to forward webhook to ${targetUrl}: ${error.message}`);
238
252
  });
239
253
 
240
254
  req.write(postData);
@@ -91,8 +91,29 @@ function isValidApiKeyFormat(apiKey) {
91
91
  return apiKey.startsWith('mr_live_') || apiKey.startsWith('mr_test_');
92
92
  }
93
93
 
94
+ /**
95
+ * Parse a forward URL into its components
96
+ * @param {string} url - The URL to parse
97
+ * @returns {object|null} - Object with hostname, port, path, protocol or null if invalid
98
+ */
99
+ function parseForwardUrl(url) {
100
+ if (!url) return null;
101
+ try {
102
+ const urlObj = new URL(url);
103
+ return {
104
+ hostname: urlObj.hostname,
105
+ port: parseInt(urlObj.port, 10) || (urlObj.protocol === 'https:' ? 443 : 80),
106
+ path: urlObj.pathname || '/webhook/modelriver',
107
+ protocol: urlObj.protocol.replace(':', '')
108
+ };
109
+ } catch (error) {
110
+ return null;
111
+ }
112
+ }
113
+
94
114
  module.exports = {
95
115
  normalizeWebhookUrl,
96
116
  extractPort,
97
- isValidApiKeyFormat
117
+ isValidApiKeyFormat,
118
+ parseForwardUrl
98
119
  };
@@ -1,5 +1,5 @@
1
1
  // Tests for URL helper utilities
2
- const { normalizeWebhookUrl, extractPort, isValidApiKeyFormat } = require('./url-helpers');
2
+ const { normalizeWebhookUrl, extractPort, isValidApiKeyFormat, parseForwardUrl } = require('./url-helpers');
3
3
 
4
4
  describe('URL Helpers', () => {
5
5
  describe('normalizeWebhookUrl', () => {
@@ -81,4 +81,54 @@ describe('URL Helpers', () => {
81
81
  expect(isValidApiKeyFormat(undefined)).toBe(false);
82
82
  });
83
83
  });
84
+
85
+ describe('parseForwardUrl', () => {
86
+ it('should parse localhost URL with port', () => {
87
+ const result = parseForwardUrl('http://localhost:4000/webhook/modelriver');
88
+ expect(result).toEqual({
89
+ hostname: 'localhost',
90
+ port: 4000,
91
+ path: '/webhook/modelriver',
92
+ protocol: 'http'
93
+ });
94
+ });
95
+
96
+ it('should parse external URL with port', () => {
97
+ const result = parseForwardUrl('http://myserver.com:3001/webhook/modelriver');
98
+ expect(result).toEqual({
99
+ hostname: 'myserver.com',
100
+ port: 3001,
101
+ path: '/webhook/modelriver',
102
+ protocol: 'http'
103
+ });
104
+ });
105
+
106
+ it('should default to port 80 for http without explicit port', () => {
107
+ const result = parseForwardUrl('http://example.com/webhook/modelriver');
108
+ expect(result.port).toBe(80);
109
+ expect(result.hostname).toBe('example.com');
110
+ });
111
+
112
+ it('should default to port 443 for https without explicit port', () => {
113
+ const result = parseForwardUrl('https://example.com/webhook/modelriver');
114
+ expect(result.port).toBe(443);
115
+ expect(result.protocol).toBe('https');
116
+ });
117
+
118
+ it('should handle URLs with different paths', () => {
119
+ const result = parseForwardUrl('http://localhost:5000/custom/path');
120
+ expect(result.path).toBe('/custom/path');
121
+ });
122
+
123
+ it('should return null for empty or falsy input', () => {
124
+ expect(parseForwardUrl('')).toBeNull();
125
+ expect(parseForwardUrl(null)).toBeNull();
126
+ expect(parseForwardUrl(undefined)).toBeNull();
127
+ });
128
+
129
+ it('should return null for invalid URLs', () => {
130
+ expect(parseForwardUrl('not-a-url')).toBeNull();
131
+ expect(parseForwardUrl('just-text')).toBeNull();
132
+ });
133
+ });
84
134
  });