paymongo-cli 1.4.4 → 1.4.7

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 (155) hide show
  1. package/.github/copilot-instructions.md +95 -95
  2. package/CHANGELOG.md +85 -1
  3. package/LICENSE +20 -20
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/commands/config.js +30 -15
  6. package/dist/commands/dev/logs.js +3 -3
  7. package/dist/commands/dev/status.js +2 -2
  8. package/dist/commands/dev/stop.js +3 -3
  9. package/dist/commands/dev.js +9 -8
  10. package/dist/commands/env.js +6 -6
  11. package/dist/commands/generate/templates/checkout-page/index.js +520 -520
  12. package/dist/commands/generate/templates/payment-intent/javascript.js +68 -68
  13. package/dist/commands/generate/templates/payment-intent/typescript.js +92 -92
  14. package/dist/commands/generate/templates/webhook-handler/javascript.js +192 -147
  15. package/dist/commands/generate/templates/webhook-handler/typescript.js +147 -117
  16. package/dist/commands/generate.js +43 -37
  17. package/dist/commands/init.js +25 -8
  18. package/dist/commands/login.js +56 -19
  19. package/dist/commands/payments.js +9 -8
  20. package/dist/commands/team/index.js +11 -9
  21. package/dist/commands/trigger.js +58 -18
  22. package/dist/commands/webhooks.js +8 -7
  23. package/dist/index.js +9 -2
  24. package/dist/services/analytics/service.js +24 -19
  25. package/dist/services/api/client.js +16 -16
  26. package/dist/services/config/manager.js +6 -8
  27. package/dist/services/dev/process-manager.js +30 -32
  28. package/dist/services/dev/server.js +45 -39
  29. package/dist/services/team/service.js +4 -1
  30. package/dist/types/schemas.js +38 -9
  31. package/dist/utils/bulk.js +36 -4
  32. package/dist/utils/constants.js +11 -1
  33. package/dist/utils/errors.js +6 -0
  34. package/dist/utils/validator.js +10 -9
  35. package/dist/utils/webhook-store.js +18 -15
  36. package/eslint.config.ts +70 -70
  37. package/package.json +2 -2
  38. package/coverage/base.css +0 -224
  39. package/coverage/block-navigation.js +0 -87
  40. package/coverage/favicon.png +0 -0
  41. package/coverage/index.html +0 -281
  42. package/coverage/lcov-report/base.css +0 -224
  43. package/coverage/lcov-report/block-navigation.js +0 -87
  44. package/coverage/lcov-report/favicon.png +0 -0
  45. package/coverage/lcov-report/index.html +0 -281
  46. package/coverage/lcov-report/prettify.css +0 -1
  47. package/coverage/lcov-report/prettify.js +0 -2
  48. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  49. package/coverage/lcov-report/sorter.js +0 -210
  50. package/coverage/lcov.info +0 -5053
  51. package/coverage/prettify.css +0 -1
  52. package/coverage/prettify.js +0 -2
  53. package/coverage/sort-arrow-sprite.png +0 -0
  54. package/coverage/sorter.js +0 -210
  55. package/dist/commands/config.d.ts +0 -21
  56. package/dist/commands/config.d.ts.map +0 -1
  57. package/dist/commands/config.js.map +0 -1
  58. package/dist/commands/dev.d.ts +0 -16
  59. package/dist/commands/dev.d.ts.map +0 -1
  60. package/dist/commands/dev.js.map +0 -1
  61. package/dist/commands/env.d.ts +0 -4
  62. package/dist/commands/env.d.ts.map +0 -1
  63. package/dist/commands/env.js.map +0 -1
  64. package/dist/commands/init.d.ts +0 -15
  65. package/dist/commands/init.d.ts.map +0 -1
  66. package/dist/commands/init.js.map +0 -1
  67. package/dist/commands/login.d.ts +0 -20
  68. package/dist/commands/login.d.ts.map +0 -1
  69. package/dist/commands/login.js.map +0 -1
  70. package/dist/commands/payments.d.ts +0 -41
  71. package/dist/commands/payments.d.ts.map +0 -1
  72. package/dist/commands/payments.js.map +0 -1
  73. package/dist/commands/team/index.d.ts +0 -4
  74. package/dist/commands/team/index.d.ts.map +0 -1
  75. package/dist/commands/team/index.js.map +0 -1
  76. package/dist/commands/trigger.d.ts +0 -4
  77. package/dist/commands/trigger.d.ts.map +0 -1
  78. package/dist/commands/trigger.js.map +0 -1
  79. package/dist/commands/webhooks.d.ts +0 -23
  80. package/dist/commands/webhooks.d.ts.map +0 -1
  81. package/dist/commands/webhooks.js.map +0 -1
  82. package/dist/index.d.ts +0 -3
  83. package/dist/index.d.ts.map +0 -1
  84. package/dist/index.js.map +0 -1
  85. package/dist/services/analytics/service.d.ts +0 -35
  86. package/dist/services/analytics/service.d.ts.map +0 -1
  87. package/dist/services/analytics/service.js.map +0 -1
  88. package/dist/services/api/client.d.ts +0 -26
  89. package/dist/services/api/client.d.ts.map +0 -1
  90. package/dist/services/api/client.js.map +0 -1
  91. package/dist/services/api/rate-limiter.d.ts +0 -64
  92. package/dist/services/api/rate-limiter.d.ts.map +0 -1
  93. package/dist/services/api/rate-limiter.js.map +0 -1
  94. package/dist/services/api/undici-client.d.ts +0 -39
  95. package/dist/services/api/undici-client.d.ts.map +0 -1
  96. package/dist/services/api/undici-client.js +0 -288
  97. package/dist/services/api/undici-client.js.map +0 -1
  98. package/dist/services/config/manager.d.ts +0 -16
  99. package/dist/services/config/manager.d.ts.map +0 -1
  100. package/dist/services/config/manager.js.map +0 -1
  101. package/dist/services/dev/process-manager.d.ts +0 -50
  102. package/dist/services/dev/process-manager.d.ts.map +0 -1
  103. package/dist/services/dev/process-manager.js.map +0 -1
  104. package/dist/services/github/auth.d.ts +0 -15
  105. package/dist/services/github/auth.d.ts.map +0 -1
  106. package/dist/services/github/auth.js +0 -79
  107. package/dist/services/github/auth.js.map +0 -1
  108. package/dist/services/github/client.d.ts +0 -95
  109. package/dist/services/github/client.d.ts.map +0 -1
  110. package/dist/services/github/client.js +0 -130
  111. package/dist/services/github/client.js.map +0 -1
  112. package/dist/services/github/sync.d.ts +0 -26
  113. package/dist/services/github/sync.d.ts.map +0 -1
  114. package/dist/services/github/sync.js +0 -203
  115. package/dist/services/github/sync.js.map +0 -1
  116. package/dist/services/payments/simulator.d.ts +0 -28
  117. package/dist/services/payments/simulator.d.ts.map +0 -1
  118. package/dist/services/payments/simulator.js.map +0 -1
  119. package/dist/services/team/service.d.ts +0 -44
  120. package/dist/services/team/service.d.ts.map +0 -1
  121. package/dist/services/team/service.js.map +0 -1
  122. package/dist/services/web/server.d.ts +0 -31
  123. package/dist/services/web/server.d.ts.map +0 -1
  124. package/dist/services/web/server.js +0 -206
  125. package/dist/services/web/server.js.map +0 -1
  126. package/dist/types/paymongo.d.ts +0 -204
  127. package/dist/types/paymongo.d.ts.map +0 -1
  128. package/dist/types/paymongo.js.map +0 -1
  129. package/dist/types/schemas.d.ts +0 -80
  130. package/dist/types/schemas.d.ts.map +0 -1
  131. package/dist/types/schemas.js.map +0 -1
  132. package/dist/utils/bulk.d.ts +0 -62
  133. package/dist/utils/bulk.d.ts.map +0 -1
  134. package/dist/utils/bulk.js.map +0 -1
  135. package/dist/utils/cache.d.ts +0 -22
  136. package/dist/utils/cache.d.ts.map +0 -1
  137. package/dist/utils/cache.js.map +0 -1
  138. package/dist/utils/constants.d.ts +0 -32
  139. package/dist/utils/constants.d.ts.map +0 -1
  140. package/dist/utils/constants.js.map +0 -1
  141. package/dist/utils/errors.d.ts +0 -34
  142. package/dist/utils/errors.d.ts.map +0 -1
  143. package/dist/utils/errors.js.map +0 -1
  144. package/dist/utils/logger.d.ts +0 -20
  145. package/dist/utils/logger.d.ts.map +0 -1
  146. package/dist/utils/logger.js.map +0 -1
  147. package/dist/utils/spinner.d.ts +0 -17
  148. package/dist/utils/spinner.d.ts.map +0 -1
  149. package/dist/utils/spinner.js.map +0 -1
  150. package/dist/utils/validator.d.ts +0 -10
  151. package/dist/utils/validator.d.ts.map +0 -1
  152. package/dist/utils/validator.js.map +0 -1
  153. package/dist/utils/webhook-store.d.ts +0 -22
  154. package/dist/utils/webhook-store.d.ts.map +0 -1
  155. package/dist/utils/webhook-store.js.map +0 -1
