paymongo-cli 1.0.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +185 -0
  2. package/bin/paymongo.js +1 -1
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/config.d.ts.map +1 -1
  5. package/dist/commands/config.js +103 -128
  6. package/dist/commands/config.js.map +1 -1
  7. package/dist/commands/dev.d.ts.map +1 -1
  8. package/dist/commands/dev.js +319 -110
  9. package/dist/commands/dev.js.map +1 -1
  10. package/dist/commands/gui.js +26 -31
  11. package/dist/commands/gui.js.map +1 -1
  12. package/dist/commands/init.d.ts.map +1 -1
  13. package/dist/commands/init.js +109 -159
  14. package/dist/commands/init.js.map +1 -1
  15. package/dist/commands/login.d.ts.map +1 -1
  16. package/dist/commands/login.js +81 -121
  17. package/dist/commands/login.js.map +1 -1
  18. package/dist/commands/payments.d.ts.map +1 -1
  19. package/dist/commands/payments.js +63 -65
  20. package/dist/commands/payments.js.map +1 -1
  21. package/dist/commands/team/index.js +31 -36
  22. package/dist/commands/team/index.js.map +1 -1
  23. package/dist/commands/trigger.d.ts.map +1 -1
  24. package/dist/commands/trigger.js +144 -93
  25. package/dist/commands/trigger.js.map +1 -1
  26. package/dist/commands/webhooks.d.ts.map +1 -1
  27. package/dist/commands/webhooks.js +144 -150
  28. package/dist/commands/webhooks.js.map +1 -1
  29. package/dist/index.js +37 -44
  30. package/dist/index.js.map +1 -1
  31. package/dist/services/analytics/service.d.ts +7 -1
  32. package/dist/services/analytics/service.d.ts.map +1 -1
  33. package/dist/services/analytics/service.js +15 -20
  34. package/dist/services/analytics/service.js.map +1 -1
  35. package/dist/services/api/client.d.ts +9 -8
  36. package/dist/services/api/client.d.ts.map +1 -1
  37. package/dist/services/api/client.js +34 -30
  38. package/dist/services/api/client.js.map +1 -1
  39. package/dist/services/config/manager.d.ts +1 -1
  40. package/dist/services/config/manager.d.ts.map +1 -1
  41. package/dist/services/config/manager.js +30 -72
  42. package/dist/services/config/manager.js.map +1 -1
  43. package/dist/services/dev/process-manager.d.ts +50 -0
  44. package/dist/services/dev/process-manager.d.ts.map +1 -0
  45. package/dist/services/dev/process-manager.js +135 -0
  46. package/dist/services/dev/process-manager.js.map +1 -0
  47. package/dist/services/github/auth.d.ts +2 -2
  48. package/dist/services/github/auth.d.ts.map +1 -1
  49. package/dist/services/github/auth.js +15 -55
  50. package/dist/services/github/auth.js.map +1 -1
  51. package/dist/services/github/client.d.ts +1 -1
  52. package/dist/services/github/client.d.ts.map +1 -1
  53. package/dist/services/github/client.js +12 -17
  54. package/dist/services/github/client.js.map +1 -1
  55. package/dist/services/github/sync.d.ts +2 -2
  56. package/dist/services/github/sync.d.ts.map +1 -1
  57. package/dist/services/github/sync.js +14 -11
  58. package/dist/services/github/sync.js.map +1 -1
  59. package/dist/services/web/server.d.ts +5 -4
  60. package/dist/services/web/server.d.ts.map +1 -1
  61. package/dist/services/web/server.js +74 -27
  62. package/dist/services/web/server.js.map +1 -1
  63. package/dist/types/paymongo.d.ts +53 -0
  64. package/dist/types/paymongo.d.ts.map +1 -1
  65. package/dist/types/paymongo.js +1 -2
  66. package/dist/types/schemas.d.ts +80 -0
  67. package/dist/types/schemas.d.ts.map +1 -0
  68. package/dist/types/schemas.js +63 -0
  69. package/dist/types/schemas.js.map +1 -0
  70. package/dist/utils/cache.d.ts +2 -0
  71. package/dist/utils/cache.d.ts.map +1 -1
  72. package/dist/utils/cache.js +65 -77
  73. package/dist/utils/cache.js.map +1 -1
  74. package/dist/utils/constants.js +16 -19
  75. package/dist/utils/constants.js.map +1 -1
  76. package/dist/utils/errors.d.ts +1 -0
  77. package/dist/utils/errors.d.ts.map +1 -1
  78. package/dist/utils/errors.js +19 -20
  79. package/dist/utils/errors.js.map +1 -1
  80. package/dist/utils/logger.d.ts +5 -4
  81. package/dist/utils/logger.d.ts.map +1 -1
  82. package/dist/utils/logger.js +12 -16
  83. package/dist/utils/logger.js.map +1 -1
  84. package/dist/utils/spinner.js +4 -8
  85. package/dist/utils/spinner.js.map +1 -1
  86. package/dist/utils/validator.d.ts +1 -1
  87. package/dist/utils/validator.d.ts.map +1 -1
  88. package/dist/utils/validator.js +7 -14
  89. package/dist/utils/validator.js.map +1 -1
  90. package/eslint.config.ts +70 -0
  91. package/jest.config.ts +30 -0
  92. package/package.json +76 -70
