paymongo-cli 1.4.12 → 1.4.14

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 (51) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/copilot-instructions.md +8 -6
  3. package/AGENTS.md +8 -9
  4. package/CHANGELOG.md +21 -0
  5. package/README.md +3 -3
  6. package/biome.json +72 -0
  7. package/dist/.tsbuildinfo +1 -1
  8. package/dist/commands/config/actions.js +13 -5
  9. package/dist/commands/config/analytics.js +75 -0
  10. package/dist/commands/config/helpers.js +14 -25
  11. package/dist/commands/config/rate-limit.js +3 -3
  12. package/dist/commands/config.js +7 -1
  13. package/dist/commands/dev/logs.js +13 -4
  14. package/dist/commands/dev/status.js +1 -1
  15. package/dist/commands/dev/stop.js +2 -2
  16. package/dist/commands/dev.js +10 -258
  17. package/dist/commands/doctor.js +7 -8
  18. package/dist/commands/env.js +10 -19
  19. package/dist/commands/generate/templates/index.js +3 -3
  20. package/dist/commands/generate.js +7 -7
  21. package/dist/commands/init.js +22 -36
  22. package/dist/commands/login.js +18 -29
  23. package/dist/commands/payments/actions.js +15 -15
  24. package/dist/commands/payments/helpers.js +6 -24
  25. package/dist/commands/payments.js +1 -1
  26. package/dist/commands/shared/auth.js +23 -0
  27. package/dist/commands/shared/runtime.js +35 -0
  28. package/dist/commands/team/index.js +4 -4
  29. package/dist/commands/trigger/actions.js +2 -2
  30. package/dist/commands/trigger/helpers.js +13 -9
  31. package/dist/commands/trigger.js +2 -2
  32. package/dist/commands/webhooks/actions.js +11 -11
  33. package/dist/commands/webhooks/helpers.js +5 -23
  34. package/dist/commands/webhooks.js +1 -1
  35. package/dist/index.js +37 -19
  36. package/dist/services/analytics/service.js +3 -3
  37. package/dist/services/api/client.js +8 -4
  38. package/dist/services/api/rate-limiter.js +3 -2
  39. package/dist/services/config/manager.js +13 -3
  40. package/dist/services/dev/process-manager.js +4 -4
  41. package/dist/services/dev/server.js +7 -8
  42. package/dist/services/dev/session.js +354 -0
  43. package/dist/services/team/service.js +1 -1
  44. package/dist/types/schemas.js +3 -2
  45. package/dist/utils/bulk.js +11 -11
  46. package/dist/utils/cache.js +5 -5
  47. package/dist/utils/constants.js +1 -1
  48. package/dist/utils/webhook-store.js +3 -3
  49. package/package.json +11 -25
  50. package/vitest.config.ts +18 -0
  51. package/eslint.config.ts +0 -70
@@ -1,6 +1,6 @@
1
+ import chalk from 'chalk';
1
2
  import { Command } from 'commander';
2
3
  import WebhookEventStore from '../utils/webhook-store.js';
3
- import chalk from 'chalk';
4
4
  import { replayWebhookEvent, sendWebhookEvent } from './trigger/actions.js';
5
5
  const command = new Command('trigger');
6
6
  command
@@ -40,5 +40,5 @@ command
40
40
  command.help();
41
41
  }
42
42
  });
43
- export { sendWebhookEvent, replayWebhookEvent };
43
+ export { replayWebhookEvent, sendWebhookEvent };
44
44
  export default command;
@@ -1,5 +1,5 @@
1
- import Table from 'cli-table3';
2
1
  import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
3
  import { BulkOperations } from '../../utils/bulk.js';
4
4
  import { CommandError } from '../../utils/errors.js';
5
5
  import { validateEventTypes, validateWebhookUrl } from '../../utils/validator.js';
