paymongo-cli 1.4.11 → 1.4.13
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/CHANGELOG.md +36 -0
- package/README.md +4 -3
- package/biome.json +72 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/config/actions.js +13 -5
- package/dist/commands/config/analytics.js +75 -0
- package/dist/commands/config/helpers.js +14 -25
- package/dist/commands/config/rate-limit.js +3 -3
- package/dist/commands/config.js +7 -1
- package/dist/commands/dev/logs.js +13 -4
- package/dist/commands/dev/status.js +1 -1
- package/dist/commands/dev/stop.js +2 -2
- package/dist/commands/dev.js +10 -258
- package/dist/commands/doctor.js +241 -0
- package/dist/commands/env.js +10 -19
- package/dist/commands/generate/templates/index.js +3 -3
- package/dist/commands/generate.js +6 -6
- package/dist/commands/init.js +22 -36
- package/dist/commands/login.js +18 -29
- package/dist/commands/payments/actions.js +15 -15
- package/dist/commands/payments/helpers.js +6 -24
- package/dist/commands/payments.js +1 -1
- package/dist/commands/shared/auth.js +23 -0
- package/dist/commands/shared/runtime.js +35 -0
- package/dist/commands/team/index.js +3 -3
- package/dist/commands/trigger/actions.js +2 -2
- package/dist/commands/trigger/helpers.js +13 -9
- package/dist/commands/trigger.js +2 -2
- package/dist/commands/webhooks/actions.js +11 -11
- package/dist/commands/webhooks/helpers.js +5 -23
- package/dist/commands/webhooks.js +1 -1
- package/dist/index.js +37 -14
- package/dist/services/analytics/service.js +3 -3
- package/dist/services/api/client.js +8 -4
- package/dist/services/config/manager.js +3 -3
- package/dist/services/dev/process-manager.js +4 -4
- package/dist/services/dev/server.js +4 -6
- package/dist/services/dev/session.js +353 -0
- package/dist/services/team/service.js +1 -1
- package/dist/utils/bulk.js +11 -11
- package/dist/utils/cache.js +5 -5
- package/dist/utils/constants.js +1 -1
- package/dist/utils/webhook-store.js +3 -3
- package/package.json +11 -25
- package/vitest.config.ts +18 -0
- package/eslint.config.ts +0 -70
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { CommandError, withRetry } from '../../utils/errors.js';
|
|
5
|
+
import ApiClient from '../api/client.js';
|
|
6
|
+
import { DevProcessManager } from './process-manager.js';
|
|
7
|
+
import DevServer from './server.js';
|
|
8
|
+
export class DevSessionService {
|
|
9
|
+
spinner;
|
|
10
|
+
configManager;
|
|
11
|
+
onMissingConfig;
|
|
12
|
+
constructor({ spinner, configManager, onMissingConfig }) {
|
|
13
|
+
this.spinner = spinner;
|
|
14
|
+
this.configManager = configManager;
|
|
15
|
+
this.onMissingConfig = onMissingConfig;
|
|
16
|
+
}
|
|
17
|
+
async run(options) {
|
|
18
|
+
let tunnel;
|
|
19
|
+
if (await this.handleDetachedStart(options)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const config = await this.loadConfig();
|
|
24
|
+
if (!config) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const port = this.getPort(options);
|
|
28
|
+
tunnel = await this.createTunnelWithStatus(port, options.ngrokToken);
|
|
29
|
+
const tunnelUrl = tunnel.url() ?? '';
|
|
30
|
+
const devServer = new DevServer(port, config);
|
|
31
|
+
await devServer.start();
|
|
32
|
+
await this.cleanupStaleWebhooks(config);
|
|
33
|
+
const { webhookId, webhookUrl } = await this.registerWebhookIfNeeded(config, options, tunnelUrl);
|
|
34
|
+
const { localWebhookUrl, externalWebhookUrl } = this.printStatus(config, options, port, tunnelUrl, webhookId);
|
|
35
|
+
await this.saveState(config, options, port, tunnelUrl, webhookId, webhookUrl || externalWebhookUrl, localWebhookUrl);
|
|
36
|
+
const cleanup = this.createCleanupHandler({
|
|
37
|
+
config,
|
|
38
|
+
devServer,
|
|
39
|
+
tunnel,
|
|
40
|
+
webhookId,
|
|
41
|
+
});
|
|
42
|
+
process.once('SIGINT', () => {
|
|
43
|
+
void cleanup();
|
|
44
|
+
});
|
|
45
|
+
process.once('SIGTERM', () => {
|
|
46
|
+
void cleanup();
|
|
47
|
+
});
|
|
48
|
+
await new Promise(() => { });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.spinner.stop();
|
|
52
|
+
const err = error;
|
|
53
|
+
this.printTunnelError(err);
|
|
54
|
+
await this.cleanupAfterStartupFailure(tunnel);
|
|
55
|
+
throw new CommandError();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
getPort(options) {
|
|
59
|
+
return parseInt(options.port || '3000', 10);
|
|
60
|
+
}
|
|
61
|
+
getEvents(options) {
|
|
62
|
+
return (options.events || 'payment.paid,payment.failed').split(',');
|
|
63
|
+
}
|
|
64
|
+
getProjectSlug(projectName) {
|
|
65
|
+
return projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
66
|
+
}
|
|
67
|
+
buildWebhookUrls(projectName, port, tunnelUrl) {
|
|
68
|
+
const projectSlug = this.getProjectSlug(projectName);
|
|
69
|
+
return {
|
|
70
|
+
localWebhookUrl: `http://localhost:${port}/webhook/${projectSlug}`,
|
|
71
|
+
externalWebhookUrl: `${tunnelUrl}/webhook/${projectSlug}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async handleDetachedStart(options) {
|
|
75
|
+
if (!options.detach) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const existingState = await DevProcessManager.loadState();
|
|
79
|
+
if (existingState && DevProcessManager.isProcessRunning(existingState.pid)) {
|
|
80
|
+
console.log(chalk.yellow('⚠️ Dev server is already running in background'));
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(chalk.bold('Status:'));
|
|
83
|
+
console.log(chalk.gray(' PID:'), existingState.pid);
|
|
84
|
+
console.log(chalk.gray(' Port:'), existingState.port);
|
|
85
|
+
console.log(chalk.gray(' Tunnel:'), existingState.tunnelUrl);
|
|
86
|
+
console.log(chalk.gray(' Uptime:'), DevProcessManager.formatUptime(existingState.startedAt));
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(chalk.gray('Use "paymongo dev stop" to stop the server first.'));
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
const entryScript = process.argv[1];
|
|
92
|
+
if (!entryScript) {
|
|
93
|
+
throw new Error('Unable to determine the current CLI entrypoint for detached mode');
|
|
94
|
+
}
|
|
95
|
+
const args = [entryScript, 'dev', '--port', options.port || '3000'];
|
|
96
|
+
if (options.noRegister) {
|
|
97
|
+
args.push('--no-register');
|
|
98
|
+
}
|
|
99
|
+
if (options.events) {
|
|
100
|
+
args.push('--events', options.events);
|
|
101
|
+
}
|
|
102
|
+
if (options.ngrokToken) {
|
|
103
|
+
args.push('--ngrok-token', options.ngrokToken);
|
|
104
|
+
}
|
|
105
|
+
const logFile = await DevProcessManager.getLogFile();
|
|
106
|
+
const out = fs.openSync(logFile, 'a');
|
|
107
|
+
const err = fs.openSync(logFile, 'a');
|
|
108
|
+
const child = spawn(process.execPath, args, {
|
|
109
|
+
detached: true,
|
|
110
|
+
stdio: ['ignore', out, err],
|
|
111
|
+
cwd: process.cwd(),
|
|
112
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
113
|
+
});
|
|
114
|
+
child.unref();
|
|
115
|
+
console.log(chalk.green('✓'), 'Dev server starting in background...');
|
|
116
|
+
console.log(chalk.gray(' PID:'), child.pid);
|
|
117
|
+
console.log(chalk.gray(' Logs:'), logFile);
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(chalk.gray('Use "paymongo dev status" to check server status'));
|
|
120
|
+
console.log(chalk.gray('Use "paymongo dev stop" to stop the server'));
|
|
121
|
+
console.log(chalk.gray('Use "paymongo dev logs" to view server logs'));
|
|
122
|
+
await new Promise((resolve) => {
|
|
123
|
+
setTimeout(resolve, 500);
|
|
124
|
+
});
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
async loadConfig() {
|
|
128
|
+
this.spinner.start('Loading configuration...');
|
|
129
|
+
const config = await this.configManager.load();
|
|
130
|
+
if (!config) {
|
|
131
|
+
this.spinner.fail('No configuration found');
|
|
132
|
+
this.onMissingConfig?.();
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
this.spinner.succeed('Configuration loaded');
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
async createTunnelWithStatus(port, ngrokToken) {
|
|
139
|
+
this.spinner.start('Creating tunnel...');
|
|
140
|
+
const tunnel = await this.createTunnel(port, ngrokToken);
|
|
141
|
+
this.spinner.succeed('Tunnel created');
|
|
142
|
+
return tunnel;
|
|
143
|
+
}
|
|
144
|
+
async createTunnel(port, ngrokToken) {
|
|
145
|
+
const { default: ngrok } = await import('@ngrok/ngrok');
|
|
146
|
+
return withRetry(async () => {
|
|
147
|
+
const authtoken = ngrokToken || process.env.NGROK_AUTHTOKEN;
|
|
148
|
+
if (!authtoken) {
|
|
149
|
+
throw new Error('ngrok authtoken not found. Please either:\n' +
|
|
150
|
+
' 1. Set NGROK_AUTHTOKEN environment variable, or\n' +
|
|
151
|
+
' 2. Use --ngrok-token option: paymongo dev --ngrok-token YOUR_TOKEN\n' +
|
|
152
|
+
' Get your token from: https://dashboard.ngrok.com/get-started/your-authtoken');
|
|
153
|
+
}
|
|
154
|
+
const tunnel = await ngrok.forward({
|
|
155
|
+
addr: port,
|
|
156
|
+
authtoken,
|
|
157
|
+
});
|
|
158
|
+
return tunnel;
|
|
159
|
+
}, {
|
|
160
|
+
maxRetries: 3,
|
|
161
|
+
delayMs: 2000,
|
|
162
|
+
retryCondition: (error) => {
|
|
163
|
+
return (error.message.includes('connection') ||
|
|
164
|
+
error.message.includes('timeout') ||
|
|
165
|
+
error.message.includes('tunnel') ||
|
|
166
|
+
error.message.includes('ngrok'));
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async cleanupStaleWebhooks(config) {
|
|
171
|
+
if (!config.registeredWebhooks || config.registeredWebhooks.length === 0) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.spinner.start('Cleaning up stale webhooks...');
|
|
175
|
+
const apiClient = new ApiClient({ config });
|
|
176
|
+
let cleanedCount = 0;
|
|
177
|
+
for (const webhook of config.registeredWebhooks) {
|
|
178
|
+
try {
|
|
179
|
+
await apiClient.disableWebhook(webhook.id);
|
|
180
|
+
cleanedCount++;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
config.registeredWebhooks = [];
|
|
186
|
+
await this.configManager.save(config);
|
|
187
|
+
if (cleanedCount > 0) {
|
|
188
|
+
this.spinner.succeed(`Cleaned up ${cleanedCount} stale webhook(s)`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.spinner.succeed('No stale webhooks to clean up');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async registerWebhookIfNeeded(config, options, tunnelUrl) {
|
|
195
|
+
const shouldRegister = !options.noRegister && config.dev.autoRegisterWebhook !== false;
|
|
196
|
+
if (!shouldRegister) {
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
this.spinner.start('Registering webhook...');
|
|
200
|
+
const events = this.getEvents(options);
|
|
201
|
+
const { externalWebhookUrl } = this.buildWebhookUrls(config.projectName, this.getPort(options), tunnelUrl);
|
|
202
|
+
try {
|
|
203
|
+
const webhook = (await new ApiClient({ config }).createWebhook(externalWebhookUrl, events));
|
|
204
|
+
await this.persistRegisteredWebhook(config, webhook, externalWebhookUrl);
|
|
205
|
+
if (webhook.attributes?.secret) {
|
|
206
|
+
this.spinner.succeed(`Webhook registered: ${webhook.id} (with signature verification)`);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
this.spinner.succeed(`Webhook registered: ${webhook.id}`);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
webhookId: webhook.id,
|
|
213
|
+
webhookUrl: externalWebhookUrl,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const err = error;
|
|
218
|
+
this.spinner.warn('Webhook registration failed - server will start without webhook');
|
|
219
|
+
this.printWebhookRegistrationFailure(err, externalWebhookUrl, config);
|
|
220
|
+
return {
|
|
221
|
+
webhookUrl: externalWebhookUrl,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async persistRegisteredWebhook(config, webhook, webhookUrl) {
|
|
226
|
+
if (webhook.attributes?.secret) {
|
|
227
|
+
config.webhookSecrets = config.webhookSecrets || {};
|
|
228
|
+
config.webhookSecrets[webhook.id] = webhook.attributes.secret;
|
|
229
|
+
}
|
|
230
|
+
config.registeredWebhooks = config.registeredWebhooks || [];
|
|
231
|
+
config.registeredWebhooks.push({
|
|
232
|
+
id: webhook.id,
|
|
233
|
+
url: webhookUrl,
|
|
234
|
+
createdAt: Date.now(),
|
|
235
|
+
});
|
|
236
|
+
await this.configManager.save(config);
|
|
237
|
+
}
|
|
238
|
+
printWebhookRegistrationFailure(error, webhookUrl, config) {
|
|
239
|
+
console.log(chalk.yellow('⚠️'), 'Webhook registration failed:', error.message);
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(chalk.blue('ℹ️'), 'You can still test webhooks manually:');
|
|
242
|
+
console.log(chalk.gray(` Webhook URL: ${webhookUrl}`));
|
|
243
|
+
console.log(chalk.gray(' Copy this URL to your PayMongo dashboard'));
|
|
244
|
+
if (config.dev.verifyWebhookSignatures) {
|
|
245
|
+
console.log(chalk.gray(' Signature verification is currently enabled'));
|
|
246
|
+
console.log(chalk.gray(' For manual unsigned testing, run: paymongo config set dev.verifySignatures false'));
|
|
247
|
+
}
|
|
248
|
+
console.log('');
|
|
249
|
+
if (error.message.includes('API key') || error.message.includes('unauthorized')) {
|
|
250
|
+
console.log(chalk.yellow('💡 To fix webhook registration:'));
|
|
251
|
+
console.log(chalk.gray(' 1. Run "paymongo login" to update your API keys'));
|
|
252
|
+
console.log(chalk.gray(' 2. Restart the development server'));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
printStatus(config, options, port, tunnelUrl, webhookId) {
|
|
256
|
+
const { localWebhookUrl, externalWebhookUrl } = this.buildWebhookUrls(config.projectName, port, tunnelUrl);
|
|
257
|
+
console.log(`\n${chalk.green('🚀 PayMongo Development Server')}`);
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(chalk.bold('URLs:'));
|
|
260
|
+
console.log(chalk.gray(' ├─'), chalk.cyan('External (PayMongo sends here):'));
|
|
261
|
+
console.log(chalk.gray(' │ '), chalk.yellow(externalWebhookUrl));
|
|
262
|
+
console.log(chalk.gray(' │'));
|
|
263
|
+
console.log(chalk.gray(' └─'), chalk.cyan('Local (Your server receives here):'));
|
|
264
|
+
console.log(chalk.gray(' '), chalk.green(localWebhookUrl));
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log(chalk.bold('Forwarding:'));
|
|
267
|
+
console.log(chalk.gray(' '), `${chalk.yellow(tunnelUrl)} ${chalk.gray('→')} ${chalk.green(`http://localhost:${port}`)}`);
|
|
268
|
+
console.log('');
|
|
269
|
+
if (webhookId) {
|
|
270
|
+
console.log(chalk.bold('Webhook ID:'), chalk.gray(webhookId));
|
|
271
|
+
}
|
|
272
|
+
console.log(chalk.bold('Events:'), this.getEvents(options).join(', '));
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log(chalk.gray('💡 Tip: Use the External URL in PayMongo dashboard, requests will forward to your local server'));
|
|
275
|
+
console.log(chalk.gray('Press Ctrl+C to stop'));
|
|
276
|
+
return { localWebhookUrl, externalWebhookUrl };
|
|
277
|
+
}
|
|
278
|
+
async saveState(config, options, port, tunnelUrl, webhookId, webhookUrl, localUrl) {
|
|
279
|
+
await DevProcessManager.saveState({
|
|
280
|
+
pid: process.pid,
|
|
281
|
+
port,
|
|
282
|
+
tunnelUrl,
|
|
283
|
+
webhookId,
|
|
284
|
+
webhookUrl,
|
|
285
|
+
localUrl,
|
|
286
|
+
events: this.getEvents(options),
|
|
287
|
+
startedAt: Date.now(),
|
|
288
|
+
projectName: config.projectName,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
createCleanupHandler({ config, devServer, tunnel, webhookId, }) {
|
|
292
|
+
let cleanedUp = false;
|
|
293
|
+
return async () => {
|
|
294
|
+
if (cleanedUp) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
cleanedUp = true;
|
|
298
|
+
console.log(`\n${chalk.yellow('Shutting down...')}`);
|
|
299
|
+
await DevProcessManager.clearState();
|
|
300
|
+
try {
|
|
301
|
+
if (tunnel) {
|
|
302
|
+
await tunnel.close();
|
|
303
|
+
console.log(chalk.yellow('✓'), 'Tunnel closed');
|
|
304
|
+
}
|
|
305
|
+
await devServer.stop();
|
|
306
|
+
if (webhookId) {
|
|
307
|
+
this.spinner.start('Cleaning up webhook...');
|
|
308
|
+
await new ApiClient({ config }).disableWebhook(webhookId);
|
|
309
|
+
if (config.registeredWebhooks) {
|
|
310
|
+
config.registeredWebhooks = config.registeredWebhooks.filter((webhook) => {
|
|
311
|
+
return webhook.id !== webhookId;
|
|
312
|
+
});
|
|
313
|
+
if (config.webhookSecrets) {
|
|
314
|
+
delete config.webhookSecrets[webhookId];
|
|
315
|
+
}
|
|
316
|
+
await this.configManager.save(config);
|
|
317
|
+
}
|
|
318
|
+
this.spinner.succeed('Webhook disabled');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
console.error(chalk.red('Error during cleanup:'), error.message);
|
|
323
|
+
console.log(chalk.yellow('⚠️'), 'Some cleanup tasks may not have completed');
|
|
324
|
+
}
|
|
325
|
+
process.exit(0);
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
async cleanupAfterStartupFailure(tunnel) {
|
|
329
|
+
await DevProcessManager.clearState();
|
|
330
|
+
try {
|
|
331
|
+
if (tunnel) {
|
|
332
|
+
await tunnel.close();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
printTunnelError(error) {
|
|
339
|
+
if (!error.message.includes('ngrok') && !error.message.includes('tunnel')) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
console.error(chalk.red('❌ Failed to create tunnel:'), error.message);
|
|
343
|
+
console.log('');
|
|
344
|
+
console.log(chalk.yellow('💡 Troubleshooting suggestions:'));
|
|
345
|
+
console.log(chalk.gray('• Check your internet connection'));
|
|
346
|
+
console.log(chalk.gray('• Make sure ngrok is not blocked by firewall/antivirus'));
|
|
347
|
+
console.log(chalk.gray('• Set up ngrok authentication: export NGROK_AUTHTOKEN=your_token'));
|
|
348
|
+
console.log(chalk.gray('• Get your authtoken from: https://dashboard.ngrok.com/get-started/your-authtoken'));
|
|
349
|
+
console.log(chalk.gray('• Try a different port: paymongo dev --port 3001'));
|
|
350
|
+
console.log(chalk.gray('• Visit https://ngrok.com for status updates'));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
export default DevSessionService;
|
package/dist/utils/bulk.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { PayMongoError } from './errors.js';
|
|
4
4
|
export class BulkOperations {
|
|
5
5
|
static EXPORT_VERSION = '1.0';
|
|
@@ -8,7 +8,7 @@ export class BulkOperations {
|
|
|
8
8
|
metadata: {
|
|
9
9
|
exported_at: new Date().toISOString(),
|
|
10
10
|
exported_by: 'paymongo-cli',
|
|
11
|
-
version:
|
|
11
|
+
version: BulkOperations.EXPORT_VERSION,
|
|
12
12
|
environment,
|
|
13
13
|
},
|
|
14
14
|
data: webhooks,
|
|
@@ -22,7 +22,7 @@ export class BulkOperations {
|
|
|
22
22
|
metadata: {
|
|
23
23
|
exported_at: new Date().toISOString(),
|
|
24
24
|
exported_by: 'paymongo-cli',
|
|
25
|
-
version:
|
|
25
|
+
version: BulkOperations.EXPORT_VERSION,
|
|
26
26
|
environment,
|
|
27
27
|
},
|
|
28
28
|
data: payments,
|
|
@@ -50,7 +50,7 @@ export class BulkOperations {
|
|
|
50
50
|
catch {
|
|
51
51
|
throw new PayMongoError(`Invalid JSON in ${filename}`, 'INVALID_JSON', 400);
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
BulkOperations.validateImportData(data, 'webhooks');
|
|
54
54
|
return {
|
|
55
55
|
webhooks: data.data,
|
|
56
56
|
metadata: data.metadata,
|
|
@@ -75,7 +75,7 @@ export class BulkOperations {
|
|
|
75
75
|
catch {
|
|
76
76
|
throw new PayMongoError(`Invalid JSON in ${filename}`, 'INVALID_JSON', 400);
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
BulkOperations.validateImportData(data, 'payments');
|
|
79
79
|
return {
|
|
80
80
|
payments: data.data,
|
|
81
81
|
metadata: data.metadata,
|
|
@@ -93,17 +93,17 @@ export class BulkOperations {
|
|
|
93
93
|
if (typeof metadata.version !== 'string') {
|
|
94
94
|
throw new PayMongoError('Invalid metadata - version must be a string', 'INVALID_FILE_FORMAT', 400);
|
|
95
95
|
}
|
|
96
|
-
if (metadata.version !==
|
|
97
|
-
throw new PayMongoError(`Unsupported export version: ${metadata.version}. Current version: ${
|
|
96
|
+
if (metadata.version !== BulkOperations.EXPORT_VERSION) {
|
|
97
|
+
throw new PayMongoError(`Unsupported export version: ${metadata.version}. Current version: ${BulkOperations.EXPORT_VERSION}`, 'UNSUPPORTED_VERSION', 400);
|
|
98
98
|
}
|
|
99
99
|
if (obj.data.length === 0) {
|
|
100
100
|
throw new PayMongoError('No data found in export file', 'EMPTY_FILE', 400);
|
|
101
101
|
}
|
|
102
102
|
if (type === 'webhooks') {
|
|
103
|
-
|
|
103
|
+
BulkOperations.validateWebhookData(obj.data);
|
|
104
104
|
}
|
|
105
105
|
else if (type === 'payments') {
|
|
106
|
-
|
|
106
|
+
BulkOperations.validatePaymentData(obj.data);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
static validateWebhookData(webhooks) {
|
|
@@ -150,6 +150,6 @@ export class BulkOperations {
|
|
|
150
150
|
if (path.extname(filename).toLowerCase() === '.json') {
|
|
151
151
|
return filename;
|
|
152
152
|
}
|
|
153
|
-
return filename
|
|
153
|
+
return `${filename}.json`;
|
|
154
154
|
}
|
|
155
155
|
}
|
package/dist/utils/cache.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
5
|
export class Cache {
|
|
6
6
|
cacheDir;
|
|
7
7
|
options;
|
|
@@ -26,7 +26,7 @@ export class Cache {
|
|
|
26
26
|
return crypto.createHash('md5').update(key).digest('hex');
|
|
27
27
|
}
|
|
28
28
|
getCachePath(key) {
|
|
29
|
-
return path.join(this.cacheDir, this.getCacheKey(key)
|
|
29
|
+
return path.join(this.cacheDir, `${this.getCacheKey(key)}.json`);
|
|
30
30
|
}
|
|
31
31
|
async isExpired(filePath) {
|
|
32
32
|
try {
|
package/dist/utils/constants.js
CHANGED
|
@@ -12,7 +12,7 @@ export const ENVIRONMENTS = ['test', 'live'];
|
|
|
12
12
|
export const CONFIG_FILE_NAME = '.paymongo';
|
|
13
13
|
export const ENV_FILE_NAME = '.env';
|
|
14
14
|
export const CLI_NAME = 'paymongo';
|
|
15
|
-
import { createRequire } from 'module';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
16
|
const _require = createRequire(import.meta.url);
|
|
17
17
|
const _pkg = _require('../../package.json');
|
|
18
18
|
export const CLI_VERSION = _pkg.version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paymongo-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.13",
|
|
4
4
|
"description": "Developer-first CLI tool for PayMongo integration development with local webhook forwarding, payment testing, and team collaboration features. See USER_GUIDE.md for comprehensive documentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,23 +12,17 @@
|
|
|
12
12
|
"build:incremental": "tsc --incremental",
|
|
13
13
|
"dev": "tsc --watch",
|
|
14
14
|
"start": "node dist/index.js",
|
|
15
|
-
"test": "
|
|
16
|
-
"test:watch": "
|
|
17
|
-
"lint": "
|
|
18
|
-
"lint:fix": "
|
|
19
|
-
"lint:src": "
|
|
20
|
-
"lint:tests": "
|
|
21
|
-
"format": "
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"lint": "biome check .",
|
|
18
|
+
"lint:fix": "biome check --write .",
|
|
19
|
+
"lint:src": "biome check src",
|
|
20
|
+
"lint:tests": "biome check tests",
|
|
21
|
+
"format": "biome format --write .",
|
|
22
22
|
"benchmark": "npx tsx scripts/benchmark.ts",
|
|
23
23
|
"prepare": "npm run build"
|
|
24
24
|
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"paymongo",
|
|
27
|
-
"cli",
|
|
28
|
-
"payments",
|
|
29
|
-
"webhooks",
|
|
30
|
-
"development"
|
|
31
|
-
],
|
|
25
|
+
"keywords": ["paymongo", "cli", "payments", "webhooks", "development"],
|
|
32
26
|
"author": "Leodyver Semilla",
|
|
33
27
|
"license": "MIT",
|
|
34
28
|
"repository": {
|
|
@@ -54,20 +48,12 @@
|
|
|
54
48
|
"zod": "^4.3.6"
|
|
55
49
|
},
|
|
56
50
|
"devDependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"@types/jest": "^30.0.0",
|
|
51
|
+
"@biomejs/biome": "2.4.10",
|
|
59
52
|
"@types/node": "^25.0.10",
|
|
60
|
-
"cross-env": "^10.1.0",
|
|
61
|
-
"eslint": "^9.0.0",
|
|
62
|
-
"globals": "^17.1.0",
|
|
63
|
-
"jest": "^30.2.0",
|
|
64
|
-
"jiti": "^2.6.1",
|
|
65
|
-
"prettier": "^3.1.1",
|
|
66
|
-
"ts-jest": "^29.4.6",
|
|
67
53
|
"ts-node": "^10.9.2",
|
|
68
54
|
"tsx": "^4.7.0",
|
|
69
55
|
"typescript": "^5.3.3",
|
|
70
|
-
"
|
|
56
|
+
"vitest": "^4.1.2"
|
|
71
57
|
},
|
|
72
58
|
"publishConfig": {
|
|
73
59
|
"access": "public"
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
environment: 'node',
|
|
6
|
+
globals: true,
|
|
7
|
+
include: ['tests/**/*.test.ts'],
|
|
8
|
+
setupFiles: ['tests/setup-vitest.ts'],
|
|
9
|
+
coverage: {
|
|
10
|
+
provider: 'v8',
|
|
11
|
+
reporter: ['text', 'lcov', 'html'],
|
|
12
|
+
reportsDirectory: 'coverage',
|
|
13
|
+
include: ['src/**/*.ts'],
|
|
14
|
+
exclude: ['src/**/*.d.ts'],
|
|
15
|
+
},
|
|
16
|
+
silent: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
package/eslint.config.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import eslint from '@eslint/js';
|
|
2
|
-
import tseslint from 'typescript-eslint';
|
|
3
|
-
import globals from 'globals';
|
|
4
|
-
|
|
5
|
-
export default tseslint.config(
|
|
6
|
-
eslint.configs.recommended,
|
|
7
|
-
...tseslint.configs.recommended,
|
|
8
|
-
{
|
|
9
|
-
files: ['src/**/*.ts'],
|
|
10
|
-
languageOptions: {
|
|
11
|
-
ecmaVersion: 2022,
|
|
12
|
-
sourceType: 'module',
|
|
13
|
-
globals: {
|
|
14
|
-
...globals.node,
|
|
15
|
-
...globals.es2022,
|
|
16
|
-
},
|
|
17
|
-
parserOptions: {
|
|
18
|
-
project: './tsconfig.json',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
rules: {
|
|
22
|
-
'no-unused-vars': 'off',
|
|
23
|
-
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
24
|
-
argsIgnorePattern: '^_',
|
|
25
|
-
varsIgnorePattern: '^_',
|
|
26
|
-
caughtErrorsIgnorePattern: '^_'
|
|
27
|
-
}],
|
|
28
|
-
'no-console': 'off',
|
|
29
|
-
'no-redeclare': 'off',
|
|
30
|
-
'@typescript-eslint/no-redeclare': 'error',
|
|
31
|
-
'no-shadow': 'off',
|
|
32
|
-
'@typescript-eslint/no-shadow': 'error',
|
|
33
|
-
'no-undef': 'off',
|
|
34
|
-
'no-empty': 'error',
|
|
35
|
-
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
36
|
-
'@typescript-eslint/no-explicit-any': 'warn',
|
|
37
|
-
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
38
|
-
eqeqeq: ['error', 'always'],
|
|
39
|
-
curly: ['error', 'all'],
|
|
40
|
-
'no-var': 'error',
|
|
41
|
-
'prefer-const': 'error',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
files: ['tests/**/*.ts'],
|
|
46
|
-
languageOptions: {
|
|
47
|
-
ecmaVersion: 2022,
|
|
48
|
-
sourceType: 'module',
|
|
49
|
-
globals: {
|
|
50
|
-
...globals.node,
|
|
51
|
-
...globals.es2022,
|
|
52
|
-
...globals.jest,
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
rules: {
|
|
56
|
-
'no-unused-vars': 'off',
|
|
57
|
-
'@typescript-eslint/no-unused-vars': ['warn', {
|
|
58
|
-
argsIgnorePattern: '^_',
|
|
59
|
-
varsIgnorePattern: '^_',
|
|
60
|
-
caughtErrorsIgnorePattern: '^_'
|
|
61
|
-
}],
|
|
62
|
-
'@typescript-eslint/no-explicit-any': 'off', // Allow any in tests for mocking
|
|
63
|
-
'no-console': 'off',
|
|
64
|
-
'no-undef': 'off',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
ignores: ['dist/', 'node_modules/', '*.js', '*.cjs', 'coverage/'],
|
|
69
|
-
}
|
|
70
|
-
);
|