@@ -5,6 +5,38 @@ import ConfigManager from '../services/config/manager.js';
5
5
  import Spinner from '../utils/spinner.js';
6
6
  import Logger from '../utils/logger.js';
7
7
  import WebhookEventStore from '../utils/webhook-store.js';
8
+ import crypto from 'crypto';
9
+ import { CLI_VERSION } from '../utils/constants.js';
10
+ import { CommandError } from '../utils/errors.js';
11
+ function buildSignatureHeader(config, webhookUrl, body) {
12
+ if (!config?.webhookSecrets || Object.keys(config.webhookSecrets).length === 0) {
13
+ return undefined;
14
+ }
15
+ const registered = config.registeredWebhooks || [];
16
+ const match = registered.find((w) => w.url === webhookUrl);
17
+ const webhookId = match?.id;
18
+ let secret;
19
+ if (webhookId && config.webhookSecrets[webhookId]) {
20
+ secret = config.webhookSecrets[webhookId];
21
+ }
22
+ else {
23
+ const secrets = Object.values(config.webhookSecrets).filter((value) => typeof value === 'string' && value.length > 0);
24
+ secret = secrets[0];
25
+ }
26
+ if (!secret) {
27
+ return undefined;
28
+ }
29
+ const timestamp = Math.floor(Date.now() / 1000).toString();
30
+ const signature = crypto
31
+ .createHmac('sha256', secret)
32
+ .update(`${timestamp}.${body}`)
33
+ .digest('hex');
34
+ const parts = [`t=${timestamp}`, `te=${signature}`];
35
+ if (webhookId) {
36
+ parts.push(`li=${webhookId}`);
37
+ }
38
+ return parts.join(',');
39
+ }
8
40
  const command = new Command('trigger');
