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