paymongo-cli 1.4.12 → 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 +21 -0
- package/README.md +3 -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 +6 -7
- 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 +32 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
1
3
|
import chalk from 'chalk';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
4
|
import { CommandError } from '../../utils/errors.js';
|
|
5
5
|
import { checkConfigConflicts, createConfigContext, handleCommandFailure, loadRequiredConfig, setConfigValue, validateImportedConfig, validateImportedConfigWithSchema, } from './helpers.js';
|
|
6
6
|
export async function showAction(options) {
|
|
@@ -19,7 +19,7 @@ export async function showAction(options) {
|
|
|
19
19
|
console.log(JSON.stringify(config, null, 2));
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
console.log(
|
|
22
|
+
console.log(`\n${chalk.bold('Configuration (.paymongo)')}`);
|
|
23
23
|
console.log('');
|
|
24
24
|
console.log(chalk.bold('Project:'), config.projectName);
|
|
25
25
|
console.log(chalk.bold('Environment:'), config.environment);
|
|
@@ -32,6 +32,10 @@ export async function showAction(options) {
|
|
|
32
32
|
console.log(chalk.bold('Auto Register Webhook:'), config.dev.autoRegisterWebhook ? 'Yes' : 'No');
|
|
33
33
|
console.log(chalk.bold('Verify Webhook Signatures:'), config.dev.verifyWebhookSignatures ? 'Yes' : 'No');
|
|
34
34
|
console.log('');
|
|
35
|
+
if (config.analytics) {
|
|
36
|
+
console.log(chalk.bold('Analytics:'), config.analytics.enabled ? 'Enabled' : 'Disabled');
|
|
37
|
+
console.log('');
|
|
38
|
+
}
|
|
35
39
|
if (config.rateLimiting) {
|
|
36
40
|
console.log(chalk.bold('Rate Limiting:'));
|
|
37
41
|
console.log(chalk.bold(' Enabled:'), config.rateLimiting.enabled ? chalk.green('Yes') : chalk.red('No'));
|
|
@@ -190,7 +194,9 @@ export async function importAction(filePath, options) {
|
|
|
190
194
|
if (validationErrors.length > 0) {
|
|
191
195
|
spinner.fail('Invalid configuration');
|
|
192
196
|
console.error(chalk.red('❌ Configuration validation failed:'));
|
|
193
|
-
validationErrors.forEach((err) =>
|
|
197
|
+
validationErrors.forEach((err) => {
|
|
198
|
+
console.error(chalk.gray(` • ${err}`));
|
|
199
|
+
});
|
|
194
200
|
throw new CommandError();
|
|
195
201
|
}
|
|
196
202
|
spinner.succeed('Configuration validated');
|
|
@@ -201,7 +207,9 @@ export async function importAction(filePath, options) {
|
|
|
201
207
|
if (conflicts.length > 0) {
|
|
202
208
|
spinner.stop();
|
|
203
209
|
console.log(chalk.yellow('⚠️ Configuration conflicts detected:'));
|
|
204
|
-
conflicts.forEach((conflict) =>
|
|
210
|
+
conflicts.forEach((conflict) => {
|
|
211
|
+
console.log(chalk.gray(` • ${conflict}`));
|
|
212
|
+
});
|
|
205
213
|
console.log('');
|
|
206
214
|
console.log(chalk.bold('Use --force to overwrite existing configuration'));
|
|
207
215
|
throw new CommandError();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { CommandError } from '../../utils/errors.js';
|
|
3
|
+
import { createConfigContext, ensureAnalyticsConfig, loadRequiredConfig } from './helpers.js';
|
|
4
|
+
export async function analyticsEnableAction() {
|
|
5
|
+
const { spinner, configManager } = createConfigContext();
|
|
6
|
+
try {
|
|
7
|
+
const config = await loadRequiredConfig(spinner, configManager);
|
|
8
|
+
if (!config) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
ensureAnalyticsConfig(config).enabled = true;
|
|
12
|
+
spinner.start('Enabling analytics...');
|
|
13
|
+
await configManager.save(config);
|
|
14
|
+
spinner.succeed('Analytics enabled');
|
|
15
|
+
console.log(chalk.green('✓ Analytics enabled'));
|
|
16
|
+
console.log(chalk.gray('Webhook events will now be tracked locally on this machine'));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
spinner.stop();
|
|
20
|
+
const err = error;
|
|
21
|
+
console.error(chalk.red('❌ Failed to enable analytics:'), err.message);
|
|
22
|
+
throw new CommandError();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function analyticsDisableAction() {
|
|
26
|
+
const { spinner, configManager } = createConfigContext();
|
|
27
|
+
try {
|
|
28
|
+
const config = await loadRequiredConfig(spinner, configManager);
|
|
29
|
+
if (!config) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
ensureAnalyticsConfig(config).enabled = false;
|
|
33
|
+
spinner.start('Disabling analytics...');
|
|
34
|
+
await configManager.save(config);
|
|
35
|
+
spinner.succeed('Analytics disabled');
|
|
36
|
+
console.log(chalk.green('✓ Analytics disabled'));
|
|
37
|
+
console.log(chalk.gray('Existing analytics data remains local until you remove it manually'));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
spinner.stop();
|
|
41
|
+
const err = error;
|
|
42
|
+
console.error(chalk.red('❌ Failed to disable analytics:'), err.message);
|
|
43
|
+
throw new CommandError();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function analyticsStatusAction() {
|
|
47
|
+
const { spinner, configManager } = createConfigContext();
|
|
48
|
+
try {
|
|
49
|
+
const config = await loadRequiredConfig(spinner, configManager);
|
|
50
|
+
if (!config) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
console.log(`\n${chalk.bold('Analytics Status')}`);
|
|
54
|
+
console.log('');
|
|
55
|
+
if (!config.analytics?.enabled) {
|
|
56
|
+
console.log(chalk.yellow('Status: Disabled'));
|
|
57
|
+
console.log(chalk.gray('Webhook analytics are currently opt-out for this project'));
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log(chalk.gray("Run 'paymongo config analytics enable' to enable"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.log(chalk.green('Status: Enabled'));
|
|
63
|
+
console.log(chalk.gray('Webhook analytics are stored locally and never sent externally'));
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(chalk.gray('Commands:'));
|
|
66
|
+
console.log(chalk.gray("• 'paymongo config analytics disable' - Disable analytics"));
|
|
67
|
+
console.log(chalk.gray("• 'paymongo config show' - View current analytics setting"));
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
spinner.stop();
|
|
71
|
+
const err = error;
|
|
72
|
+
console.error(chalk.red('❌ Failed to check analytics status:'), err.message);
|
|
73
|
+
throw new CommandError();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import { validateConfig as zodValidateConfig } from '../../types/schemas.js';
|
|
3
|
-
import
|
|
4
|
-
import Spinner from '../../utils/spinner.js';
|
|
5
|
-
import { CommandError } from '../../utils/errors.js';
|
|
2
|
+
import { createCommandContext, failCommand, loadCommandConfig } from '../shared/runtime.js';
|
|
6
3
|
export const CONFIG_KEY_MAPPINGS = {
|
|
7
4
|
'project.name': 'projectName',
|
|
8
5
|
'webhook.url': 'webhooks.url',
|
|
@@ -10,35 +7,19 @@ export const CONFIG_KEY_MAPPINGS = {
|
|
|
10
7
|
'dev.port': 'dev.port',
|
|
11
8
|
'dev.autoRegister': 'dev.autoRegisterWebhook',
|
|
12
9
|
'dev.verifySignatures': 'dev.verifyWebhookSignatures',
|
|
10
|
+
'analytics.enabled': 'analytics.enabled',
|
|
13
11
|
'rateLimit.enabled': 'rateLimiting.enabled',
|
|
14
12
|
'rateLimit.maxRequests': 'rateLimiting.maxRequests',
|
|
15
13
|
'rateLimit.windowMs': 'rateLimiting.windowMs',
|
|
16
14
|
};
|
|
17
15
|
export function createConfigContext() {
|
|
18
|
-
return
|
|
19
|
-
spinner: new Spinner(),
|
|
20
|
-
configManager: new ConfigManager(),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
export function showNoConfigMessage(message = "Run 'paymongo init' to set up your project first.") {
|
|
24
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
25
|
-
console.log(chalk.gray(message));
|
|
16
|
+
return createCommandContext();
|
|
26
17
|
}
|
|
27
18
|
export async function loadRequiredConfig(spinner, configManager, loadingText = 'Loading configuration...') {
|
|
28
|
-
spinner
|
|
29
|
-
const config = await configManager.load();
|
|
30
|
-
if (!config) {
|
|
31
|
-
spinner.fail('No configuration found');
|
|
32
|
-
showNoConfigMessage();
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
spinner.succeed('Configuration loaded');
|
|
36
|
-
return config;
|
|
19
|
+
return loadCommandConfig(spinner, configManager, loadingText);
|
|
37
20
|
}
|
|
38
21
|
export function handleCommandFailure(prefix, error) {
|
|
39
|
-
|
|
40
|
-
console.error(chalk.red(prefix), err.message);
|
|
41
|
-
throw new CommandError();
|
|
22
|
+
return failCommand(prefix, error);
|
|
42
23
|
}
|
|
43
24
|
export function validateImportedConfig(config) {
|
|
44
25
|
if (typeof config !== 'object' || config === null) {
|
|
@@ -135,7 +116,7 @@ export function coerceConfigValue(value) {
|
|
|
135
116
|
if (value === 'false') {
|
|
136
117
|
return false;
|
|
137
118
|
}
|
|
138
|
-
if (!isNaN(Number(value))) {
|
|
119
|
+
if (!Number.isNaN(Number(value))) {
|
|
139
120
|
return Number(value);
|
|
140
121
|
}
|
|
141
122
|
return value;
|
|
@@ -151,3 +132,11 @@ export function ensureRateLimitingConfig(config) {
|
|
|
151
132
|
}
|
|
152
133
|
return config.rateLimiting;
|
|
153
134
|
}
|
|
135
|
+
export function ensureAnalyticsConfig(config) {
|
|
136
|
+
if (!config.analytics) {
|
|
137
|
+
config.analytics = {
|
|
138
|
+
enabled: false,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return config.analytics;
|
|
142
|
+
}
|
|
@@ -3,7 +3,7 @@ import { CommandError } from '../../utils/errors.js';
|
|
|
3
3
|
import { createConfigContext, ensureRateLimitingConfig, loadRequiredConfig } from './helpers.js';
|
|
4
4
|
function parsePositiveInt(value, message) {
|
|
5
5
|
const parsed = parseInt(value, 10);
|
|
6
|
-
if (isNaN(parsed) || parsed < 1) {
|
|
6
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
7
7
|
console.error(chalk.red(message));
|
|
8
8
|
throw new CommandError();
|
|
9
9
|
}
|
|
@@ -102,9 +102,9 @@ export async function rateLimitStatusAction() {
|
|
|
102
102
|
if (!config) {
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
-
console.log(
|
|
105
|
+
console.log(`\n${chalk.bold('Rate Limiting Status')}`);
|
|
106
106
|
console.log('');
|
|
107
|
-
if (!config.rateLimiting
|
|
107
|
+
if (!config.rateLimiting?.enabled) {
|
|
108
108
|
console.log(chalk.yellow('Status: Disabled'));
|
|
109
109
|
console.log(chalk.gray('Rate limiting is not currently active'));
|
|
110
110
|
console.log('');
|
package/dist/commands/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { backupAction, importAction, resetAction, setAction, showAction, } from './config/actions.js';
|
|
3
|
+
import { analyticsDisableAction, analyticsEnableAction, analyticsStatusAction, } from './config/analytics.js';
|
|
3
4
|
import { rateLimitDisableAction, rateLimitEnableAction, rateLimitSetMaxRequestsAction, rateLimitSetWindowAction, rateLimitStatusAction, } from './config/rate-limit.js';
|
|
4
5
|
const command = new Command('config');
|
|
5
6
|
command
|
|
@@ -23,6 +24,11 @@ command
|
|
|
23
24
|
.arguments('<file>')
|
|
24
25
|
.option('-f, --force', 'Overwrite existing configuration without confirmation')
|
|
25
26
|
.action(importAction))
|
|
27
|
+
.addCommand(new Command('analytics')
|
|
28
|
+
.description('Configure local webhook analytics')
|
|
29
|
+
.addCommand(new Command('enable').description('Enable analytics').action(analyticsEnableAction))
|
|
30
|
+
.addCommand(new Command('disable').description('Disable analytics').action(analyticsDisableAction))
|
|
31
|
+
.addCommand(new Command('status').description('Show analytics status').action(analyticsStatusAction)))
|
|
26
32
|
.addCommand(new Command('rate-limit')
|
|
27
33
|
.description('Configure rate limiting settings')
|
|
28
34
|
.addCommand(new Command('enable').description('Enable rate limiting').action(rateLimitEnableAction))
|
|
@@ -38,5 +44,5 @@ command
|
|
|
38
44
|
.addCommand(new Command('status')
|
|
39
45
|
.description('Show current rate limiting settings')
|
|
40
46
|
.action(rateLimitStatusAction)));
|
|
41
|
-
export {
|
|
47
|
+
export { analyticsDisableAction, analyticsEnableAction, analyticsStatusAction, backupAction, importAction, rateLimitDisableAction, rateLimitEnableAction, rateLimitSetMaxRequestsAction, rateLimitSetWindowAction, rateLimitStatusAction, resetAction, setAction, showAction, };
|
|
42
48
|
export default command;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as fs from 'fs';
|
|
1
|
+
import * as fs from 'node:fs';
|
|
3
2
|
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
4
|
import { DevProcessManager } from '../../services/dev/process-manager.js';
|
|
5
5
|
const logsCommand = new Command('logs')
|
|
6
6
|
.description('View dev server logs')
|
|
@@ -14,7 +14,7 @@ const logsCommand = new Command('logs')
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
const logFile = await DevProcessManager.getLogFile();
|
|
17
|
-
const lines = await DevProcessManager.readLogs(parseInt(options.lines));
|
|
17
|
+
const lines = await DevProcessManager.readLogs(parseInt(options.lines, 10));
|
|
18
18
|
if (lines.length === 0) {
|
|
19
19
|
console.log(chalk.yellow('No logs available.'));
|
|
20
20
|
console.log(chalk.gray('Log file:'), logFile);
|
|
@@ -24,11 +24,18 @@ const logsCommand = new Command('logs')
|
|
|
24
24
|
console.log(chalk.gray(`(Last ${lines.length} lines from ${logFile})`));
|
|
25
25
|
console.log(chalk.gray('─'.repeat(60)));
|
|
26
26
|
console.log('');
|
|
27
|
-
lines.forEach((line) =>
|
|
27
|
+
lines.forEach((line) => {
|
|
28
|
+
console.log(line);
|
|
29
|
+
});
|
|
28
30
|
if (options.follow) {
|
|
29
31
|
console.log('');
|
|
30
32
|
console.log(chalk.gray('Following logs... Press Ctrl+C to stop'));
|
|
31
33
|
let lastSize = fs.statSync(logFile).size;
|
|
34
|
+
const stopWatching = () => {
|
|
35
|
+
fs.unwatchFile(logFile);
|
|
36
|
+
process.removeListener('SIGINT', stopWatching);
|
|
37
|
+
process.removeListener('SIGTERM', stopWatching);
|
|
38
|
+
};
|
|
32
39
|
fs.watchFile(logFile, { interval: 500 }, () => {
|
|
33
40
|
const newSize = fs.statSync(logFile).size;
|
|
34
41
|
if (newSize > lastSize) {
|
|
@@ -40,6 +47,8 @@ const logsCommand = new Command('logs')
|
|
|
40
47
|
lastSize = newSize;
|
|
41
48
|
}
|
|
42
49
|
});
|
|
50
|
+
process.on('SIGINT', stopWatching);
|
|
51
|
+
process.on('SIGTERM', stopWatching);
|
|
43
52
|
await new Promise(() => { });
|
|
44
53
|
}
|
|
45
54
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
1
|
import chalk from 'chalk';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
3
|
import { DevProcessManager } from '../../services/dev/process-manager.js';
|
|
4
4
|
const statusCommand = new Command('status')
|
|
5
5
|
.description('Check if dev server is running in background')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
1
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
2
|
+
import { Command } from 'commander';
|
|
4
3
|
import { DevProcessManager } from '../../services/dev/process-manager.js';
|
|
4
|
+
import Spinner from '../../utils/spinner.js';
|
|
5
5
|
const stopCommand = new Command('stop')
|
|
6
6
|
.description('Stop the background dev server')
|
|
7
7
|
.action(async () => {
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import { spawn } from 'child_process';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import ConfigManager from '../services/config/manager.js';
|
|
6
|
-
import ApiClient from '../services/api/client.js';
|
|
7
|
-
import Spinner from '../utils/spinner.js';
|
|
8
|
-
import { withRetry, CommandError } from '../utils/errors.js';
|
|
9
|
-
import { DevProcessManager } from '../services/dev/process-manager.js';
|
|
10
2
|
import { DevServer } from '../services/dev/server.js';
|
|
3
|
+
import DevSessionService from '../services/dev/session.js';
|
|
4
|
+
import logsCommand from './dev/logs.js';
|
|
11
5
|
import statusCommand from './dev/status.js';
|
|
12
6
|
import stopCommand from './dev/stop.js';
|
|
13
|
-
import
|
|
7
|
+
import { createCommandContext, showNoConfigMessage } from './shared/runtime.js';
|
|
14
8
|
const command = new Command('dev');
|
|
15
9
|
command
|
|
16
10
|
.description('Start local development server')
|
|
@@ -20,255 +14,13 @@ command
|
|
|
20
14
|
.option('--ngrok-token <token>', 'ngrok authtoken (if not set in environment)')
|
|
21
15
|
.option('-d, --detach', 'Run server in background (detached mode)')
|
|
22
16
|
.action(async (options) => {
|
|
23
|
-
const spinner =
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log('');
|
|
31
|
-
console.log(chalk.bold('Status:'));
|
|
32
|
-
console.log(chalk.gray(' PID:'), existingState.pid);
|
|
33
|
-
console.log(chalk.gray(' Port:'), existingState.port);
|
|
34
|
-
console.log(chalk.gray(' Tunnel:'), existingState.tunnelUrl);
|
|
35
|
-
console.log(chalk.gray(' Uptime:'), DevProcessManager.formatUptime(existingState.startedAt));
|
|
36
|
-
console.log('');
|
|
37
|
-
console.log(chalk.gray('Use "paymongo dev stop" to stop the server first.'));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const args = ['dist/index.js', 'dev', '--port', options.port || '3000'];
|
|
41
|
-
if (options.noRegister) {
|
|
42
|
-
args.push('--no-register');
|
|
43
|
-
}
|
|
44
|
-
if (options.events) {
|
|
45
|
-
args.push('--events', options.events);
|
|
46
|
-
}
|
|
47
|
-
if (options.ngrokToken) {
|
|
48
|
-
args.push('--ngrok-token', options.ngrokToken);
|
|
49
|
-
}
|
|
50
|
-
const logFile = await DevProcessManager.getLogFile();
|
|
51
|
-
const out = fs.openSync(logFile, 'a');
|
|
52
|
-
const err = fs.openSync(logFile, 'a');
|
|
53
|
-
const child = spawn(process.execPath, args, {
|
|
54
|
-
detached: true,
|
|
55
|
-
stdio: ['ignore', out, err],
|
|
56
|
-
cwd: process.cwd(),
|
|
57
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
58
|
-
});
|
|
59
|
-
child.unref();
|
|
60
|
-
console.log(chalk.green('✓'), 'Dev server starting in background...');
|
|
61
|
-
console.log(chalk.gray(' PID:'), child.pid);
|
|
62
|
-
console.log(chalk.gray(' Logs:'), logFile);
|
|
63
|
-
console.log('');
|
|
64
|
-
console.log(chalk.gray('Use "paymongo dev status" to check server status'));
|
|
65
|
-
console.log(chalk.gray('Use "paymongo dev stop" to stop the server'));
|
|
66
|
-
console.log(chalk.gray('Use "paymongo dev logs" to view server logs'));
|
|
67
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
spinner.start('Loading configuration...');
|
|
72
|
-
const config = await configManager.load();
|
|
73
|
-
if (!config) {
|
|
74
|
-
spinner.fail('No configuration found');
|
|
75
|
-
console.log(chalk.yellow('No PayMongo configuration found.'));
|
|
76
|
-
console.log(chalk.gray("Run 'paymongo init' to set up your project first."));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
spinner.succeed('Configuration loaded');
|
|
80
|
-
spinner.start('Creating tunnel...');
|
|
81
|
-
const port = parseInt(options.port || '3000');
|
|
82
|
-
const { default: ngrok } = await import('@ngrok/ngrok');
|
|
83
|
-
const tunnelUrl = await withRetry(async () => {
|
|
84
|
-
try {
|
|
85
|
-
const authtoken = options.ngrokToken || process.env.NGROK_AUTHTOKEN;
|
|
86
|
-
if (!authtoken) {
|
|
87
|
-
throw new Error('ngrok authtoken not found. Please either:\n' +
|
|
88
|
-
' 1. Set NGROK_AUTHTOKEN environment variable, or\n' +
|
|
89
|
-
' 2. Use --ngrok-token option: paymongo dev --ngrok-token YOUR_TOKEN\n' +
|
|
90
|
-
' Get your token from: https://dashboard.ngrok.com/get-started/your-authtoken');
|
|
91
|
-
}
|
|
92
|
-
tunnel = await ngrok.forward({
|
|
93
|
-
addr: port,
|
|
94
|
-
authtoken: authtoken,
|
|
95
|
-
});
|
|
96
|
-
return tunnel.url();
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
console.log(chalk.yellow('Debug: ngrok error details:'), error.message);
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
}, {
|
|
103
|
-
maxRetries: 3,
|
|
104
|
-
delayMs: 2000,
|
|
105
|
-
retryCondition: (error) => {
|
|
106
|
-
return (error.message.includes('connection') ||
|
|
107
|
-
error.message.includes('timeout') ||
|
|
108
|
-
error.message.includes('tunnel') ||
|
|
109
|
-
error.message.includes('ngrok') ||
|
|
110
|
-
error.message.includes('authtoken'));
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
spinner.succeed('Tunnel created');
|
|
114
|
-
const devServer = new DevServer(port, config);
|
|
115
|
-
await devServer.start();
|
|
116
|
-
if (config.registeredWebhooks && config.registeredWebhooks.length > 0) {
|
|
117
|
-
spinner.start('Cleaning up stale webhooks...');
|
|
118
|
-
const apiClient = new ApiClient({ config });
|
|
119
|
-
let cleanedCount = 0;
|
|
120
|
-
for (const webhook of config.registeredWebhooks) {
|
|
121
|
-
try {
|
|
122
|
-
await apiClient.disableWebhook(webhook.id);
|
|
123
|
-
cleanedCount++;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
config.registeredWebhooks = [];
|
|
129
|
-
await configManager.save(config);
|
|
130
|
-
if (cleanedCount > 0) {
|
|
131
|
-
spinner.succeed(`Cleaned up ${cleanedCount} stale webhook(s)`);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
spinner.succeed('No stale webhooks to clean up');
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
let webhookId;
|
|
138
|
-
const shouldRegister = !options.noRegister && config.dev.autoRegisterWebhook !== false;
|
|
139
|
-
if (shouldRegister) {
|
|
140
|
-
spinner.start('Registering webhook...');
|
|
141
|
-
const events = (options.events || 'payment.paid,payment.failed').split(',');
|
|
142
|
-
const projectSlug = config.projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
143
|
-
const webhookUrl = `${tunnelUrl}/webhook/${projectSlug}`;
|
|
144
|
-
try {
|
|
145
|
-
const webhook = await new ApiClient({ config }).createWebhook(webhookUrl, events);
|
|
146
|
-
webhookId = webhook.id;
|
|
147
|
-
if (webhook.attributes?.secret) {
|
|
148
|
-
config.webhookSecrets = config.webhookSecrets || {};
|
|
149
|
-
config.webhookSecrets[webhook.id] = webhook.attributes.secret;
|
|
150
|
-
}
|
|
151
|
-
config.registeredWebhooks = config.registeredWebhooks || [];
|
|
152
|
-
config.registeredWebhooks.push({
|
|
153
|
-
id: webhook.id,
|
|
154
|
-
url: webhookUrl,
|
|
155
|
-
createdAt: Date.now(),
|
|
156
|
-
});
|
|
157
|
-
await configManager.save(config);
|
|
158
|
-
if (webhook.attributes?.secret) {
|
|
159
|
-
spinner.succeed(`Webhook registered: ${webhookId} (with signature verification)`);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
spinner.succeed(`Webhook registered: ${webhookId}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
const err = error;
|
|
167
|
-
spinner.warn('Webhook registration failed - server will start without webhook');
|
|
168
|
-
console.log(chalk.yellow('⚠️'), 'Webhook registration failed:', err.message);
|
|
169
|
-
console.log('');
|
|
170
|
-
console.log(chalk.blue('ℹ️'), 'You can still test webhooks manually:');
|
|
171
|
-
console.log(chalk.gray(` Webhook URL: ${webhookUrl}`));
|
|
172
|
-
console.log(chalk.gray(' Copy this URL to your PayMongo dashboard'));
|
|
173
|
-
if (config.dev.verifyWebhookSignatures) {
|
|
174
|
-
console.log(chalk.gray(' Signature verification is currently enabled'));
|
|
175
|
-
console.log(chalk.gray(' For manual unsigned testing, run: paymongo config set dev.verifySignatures false'));
|
|
176
|
-
}
|
|
177
|
-
console.log('');
|
|
178
|
-
if (err.message.includes('API key') || err.message.includes('unauthorized')) {
|
|
179
|
-
console.log(chalk.yellow('💡 To fix webhook registration:'));
|
|
180
|
-
console.log(chalk.gray(' 1. Run "paymongo login" to update your API keys'));
|
|
181
|
-
console.log(chalk.gray(' 2. Restart the development server'));
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const projectSlug = config.projectName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
186
|
-
const localWebhookUrl = `http://localhost:${port}/webhook/${projectSlug}`;
|
|
187
|
-
const externalWebhookUrl = `${tunnelUrl}/webhook/${projectSlug}`;
|
|
188
|
-
console.log('\n' + chalk.green('🚀 PayMongo Development Server'));
|
|
189
|
-
console.log('');
|
|
190
|
-
console.log(chalk.bold('URLs:'));
|
|
191
|
-
console.log(chalk.gray(' ├─'), chalk.cyan('External (PayMongo sends here):'));
|
|
192
|
-
console.log(chalk.gray(' │ '), chalk.yellow(externalWebhookUrl));
|
|
193
|
-
console.log(chalk.gray(' │'));
|
|
194
|
-
console.log(chalk.gray(' └─'), chalk.cyan('Local (Your server receives here):'));
|
|
195
|
-
console.log(chalk.gray(' '), chalk.green(localWebhookUrl));
|
|
196
|
-
console.log('');
|
|
197
|
-
console.log(chalk.bold('Forwarding:'));
|
|
198
|
-
console.log(chalk.gray(' '), `${chalk.yellow(tunnelUrl)} ${chalk.gray('→')} ${chalk.green(`http://localhost:${port}`)}`);
|
|
199
|
-
console.log('');
|
|
200
|
-
if (webhookId) {
|
|
201
|
-
console.log(chalk.bold('Webhook ID:'), chalk.gray(webhookId));
|
|
202
|
-
}
|
|
203
|
-
console.log(chalk.bold('Events:'), (options.events || 'payment.paid,payment.failed').split(',').join(', '));
|
|
204
|
-
console.log('');
|
|
205
|
-
console.log(chalk.gray('💡 Tip: Use the External URL in PayMongo dashboard, requests will forward to your local server'));
|
|
206
|
-
console.log(chalk.gray('Press Ctrl+C to stop'));
|
|
207
|
-
await DevProcessManager.saveState({
|
|
208
|
-
pid: process.pid,
|
|
209
|
-
port,
|
|
210
|
-
tunnelUrl: tunnelUrl ?? '',
|
|
211
|
-
webhookId,
|
|
212
|
-
webhookUrl: externalWebhookUrl,
|
|
213
|
-
localUrl: localWebhookUrl,
|
|
214
|
-
events: (options.events || 'payment.paid,payment.failed').split(','),
|
|
215
|
-
startedAt: Date.now(),
|
|
216
|
-
projectName: config.projectName,
|
|
217
|
-
});
|
|
218
|
-
const cleanup = async () => {
|
|
219
|
-
console.log('\n' + chalk.yellow('Shutting down...'));
|
|
220
|
-
await DevProcessManager.clearState();
|
|
221
|
-
try {
|
|
222
|
-
if (tunnel) {
|
|
223
|
-
await tunnel.close();
|
|
224
|
-
console.log(chalk.yellow('✓'), 'Tunnel closed');
|
|
225
|
-
}
|
|
226
|
-
await devServer.stop();
|
|
227
|
-
if (webhookId) {
|
|
228
|
-
spinner.start('Cleaning up webhook...');
|
|
229
|
-
await new ApiClient({ config }).disableWebhook(webhookId);
|
|
230
|
-
if (config.registeredWebhooks) {
|
|
231
|
-
config.registeredWebhooks = config.registeredWebhooks.filter((w) => w.id !== webhookId);
|
|
232
|
-
delete config.webhookSecrets[webhookId];
|
|
233
|
-
await configManager.save(config);
|
|
234
|
-
}
|
|
235
|
-
spinner.succeed('Webhook disabled');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
console.error(chalk.red('Error during cleanup:'), error.message);
|
|
240
|
-
console.log(chalk.yellow('⚠️'), 'Some cleanup tasks may not have completed');
|
|
241
|
-
}
|
|
242
|
-
process.exit(0);
|
|
243
|
-
};
|
|
244
|
-
process.on('SIGINT', cleanup);
|
|
245
|
-
process.on('SIGTERM', cleanup);
|
|
246
|
-
await new Promise(() => { });
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
spinner.stop();
|
|
250
|
-
const err = error;
|
|
251
|
-
if (err.message.includes('ngrok') || err.message.includes('tunnel')) {
|
|
252
|
-
console.error(chalk.red('❌ Failed to create tunnel:'), err.message);
|
|
253
|
-
console.log('');
|
|
254
|
-
console.log(chalk.yellow('💡 Troubleshooting suggestions:'));
|
|
255
|
-
console.log(chalk.gray('• Check your internet connection'));
|
|
256
|
-
console.log(chalk.gray('• Make sure ngrok is not blocked by firewall/antivirus'));
|
|
257
|
-
console.log(chalk.gray('• Set up ngrok authentication: export NGROK_AUTHTOKEN=your_token'));
|
|
258
|
-
console.log(chalk.gray('• Get your authtoken from: https://dashboard.ngrok.com/get-started/your-authtoken'));
|
|
259
|
-
console.log(chalk.gray('• Try a different port: paymongo dev --port 3001'));
|
|
260
|
-
console.log(chalk.gray('• Visit https://ngrok.com for status updates'));
|
|
261
|
-
}
|
|
262
|
-
await DevProcessManager.clearState();
|
|
263
|
-
try {
|
|
264
|
-
if (tunnel) {
|
|
265
|
-
await tunnel.close();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
}
|
|
270
|
-
throw new CommandError();
|
|
271
|
-
}
|
|
17
|
+
const { spinner, configManager } = createCommandContext();
|
|
18
|
+
const session = new DevSessionService({
|
|
19
|
+
spinner,
|
|
20
|
+
configManager,
|
|
21
|
+
onMissingConfig: showNoConfigMessage,
|
|
22
|
+
});
|
|
23
|
+
await session.run(options);
|
|
272
24
|
});
|
|
273
25
|
command.addCommand(statusCommand);
|
|
274
26
|
command.addCommand(stopCommand);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
1
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { ApiKeyError, CommandError, NetworkError, PayMongoError } from '../utils/errors.js';
|
|
5
4
|
import { validateApiKey, validateWebhookUrl } from '../utils/validator.js';
|
|
6
|
-
import {
|
|
5
|
+
import { createApiClient, createCommandContext } from './shared/runtime.js';
|
|
7
6
|
function statusIcon(status) {
|
|
8
7
|
switch (status) {
|
|
9
8
|
case 'pass':
|
|
@@ -25,7 +24,7 @@ function hasNgrokToken() {
|
|
|
25
24
|
return typeof token === 'string' && token.trim().length > 0;
|
|
26
25
|
}
|
|
27
26
|
async function runDoctor(options) {
|
|
28
|
-
const configManager =
|
|
27
|
+
const { configManager } = createCommandContext();
|
|
29
28
|
const checks = [];
|
|
30
29
|
const config = await configManager.load();
|
|
31
30
|
if (!config) {
|
|
@@ -33,7 +32,7 @@ async function runDoctor(options) {
|
|
|
33
32
|
name: 'Configuration',
|
|
34
33
|
status: 'fail',
|
|
35
34
|
message: 'No .paymongo configuration found.',
|
|
36
|
-
fix:
|
|
35
|
+
fix: 'Run `paymongo init` to create project configuration.',
|
|
37
36
|
});
|
|
38
37
|
return checks;
|
|
39
38
|
}
|
|
@@ -159,7 +158,7 @@ async function runDoctor(options) {
|
|
|
159
158
|
: 'No project-managed webhooks are currently tracked.',
|
|
160
159
|
});
|
|
161
160
|
if (options.network !== false && envKeys?.secret && validateApiKey(envKeys.secret, 'secret')) {
|
|
162
|
-
const apiClient =
|
|
161
|
+
const apiClient = createApiClient(config);
|
|
163
162
|
try {
|
|
164
163
|
await apiClient.validateApiKey();
|
|
165
164
|
checks.push({
|