@@ -24,7 +24,7 @@ export async function exportAction(options) {
24
24
  spinner.start(`Exporting to ${filename}...`);
25
25
  await BulkOperations.exportWebhooks(webhooks, filename, config.environment);
26
26
  spinner.succeed('Export completed');
27
- console.log('\n' + chalk.green('✅ Webhooks exported successfully!'));
27
+ console.log(`\n${chalk.green('✅ Webhooks exported successfully!')}`);
28
28
  console.log('');
29
29
  console.log(`${chalk.bold('File:')} ${filename}`);
30
30
  console.log(`${chalk.bold('Webhooks:')} ${webhooks.length}`);
@@ -51,7 +51,7 @@ export async function importAction(filename, options) {
51
51
  console.log(JSON.stringify({ webhooks, metadata }, null, 2));
52
52
  return;
53
53
  }
54
- console.log('\n' + chalk.green('✅ Webhooks loaded successfully!'));
54
+ console.log(`\n${chalk.green('✅ Webhooks loaded successfully!')}`);
55
55
  console.log('');
56
56
  console.log(`${chalk.bold('Source:')} ${filename}`);
57
57
  console.log(`${chalk.bold('Webhooks:')} ${webhooks.length}`);
@@ -61,7 +61,7 @@ export async function importAction(filename, options) {
61
61
  console.log(chalk.yellow('No webhooks found in the export file.'));
62
62
  return;
63
63
  }
64
- console.log('\n' + chalk.bold('Webhooks to import:'));
64
+ console.log(`\n${chalk.bold('Webhooks to import:')}`);
65
65
  console.log(chalk.gray('─'.repeat(80)));
66
66
  webhooks.forEach((webhook, index) => {
67
67
  const status = options.dryRun ? chalk.gray('pending') : chalk.yellow('will create');
@@ -105,14 +105,14 @@ export async function importAction(filename, options) {
105
105
  }
106
106
  const successful = results.filter((result) => result.success).length;
107
107
  const failed = results.filter((result) => !result.success).length;
108
- console.log('\n' + chalk.bold('Import Results:'));
108
+ console.log(`\n${chalk.bold('Import Results:')}`);
109
109
  console.log(chalk.gray('─'.repeat(50)));
110
110
  console.log(`${chalk.green('Successful:')} ${successful}`);
111
111
  if (failed > 0) {
112
112
  console.log(`${chalk.red('Failed:')} ${failed}`);
113
113
  }
114
114
  if (successful > 0) {
115
- console.log('\n' + chalk.green('✅ Successfully created webhooks:'));
115
+ console.log(`\n${chalk.green('✅ Successfully created webhooks:')}`);
116
116
  results
117
117
  .filter((result) => result.success)
118
118
  .forEach((result, index) => {
@@ -120,7 +120,7 @@ export async function importAction(filename, options) {
120
120
  });
121
121
  }
122
122
  if (failed > 0) {
123
- console.log('\n' + chalk.red('❌ Failed webhooks:'));
123
+ console.log(`\n${chalk.red('❌ Failed webhooks:')}`);
124
124
  results
125
125
  .filter((result) => !result.success)
126
126
  .forEach((result, index) => {
@@ -206,7 +206,7 @@ export async function createAction(options) {
206
206
  config.webhookSecrets[webhook.id] = webhook.attributes.secret;
207
207
  await configManager.save(config);
208
208
  }
209
- console.log('\n' + chalk.green('✓ Webhook created successfully!'));
209
+ console.log(`\n${chalk.green('✓ Webhook created successfully!')}`);
210
210
  console.log('');
211
211
  console.log(chalk.bold('ID:'), webhook.id);
212
212
  console.log(chalk.bold('URL:'), webhook.attributes.url);
@@ -276,7 +276,7 @@ export async function listAction(options) {
276
276
  console.log(JSON.stringify(filteredWebhooks, null, 2));
277
277
  return;
278
278
  }
279
- console.log('\n' + chalk.bold('Webhooks'));
279
+ console.log(`\n${chalk.bold('Webhooks')}`);
280
280
  console.log(chalk.gray('─'.repeat(95)));
281
281
  const table = new Table({
282
282
  head: [chalk.bold('ID'), chalk.bold('URL'), chalk.bold('Status'), chalk.bold('Events')],
@@ -289,7 +289,7 @@ export async function listAction(options) {
289
289
  filteredWebhooks.forEach((webhook) => {
290
290
  const id = webhook.id.substring(0, 12) + (webhook.id.length > 12 ? '...' : '');
291
291
  const url = webhook.attributes.url.length > 30
292
- ? webhook.attributes.url.substring(0, 27) + '...'
292
+ ? `${webhook.attributes.url.substring(0, 27)}...`
293
293
  : webhook.attributes.url;
294
294
  const events = webhook.attributes.events.length > 1
295
295
  ? `${webhook.attributes.events[0]} +${webhook.attributes.events.length - 1} more`
@@ -415,7 +415,7 @@ export async function showAction(id) {
415
415
  spinner.start('Fetching webhook details...');
416
416
  const webhook = await createApiClient(config).getWebhook(id);
417
417
  spinner.succeed('Webhook details loaded');
418
- console.log('\n' + chalk.bold('Webhook Details'));
418
+ console.log(`\n${chalk.bold('Webhook Details')}`);
419
419
  console.log('═'.repeat(50));
420
420
  console.log(chalk.bold('ID:'), webhook.id);
421
421
  console.log(chalk.bold('URL:'), webhook.attributes.url);
@@ -1,28 +1,13 @@
1
1
  import chalk from 'chalk';
2
- import ApiClient from '../../services/api/client.js';
3
- import ConfigManager from '../../services/config/manager.js';
4
- import Spinner from '../../utils/spinner.js';
5
- import { CommandError } from '../../utils/errors.js';
2
+ import { createCommandContext, createApiClient as createSharedApiClient, failCommand, loadCommandConfig, } from '../shared/runtime.js';
6
3
  export function createWebhooksContext() {
7
- return {
8
- spinner: new Spinner(),
9
- configManager: new ConfigManager(),
10
- };
4
+ return createCommandContext();
11
5
  }
12
6
  export async function loadWebhooksConfig(spinner, configManager) {
13
- spinner.start('Loading configuration...');
14
- const config = await configManager.load();
15
- if (!config) {
16
- spinner.fail('No configuration found');
17
- console.log(chalk.yellow('No PayMongo configuration found.'));
18
- console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
19
- return null;
20
- }
21
- spinner.succeed('Configuration loaded');
22
- return config;
7
+ return loadCommandConfig(spinner, configManager);
23
8
  }
24
9
  export function createApiClient(config) {
25
- return new ApiClient({ config });
10
+ return createSharedApiClient(config);
26
11
  }
27
12
  export function getWebhookStatusColor(status) {
28
13
  switch (status) {
@@ -35,8 +20,5 @@ export function getWebhookStatusColor(status) {
35
20
  }
36
21
  }
37
22
  export function handleWebhooksError(prefix, spinner, error) {
38
- spinner.stop();
39
- const err = error;
40
- console.error(chalk.red(prefix), err.message);
41
- throw new CommandError();
23
+ return failCommand(prefix, error, spinner);
42
24
  }
@@ -37,5 +37,5 @@ const command = new Command('webhooks')
37
37
  .description('Show webhook details')
38
38
  .argument('<id>', 'Webhook ID to show')
39
39
  .action(async (id) => showAction(id)));
40
- export { exportAction, importAction, createAction, listAction, disableAction, enableAction, deleteAction, showAction, };
40
+ export { createAction, deleteAction, disableAction, enableAction, exportAction, importAction, listAction, showAction, };
41
41
  export default command;
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
2
+ import { createRequire } from 'node:module';
3
3
  import chalk from 'chalk';
4
- import { createRequire } from 'module';
4
+ import { Command } from 'commander';
5
5
  import { CommandError } from './utils/errors.js';
6
6
  const require = createRequire(import.meta.url);
7
7
  const { version } = require('../package.json');
8
+ const uncaughtExceptionHandlerKey = Symbol.for('paymongo.cli.uncaughtExceptionHandler');
9
+ const unhandledRejectionHandlerKey = Symbol.for('paymongo.cli.unhandledRejectionHandler');
10
+ const globalHandlers = globalThis;
8
11
  const program = new Command();
9
12
  program
10
13
  .name('paymongo')
@@ -12,6 +15,15 @@ program
12
15
  .version(version)
13
16
  .option('--no-rate-limit', 'Disable rate limiting for this command')
14
17
  .showHelpAfterError('(add --help for additional information)');
18
+ program.hook('preAction', (actionCommand) => {
19
+ const options = actionCommand.optsWithGlobals();
20
+ if (options.rateLimit === false) {
21
+ process.env.PAYMONGO_DISABLE_RATE_LIMIT = '1';
22
+ }
23
+ else {
24
+ delete process.env.PAYMONGO_DISABLE_RATE_LIMIT;
25
+ }
26
+ });
15
27
  program.addCommand(await import('./commands/init.js').then((m) => m.default));
16
28
  program.addCommand((await import('./commands/dev.js')).command);
17
29
  program.addCommand((await import('./commands/login.js')).command);
@@ -54,17 +66,17 @@ EXAMPLES
54
66
 
55
67
  Code Generation:
56
68
  $ paymongo generate webhook-handler # Generate webhook handler boilerplate
57
- $ paymongo generate payment-intent --language ts # Generate TypeScript payment intent code
58
- $ paymongo generate checkout-page --framework react # Generate React checkout component
69
+ $ paymongo generate payment-intent --language typescript # Generate TypeScript payment intent code
70
+ $ paymongo generate checkout-page --language react # Generate React checkout component
59
71
 
60
72
  Diagnostics:
61
73
  $ paymongo doctor # Check config, keys, ngrok, and webhook setup
62
74
  $ paymongo doctor --no-network # Run offline diagnostics only
63
75
 
64
76
  Team Collaboration:
65
- $ paymongo team create "My Team" # Create a new team
66
- $ paymongo team share live # Share live API keys with team
67
- $ paymongo team sync # Sync team updates
77
+ $ paymongo team rename "My Team" # Set or update the team name
78
+ $ paymongo team share-keys --env live # Share live API keys with the team
79
+ $ paymongo team list-members # View team members and shared keys
68
80
 
69
81
  Configuration:
70
82
  $ paymongo config # View current configuration
@@ -73,18 +85,24 @@ EXAMPLES
73
85
 
74
86
  For more information, visit: https://github.com/leodyversemilla07/paymongo-cli
75
87
  `);
76
- process.on('uncaughtException', (error) => {
77
- if (error instanceof CommandError) {
88
+ if (!globalHandlers[uncaughtExceptionHandlerKey]) {
89
+ globalHandlers[uncaughtExceptionHandlerKey] = (error) => {
90
+ if (error instanceof CommandError) {
91
+ process.exit(1);
92
+ }
93
+ console.error(chalk.red('An unexpected error occurred:'), error.message);
78
94
  process.exit(1);
79
- }
80
- console.error(chalk.red('An unexpected error occurred:'), error.message);
81
- process.exit(1);
82
- });
83
- process.on('unhandledRejection', (reason) => {
84
- if (reason instanceof CommandError) {
95
+ };
96
+ process.on('uncaughtException', globalHandlers[uncaughtExceptionHandlerKey]);
97
+ }
98
+ if (!globalHandlers[unhandledRejectionHandlerKey]) {
99
+ globalHandlers[unhandledRejectionHandlerKey] = (reason) => {
100
+ if (reason instanceof CommandError) {
101
+ process.exit(1);
102
+ }
103
+ console.error(chalk.red('An unexpected error occurred:'), reason instanceof Error ? reason.message : String(reason));
85
104
  process.exit(1);
86
- }
87
- console.error(chalk.red('An unexpected error occurred:'), reason instanceof Error ? reason.message : String(reason));
88
- process.exit(1);
89
- });
105
+ };
106
+ process.on('unhandledRejection', globalHandlers[unhandledRejectionHandlerKey]);
107
+ }
90
108
  program.parse();
@@ -1,6 +1,6 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import os from 'os';
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
4
  import Logger from '../../utils/logger.js';
5
5
  export class AnalyticsService {
6
6
  events = [];
@@ -1,8 +1,8 @@
1
1
  import { request } from 'undici';
2
- import { NetworkError, ApiKeyError, PayMongoError, withRetry } from '../../utils/errors.js';
3
2
  import Cache from '../../utils/cache.js';
3
+ import { CACHE_TTL, CLI_VERSION, PAYMONGO_API_BASE, RATE_LIMIT_DEFAULT_MAX, RATE_LIMIT_ENV_MULTIPLIER, RATE_LIMIT_PAYMENTS_MAX, RATE_LIMIT_REFUNDS_MAX, RATE_LIMIT_WEBHOOKS_MAX, RATE_LIMIT_WINDOW_MS, REQUEST_TIMEOUT, } from '../../utils/constants.js';
4
+ import { ApiKeyError, NetworkError, PayMongoError, withRetry } from '../../utils/errors.js';
4
5
  import RateLimiter from './rate-limiter.js';
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;
@@ -19,7 +19,10 @@ export class ApiClient {
19
19
  'User-Agent': `paymongo-cli/${CLI_VERSION}`,
20
20
  };
21
21
  this.cache = new Cache({ ttl: CACHE_TTL });
22
- const rateLimitEnabled = options.enableRateLimiting !== false && this.config.rateLimiting?.enabled !== false;
22
+ const globalRateLimitDisabled = process.env.PAYMONGO_DISABLE_RATE_LIMIT === '1';
23
+ const rateLimitEnabled = options.enableRateLimiting !== false &&
24
+ !globalRateLimitDisabled &&
25
+ this.config.rateLimiting?.enabled !== false;
23
26
  if (rateLimitEnabled) {
24
27
  const rateLimitConfig = options.rateLimitConfig || this.getDefaultRateLimitConfig();
25
28
  if (this.config.rateLimiting) {
@@ -106,6 +109,7 @@ export class ApiClient {
106
109
  }
107
110
  const controller = new AbortController();
108
111
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
112
+ timeoutId.unref?.();
109
113
  try {
110
114
  const response = await request(url.toString(), {
111
115
  method,
@@ -120,7 +124,7 @@ export class ApiClient {
120
124
  }
121
125
  let data;
122
126
  const contentType = response.headers['content-type'];
123
- if (contentType && contentType.includes('application/json')) {
127
+ if (contentType?.includes('application/json')) {
124
128
  data = await response.body.json();
125
129
  }
126
130
  else {
@@ -47,8 +47,9 @@ export class RateLimiter {
47
47
  if (envOverrides) {
48
48
  policy = { ...policy, ...envOverrides };
49
49
  }
50
- if (policy.environmentMultiplier !== undefined) {
51
- policy.maxRequests = Math.floor(policy.maxRequests * policy.environmentMultiplier);
50
+ if (env === 'live' && envOverrides?.maxRequests === undefined) {
51
+ const multiplier = policy.environmentMultiplier ?? 1;
52
+ policy.maxRequests = Math.max(1, Math.floor(policy.maxRequests * multiplier));
52
53
  }
53
54
  return policy;
54
55
  }
@@ -1,8 +1,8 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
3
  import { cosmiconfig } from 'cosmiconfig';
4
- import { ConfigError, ValidationError } from '../../utils/errors.js';
5
4
  import { validateConfig as zodValidateConfig } from '../../types/schemas.js';
5
+ import { ConfigError, ValidationError } from '../../utils/errors.js';
6
6
  const CONFIG_FILE_NAME = '.paymongo';
7
7
  export class ConfigManager {
8
8
  explorer = cosmiconfig('paymongo');
@@ -60,6 +60,13 @@ export class ConfigManager {
60
60
  }
61
61
  async save(config) {
62
62
  try {
63
+ if (!config.apiKeys) {
64
+ config.apiKeys = {};
65
+ }
66
+ if (!config.webhookSecrets) {
67
+ config.webhookSecrets = {};
68
+ }
69
+ this.validateConfig(config);
63
70
  const dir = path.dirname(this.configPath);
64
71
  if (!fs.existsSync(dir)) {
65
72
  fs.mkdirSync(dir, { recursive: true });
@@ -78,6 +85,9 @@ export class ConfigManager {
78
85
  }
79
86
  }
80
87
  catch (error) {
88
+ if (error instanceof ValidationError) {
89
+ throw error;
90
+ }
81
91
  throw new ConfigError(`Failed to save config: ${error.message}`, this.configPath);
82
92
  }
83
93
  }
@@ -1,7 +1,7 @@
1
- import fs from 'fs/promises';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
- import { execSync } from 'child_process';
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
5
  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');
@@ -1,8 +1,8 @@
1
- import * as http from 'http';
2
- import * as crypto from 'crypto';
1
+ import * as crypto from 'node:crypto';
2
+ import * as http from 'node:http';
3
3
  import chalk from 'chalk';
4
- import { AnalyticsService } from '../analytics/service.js';
5
4
  import Logger from '../../utils/logger.js';
5
+ import { AnalyticsService } from '../analytics/service.js';
6
6
  export class DevServer {
7
7
  server;
8
8
  port;
@@ -87,6 +87,7 @@ export class DevServer {
87
87
  const timestamp = new Date().toLocaleTimeString();
88
88
  const eventType = event.data?.type || 'unknown';
89
89
  const eventId = event.data?.id || 'unknown';
90
+ const isPaymentEvent = eventType.startsWith('payment');
90
91
  await this.analytics.recordEvent({
91
92
  type: eventType,
92
93
  success: true,
@@ -95,7 +96,7 @@ export class DevServer {
95
96
  console.log('');
96
97
  console.log(chalk.gray('────────────────────────────────────────────────────────────'));
97
98
  console.log(chalk.blue(`[${timestamp}]`), chalk.bold(eventType.toUpperCase()));
98
- if (eventType === 'payment') {
99
+ if (isPaymentEvent) {
99
100
  const attributes = event.data.attributes;
100
101
  const amount = attributes.amount ?? 0;
101
102
  const status = attributes.status ?? 'unknown';
@@ -103,7 +104,7 @@ export class DevServer {
103
104
  console.log(chalk.gray('└─'), `Status: ${status}`);
104
105
  console.log(chalk.gray('└─'), `Payment ID: ${eventId}`);
105
106
  }
106
- console.log(chalk.gray('└─'), `View: https://dashboard.paymongo.com/${eventType === 'payment' ? 'payments' : 'webhooks'}/${eventId}`);
107
+ console.log(chalk.gray('└─'), `View: https://dashboard.paymongo.com/${isPaymentEvent ? 'payments' : 'webhooks'}/${eventId}`);
107
108
  }
108
109
  verifyWebhookSignature(req, body, event) {
109
110
  if (!this.config.dev.verifyWebhookSignatures) {
@@ -147,9 +148,7 @@ export class DevServer {
147
148
  break;
148
149
  }
149
150
  }
150
- catch (_error) {
151
- continue;
152
- }
151
+ catch (_error) { }
153
152
  }
154
153
  if (isValid) {
155
154
  this.logger.success('Signature verified successfully');