@analyticscli/growth-engineer 0.1.1-preview.5 → 0.1.1-preview.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/export-paddle-summary.mjs +72 -18
- package/dist/runtime/export-paddle-summary.mjs.map +1 -1
- package/dist/runtime/openclaw-exporters-lib.d.mts +17 -0
- package/dist/runtime/openclaw-exporters-lib.mjs +64 -0
- package/dist/runtime/openclaw-exporters-lib.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-preflight.mjs +47 -15
- package/dist/runtime/openclaw-growth-preflight.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-runner.mjs +1 -1
- package/dist/runtime/openclaw-growth-runner.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-wizard.mjs +205 -27
- package/dist/runtime/openclaw-growth-wizard.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -84,7 +84,7 @@ const CONNECTOR_DEFINITIONS = [
|
|
|
84
84
|
key: 'paddle',
|
|
85
85
|
label: 'Paddle Billing metrics',
|
|
86
86
|
summary: 'Read web checkout, revenue, MRR, refunds, chargebacks, and active subscriber metrics.',
|
|
87
|
-
needs: 'A Paddle API key for the live account
|
|
87
|
+
needs: 'A scoped Paddle API key for the live account with metrics.read permission.',
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
key: 'seo',
|
|
@@ -906,7 +906,9 @@ function sourceCommandNeedsActiveConfig(sourceName, command) {
|
|
|
906
906
|
const value = String(command || '').toLowerCase();
|
|
907
907
|
return (normalized === 'sentry' ||
|
|
908
908
|
normalized === 'glitchtip' ||
|
|
909
|
+
normalized === 'paddle' ||
|
|
909
910
|
normalized === 'coolify' ||
|
|
911
|
+
value.includes('export-paddle-summary') ||
|
|
910
912
|
value.includes('export-sentry-summary') ||
|
|
911
913
|
value.includes('export-coolify-summary') ||
|
|
912
914
|
value.includes('exporters coolify-summary'));
|
|
@@ -2075,7 +2077,7 @@ function summarizeFailureFix(connector, blockers) {
|
|
|
2075
2077
|
return 'Paste a RevenueCat v2 secret API key with read-only project permissions, then rerun setup.';
|
|
2076
2078
|
}
|
|
2077
2079
|
if (connector === 'paddle') {
|
|
2078
|
-
return 'Paste a live Paddle API key from Developer Tools > Authentication
|
|
2080
|
+
return 'Paste a live Paddle API key from Developer Tools > Authentication v2 with metrics.read permission, then rerun setup.';
|
|
2079
2081
|
}
|
|
2080
2082
|
if (connector === 'seo') {
|
|
2081
2083
|
return 'Configure Search Console read access. Leave GSC_SITE_URL empty to scan all verified properties in the account, or set it only when you intentionally want one property.';
|
|
@@ -2451,10 +2453,13 @@ async function saveSecretsImmediately(secrets) {
|
|
|
2451
2453
|
process.stdout.write(`Saved local secrets to ${secretsFile} with chmod 600.\n`);
|
|
2452
2454
|
return true;
|
|
2453
2455
|
}
|
|
2454
|
-
async function runImmediateConnectorHealthCheck({ rl, configPath, connector, secrets, sentryAccounts = [], }) {
|
|
2456
|
+
async function runImmediateConnectorHealthCheck({ rl, configPath, connector, secrets, sentryAccounts = [], paddleAccounts = [], }) {
|
|
2455
2457
|
if (connector === 'sentry' && sentryAccounts.length > 0) {
|
|
2456
2458
|
await upsertSentryAccountsConfig(configPath, sentryAccounts);
|
|
2457
2459
|
}
|
|
2460
|
+
if (connector === 'paddle' && paddleAccounts.length > 0) {
|
|
2461
|
+
await upsertPaddleAccountsConfig(configPath, paddleAccounts);
|
|
2462
|
+
}
|
|
2458
2463
|
await saveSecretsImmediately(secrets);
|
|
2459
2464
|
const env = {
|
|
2460
2465
|
...process.env,
|
|
@@ -3133,6 +3138,71 @@ async function verifySentryAccountsConfig(configPath, expectedAccounts) {
|
|
|
3133
3138
|
}
|
|
3134
3139
|
return { ok: true, detail: `${realAccounts.length} active Sentry-compatible account(s) configured` };
|
|
3135
3140
|
}
|
|
3141
|
+
async function upsertPaddleAccountsConfig(configPath, accounts) {
|
|
3142
|
+
if (!accounts.length || !(await fileExists(configPath)))
|
|
3143
|
+
return false;
|
|
3144
|
+
const config = await readJsonFile(configPath);
|
|
3145
|
+
const existingAccounts = Array.isArray(config?.sources?.paddle?.accounts)
|
|
3146
|
+
? config.sources.paddle.accounts
|
|
3147
|
+
: [];
|
|
3148
|
+
const merged = new Map();
|
|
3149
|
+
for (const account of existingAccounts) {
|
|
3150
|
+
const id = String(account?.id || account?.key || account?.label || '').trim();
|
|
3151
|
+
if (id)
|
|
3152
|
+
merged.set(id, account);
|
|
3153
|
+
}
|
|
3154
|
+
for (const account of accounts) {
|
|
3155
|
+
merged.set(account.id, {
|
|
3156
|
+
...(merged.get(account.id) || {}),
|
|
3157
|
+
...account,
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
const tokenEnv = accounts[0]?.tokenEnv || config?.sources?.paddle?.tokenEnv || config?.secrets?.paddleTokenEnv || 'PADDLE_API_KEY';
|
|
3161
|
+
config.sources = {
|
|
3162
|
+
...(config.sources || {}),
|
|
3163
|
+
paddle: {
|
|
3164
|
+
...(config.sources?.paddle || {}),
|
|
3165
|
+
enabled: true,
|
|
3166
|
+
mode: 'command',
|
|
3167
|
+
command: normalizeWizardSourceCommand('paddle', config.sources?.paddle || {}, configPath),
|
|
3168
|
+
environment: config.sources?.paddle?.environment || 'live',
|
|
3169
|
+
tokenEnv,
|
|
3170
|
+
accounts: [...merged.values()],
|
|
3171
|
+
},
|
|
3172
|
+
};
|
|
3173
|
+
config.secrets = {
|
|
3174
|
+
...(config.secrets || {}),
|
|
3175
|
+
paddleTokenEnv: tokenEnv,
|
|
3176
|
+
paddleTokenRef: { source: 'env', provider: 'default', id: tokenEnv },
|
|
3177
|
+
};
|
|
3178
|
+
await writeJsonFile(configPath, config);
|
|
3179
|
+
return true;
|
|
3180
|
+
}
|
|
3181
|
+
async function verifyPaddleAccountsConfig(configPath, expectedAccounts) {
|
|
3182
|
+
if (!(await fileExists(configPath))) {
|
|
3183
|
+
return { ok: false, detail: `${configPath} does not exist` };
|
|
3184
|
+
}
|
|
3185
|
+
const config = await readJsonFile(configPath);
|
|
3186
|
+
const source = config?.sources?.paddle;
|
|
3187
|
+
if (!source || source.enabled !== true) {
|
|
3188
|
+
return { ok: false, detail: 'sources.paddle.enabled is not true' };
|
|
3189
|
+
}
|
|
3190
|
+
if (source.mode !== 'command') {
|
|
3191
|
+
return { ok: false, detail: 'sources.paddle.mode is not command' };
|
|
3192
|
+
}
|
|
3193
|
+
const configuredAccounts = Array.isArray(source.accounts) ? source.accounts : [];
|
|
3194
|
+
if (configuredAccounts.length === 0) {
|
|
3195
|
+
return { ok: false, detail: 'sources.paddle.accounts contains no account' };
|
|
3196
|
+
}
|
|
3197
|
+
const configuredIds = new Set(configuredAccounts.map((account) => String(account?.id || account?.key || '').trim()).filter(Boolean));
|
|
3198
|
+
const missingIds = expectedAccounts
|
|
3199
|
+
.map((account) => String(account?.id || '').trim())
|
|
3200
|
+
.filter((id) => id && !configuredIds.has(id));
|
|
3201
|
+
if (missingIds.length > 0) {
|
|
3202
|
+
return { ok: false, detail: `sources.paddle.accounts is missing configured account id(s): ${missingIds.join(', ')}` };
|
|
3203
|
+
}
|
|
3204
|
+
return { ok: true, detail: `${configuredAccounts.length} Paddle account(s) configured` };
|
|
3205
|
+
}
|
|
3136
3206
|
async function upsertCoolifyConfig(configPath, { baseUrl, tokenEnv = 'COOLIFY_API_TOKEN' }) {
|
|
3137
3207
|
if (!(await fileExists(configPath)))
|
|
3138
3208
|
return false;
|
|
@@ -3430,22 +3500,55 @@ async function guideRevenueCatConnector(rl, secrets) {
|
|
|
3430
3500
|
if (apiKey)
|
|
3431
3501
|
secrets.REVENUECAT_API_KEY = apiKey;
|
|
3432
3502
|
}
|
|
3503
|
+
function paddleAccountIdFromLabel(label, index) {
|
|
3504
|
+
const normalized = String(label || '')
|
|
3505
|
+
.trim()
|
|
3506
|
+
.toLowerCase()
|
|
3507
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
3508
|
+
.replace(/^_+|_+$/g, '');
|
|
3509
|
+
return normalized || `paddle_${index + 1}`;
|
|
3510
|
+
}
|
|
3511
|
+
function paddleTokenEnvForAccount(index, label) {
|
|
3512
|
+
if (index === 0)
|
|
3513
|
+
return 'PADDLE_API_KEY';
|
|
3514
|
+
const suffix = paddleAccountIdFromLabel(label, index).toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
3515
|
+
const base = suffix && suffix !== `PADDLE_${index + 1}` ? `PADDLE_API_KEY_${suffix}` : `PADDLE_API_KEY_${index + 1}`;
|
|
3516
|
+
return base.replace(/_+/g, '_');
|
|
3517
|
+
}
|
|
3433
3518
|
async function guidePaddleConnector(rl, secrets) {
|
|
3434
3519
|
printSection('Paddle Billing metrics', [
|
|
3435
3520
|
'Use this when OpenClaw should read web checkout, revenue, MRR, refunds, chargebacks, and active subscriber metrics.',
|
|
3436
3521
|
]);
|
|
3437
|
-
process.stdout.write('\nCreate or update a Paddle API key here:\n https://vendors.paddle.com/authentication\n\n');
|
|
3522
|
+
process.stdout.write('\nCreate or update a scoped Paddle API key here:\n https://vendors.paddle.com/authentication-v2\n\n');
|
|
3438
3523
|
printBullets([
|
|
3439
3524
|
'Open Paddle > Developer Tools > Authentication.',
|
|
3440
|
-
'
|
|
3441
|
-
'
|
|
3442
|
-
'If your Paddle UI does not offer scope selection, use a dedicated Growth Engineer key and rotate/revoke it from Paddle when needed.',
|
|
3525
|
+
'Use the API keys tab and create a new live API key.',
|
|
3526
|
+
'Grant `metrics.read`. Do not grant write permissions unless another workflow explicitly needs them.',
|
|
3443
3527
|
'Do not select or hard-code a single product in the wizard; the Growth Engineer should keep account-level metrics context.',
|
|
3444
3528
|
'Paste the key here so it is stored only in the local chmod 600 secrets file.',
|
|
3445
3529
|
]);
|
|
3446
|
-
const
|
|
3447
|
-
|
|
3448
|
-
|
|
3530
|
+
const accounts = [];
|
|
3531
|
+
let index = 0;
|
|
3532
|
+
while (true) {
|
|
3533
|
+
const label = await ask(rl, index === 0 ? 'Paddle account label' : 'Next Paddle account label (empty = done)', index === 0 ? 'Paddle' : '');
|
|
3534
|
+
if (!label.trim())
|
|
3535
|
+
break;
|
|
3536
|
+
const tokenEnv = paddleTokenEnvForAccount(index, label);
|
|
3537
|
+
const apiKey = await maybePromptSecret(rl, `Paste ${tokenEnv} into this local terminal`, tokenEnv);
|
|
3538
|
+
if (apiKey)
|
|
3539
|
+
secrets[tokenEnv] = apiKey;
|
|
3540
|
+
accounts.push({
|
|
3541
|
+
id: paddleAccountIdFromLabel(label, index),
|
|
3542
|
+
label: label.trim(),
|
|
3543
|
+
tokenEnv,
|
|
3544
|
+
environment: 'live',
|
|
3545
|
+
});
|
|
3546
|
+
index += 1;
|
|
3547
|
+
const addAnother = await askYesNo(rl, 'Add another Paddle account?', false);
|
|
3548
|
+
if (!addAnother)
|
|
3549
|
+
break;
|
|
3550
|
+
}
|
|
3551
|
+
return accounts;
|
|
3449
3552
|
}
|
|
3450
3553
|
async function guideSeoConnector(rl, secrets) {
|
|
3451
3554
|
printSection('SEO / Google Search Console / DataForSEO', [
|
|
@@ -3477,10 +3580,15 @@ async function guideSeoConnector(rl, secrets) {
|
|
|
3477
3580
|
secrets.DATAFORSEO_PASSWORD = password;
|
|
3478
3581
|
}
|
|
3479
3582
|
}
|
|
3480
|
-
function buildAccountSignalExtraSourceConfig(key, existing = {}) {
|
|
3583
|
+
function buildAccountSignalExtraSourceConfig(key, existing = {}, accounts = []) {
|
|
3481
3584
|
const definition = getAccountSignalConnectorDefinition(key);
|
|
3482
3585
|
if (!definition)
|
|
3483
3586
|
return existing;
|
|
3587
|
+
const accountConfig = accounts.length > 0
|
|
3588
|
+
? {
|
|
3589
|
+
accounts: mergeConnectorAccounts(existing.accounts, accounts),
|
|
3590
|
+
}
|
|
3591
|
+
: {};
|
|
3484
3592
|
return {
|
|
3485
3593
|
...buildExtraSourceConfig(definition.service, {
|
|
3486
3594
|
key: definition.key,
|
|
@@ -3504,9 +3612,28 @@ function buildAccountSignalExtraSourceConfig(key, existing = {}) {
|
|
|
3504
3612
|
signalKind: definition.sourceKind,
|
|
3505
3613
|
experimental: Boolean(definition.experimental),
|
|
3506
3614
|
hint: existing.hint || definition.signalHint,
|
|
3615
|
+
...accountConfig,
|
|
3507
3616
|
};
|
|
3508
3617
|
}
|
|
3509
|
-
|
|
3618
|
+
function mergeConnectorAccounts(existingAccounts, nextAccounts) {
|
|
3619
|
+
const merged = new Map();
|
|
3620
|
+
for (const account of Array.isArray(existingAccounts) ? existingAccounts : []) {
|
|
3621
|
+
const id = String(account?.id || account?.key || account?.label || '').trim();
|
|
3622
|
+
if (id)
|
|
3623
|
+
merged.set(id, account);
|
|
3624
|
+
}
|
|
3625
|
+
for (const account of nextAccounts) {
|
|
3626
|
+
const id = String(account?.id || account?.key || account?.label || '').trim();
|
|
3627
|
+
if (!id)
|
|
3628
|
+
continue;
|
|
3629
|
+
merged.set(id, {
|
|
3630
|
+
...(merged.get(id) || {}),
|
|
3631
|
+
...account,
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
return [...merged.values()];
|
|
3635
|
+
}
|
|
3636
|
+
async function upsertAccountSignalConnectorConfig(configPath, key, accounts = []) {
|
|
3510
3637
|
const definition = getAccountSignalConnectorDefinition(key);
|
|
3511
3638
|
if (!definition)
|
|
3512
3639
|
return false;
|
|
@@ -3515,7 +3642,7 @@ async function upsertAccountSignalConnectorConfig(configPath, key) {
|
|
|
3515
3642
|
const extra = Array.isArray(sources.extra) ? sources.extra : [];
|
|
3516
3643
|
const nextExtra = extra.filter((source) => String(source?.key || source?.service || '') !== definition.key);
|
|
3517
3644
|
const existing = extra.find((source) => String(source?.key || source?.service || '') === definition.key) || {};
|
|
3518
|
-
nextExtra.push(buildAccountSignalExtraSourceConfig(key, existing));
|
|
3645
|
+
nextExtra.push(buildAccountSignalExtraSourceConfig(key, existing, accounts));
|
|
3519
3646
|
config.sources = {
|
|
3520
3647
|
...sources,
|
|
3521
3648
|
extra: nextExtra,
|
|
@@ -3523,28 +3650,58 @@ async function upsertAccountSignalConnectorConfig(configPath, key) {
|
|
|
3523
3650
|
await writeJsonFile(configPath, config);
|
|
3524
3651
|
return true;
|
|
3525
3652
|
}
|
|
3653
|
+
function accountSignalTokenEnvForAccount(baseEnv, key, index, label) {
|
|
3654
|
+
if (index === 0)
|
|
3655
|
+
return baseEnv;
|
|
3656
|
+
const suffix = toConfigId(label || key, `${key}_${index + 1}`).toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
3657
|
+
return `${baseEnv}_${suffix}`.replace(/_+/g, '_');
|
|
3658
|
+
}
|
|
3526
3659
|
async function guideAccountSignalConnector(rl, secrets, key) {
|
|
3527
3660
|
const definition = getAccountSignalConnectorDefinition(key);
|
|
3528
3661
|
if (!definition)
|
|
3529
|
-
return;
|
|
3662
|
+
return [];
|
|
3530
3663
|
printSection(definition.label, [
|
|
3531
3664
|
definition.summary,
|
|
3532
3665
|
'Setup is account-wide. Do not paste project IDs, app IDs, product IDs, package names, paywall IDs, service names, or tags here.',
|
|
3533
3666
|
]);
|
|
3534
3667
|
process.stdout.write(`Docs: ${definition.docsUrl}\n\n`);
|
|
3535
3668
|
printBullets(definition.steps);
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3669
|
+
const accounts = [];
|
|
3670
|
+
let index = 0;
|
|
3671
|
+
while (true) {
|
|
3672
|
+
const label = await ask(rl, index === 0 ? `${definition.label} account label` : `Next ${definition.label} account label (empty = done)`, index === 0 ? definition.label.replace(/\s+\(experimental\)$/i, '') : '');
|
|
3673
|
+
if (!label.trim())
|
|
3674
|
+
break;
|
|
3675
|
+
const credentialEnvs = {};
|
|
3676
|
+
for (const credential of definition.credentials) {
|
|
3677
|
+
const envName = accountSignalTokenEnvForAccount(credential.env, key, index, label);
|
|
3678
|
+
const defaultValue = index === 0 ? credential.defaultValue ?? process.env[credential.env] ?? '' : '';
|
|
3679
|
+
const prompt = envName === credential.env ? credential.prompt : `${credential.prompt.replace(credential.env, envName)}`;
|
|
3680
|
+
const value = credential.optional
|
|
3681
|
+
? await maybePromptSecret(rl, prompt, envName)
|
|
3682
|
+
: await maybePromptSecret(rl, prompt, envName);
|
|
3683
|
+
const finalValue = value || defaultValue;
|
|
3684
|
+
credentialEnvs[credential.env] = envName;
|
|
3685
|
+
if (finalValue)
|
|
3686
|
+
secrets[envName] = finalValue;
|
|
3687
|
+
else if (!credential.optional) {
|
|
3688
|
+
process.stdout.write(`${envName} was not saved. ${definition.label} setup remains pending for ${label}; rerun this wizard when ready.\n`);
|
|
3689
|
+
}
|
|
3546
3690
|
}
|
|
3691
|
+
accounts.push({
|
|
3692
|
+
id: toConfigId(label, `${key}_${index + 1}`),
|
|
3693
|
+
label: label.trim(),
|
|
3694
|
+
credentialEnvs,
|
|
3695
|
+
tokenEnv: credentialEnvs[definition.credentials[0]?.env] || definition.credentials[0]?.env || null,
|
|
3696
|
+
accountWide: true,
|
|
3697
|
+
projectScope: 'discover_from_account',
|
|
3698
|
+
});
|
|
3699
|
+
index += 1;
|
|
3700
|
+
const addAnother = await askYesNo(rl, `Add another ${definition.label} account?`, false);
|
|
3701
|
+
if (!addAnother)
|
|
3702
|
+
break;
|
|
3547
3703
|
}
|
|
3704
|
+
return accounts;
|
|
3548
3705
|
}
|
|
3549
3706
|
async function guideSentryConnector(rl, secrets) {
|
|
3550
3707
|
printSection('Sentry / GlitchTip', [
|
|
@@ -3853,6 +4010,7 @@ async function runConnectorSetupSteps({ rl, args, selected, healthByConnector, a
|
|
|
3853
4010
|
process.stdout.write('\n');
|
|
3854
4011
|
const secrets = {};
|
|
3855
4012
|
let sentryAccounts = [];
|
|
4013
|
+
let paddleAccounts = [];
|
|
3856
4014
|
let coolifyConfig = null;
|
|
3857
4015
|
if (selected.includes('analytics')) {
|
|
3858
4016
|
let forceFreshAnalyticsToken = shouldForceFreshAnalyticsToken(healthByConnector);
|
|
@@ -3901,12 +4059,13 @@ async function runConnectorSetupSteps({ rl, args, selected, healthByConnector, a
|
|
|
3901
4059
|
if (selected.includes('paddle')) {
|
|
3902
4060
|
while (true) {
|
|
3903
4061
|
clearTerminal();
|
|
3904
|
-
await guidePaddleConnector(rl, secrets);
|
|
4062
|
+
paddleAccounts = await guidePaddleConnector(rl, secrets);
|
|
3905
4063
|
const check = await runImmediateConnectorHealthCheck({
|
|
3906
4064
|
rl,
|
|
3907
4065
|
configPath: args.config,
|
|
3908
4066
|
connector: 'paddle',
|
|
3909
4067
|
secrets,
|
|
4068
|
+
paddleAccounts,
|
|
3910
4069
|
});
|
|
3911
4070
|
if (!check.retry)
|
|
3912
4071
|
break;
|
|
@@ -3975,8 +4134,8 @@ async function runConnectorSetupSteps({ rl, args, selected, healthByConnector, a
|
|
|
3975
4134
|
for (const connector of selected.filter(isAccountSignalConnector)) {
|
|
3976
4135
|
while (true) {
|
|
3977
4136
|
clearTerminal();
|
|
3978
|
-
await guideAccountSignalConnector(rl, secrets, connector);
|
|
3979
|
-
await upsertAccountSignalConnectorConfig(args.config, connector);
|
|
4137
|
+
const accountSignalAccounts = await guideAccountSignalConnector(rl, secrets, connector);
|
|
4138
|
+
await upsertAccountSignalConnectorConfig(args.config, connector, accountSignalAccounts);
|
|
3980
4139
|
const check = await runImmediateConnectorHealthCheck({
|
|
3981
4140
|
rl,
|
|
3982
4141
|
configPath: args.config,
|
|
@@ -4003,6 +4162,12 @@ async function runConnectorSetupSteps({ rl, args, selected, healthByConnector, a
|
|
|
4003
4162
|
process.stdout.write(`Configured ${sentryAccounts.length} Sentry-compatible account(s) in ${args.config}.\n`);
|
|
4004
4163
|
}
|
|
4005
4164
|
}
|
|
4165
|
+
if (paddleAccounts.length > 0 && await upsertPaddleAccountsConfig(args.config, paddleAccounts)) {
|
|
4166
|
+
const readiness = await verifyPaddleAccountsConfig(args.config, paddleAccounts);
|
|
4167
|
+
if (readiness.ok) {
|
|
4168
|
+
process.stdout.write(`Configured ${paddleAccounts.length} Paddle account(s) in ${args.config}.\n`);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4006
4171
|
if (coolifyConfig?.baseUrl && await upsertCoolifyConfig(args.config, coolifyConfig)) {
|
|
4007
4172
|
process.stdout.write(`Configured Coolify monitoring for ${coolifyConfig.baseUrl} in ${args.config}.\n`);
|
|
4008
4173
|
}
|
|
@@ -4027,6 +4192,19 @@ async function runConnectorSetupSteps({ rl, args, selected, healthByConnector, a
|
|
|
4027
4192
|
});
|
|
4028
4193
|
}
|
|
4029
4194
|
}
|
|
4195
|
+
if (paddleAccounts.length > 0 && await upsertPaddleAccountsConfig(args.config, paddleAccounts)) {
|
|
4196
|
+
const readiness = await verifyPaddleAccountsConfig(args.config, paddleAccounts);
|
|
4197
|
+
if (readiness.ok) {
|
|
4198
|
+
process.stdout.write(`Paddle account config is up to date in ${args.config}.\n`);
|
|
4199
|
+
}
|
|
4200
|
+
else {
|
|
4201
|
+
postSetupBlockers.push({
|
|
4202
|
+
check: 'connection:paddle',
|
|
4203
|
+
detail: readiness.detail,
|
|
4204
|
+
remediation: 'Rerun Paddle setup so the active config persists sources.paddle.enabled=true and sources.paddle.accounts[].',
|
|
4205
|
+
});
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4030
4208
|
if (coolifyConfig?.baseUrl && await upsertCoolifyConfig(args.config, coolifyConfig)) {
|
|
4031
4209
|
process.stdout.write(`Coolify config is up to date in ${args.config}.\n`);
|
|
4032
4210
|
}
|