@@ -1,50 +1,18 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- const commander_1 = require("commander");
40
- const http = __importStar(require("http"));
41
- const crypto = __importStar(require("crypto"));
42
- const chalk_1 = __importDefault(require("chalk"));
43
- const manager_1 = __importDefault(require("../services/config/manager"));
44
- const client_1 = __importDefault(require("../services/api/client"));
45
- const spinner_1 = __importDefault(require("../utils/spinner"));
46
- const errors_1 = require("../utils/errors");
1
+ import { Command } from 'commander';
2
+ import * as http from 'http';
3
+ import * as crypto from 'crypto';
4
+ import * as fs from 'fs';
5
+ import { spawn } from 'child_process';
6
+ import chalk from 'chalk';
7
+ import ConfigManager from '../services/config/manager.js';
8
+ import ApiClient from '../services/api/client.js';
9
+ import Spinner from '../utils/spinner.js';
10
+ import { withRetry } from '../utils/errors.js';
11
+ import { DevProcessManager } from '../services/dev/process-manager.js';
47
12
  class DevServer {
13
+ server;
14
+ port;
15
+ config;
48
16
  constructor(port, config) {
49
17
  this.port = port;
50
18
  this.config = config;
@@ -56,7 +24,7 @@ class DevServer {
56
24
  // Start HTTP server
57
25
  return new Promise((resolve, reject) => {
58
26
  this.server.listen(this.port, () => {
59
- console.log(chalk_1.default.green('✓'), `Webhook server listening on http://localhost:${this.port}`);
27
+ console.log(chalk.green('✓'), `Webhook server listening on http://localhost:${this.port}`);
60
28
  resolve();
61
29
  });
62
30
  this.server.on('error', (error) => {
@@ -67,13 +35,15 @@ class DevServer {
67
35
  async stop() {
68
36
  return new Promise((resolve) => {
69
37
  this.server.close(() => {
70
- console.log(chalk_1.default.yellow('✓'), 'Webhook server stopped');
38
+ console.log(chalk.yellow('✓'), 'Webhook server stopped');
71
39
  resolve();
72
40
  });
73
41
  });
74
42
  }
75
43
  handleWebhookRequest(req, res) {
76
- if (req.method !== 'POST' || req.url !== '/webhook') {
44
+ // Accept both /webhook and /webhook/{project-slug} paths
45
+ const isWebhookPath = req.url?.startsWith('/webhook');
46
+ if (req.method !== 'POST' || !isWebhookPath) {
77
47
  res.writeHead(404);
78
48
  res.end('Not Found');
79
49
  return;
@@ -88,7 +58,7 @@ class DevServer {
88
58
  // Verify webhook signature if enabled
89
59
  const signatureValid = this.verifyWebhookSignature(req, body);
90
60
  if (!signatureValid) {
91
- console.log(chalk_1.default.red('⚠️'), 'Webhook signature verification failed');
61
+ console.log(chalk.red('⚠️'), 'Webhook signature verification failed');
92
62
  res.writeHead(401, { 'Content-Type': 'application/json' });
93
63
  res.end(JSON.stringify({ error: 'Invalid signature' }));
94
64
  return;
@@ -99,7 +69,7 @@ class DevServer {
99
69
  res.end(JSON.stringify({ success: true }));
100
70
  }
101
71
  catch (error) {
102
- console.error(chalk_1.default.red('✗'), 'Failed to process webhook:', error.message);
72
+ console.error(chalk.red('✗'), 'Failed to process webhook:', error.message);
103
73
  res.writeHead(400, { 'Content-Type': 'application/json' });
104
74
  res.end(JSON.stringify({ error: 'Invalid JSON' }));
105
75
  }
@@ -110,38 +80,39 @@ class DevServer {
110
80
  const eventType = event.data?.type || 'unknown';
111
81
  const eventId = event.data?.id || 'unknown';
112
82
  console.log('');
113
- console.log(chalk_1.default.gray('────────────────────────────────────────────────────────────'));
114
- console.log(chalk_1.default.blue(`[${timestamp}]`), chalk_1.default.bold(eventType.toUpperCase()));
83
+ console.log(chalk.gray('────────────────────────────────────────────────────────────'));
84
+ console.log(chalk.blue(`[${timestamp}]`), chalk.bold(eventType.toUpperCase()));
115
85
  if (eventType === 'payment') {
116
- const amount = event.data.attributes.amount;
117
- const status = event.data.attributes.status;
118
- console.log(chalk_1.default.gray('└─'), `Amount: ₱${(amount / 100).toFixed(2)}`);
119
- console.log(chalk_1.default.gray('└─'), `Status: ${status}`);
120
- console.log(chalk_1.default.gray('└─'), `Payment ID: ${eventId}`);
86
+ const attributes = event.data.attributes;
87
+ const amount = attributes.amount ?? 0;
88
+ const status = attributes.status ?? 'unknown';
89
+ console.log(chalk.gray('└─'), `Amount: ₱${(amount / 100).toFixed(2)}`);
90
+ console.log(chalk.gray('└─'), `Status: ${status}`);
91
+ console.log(chalk.gray('└─'), `Payment ID: ${eventId}`);
121
92
  }
122
- console.log(chalk_1.default.gray('└─'), `View: https://dashboard.paymongo.com/${eventType === 'payment' ? 'payments' : 'webhooks'}/${eventId}`);
93
+ console.log(chalk.gray('└─'), `View: https://dashboard.paymongo.com/${eventType === 'payment' ? 'payments' : 'webhooks'}/${eventId}`);
123
94
  }
124
95
  verifyWebhookSignature(req, body) {
125
96
  // Check if signature verification is enabled in config
126
97
  if (!this.config.dev.verifyWebhookSignatures) {
127
- console.log(chalk_1.default.yellow('ℹ️'), 'Webhook signature verification disabled in config');
98
+ console.log(chalk.yellow('ℹ️'), 'Webhook signature verification disabled in config');
128
99
  return true; // Allow all requests when verification is disabled
129
100
  }
130
101
  const signatureHeader = req.headers['paymongo-signature'];
131
102
  if (!signatureHeader) {
132
- console.log(chalk_1.default.red('⚠️'), 'Signature verification required but no signature header found');
103
+ console.log(chalk.red('⚠️'), 'Signature verification required but no signature header found');
133
104
  return false;
134
105
  }
135
106
  // Parse signature header: t=<timestamp>,te=<signature>,li=
136
107
  const signatureParts = signatureHeader.split(',');
137
108
  if (signatureParts.length < 2) {
138
- console.log(chalk_1.default.red('⚠️'), 'Invalid signature format');
109
+ console.log(chalk.red('⚠️'), 'Invalid signature format');
139
110
  return false;
140
111
  }
141
112
  const timestamp = signatureParts.find((part) => part.startsWith('t='))?.split('=')[1];
142
113
  const signature = signatureParts.find((part) => part.startsWith('te='))?.split('=')[1];
143
114
  if (!timestamp || !signature) {
144
- console.log(chalk_1.default.red('⚠️'), 'Missing timestamp or signature in header');
115
+ console.log(chalk.red('⚠️'), 'Missing timestamp or signature in header');
145
116
  return false;
146
117
  }
147
118
  // For now, look for any webhook secret in config
@@ -149,7 +120,7 @@ class DevServer {
149
120
  const webhookSecrets = this.config.webhookSecrets || {};
150
121
  const secretKeys = Object.values(webhookSecrets);
151
122
  if (secretKeys.length === 0) {
152
- console.log(chalk_1.default.yellow('⚠️'), 'Signature verification enabled but no webhook secrets configured');
123
+ console.log(chalk.yellow('⚠️'), 'Signature verification enabled but no webhook secrets configured');
153
124
  return true; // Allow requests when no secrets are configured yet
154
125
  }
155
126
  // Try to verify with each available secret
@@ -165,40 +136,89 @@ class DevServer {
165
136
  break;
166
137
  }
167
138
  }
168
- catch (error) {
139
+ catch (_error) {
169
140
  // Continue trying other secrets
170
141
  continue;
171
142
  }
172
143
  }
173
144
  if (isValid) {
174
- console.log(chalk_1.default.green('✓'), 'Signature verified successfully');
145
+ console.log(chalk.green('✓'), 'Signature verified successfully');
175
146
  return true;
176
147
  }
177
148
  else {
178
- console.log(chalk_1.default.red('✗'), 'Signature verification failed');
149
+ console.log(chalk.red('✗'), 'Signature verification failed');
179
150
  return false;
180
151
  }
181
152
  }
182
153
  }
183
- const command = new commander_1.Command('dev');
154
+ const command = new Command('dev');
184
155
  command
185
156
  .description('Start local development server')
186
157
  .option('-p, --port <port>', 'Port to run the webhook server on', '3000')
187
158
  .option('--no-register', 'Skip automatic webhook registration')
188
159
  .option('-e, --events <events>', 'Comma-separated events to listen for', 'payment.paid,payment.failed')
189
160
  .option('--ngrok-token <token>', 'ngrok authtoken (if not set in environment)')
161
+ .option('-d, --detach', 'Run server in background (detached mode)')
190
162
  .action(async (options) => {
191
- const spinner = new spinner_1.default();
192
- const configManager = new manager_1.default();
163
+ const spinner = new Spinner();
164
+ const configManager = new ConfigManager();
193
165
  let tunnel;
166
+ // Check if detach mode is requested
167
+ if (options.detach) {
168
+ // Check if already running
169
+ const existingState = DevProcessManager.loadState();
170
+ if (existingState && DevProcessManager.isProcessRunning(existingState.pid)) {
171
+ console.log(chalk.yellow('⚠️ Dev server is already running in background'));
172
+ console.log('');
173
+ console.log(chalk.bold('Status:'));
174
+ console.log(chalk.gray(' PID:'), existingState.pid);
175
+ console.log(chalk.gray(' Port:'), existingState.port);
176
+ console.log(chalk.gray(' Tunnel:'), existingState.tunnelUrl);
177
+ console.log(chalk.gray(' Uptime:'), DevProcessManager.formatUptime(existingState.startedAt));
178
+ console.log('');
179
+ console.log(chalk.gray('Use "paymongo dev stop" to stop the server first.'));
180
+ return;
181
+ }
182
+ // Spawn detached process
183
+ const args = ['dist/index.js', 'dev', '--port', options.port || '3000'];
184
+ if (options.noRegister) {
185
+ args.push('--no-register');
186
+ }
187
+ if (options.events) {
188
+ args.push('--events', options.events);
189
+ }
190
+ if (options.ngrokToken) {
191
+ args.push('--ngrok-token', options.ngrokToken);
192
+ }
193
+ const logFile = DevProcessManager.getLogFile();
194
+ const out = fs.openSync(logFile, 'a');
195
+ const err = fs.openSync(logFile, 'a');
196
+ const child = spawn(process.execPath, args, {
197
+ detached: true,
198
+ stdio: ['ignore', out, err],
199
+ cwd: process.cwd(),
200
+ env: { ...process.env, FORCE_COLOR: '1' },
201
+ });
202
+ child.unref();
203
+ console.log(chalk.green('✓'), 'Dev server starting in background...');
204
+ console.log(chalk.gray(' PID:'), child.pid);
205
+ console.log(chalk.gray(' Logs:'), logFile);
206
+ console.log('');
207
+ console.log(chalk.gray('Use "paymongo dev status" to check server status'));
208
+ console.log(chalk.gray('Use "paymongo dev stop" to stop the server'));
209
+ console.log(chalk.gray('Use "paymongo dev logs" to view server logs'));
210
+ // Give the server a moment to start, then exit
211
+ await new Promise(resolve => setTimeout(resolve, 500));
212
+ return;
213
+ }
194
214
  try {
195
215
  // Load configuration
196
216
  spinner.start('Loading configuration...');
197
217
  const config = await configManager.load();
198
218
  if (!config) {
199
219
  spinner.fail('No configuration found');
200
- console.log(chalk_1.default.yellow('No PayMongo configuration found.'));
201
- console.log(chalk_1.default.gray("Run 'paymongo init' to set up your project first."));
220
+ console.log(chalk.yellow('No PayMongo configuration found.'));
221
+ console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
202
222
  return;
203
223
  }
204
224
  spinner.succeed('Configuration loaded');
@@ -206,8 +226,8 @@ command
206
226
  spinner.start('Creating tunnel...');
207
227
  const port = parseInt(options.port || '3000');
208
228
  // Lazy load ngrok to reduce startup time
209
- const { default: ngrok } = await Promise.resolve().then(() => __importStar(require('@ngrok/ngrok')));
210
- const tunnelUrl = await (0, errors_1.withRetry)(async () => {
229
+ const { default: ngrok } = await import('@ngrok/ngrok');
230
+ const tunnelUrl = await withRetry(async () => {
211
231
  try {
212
232
  // Try to get authtoken from command line option or environment
213
233
  const authtoken = options.ngrokToken || process.env.NGROK_AUTHTOKEN;
@@ -224,7 +244,7 @@ command
224
244
  return tunnel.url();
225
245
  }
226
246
  catch (error) {
227
- console.log(chalk_1.default.yellow('Debug: ngrok error details:'), error.message);
247
+ console.log(chalk.yellow('Debug: ngrok error details:'), error.message);
228
248
  throw error;
229
249
  }
230
250
  }, {
@@ -243,20 +263,54 @@ command
243
263
  // Start webhook server
244
264
  const devServer = new DevServer(port, config);
245
265
  await devServer.start();
266
+ // Clean up any stale webhooks from previous sessions
267
+ if (config.registeredWebhooks && config.registeredWebhooks.length > 0) {
268
+ spinner.start('Cleaning up stale webhooks...');
269
+ const apiClient = new ApiClient({ config });
270
+ let cleanedCount = 0;
271
+ for (const webhook of config.registeredWebhooks) {
272
+ try {
273
+ await apiClient.deleteWebhook(webhook.id);
274
+ cleanedCount++;
275
+ }
276
+ catch {
277
+ // Webhook may already be deleted, ignore errors
278
+ }
279
+ }
280
+ config.registeredWebhooks = [];
281
+ await configManager.save(config);
282
+ if (cleanedCount > 0) {
283
+ spinner.succeed(`Cleaned up ${cleanedCount} stale webhook(s)`);
284
+ }
285
+ else {
286
+ spinner.succeed('No stale webhooks to clean up');
287
+ }
288
+ }
246
289
  // Register webhook (unless disabled)
247
290
  let webhookId;
248
291
  if (!options.noRegister) {
249
292
  spinner.start('Registering webhook...');
250
293
  const events = (options.events || 'payment.paid,payment.failed').split(',');
251
- const webhookUrl = `${tunnelUrl}/webhook`;
294
+ // Use project-specific webhook path
295
+ const projectSlug = config.projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
296
+ const webhookUrl = `${tunnelUrl}/webhook/${projectSlug}`;
252
297
  try {
253
- const webhook = await new client_1.default({ config }).createWebhook(webhookUrl, events);
298
+ const webhook = await new ApiClient({ config }).createWebhook(webhookUrl, events);
254
299
  webhookId = webhook.id;
255
- // Store webhook secret if available
300
+ // Store webhook secret and track registered webhook
256
301
  if (webhook.attributes?.secret) {
257
302
  config.webhookSecrets = config.webhookSecrets || {};
258
303
  config.webhookSecrets[webhook.id] = webhook.attributes.secret;
259
- await configManager.save(config);
304
+ }
305
+ // Track this webhook for project-specific cleanup
306
+ config.registeredWebhooks = config.registeredWebhooks || [];
307
+ config.registeredWebhooks.push({
308
+ id: webhook.id,
309
+ url: webhookUrl,
310
+ createdAt: Date.now(),
311
+ });
312
+ await configManager.save(config);
313
+ if (webhook.attributes?.secret) {
260
314
  spinner.succeed(`Webhook registered: ${webhookId} (with signature verification)`);
261
315
  }
262
316
  else {
@@ -266,54 +320,83 @@ command
266
320
  catch (error) {
267
321
  const err = error;
268
322
  spinner.warn('Webhook registration failed - server will start without webhook');
269
- console.log(chalk_1.default.yellow('⚠️'), 'Webhook registration failed:', err.message);
323
+ console.log(chalk.yellow('⚠️'), 'Webhook registration failed:', err.message);
270
324
  console.log('');
271
- console.log(chalk_1.default.blue('ℹ️'), 'You can still test webhooks manually:');
272
- console.log(chalk_1.default.gray(` Webhook URL: ${webhookUrl}`));
273
- console.log(chalk_1.default.gray(' Copy this URL to your PayMongo dashboard'));
325
+ console.log(chalk.blue('ℹ️'), 'You can still test webhooks manually:');
326
+ console.log(chalk.gray(` Webhook URL: ${webhookUrl}`));
327
+ console.log(chalk.gray(' Copy this URL to your PayMongo dashboard'));
274
328
  console.log('');
275
329
  if (err.message.includes('API key') || err.message.includes('unauthorized')) {
276
- console.log(chalk_1.default.yellow('💡 To fix webhook registration:'));
277
- console.log(chalk_1.default.gray(' 1. Run "paymongo login" to update your API keys'));
278
- console.log(chalk_1.default.gray(' 2. Restart the development server'));
330
+ console.log(chalk.yellow('💡 To fix webhook registration:'));
331
+ console.log(chalk.gray(' 1. Run "paymongo login" to update your API keys'));
332
+ console.log(chalk.gray(' 2. Restart the development server'));
279
333
  }
280
334
  }
281
335
  }
282
336
  // Display status
283
- console.log('\n' + chalk_1.default.green('🚀 PayMongo Development Server'));
337
+ const projectSlug = config.projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
338
+ const localWebhookUrl = `http://localhost:${port}/webhook/${projectSlug}`;
339
+ const externalWebhookUrl = `${tunnelUrl}/webhook/${projectSlug}`;
340
+ console.log('\n' + chalk.green('🚀 PayMongo Development Server'));
284
341
  console.log('');
285
- console.log(chalk_1.default.green(''), `Tunnel: ${tunnelUrl}`);
286
- if (webhookId) {
287
- console.log(chalk_1.default.green(''), `Webhook: ${webhookId}`);
288
- }
289
- console.log(chalk_1.default.green(''), `Server: http://localhost:${port}/webhook`);
342
+ console.log(chalk.bold('URLs:'));
343
+ console.log(chalk.gray(' ├─'), chalk.cyan('External (PayMongo sends here):'));
344
+ console.log(chalk.gray(''), chalk.yellow(externalWebhookUrl));
345
+ console.log(chalk.gray(' │'));
346
+ console.log(chalk.gray(' └─'), chalk.cyan('Local (Your server receives here):'));
347
+ console.log(chalk.gray(' '), chalk.green(localWebhookUrl));
290
348
  console.log('');
291
- console.log(chalk_1.default.bold('Forwarding:'), `${tunnelUrl} → http://localhost:${port}`);
349
+ console.log(chalk.bold('Forwarding:'));
350
+ console.log(chalk.gray(' '), `${chalk.yellow(tunnelUrl)} ${chalk.gray('→')} ${chalk.green(`http://localhost:${port}`)}`);
292
351
  console.log('');
293
- console.log(chalk_1.default.bold('Events:'), (options.events || 'payment.paid,payment.failed').split(',').join(', '));
352
+ if (webhookId) {
353
+ console.log(chalk.bold('Webhook ID:'), chalk.gray(webhookId));
354
+ }
355
+ console.log(chalk.bold('Events:'), (options.events || 'payment.paid,payment.failed').split(',').join(', '));
294
356
  console.log('');
295
- console.log(chalk_1.default.gray('Press Ctrl+C to stop'));
357
+ console.log(chalk.gray('💡 Tip: Use the External URL in PayMongo dashboard, requests will forward to your local server'));
358
+ console.log(chalk.gray('Press Ctrl+C to stop'));
359
+ // Save state for background process management
360
+ DevProcessManager.saveState({
361
+ pid: process.pid,
362
+ port,
363
+ tunnelUrl: tunnelUrl ?? '',
364
+ webhookId,
365
+ webhookUrl: externalWebhookUrl,
366
+ localUrl: localWebhookUrl,
367
+ events: (options.events || 'payment.paid,payment.failed').split(','),
368
+ startedAt: Date.now(),
369
+ projectName: config.projectName,
370
+ });
296
371
  // Handle cleanup on exit
297
372
  const cleanup = async () => {
298
- console.log('\n' + chalk_1.default.yellow('Shutting down...'));
373
+ console.log('\n' + chalk.yellow('Shutting down...'));
374
+ // Clear saved state
375
+ DevProcessManager.clearState();
299
376
  try {
300
377
  // Disconnect ngrok
301
378
  if (tunnel) {
302
379
  await tunnel.close();
303
- console.log(chalk_1.default.yellow('✓'), 'Tunnel closed');
380
+ console.log(chalk.yellow('✓'), 'Tunnel closed');
304
381
  }
305
382
  // Stop server
306
383
  await devServer.stop();
307
- // Delete webhook
384
+ // Delete webhook and remove from tracked list
308
385
  if (webhookId) {
309
386
  spinner.start('Cleaning up webhook...');
310
- await new client_1.default({ config }).deleteWebhook(webhookId);
387
+ await new ApiClient({ config }).deleteWebhook(webhookId);
388
+ // Remove from tracked webhooks
389
+ if (config.registeredWebhooks) {
390
+ config.registeredWebhooks = config.registeredWebhooks.filter(w => w.id !== webhookId);
391
+ delete config.webhookSecrets[webhookId];
392
+ await configManager.save(config);
393
+ }
311
394
  spinner.succeed('Webhook deleted');
312
395
  }
313
396
  }
314
397
  catch (error) {
315
- console.error(chalk_1.default.red('Error during cleanup:'), error.message);
316
- console.log(chalk_1.default.yellow('⚠️'), 'Some cleanup tasks may not have completed');
398
+ console.error(chalk.red('Error during cleanup:'), error.message);
399
+ console.log(chalk.yellow('⚠️'), 'Some cleanup tasks may not have completed');
317
400
  }
318
401
  process.exit(0);
319
402
  };
@@ -327,17 +410,18 @@ command
327
410
  const err = error;
328
411
  // Provide actionable error messages based on error type
329
412
  if (err.message.includes('ngrok') || err.message.includes('tunnel')) {
330
- console.error(chalk_1.default.red('❌ Failed to create tunnel:'), err.message);
413
+ console.error(chalk.red('❌ Failed to create tunnel:'), err.message);
331
414
  console.log('');
332
- console.log(chalk_1.default.yellow('💡 Troubleshooting suggestions:'));
333
- console.log(chalk_1.default.gray('• Check your internet connection'));
334
- console.log(chalk_1.default.gray('• Make sure ngrok is not blocked by firewall/antivirus'));
335
- console.log(chalk_1.default.gray('• Set up ngrok authentication: export NGROK_AUTHTOKEN=your_token'));
336
- console.log(chalk_1.default.gray('• Get your authtoken from: https://dashboard.ngrok.com/get-started/your-authtoken'));
337
- console.log(chalk_1.default.gray('• Try a different port: paymongo dev --port 3001'));
338
- console.log(chalk_1.default.gray('• Visit https://ngrok.com for status updates'));
415
+ console.log(chalk.yellow('💡 Troubleshooting suggestions:'));
416
+ console.log(chalk.gray('• Check your internet connection'));
417
+ console.log(chalk.gray('• Make sure ngrok is not blocked by firewall/antivirus'));
418
+ console.log(chalk.gray('• Set up ngrok authentication: export NGROK_AUTHTOKEN=your_token'));
419
+ console.log(chalk.gray('• Get your authtoken from: https://dashboard.ngrok.com/get-started/your-authtoken'));
420
+ console.log(chalk.gray('• Try a different port: paymongo dev --port 3001'));
421
+ console.log(chalk.gray('• Visit https://ngrok.com for status updates'));
339
422
  }
340
423
  // Cleanup on error
424
+ DevProcessManager.clearState();
341
425
  try {
342
426
  if (tunnel) {
343
427
  await tunnel.close();
@@ -349,5 +433,130 @@ command
349
433
  process.exit(1);
350
434
  }
351
435
  });
352
- exports.default = command;
436
+ // Subcommand: dev status
437
+ command
438
+ .command('status')
439
+ .description('Check if dev server is running in background')
440
+ .action(async () => {
441
+ const state = DevProcessManager.loadState();
442
+ if (!state) {
443
+ console.log(chalk.yellow('No dev server is running in background.'));
444
+ console.log(chalk.gray('Start one with: paymongo dev --detach'));
445
+ return;
446
+ }
447
+ const isRunning = DevProcessManager.isProcessRunning(state.pid);
448
+ if (!isRunning) {
449
+ console.log(chalk.yellow('Dev server process is not running (stale state).'));
450
+ DevProcessManager.clearState();
451
+ console.log(chalk.gray('Start a new one with: paymongo dev --detach'));
452
+ return;
453
+ }
454
+ console.log(chalk.green('✓ Dev server is running'));
455
+ console.log('');
456
+ console.log(chalk.bold('Process:'));
457
+ console.log(chalk.gray(' PID:'), state.pid);
458
+ console.log(chalk.gray(' Uptime:'), DevProcessManager.formatUptime(state.startedAt));
459
+ console.log(chalk.gray(' Project:'), state.projectName);
460
+ console.log('');
461
+ console.log(chalk.bold('URLs:'));
462
+ console.log(chalk.gray(' External:'), chalk.yellow(state.webhookUrl));
463
+ console.log(chalk.gray(' Local:'), chalk.green(state.localUrl));
464
+ console.log('');
465
+ console.log(chalk.bold('Configuration:'));
466
+ console.log(chalk.gray(' Port:'), state.port);
467
+ console.log(chalk.gray(' Events:'), state.events.join(', '));
468
+ if (state.webhookId) {
469
+ console.log(chalk.gray(' Webhook ID:'), state.webhookId);
470
+ }
471
+ console.log('');
472
+ console.log(chalk.gray('Use "paymongo dev stop" to stop the server'));
473
+ console.log(chalk.gray('Use "paymongo dev logs" to view server logs'));
474
+ });
475
+ // Subcommand: dev stop
476
+ command
477
+ .command('stop')
478
+ .description('Stop the background dev server')
479
+ .action(async () => {
480
+ const spinner = new Spinner();
481
+ const state = DevProcessManager.loadState();
482
+ if (!state) {
483
+ console.log(chalk.yellow('No dev server is running in background.'));
484
+ return;
485
+ }
486
+ const isRunning = DevProcessManager.isProcessRunning(state.pid);
487
+ if (!isRunning) {
488
+ console.log(chalk.yellow('Dev server process is not running (cleaning up stale state).'));
489
+ DevProcessManager.clearState();
490
+ return;
491
+ }
492
+ spinner.start('Stopping dev server...');
493
+ // Kill the process
494
+ const killed = DevProcessManager.killProcess(state.pid);
495
+ if (killed) {
496
+ // Wait a moment for cleanup
497
+ await new Promise(resolve => setTimeout(resolve, 1000));
498
+ DevProcessManager.clearState();
499
+ spinner.succeed('Dev server stopped');
500
+ // Note: The webhook might still be registered if the process didn't clean up properly
501
+ // The next dev start will clean it up
502
+ console.log('');
503
+ console.log(chalk.gray('Note: If the webhook was not cleaned up, it will be removed on next "paymongo dev" start.'));
504
+ }
505
+ else {
506
+ spinner.fail('Failed to stop dev server');
507
+ console.log(chalk.yellow('Try manually killing the process:'));
508
+ console.log(chalk.gray(` PID: ${state.pid}`));
509
+ if (process.platform === 'win32') {
510
+ console.log(chalk.gray(` Run: taskkill /pid ${state.pid} /f`));
511
+ }
512
+ else {
513
+ console.log(chalk.gray(` Run: kill -9 ${state.pid}`));
514
+ }
515
+ }
516
+ });
517
+ // Subcommand: dev logs
518
+ command
519
+ .command('logs')
520
+ .description('View dev server logs')
521
+ .option('-n, --lines <number>', 'Number of lines to show', '50')
522
+ .option('-f, --follow', 'Follow log output (like tail -f)')
523
+ .option('--clear', 'Clear the log file')
524
+ .action(async (options) => {
525
+ if (options.clear) {
526
+ DevProcessManager.clearLogs();
527
+ console.log(chalk.green('✓ Logs cleared'));
528
+ return;
529
+ }
530
+ const logFile = DevProcessManager.getLogFile();
531
+ const lines = DevProcessManager.readLogs(parseInt(options.lines));
532
+ if (lines.length === 0) {
533
+ console.log(chalk.yellow('No logs available.'));
534
+ console.log(chalk.gray('Log file:'), logFile);
535
+ return;
536
+ }
537
+ console.log(chalk.bold('Dev Server Logs'));
538
+ console.log(chalk.gray(`(Last ${lines.length} lines from ${logFile})`));
539
+ console.log(chalk.gray('─'.repeat(60)));
540
+ console.log('');
541
+ lines.forEach(line => console.log(line));
542
+ if (options.follow) {
543
+ console.log('');
544
+ console.log(chalk.gray('Following logs... Press Ctrl+C to stop'));
545
+ let lastSize = fs.statSync(logFile).size;
546
+ fs.watchFile(logFile, { interval: 500 }, () => {
547
+ const newSize = fs.statSync(logFile).size;
548
+ if (newSize > lastSize) {
549
+ const fd = fs.openSync(logFile, 'r');
550
+ const buffer = Buffer.alloc(newSize - lastSize);
551
+ fs.readSync(fd, buffer, 0, buffer.length, lastSize);
552
+ fs.closeSync(fd);
553
+ process.stdout.write(buffer.toString());
554
+ lastSize = newSize;
555
+ }
556
+ });
557
+ // Keep process running
558
+ await new Promise(() => { });
559
+ }
560
+ });
561
+ export default command;
353
562
  //# sourceMappingURL=dev.js.map