9
41
  command
10
42
  .description('Simulate webhook events locally')
@@ -89,11 +121,11 @@ async function sendWebhookEvent(options) {
89
121
  }
90
122
  if (!webhookUrl) {
91
123
  console.error(chalk.red('❌ No webhook URL provided. Use --url option or configure in .paymongo file'));
92
- process.exit(1);
124
+ throw new CommandError();
93
125
  }
94
126
  if (!selectedEvent) {
95
127
  console.error(chalk.red('❌ No event selected'));
96
- process.exit(1);
128
+ throw new CommandError();
97
129
  }
98
130
  spinner.start('Generating webhook payload...');
99
131
  const webhookPayload = generateWebhookPayload(selectedEvent);
@@ -122,13 +154,16 @@ async function sendWebhookEvent(options) {
122
154
  spinner.start('Sending webhook...');
123
155
  try {
124
156
  const { request } = await import('undici');
157
+ const body = JSON.stringify(webhookPayload);
158
+ const signatureHeader = buildSignatureHeader(config, webhookUrl, body);
125
159
  const response = await request(webhookUrl, {
126
160
  method: 'POST',
127
161
  headers: {
128
162
  'Content-Type': 'application/json',
129
- 'User-Agent': 'PayMongo-CLI/1.0.0',
163
+ 'User-Agent': `PayMongo-CLI/${CLI_VERSION}`,
164
+ ...(signatureHeader ? { 'paymongo-signature': signatureHeader } : {}),
130
165
  },
131
- body: JSON.stringify(webhookPayload),
166
+ body,
132
167
  signal: AbortSignal.timeout(10000),
133
168
  });
134
169
  if (response.statusCode >= 200 && response.statusCode < 300) {
@@ -154,7 +189,7 @@ async function sendWebhookEvent(options) {
154
189
  console.log(chalk.yellow('💡 To fix:'));
155
190
  console.log(chalk.gray(` • Verify your server has a POST handler at: ${webhookUrl}`));
156
191
  console.log(chalk.gray(' • Check that your server is running and accessible'));
157
- process.exit(1);
192
+ throw new CommandError();
158
193
  }
159
194
  else if (response.statusCode >= 400 && response.statusCode < 500) {
160
195
  spinner.fail(`Webhook rejected by server (HTTP ${response.statusCode})`);
@@ -171,7 +206,7 @@ async function sendWebhookEvent(options) {
171
206
  console.log(chalk.gray(' • Invalid request format or headers'));
172
207
  console.log(chalk.gray(' • Authentication/authorization failure'));
173
208
  console.log(chalk.gray(' • Webhook signature verification failed'));
174
- process.exit(1);
209
+ throw new CommandError();
175
210
  }
176
211
  else if (response.statusCode >= 500) {
177
212
  spinner.fail(`Webhook endpoint error (HTTP ${response.statusCode})`);
@@ -187,7 +222,7 @@ async function sendWebhookEvent(options) {
187
222
  console.log(chalk.yellow('💡 This is a server-side error. Check:'));
188
223
  console.log(chalk.gray(' • Server logs for the specific error'));
189
224
  console.log(chalk.gray(' • Webhook handler code for exceptions'));
190
- process.exit(1);
225
+ throw new CommandError();
191
226
  }
192
227
  }
193
228
  catch (error) {
@@ -205,7 +240,7 @@ async function sendWebhookEvent(options) {
205
240
  console.log(chalk.yellow('💡 To fix:'));
206
241
  console.log(chalk.gray(' • Start your local server'));
207
242
  console.log(chalk.gray(` • Verify the server is listening on the correct port`));
208
- process.exit(1);
243
+ throw new CommandError();
209
244
  }
210
245
  else if (err.code === 'ENOTFOUND') {
211
246
  spinner.fail('Host not found');
@@ -216,7 +251,7 @@ async function sendWebhookEvent(options) {
216
251
  console.log(chalk.gray(' • The URL is spelled correctly'));
217
252
  console.log(chalk.gray(' • Your internet connection is working'));
218
253
  console.log(chalk.gray(' • DNS is resolving correctly'));
219
- process.exit(1);
254
+ throw new CommandError();
220
255
  }
221
256
  else if (err.code === 'ETIMEDOUT' || err.message.includes('timeout')) {
222
257
  spinner.fail('Request timed out');
@@ -231,7 +266,7 @@ async function sendWebhookEvent(options) {
231
266
  console.log(chalk.yellow('💡 To fix:'));
232
267
  console.log(chalk.gray(' • Check your webhook handler for slow operations'));
233
268
  console.log(chalk.gray(' • Ensure async operations are handled properly'));
234
- process.exit(1);
269
+ throw new CommandError();
235
270
  }
236
271
  else {
237
272
  spinner.fail(`Webhook delivery failed: ${err.message}`);
@@ -241,7 +276,7 @@ async function sendWebhookEvent(options) {
241
276
  if (err.code) {
242
277
  console.log(chalk.gray(` Code: ${err.code}`));
243
278
  }
244
- process.exit(1);
279
+ throw new CommandError();
245
280
  }
246
281
  }
247
282
  }
@@ -249,7 +284,7 @@ async function sendWebhookEvent(options) {
249
284
  const err = error;
250
285
  spinner.fail('Failed to trigger webhook event');
251
286
  logger.error('Trigger command error:', err.message);
252
- process.exit(1);
287
+ throw new CommandError();
253
288
  }
254
289
  }
255
290
  function generateWebhookPayload(eventType) {
@@ -401,6 +436,8 @@ function generateWebhookPayload(eventType) {
401
436
  }
402
437
  async function replayWebhookEvent(eventId, options) {
403
438
  const store = new WebhookEventStore();
439
+ const configManager = new ConfigManager();
440
+ const config = await configManager.load();
404
441
  try {
405
442
  if (options.list || (!eventId && !options.event)) {
406
443
  const events = await store.loadEvents();
@@ -462,7 +499,7 @@ async function replayWebhookEvent(eventId, options) {
462
499
  if (!event) {
463
500
  console.log(chalk.red(`❌ Event not found: ${eventId}`));
464
501
  console.log(chalk.gray('Use "paymongo trigger replay --list" to see available events.'));
465
- process.exit(1);
502
+ throw new CommandError();
466
503
  }
467
504
  const webhookUrl = options.url || event.url;
468
505
  console.log(chalk.bold.blue('\n🔄 Replaying Webhook Event'));
@@ -475,13 +512,16 @@ async function replayWebhookEvent(eventId, options) {
475
512
  spinner.start('Sending webhook...');
476
513
  try {
477
514
  const { request } = await import('undici');
515
+ const body = JSON.stringify(event.payload);
516
+ const signatureHeader = buildSignatureHeader(config, webhookUrl, body);
478
517
  const response = await request(webhookUrl, {
479
518
  method: 'POST',
480
519
  headers: {
481
520
  'Content-Type': 'application/json',
482
- 'User-Agent': 'PayMongo-CLI/1.0.0',
521
+ 'User-Agent': `PayMongo-CLI/${CLI_VERSION}`,
522
+ ...(signatureHeader ? { 'paymongo-signature': signatureHeader } : {}),
483
523
  },
484
- body: JSON.stringify(event.payload),
524
+ body,
485
525
  signal: AbortSignal.timeout(10000),
486
526
  });
487
527
  if (response.statusCode >= 200 && response.statusCode < 300) {
@@ -499,7 +539,7 @@ async function replayWebhookEvent(eventId, options) {
499
539
  else {
500
540
  spinner.fail(`Webhook replay failed (HTTP ${response.statusCode})`);
501
541
  console.log(chalk.red(`Server responded with: ${response.statusCode}`));
502
- process.exit(1);
542
+ throw new CommandError();
503
543
  }
504
544
  }
505
545
  catch (error) {
@@ -511,14 +551,14 @@ async function replayWebhookEvent(eventId, options) {
511
551
  else {
512
552
  console.log(chalk.red(`❌ Error: ${err.message}`));
513
553
  }
514
- process.exit(1);
554
+ throw new CommandError();
515
555
  }
516
556
  }
517
557
  }
518
558
  catch (error) {
519
559
  const err = error;
520
560
  console.error(chalk.red(`❌ Failed to replay webhook: ${err.message}`));
521
- process.exit(1);
561
+ throw new CommandError();
522
562
  }
523
563
  }
524
564
  function generateId() {
@@ -1,12 +1,12 @@
1
1
  import Table from 'cli-table3';
2
2
  import { Command } from 'commander';
3
- import { input, checkbox } from '@inquirer/prompts';
4
3
  import chalk from 'chalk';
5
4
  import ConfigManager from '../services/config/manager.js';
6
5
  import ApiClient from '../services/api/client.js';
7
6
  import { BulkOperations } from '../utils/bulk.js';
8
7
  import Spinner from '../utils/spinner.js';
9
8
  import { validateWebhookUrl, validateEventTypes } from '../utils/validator.js';
9
+ import { CommandError } from '../utils/errors.js';
10
10
  export async function exportAction(options) {
11
11
  const spinner = new Spinner();
12
12
  const configManager = new ConfigManager();
@@ -55,7 +55,7 @@ export async function exportAction(options) {
55
55
  spinner.stop();
56
56
  const err = error;
57
57
  console.error(chalk.red('❌ Failed to export webhooks:'), err.message);
58
- process.exit(1);
58
+ throw new CommandError();
59
59
  }
60
60
  }
61
61
  export async function importAction(filename, options) {
@@ -154,7 +154,7 @@ export async function importAction(filename, options) {
154
154
  spinner.stop();
155
155
  const err = error;
156
156
  console.error(chalk.red('❌ Failed to import webhooks:'), err.message);
157
- process.exit(1);
157
+ throw new CommandError();
158
158
  }
159
159
  }
160
160
  export async function createAction(options) {
@@ -178,6 +178,7 @@ export async function createAction(options) {
178
178
  };
179
179
  }
180
180
  else {
181
+ const { input, checkbox } = await import('@inquirer/prompts');
181
182
  const url = await input({
182
183
  message: 'Webhook URL:',
183
184
  default: options.url || '',
@@ -281,7 +282,7 @@ export async function createAction(options) {
281
282
  console.log('');
282
283
  console.log(chalk.yellow('💡 For help, visit: https://developers.paymongo.com/docs/webhooks'));
283
284
  }
284
- process.exit(1);
285
+ throw new CommandError();
285
286
  }
286
287
  }
287
288
  export async function listAction(options) {
@@ -374,7 +375,7 @@ export async function listAction(options) {
374
375
  else {
375
376
  console.error(chalk.red('❌ Failed to list webhooks:'), err.message);
376
377
  }
377
- process.exit(1);
378
+ throw new CommandError();
378
379
  }
379
380
  }
380
381
  export async function deleteAction(id, options) {
@@ -426,7 +427,7 @@ export async function deleteAction(id, options) {
426
427
  else {
427
428
  console.error(chalk.red('❌ Failed to delete webhook:'), err.message);
428
429
  }
429
- process.exit(1);
430
+ throw new CommandError();
430
431
  }
431
432
  }
432
433
  export async function showAction(id) {
@@ -479,7 +480,7 @@ export async function showAction(id) {
479
480
  else {
480
481
  console.error(chalk.red('❌ Failed to get webhook details:'), err.message);
481
482
  }
482
- process.exit(1);
483
+ throw new CommandError();
483
484
  }
484
485
  }
485
486
  const command = new Command('webhooks')
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { createRequire } from 'module';
5
+ import { CommandError } from './utils/errors.js';
5
6
  const require = createRequire(import.meta.url);
6
7
  const { version } = require('../package.json');
7
8
  const program = new Command();
@@ -68,11 +69,17 @@ EXAMPLES
68
69
  For more information, visit: https://github.com/leodyversemilla07/paymongo-cli
69
70
  `);
70
71
  process.on('uncaughtException', (error) => {
72
+ if (error instanceof CommandError) {
73
+ process.exit(1);
74
+ }
71
75
  console.error(chalk.red('An unexpected error occurred:'), error.message);
72
76
  process.exit(1);
73
77
  });
74
- process.on('unhandledRejection', (reason, promise) => {
75
- console.error(chalk.red('Unhandled Rejection at:'), promise, 'reason:', reason);
78
+ process.on('unhandledRejection', (reason) => {
79
+ if (reason instanceof CommandError) {
80
+ process.exit(1);
81
+ }
82
+ console.error(chalk.red('An unexpected error occurred:'), reason instanceof Error ? reason.message : String(reason));
76
83
  process.exit(1);
77
84
  });
78
85
  program.parse();
@@ -1,45 +1,50 @@
1
- import fs from 'fs';
1
+ import fs from 'fs/promises';
2
2
  import path from 'path';
3
+ import os from 'os';
3
4
  import Logger from '../../utils/logger.js';
4
5
  export class AnalyticsService {
5
6
  events = [];
6
7
  dataFile;
7
8
  logger;
8
9
  config;
10
+ _ready;
9
11
  constructor(config) {
10
12
  this.logger = new Logger();
11
13
  this.config = config;
12
- this.dataFile = path.join(process.cwd(), '.paymongo', 'analytics.json');
13
- this.loadEvents();
14
+ const analyticsDir = process.env.PAYMONGO_ANALYTICS_DIR;
15
+ this.dataFile = analyticsDir
16
+ ? path.join(analyticsDir, 'analytics.json')
17
+ : path.join(os.homedir(), '.paymongo-cli', 'analytics.json');
18
+ this._ready = this.loadEvents();
14
19
  }
15
- loadEvents() {
20
+ async loadEvents() {
16
21
  try {
17
- if (fs.existsSync(this.dataFile)) {
18
- const data = JSON.parse(fs.readFileSync(this.dataFile, 'utf-8'));
19
- this.events = data.events || [];
20
- if (this.events.length > 1000) {
21
- this.events = this.events.slice(-1000);
22
- }
22
+ const data = JSON.parse(await fs.readFile(this.dataFile, 'utf-8'));
23
+ this.events = data.events || [];
24
+ if (this.events.length > 1000) {
25
+ this.events = this.events.slice(-1000);
23
26
  }
24
27
  }
25
28
  catch (error) {
29
+ if (error.code === 'ENOENT') {
30
+ return;
31
+ }
26
32
  this.logger.error('Failed to load analytics data', error);
27
33
  this.events = [];
28
34
  }
29
35
  }
30
- saveEvents() {
36
+ async saveEvents() {
31
37
  try {
32
38
  const dir = path.dirname(this.dataFile);
33
- if (!fs.existsSync(dir)) {
34
- fs.mkdirSync(dir, { recursive: true });
35
- }
36
- fs.writeFileSync(this.dataFile, JSON.stringify({ events: this.events }, null, 2));
39
+ await fs.mkdir(dir, { recursive: true });
40
+ await fs.writeFile(this.dataFile, JSON.stringify({ events: this.events }, null, 2));
37
41
  }
38
42
  catch (error) {
39
43
  this.logger.error('Failed to save analytics data', error);
40
44
  }
41
45
  }
42
- recordEvent(event) {
46
+ async recordEvent(event) {
47
+ await this._ready;
43
48
  if (!this.config.analytics?.enabled) {
44
49
  return;
45
50
  }
@@ -52,7 +57,7 @@ export class AnalyticsService {
52
57
  if (this.events.length > 1000) {
53
58
  this.events = this.events.slice(-1000);
54
59
  }
55
- this.saveEvents();
60
+ await this.saveEvents();
56
61
  }
57
62
  getAnalytics() {
58
63
  const totalEvents = this.events.length;
@@ -83,8 +88,8 @@ export class AnalyticsService {
83
88
  errorsByType,
84
89
  };
85
90
  }
86
- clearAnalytics() {
91
+ async clearAnalytics() {
87
92
  this.events = [];
88
- this.saveEvents();
93
+ await this.saveEvents();
89
94
  }
90
95
  }
@@ -2,7 +2,7 @@ import { request } from 'undici';
2
2
  import { NetworkError, ApiKeyError, PayMongoError, withRetry } from '../../utils/errors.js';
3
3
  import Cache from '../../utils/cache.js';
4
4
  import RateLimiter from './rate-limiter.js';
5
- const REQUEST_TIMEOUT = 30000;
5
+ import { CLI_VERSION, REQUEST_TIMEOUT, CACHE_TTL, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_DEFAULT_MAX, RATE_LIMIT_WEBHOOKS_MAX, RATE_LIMIT_PAYMENTS_MAX, RATE_LIMIT_REFUNDS_MAX, RATE_LIMIT_ENV_MULTIPLIER, PAYMONGO_API_BASE, } from '../../utils/constants.js';
6
6
  export class ApiClient {
7
7
  config;
8
8
  baseUrl;
@@ -12,13 +12,13 @@ export class ApiClient {
12
12
  rateLimiter;
13
13
  constructor(options) {
14
14
  this.config = options.config;
15
- this.baseUrl = 'https://api.paymongo.com';
15
+ this.baseUrl = PAYMONGO_API_BASE;
16
16
  this.timeout = options.timeout || REQUEST_TIMEOUT;
17
17
  this.defaultHeaders = {
18
18
  'Content-Type': 'application/json',
19
- 'User-Agent': 'paymongo-cli/1.0.0',
19
+ 'User-Agent': `paymongo-cli/${CLI_VERSION}`,
20
20
  };
21
- this.cache = new Cache({ ttl: 2 * 60 * 1000 });
21
+ this.cache = new Cache({ ttl: CACHE_TTL });
22
22
  const rateLimitEnabled = options.enableRateLimiting !== false && this.config.rateLimiting?.enabled !== false;
23
23
  if (rateLimitEnabled) {
24
24
  const rateLimitConfig = options.rateLimitConfig || this.getDefaultRateLimitConfig();
@@ -42,26 +42,26 @@ export class ApiClient {
42
42
  getDefaultRateLimitConfig() {
43
43
  return {
44
44
  default: {
45
- maxRequests: 100,
46
- windowMs: 60 * 1000,
47
- environmentMultiplier: 0.5,
45
+ maxRequests: RATE_LIMIT_DEFAULT_MAX,
46
+ windowMs: RATE_LIMIT_WINDOW_MS,
47
+ environmentMultiplier: RATE_LIMIT_ENV_MULTIPLIER,
48
48
  },
49
49
  endpoints: {
50
50
  '/webhooks': {
51
- maxRequests: 30,
52
- windowMs: 60 * 1000,
51
+ maxRequests: RATE_LIMIT_WEBHOOKS_MAX,
52
+ windowMs: RATE_LIMIT_WINDOW_MS,
53
53
  },
54
54
  '/payments': {
55
- maxRequests: 60,
56
- windowMs: 60 * 1000,
55
+ maxRequests: RATE_LIMIT_PAYMENTS_MAX,
56
+ windowMs: RATE_LIMIT_WINDOW_MS,
57
57
  },
58
58
  '/payment_intents': {
59
- maxRequests: 60,
60
- windowMs: 60 * 1000,
59
+ maxRequests: RATE_LIMIT_PAYMENTS_MAX,
60
+ windowMs: RATE_LIMIT_WINDOW_MS,
61
61
  },
62
62
  '/refunds': {
63
- maxRequests: 20,
64
- windowMs: 60 * 1000,
63
+ maxRequests: RATE_LIMIT_REFUNDS_MAX,
64
+ windowMs: RATE_LIMIT_WINDOW_MS,
65
65
  },
66
66
  },
67
67
  environments: {
@@ -93,7 +93,7 @@ export class ApiClient {
93
93
  const env = this.config.environment;
94
94
  const secretKey = this.config.apiKeys[env]?.secret;
95
95
  if (!secretKey) {
96
- throw new Error('Secret API key not found');
96
+ throw new ApiKeyError('Secret API key not found', 'secret');
97
97
  }
98
98
  const headers = {
99
99
  ...this.defaultHeaders,
@@ -30,6 +30,12 @@ export class ConfigManager {
30
30
  return null;
31
31
  }
32
32
  this.validateConfig(config);
33
+ if (!config.apiKeys) {
34
+ config.apiKeys = {};
35
+ }
36
+ if (!config.webhookSecrets) {
37
+ config.webhookSecrets = {};
38
+ }
33
39
  try {
34
40
  const stats = fs.statSync(this.configPath);
35
41
  this.configCache.set(this.configPath, {
@@ -119,14 +125,6 @@ export class ConfigManager {
119
125
  const field = firstError.includes(':') ? firstError.split(':')[0] : undefined;
120
126
  throw new ValidationError(firstError, field);
121
127
  }
122
- const cfg = config;
123
- const env = cfg.environment;
124
- if (!cfg.apiKeys[env]) {
125
- throw new ValidationError(`Config file is missing API keys for environment "${env}"`, `apiKeys.${env}`);
126
- }
127
- if (!cfg.apiKeys[env]?.secret) {
128
- throw new ValidationError(`Config file is missing secret API key for environment "${env}"`, `apiKeys.${env}.secret`);
129
- }
130
128
  }
131
129
  mergeConfig(base, updates) {
132
130
  return {
@@ -1,4 +1,4 @@
1
- import * as fs from 'fs';
1
+ import fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import * as os from 'os';
4
4
  import { execSync } from 'child_process';
@@ -6,31 +6,30 @@ const STATE_DIR = path.join(os.homedir(), '.paymongo-cli');
6
6
  const STATE_FILE = path.join(STATE_DIR, 'dev-server.json');
7
7
  const LOG_FILE = path.join(STATE_DIR, 'dev-server.log');
8
8
  export class DevProcessManager {
9
- static saveState(state) {
10
- if (!fs.existsSync(STATE_DIR)) {
11
- fs.mkdirSync(STATE_DIR, { recursive: true });
12
- }
13
- fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
9
+ static async saveState(state) {
10
+ await fs.mkdir(STATE_DIR, { recursive: true });
11
+ await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2));
14
12
  }
15
- static loadState() {
13
+ static async loadState() {
16
14
  try {
17
- if (!fs.existsSync(STATE_FILE)) {
18
- return null;
19
- }
20
- const content = fs.readFileSync(STATE_FILE, 'utf-8');
15
+ const content = await fs.readFile(STATE_FILE, 'utf-8');
21
16
  return JSON.parse(content);
22
17
  }
23
- catch {
18
+ catch (error) {
19
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
20
+ return null;
21
+ }
24
22
  return null;
25
23
  }
26
24
  }
27
- static clearState() {
25
+ static async clearState() {
28
26
  try {
29
- if (fs.existsSync(STATE_FILE)) {
30
- fs.unlinkSync(STATE_FILE);
31
- }
27
+ await fs.unlink(STATE_FILE);
32
28
  }
33
- catch {
29
+ catch (error) {
30
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
31
+ return;
32
+ }
34
33
  }
35
34
  }
36
35
  static isProcessRunning(pid) {
@@ -56,32 +55,31 @@ export class DevProcessManager {
56
55
  return false;
57
56
  }
58
57
  }
59
- static getLogFile() {
60
- if (!fs.existsSync(STATE_DIR)) {
61
- fs.mkdirSync(STATE_DIR, { recursive: true });
62
- }
58
+ static async getLogFile() {
59
+ await fs.mkdir(STATE_DIR, { recursive: true });
63
60
  return LOG_FILE;
64
61
  }
65
- static readLogs(lines = 50) {
62
+ static async readLogs(lines = 50) {
66
63
  try {
67
- if (!fs.existsSync(LOG_FILE)) {
68
- return [];
69
- }
70
- const content = fs.readFileSync(LOG_FILE, 'utf-8');
64
+ const content = await fs.readFile(LOG_FILE, 'utf-8');
71
65
  const allLines = content.split('\n').filter(line => line.trim());
72
66
  return allLines.slice(-lines);
73
67
  }
74
- catch {
68
+ catch (error) {
69
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
70
+ return [];
71
+ }
75
72
  return [];
76
73
  }
77
74
  }
78
- static clearLogs() {
75
+ static async clearLogs() {
79
76
  try {
80
- if (fs.existsSync(LOG_FILE)) {
81
- fs.writeFileSync(LOG_FILE, '');
82
- }
77
+ await fs.writeFile(LOG_FILE, '');
83
78
  }
84
- catch {
79
+ catch (error) {
80
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
81
+ return;
82
+ }
85
83
  }
86
84
  }
87
85
  static formatUptime(startedAt) {