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.
- package/.github/copilot-instructions.md +95 -95
- package/CHANGELOG.md +85 -1
- package/LICENSE +20 -20
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/config.js +30 -15
- package/dist/commands/dev/logs.js +3 -3
- package/dist/commands/dev/status.js +2 -2
- package/dist/commands/dev/stop.js +3 -3
- package/dist/commands/dev.js +9 -8
- package/dist/commands/env.js +6 -6
- package/dist/commands/generate/templates/checkout-page/index.js +520 -520
- package/dist/commands/generate/templates/payment-intent/javascript.js +68 -68
- package/dist/commands/generate/templates/payment-intent/typescript.js +92 -92
- package/dist/commands/generate/templates/webhook-handler/javascript.js +192 -147
- package/dist/commands/generate/templates/webhook-handler/typescript.js +147 -117
- package/dist/commands/generate.js +43 -37
- package/dist/commands/init.js +25 -8
- package/dist/commands/login.js +56 -19
- package/dist/commands/payments.js +9 -8
- package/dist/commands/team/index.js +11 -9
- package/dist/commands/trigger.js +58 -18
- package/dist/commands/webhooks.js +8 -7
- package/dist/index.js +9 -2
- package/dist/services/analytics/service.js +24 -19
- package/dist/services/api/client.js +16 -16
- package/dist/services/config/manager.js +6 -8
- package/dist/services/dev/process-manager.js +30 -32
- package/dist/services/dev/server.js +45 -39
- package/dist/services/team/service.js +4 -1
- package/dist/types/schemas.js +38 -9
- package/dist/utils/bulk.js +36 -4
- package/dist/utils/constants.js +11 -1
- package/dist/utils/errors.js +6 -0
- package/dist/utils/validator.js +10 -9
- package/dist/utils/webhook-store.js +18 -15
- package/eslint.config.ts +70 -70
- package/package.json +2 -2
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -281
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -281
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -5053
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/dist/commands/config.d.ts +0 -21
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -16
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/env.d.ts +0 -4
- package/dist/commands/env.d.ts.map +0 -1
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/init.d.ts +0 -15
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.d.ts +0 -20
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/payments.d.ts +0 -41
- package/dist/commands/payments.d.ts.map +0 -1
- package/dist/commands/payments.js.map +0 -1
- package/dist/commands/team/index.d.ts +0 -4
- package/dist/commands/team/index.d.ts.map +0 -1
- package/dist/commands/team/index.js.map +0 -1
- package/dist/commands/trigger.d.ts +0 -4
- package/dist/commands/trigger.d.ts.map +0 -1
- package/dist/commands/trigger.js.map +0 -1
- package/dist/commands/webhooks.d.ts +0 -23
- package/dist/commands/webhooks.d.ts.map +0 -1
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/analytics/service.d.ts +0 -35
- package/dist/services/analytics/service.d.ts.map +0 -1
- package/dist/services/analytics/service.js.map +0 -1
- package/dist/services/api/client.d.ts +0 -26
- package/dist/services/api/client.d.ts.map +0 -1
- package/dist/services/api/client.js.map +0 -1
- package/dist/services/api/rate-limiter.d.ts +0 -64
- package/dist/services/api/rate-limiter.d.ts.map +0 -1
- package/dist/services/api/rate-limiter.js.map +0 -1
- package/dist/services/api/undici-client.d.ts +0 -39
- package/dist/services/api/undici-client.d.ts.map +0 -1
- package/dist/services/api/undici-client.js +0 -288
- package/dist/services/api/undici-client.js.map +0 -1
- package/dist/services/config/manager.d.ts +0 -16
- package/dist/services/config/manager.d.ts.map +0 -1
- package/dist/services/config/manager.js.map +0 -1
- package/dist/services/dev/process-manager.d.ts +0 -50
- package/dist/services/dev/process-manager.d.ts.map +0 -1
- package/dist/services/dev/process-manager.js.map +0 -1
- package/dist/services/github/auth.d.ts +0 -15
- package/dist/services/github/auth.d.ts.map +0 -1
- package/dist/services/github/auth.js +0 -79
- package/dist/services/github/auth.js.map +0 -1
- package/dist/services/github/client.d.ts +0 -95
- package/dist/services/github/client.d.ts.map +0 -1
- package/dist/services/github/client.js +0 -130
- package/dist/services/github/client.js.map +0 -1
- package/dist/services/github/sync.d.ts +0 -26
- package/dist/services/github/sync.d.ts.map +0 -1
- package/dist/services/github/sync.js +0 -203
- package/dist/services/github/sync.js.map +0 -1
- package/dist/services/payments/simulator.d.ts +0 -28
- package/dist/services/payments/simulator.d.ts.map +0 -1
- package/dist/services/payments/simulator.js.map +0 -1
- package/dist/services/team/service.d.ts +0 -44
- package/dist/services/team/service.d.ts.map +0 -1
- package/dist/services/team/service.js.map +0 -1
- package/dist/services/web/server.d.ts +0 -31
- package/dist/services/web/server.d.ts.map +0 -1
- package/dist/services/web/server.js +0 -206
- package/dist/services/web/server.js.map +0 -1
- package/dist/types/paymongo.d.ts +0 -204
- package/dist/types/paymongo.d.ts.map +0 -1
- package/dist/types/paymongo.js.map +0 -1
- package/dist/types/schemas.d.ts +0 -80
- package/dist/types/schemas.d.ts.map +0 -1
- package/dist/types/schemas.js.map +0 -1
- package/dist/utils/bulk.d.ts +0 -62
- package/dist/utils/bulk.d.ts.map +0 -1
- package/dist/utils/bulk.js.map +0 -1
- package/dist/utils/cache.d.ts +0 -22
- package/dist/utils/cache.d.ts.map +0 -1
- package/dist/utils/cache.js.map +0 -1
- package/dist/utils/constants.d.ts +0 -32
- package/dist/utils/constants.d.ts.map +0 -1
- package/dist/utils/constants.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -34
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -20
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/spinner.d.ts +0 -17
- package/dist/utils/spinner.d.ts.map +0 -1
- package/dist/utils/spinner.js.map +0 -1
- package/dist/utils/validator.d.ts +0 -10
- package/dist/utils/validator.d.ts.map +0 -1
- package/dist/utils/validator.js.map +0 -1
- package/dist/utils/webhook-store.d.ts +0 -22
- package/dist/utils/webhook-store.d.ts.map +0 -1
- package/dist/utils/webhook-store.js.map +0 -1
package/dist/commands/trigger.js
CHANGED
|
@@ -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
|
-
|
|
124
|
+
throw new CommandError();
|
|
93
125
|
}
|
|
94
126
|
if (!selectedEvent) {
|
|
95
127
|
console.error(chalk.red('❌ No event selected'));
|
|
96
|
-
|
|
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':
|
|
163
|
+
'User-Agent': `PayMongo-CLI/${CLI_VERSION}`,
|
|
164
|
+
...(signatureHeader ? { 'paymongo-signature': signatureHeader } : {}),
|
|
130
165
|
},
|
|
131
|
-
body
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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':
|
|
521
|
+
'User-Agent': `PayMongo-CLI/${CLI_VERSION}`,
|
|
522
|
+
...(signatureHeader ? { 'paymongo-signature': signatureHeader } : {}),
|
|
483
523
|
},
|
|
484
|
-
body
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
75
|
-
|
|
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
|
-
|
|
13
|
-
this.
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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 =
|
|
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':
|
|
19
|
+
'User-Agent': `paymongo-cli/${CLI_VERSION}`,
|
|
20
20
|
};
|
|
21
|
-
this.cache = new Cache({ ttl:
|
|
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:
|
|
46
|
-
windowMs:
|
|
47
|
-
environmentMultiplier:
|
|
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:
|
|
52
|
-
windowMs:
|
|
51
|
+
maxRequests: RATE_LIMIT_WEBHOOKS_MAX,
|
|
52
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
53
53
|
},
|
|
54
54
|
'/payments': {
|
|
55
|
-
maxRequests:
|
|
56
|
-
windowMs:
|
|
55
|
+
maxRequests: RATE_LIMIT_PAYMENTS_MAX,
|
|
56
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
57
57
|
},
|
|
58
58
|
'/payment_intents': {
|
|
59
|
-
maxRequests:
|
|
60
|
-
windowMs:
|
|
59
|
+
maxRequests: RATE_LIMIT_PAYMENTS_MAX,
|
|
60
|
+
windowMs: RATE_LIMIT_WINDOW_MS,
|
|
61
61
|
},
|
|
62
62
|
'/refunds': {
|
|
63
|
-
maxRequests:
|
|
64
|
-
windowMs:
|
|
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
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|