@vibecodemax/cli 0.1.8 → 0.1.9
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/cli.js +218 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import { checkS3Context, setupS3Storage } from "./storageS3.js";
|
|
7
7
|
const SETUP_STATE_PATH = path.join(".vibecodemax", "setup-state.json");
|
|
@@ -32,6 +32,10 @@ const STRIPE_EVENTS = [
|
|
|
32
32
|
"invoice.payment_succeeded",
|
|
33
33
|
"invoice.payment_failed",
|
|
34
34
|
];
|
|
35
|
+
const CONCURRENTLY_VERSION = "^9.2.1";
|
|
36
|
+
const TRIGGER_DEV_ALL_SCRIPT = 'concurrently -k -n app,jobs -c cyan,yellow "npm run dev" "npm run trigger:dev"';
|
|
37
|
+
const TRIGGER_STRIPE_DEV_ALL_SCRIPT = 'concurrently -k -n app,jobs,payments -c cyan,yellow,magenta "npm run dev" "npm run trigger:dev" "npm run stripe:listen"';
|
|
38
|
+
const STRIPE_ONLY_DEV_ALL_SCRIPT = 'concurrently -k -n app,payments -c cyan,magenta "npm run dev" "npm run stripe:listen"';
|
|
35
39
|
const LEMON_EVENTS = [
|
|
36
40
|
"order_created",
|
|
37
41
|
"subscription_created",
|
|
@@ -651,6 +655,13 @@ function getSupabaseRunner(dependencyManager) {
|
|
|
651
655
|
return "yarn supabase";
|
|
652
656
|
return "npx supabase";
|
|
653
657
|
}
|
|
658
|
+
function getDependencyInstallCommand(dependencyManager, packageName) {
|
|
659
|
+
if (dependencyManager === "pnpm")
|
|
660
|
+
return `pnpm add -D ${packageName}`;
|
|
661
|
+
if (dependencyManager === "yarn")
|
|
662
|
+
return `yarn add -D ${packageName}`;
|
|
663
|
+
return `npm install --save-dev ${packageName}`;
|
|
664
|
+
}
|
|
654
665
|
function runShellCommand(command, cwd) {
|
|
655
666
|
const result = spawnSync(process.env.SHELL || "/bin/zsh", ["-lc", command], {
|
|
656
667
|
cwd,
|
|
@@ -679,6 +690,17 @@ function runLinkedSupabaseCommand(command, cwd, failureCode, fallbackMessage) {
|
|
|
679
690
|
}
|
|
680
691
|
return typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
681
692
|
}
|
|
693
|
+
function ensureStripeCliInstalled(cwd) {
|
|
694
|
+
const result = spawnSync(process.env.SHELL || "/bin/zsh", ["-lc", "stripe --version"], {
|
|
695
|
+
cwd,
|
|
696
|
+
env: process.env,
|
|
697
|
+
encoding: "utf8",
|
|
698
|
+
});
|
|
699
|
+
if (result.status !== 0) {
|
|
700
|
+
fail("STRIPE_CLI_MISSING", "Stripe CLI is not installed. Install it from https://docs.stripe.com/stripe-cli/install before continuing Stripe localhost setup.");
|
|
701
|
+
}
|
|
702
|
+
return typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
703
|
+
}
|
|
682
704
|
function isMigrationOrderingConflict(message) {
|
|
683
705
|
return /Found local migration files to be inserted before the last migration on remote database\./i.test(message);
|
|
684
706
|
}
|
|
@@ -1016,15 +1038,140 @@ function requireWebhookUrl(value, expectedPath, provider) {
|
|
|
1016
1038
|
}
|
|
1017
1039
|
try {
|
|
1018
1040
|
const parsed = new URL(trimmed);
|
|
1019
|
-
|
|
1020
|
-
|
|
1041
|
+
const isHttps = parsed.protocol === "https:";
|
|
1042
|
+
const isLocalhost = parsed.protocol === "http:" && parsed.hostname === "localhost";
|
|
1043
|
+
if ((!isHttps && !isLocalhost) || parsed.pathname !== expectedPath) {
|
|
1044
|
+
fail("INVALID_WEBHOOK_URL", `${provider} webhook URL must be an https URL ending in ${expectedPath}, or http://localhost ending in ${expectedPath}.`);
|
|
1021
1045
|
}
|
|
1022
1046
|
}
|
|
1023
1047
|
catch {
|
|
1024
|
-
fail("INVALID_WEBHOOK_URL", `${provider} webhook URL must be a valid https URL ending in ${expectedPath}.`);
|
|
1048
|
+
fail("INVALID_WEBHOOK_URL", `${provider} webhook URL must be a valid https URL ending in ${expectedPath}, or http://localhost ending in ${expectedPath}.`);
|
|
1025
1049
|
}
|
|
1026
1050
|
return trimmed;
|
|
1027
1051
|
}
|
|
1052
|
+
function requirePackageJson(cwd) {
|
|
1053
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
1054
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
1055
|
+
fail("MISSING_PACKAGE_JSON", "package.json is missing in the project root.");
|
|
1056
|
+
}
|
|
1057
|
+
try {
|
|
1058
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
1059
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1060
|
+
fail("INVALID_PACKAGE_JSON", "package.json must contain a JSON object.");
|
|
1061
|
+
}
|
|
1062
|
+
return { packageJsonPath, packageJson: parsed };
|
|
1063
|
+
}
|
|
1064
|
+
catch {
|
|
1065
|
+
fail("INVALID_PACKAGE_JSON", "package.json is not valid JSON.");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
function writePackageJson(packageJsonPath, packageJson) {
|
|
1069
|
+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
|
1070
|
+
}
|
|
1071
|
+
function buildStripeListenScript(localhostUrl) {
|
|
1072
|
+
return `stripe listen --events ${STRIPE_EVENTS.join(",")} --forward-to ${localhostUrl}/api/webhooks/stripe`;
|
|
1073
|
+
}
|
|
1074
|
+
function ensureStripeLocalScripts(cwd, dependencyManager, localhostUrl) {
|
|
1075
|
+
const { packageJsonPath, packageJson } = requirePackageJson(cwd);
|
|
1076
|
+
const scripts = packageJson.scripts && typeof packageJson.scripts === "object" && !Array.isArray(packageJson.scripts)
|
|
1077
|
+
? { ...packageJson.scripts }
|
|
1078
|
+
: {};
|
|
1079
|
+
const devDependencies = packageJson.devDependencies && typeof packageJson.devDependencies === "object" && !Array.isArray(packageJson.devDependencies)
|
|
1080
|
+
? { ...packageJson.devDependencies }
|
|
1081
|
+
: {};
|
|
1082
|
+
const dependencies = packageJson.dependencies && typeof packageJson.dependencies === "object" && !Array.isArray(packageJson.dependencies)
|
|
1083
|
+
? packageJson.dependencies
|
|
1084
|
+
: {};
|
|
1085
|
+
const stripeListenScript = buildStripeListenScript(localhostUrl);
|
|
1086
|
+
const changedScripts = [];
|
|
1087
|
+
let installConcurrently = false;
|
|
1088
|
+
if (scripts["stripe:listen"] !== stripeListenScript) {
|
|
1089
|
+
scripts["stripe:listen"] = stripeListenScript;
|
|
1090
|
+
changedScripts.push("stripe:listen");
|
|
1091
|
+
}
|
|
1092
|
+
const currentDevAll = typeof scripts["dev:all"] === "string" ? scripts["dev:all"] : "";
|
|
1093
|
+
if (!currentDevAll) {
|
|
1094
|
+
scripts["dev:all"] = STRIPE_ONLY_DEV_ALL_SCRIPT;
|
|
1095
|
+
changedScripts.push("dev:all");
|
|
1096
|
+
if (!isNonEmptyString(devDependencies.concurrently) && !isNonEmptyString(dependencies.concurrently)) {
|
|
1097
|
+
devDependencies.concurrently = CONCURRENTLY_VERSION;
|
|
1098
|
+
installConcurrently = true;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
else if (currentDevAll === TRIGGER_DEV_ALL_SCRIPT) {
|
|
1102
|
+
scripts["dev:all"] = TRIGGER_STRIPE_DEV_ALL_SCRIPT;
|
|
1103
|
+
changedScripts.push("dev:all");
|
|
1104
|
+
}
|
|
1105
|
+
else if (currentDevAll === STRIPE_ONLY_DEV_ALL_SCRIPT || currentDevAll === TRIGGER_STRIPE_DEV_ALL_SCRIPT) {
|
|
1106
|
+
// already configured
|
|
1107
|
+
}
|
|
1108
|
+
else if (currentDevAll.includes("npm run stripe:listen")) {
|
|
1109
|
+
// preserve user-updated script if Stripe listener is already included
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
fail("DEV_ALL_CONFLICT", "package.json already defines dev:all in an unexpected shape. Update it manually to include npm run stripe:listen, or reset it to the generated Trigger pattern before retrying.");
|
|
1113
|
+
}
|
|
1114
|
+
packageJson.scripts = scripts;
|
|
1115
|
+
if (Object.keys(devDependencies).length > 0) {
|
|
1116
|
+
packageJson.devDependencies = devDependencies;
|
|
1117
|
+
}
|
|
1118
|
+
writePackageJson(packageJsonPath, packageJson);
|
|
1119
|
+
if (installConcurrently) {
|
|
1120
|
+
runShellCommand(getDependencyInstallCommand(dependencyManager, "concurrently"), cwd);
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
changedScripts,
|
|
1124
|
+
installConcurrently,
|
|
1125
|
+
scripts,
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function waitForStripeListenSecret(command, cwd) {
|
|
1129
|
+
return new Promise((resolve, reject) => {
|
|
1130
|
+
const child = spawn(process.env.SHELL || "/bin/zsh", ["-lc", command], {
|
|
1131
|
+
cwd,
|
|
1132
|
+
env: process.env,
|
|
1133
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1134
|
+
detached: true,
|
|
1135
|
+
});
|
|
1136
|
+
let buffer = "";
|
|
1137
|
+
let settled = false;
|
|
1138
|
+
const timeoutId = setTimeout(() => {
|
|
1139
|
+
if (!settled) {
|
|
1140
|
+
finish(new Error("Timed out waiting for Stripe CLI to print a webhook signing secret."));
|
|
1141
|
+
}
|
|
1142
|
+
}, 15000);
|
|
1143
|
+
const finish = (error, secret = "") => {
|
|
1144
|
+
if (settled)
|
|
1145
|
+
return;
|
|
1146
|
+
settled = true;
|
|
1147
|
+
clearTimeout(timeoutId);
|
|
1148
|
+
try {
|
|
1149
|
+
process.kill(-child.pid, "SIGTERM");
|
|
1150
|
+
}
|
|
1151
|
+
catch { }
|
|
1152
|
+
if (error)
|
|
1153
|
+
reject(error);
|
|
1154
|
+
else
|
|
1155
|
+
resolve(secret);
|
|
1156
|
+
};
|
|
1157
|
+
const inspectChunk = (chunk) => {
|
|
1158
|
+
buffer += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
1159
|
+
const match = buffer.match(/whsec_[A-Za-z0-9]+/);
|
|
1160
|
+
if (match) {
|
|
1161
|
+
finish(null, match[0]);
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
child.stdout?.on("data", inspectChunk);
|
|
1165
|
+
child.stderr?.on("data", inspectChunk);
|
|
1166
|
+
child.on("error", (error) => finish(error));
|
|
1167
|
+
child.on("close", (code) => {
|
|
1168
|
+
if (!settled) {
|
|
1169
|
+
const message = buffer.trim() || `stripe listen exited with code ${String(code ?? "")}`.trim();
|
|
1170
|
+
finish(new Error(message || "Failed to capture Stripe webhook signing secret from stripe listen."));
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1028
1175
|
function formEncode(value, prefix = "") {
|
|
1029
1176
|
const entries = [];
|
|
1030
1177
|
if (Array.isArray(value)) {
|
|
@@ -1145,6 +1292,30 @@ function checkStripeKeys(flags) {
|
|
|
1145
1292
|
checks: ["presence", "format", "mode_consistency"],
|
|
1146
1293
|
});
|
|
1147
1294
|
}
|
|
1295
|
+
function checkStripeCli() {
|
|
1296
|
+
const version = ensureStripeCliInstalled(process.cwd());
|
|
1297
|
+
printJson({
|
|
1298
|
+
ok: true,
|
|
1299
|
+
command: "payments check-stripe-cli",
|
|
1300
|
+
ready: true,
|
|
1301
|
+
version,
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
function loginStripeCli(flags) {
|
|
1305
|
+
const { values } = loadLocalEnv();
|
|
1306
|
+
ensureStripeCliInstalled(process.cwd());
|
|
1307
|
+
const authMode = readStringFlag(flags, "auth-mode") === "api_key" ? "api_key" : "tty";
|
|
1308
|
+
const command = authMode === "api_key"
|
|
1309
|
+
? `stripe login --api-key ${shellQuote(requireEnvValue(values, "STRIPE_SECRET_KEY", ".env.local"))}`
|
|
1310
|
+
: "stripe login";
|
|
1311
|
+
runShellCommand(command, process.cwd());
|
|
1312
|
+
printJson({
|
|
1313
|
+
ok: true,
|
|
1314
|
+
command: "payments login-stripe-cli",
|
|
1315
|
+
authMode,
|
|
1316
|
+
authenticated: true,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1148
1319
|
function checkLemonKeys(flags) {
|
|
1149
1320
|
const { values } = loadLocalEnv();
|
|
1150
1321
|
const mode = normalizePaymentsMode(readStringFlag(flags, "mode"));
|
|
@@ -1236,6 +1407,40 @@ async function createStripeWebhook(flags) {
|
|
|
1236
1407
|
envWritten: ["STRIPE_WEBHOOK_SECRET"],
|
|
1237
1408
|
});
|
|
1238
1409
|
}
|
|
1410
|
+
async function setupStripeLocalhost(flags) {
|
|
1411
|
+
const cwd = process.cwd();
|
|
1412
|
+
const dependencyManager = detectDependencyManager(cwd, flags);
|
|
1413
|
+
const localhostUrl = normalizeLocalUrl(readStringFlag(flags, "localhost-url"));
|
|
1414
|
+
const { envLocalPath, values } = loadLocalEnv(cwd);
|
|
1415
|
+
const secretKey = values.STRIPE_SECRET_KEY;
|
|
1416
|
+
if (!isStripeSecretKey(secretKey) || !secretKey.startsWith("sk_test_")) {
|
|
1417
|
+
fail("INVALID_PAYMENTS_ENV", "STRIPE_SECRET_KEY must be a Stripe test secret key in .env.local for localhost Stripe setup.");
|
|
1418
|
+
}
|
|
1419
|
+
ensureStripeCliInstalled(cwd);
|
|
1420
|
+
const scriptResult = ensureStripeLocalScripts(cwd, dependencyManager, localhostUrl);
|
|
1421
|
+
const listenCommand = `stripe listen --api-key ${shellQuote(secretKey.trim())} --events ${STRIPE_EVENTS.join(",")} --forward-to ${localhostUrl}/api/webhooks/stripe`;
|
|
1422
|
+
let signingSecret = "";
|
|
1423
|
+
try {
|
|
1424
|
+
signingSecret = await waitForStripeListenSecret(listenCommand, cwd);
|
|
1425
|
+
}
|
|
1426
|
+
catch (error) {
|
|
1427
|
+
fail("STRIPE_LOCALHOST_SETUP_FAILED", error instanceof Error ? error.message : "Failed to capture Stripe localhost webhook signing secret.");
|
|
1428
|
+
}
|
|
1429
|
+
mergeEnvFile(envLocalPath, {
|
|
1430
|
+
STRIPE_WEBHOOK_SECRET: signingSecret,
|
|
1431
|
+
});
|
|
1432
|
+
printJson({
|
|
1433
|
+
ok: true,
|
|
1434
|
+
command: "payments setup-stripe-localhost",
|
|
1435
|
+
localhostUrl,
|
|
1436
|
+
webhookUrl: `${localhostUrl}/api/webhooks/stripe`,
|
|
1437
|
+
scriptsChanged: scriptResult.changedScripts,
|
|
1438
|
+
installConcurrently: scriptResult.installConcurrently,
|
|
1439
|
+
devAllScript: typeof scriptResult.scripts["dev:all"] === "string" ? scriptResult.scripts["dev:all"] : null,
|
|
1440
|
+
stripeListenScript: typeof scriptResult.scripts["stripe:listen"] === "string" ? scriptResult.scripts["stripe:listen"] : null,
|
|
1441
|
+
envWritten: ["STRIPE_WEBHOOK_SECRET"],
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1239
1444
|
function checkStripeWebhookSecret(flags) {
|
|
1240
1445
|
const { values } = loadLocalEnv();
|
|
1241
1446
|
const mode = normalizePaymentsMode(readStringFlag(flags, "mode"));
|
|
@@ -1389,6 +1594,9 @@ async function main() {
|
|
|
1389
1594
|
"storage check-s3-context",
|
|
1390
1595
|
"storage setup-s3",
|
|
1391
1596
|
"payments check-stripe-keys",
|
|
1597
|
+
"payments check-stripe-cli",
|
|
1598
|
+
"payments login-stripe-cli",
|
|
1599
|
+
"payments setup-stripe-localhost",
|
|
1392
1600
|
"payments create-stripe-webhook",
|
|
1393
1601
|
"payments check-stripe-webhook-secret",
|
|
1394
1602
|
"payments check-lemonsqueezy-keys",
|
|
@@ -1422,6 +1630,12 @@ async function main() {
|
|
|
1422
1630
|
return setupS3Storage(flags);
|
|
1423
1631
|
if (command === "payments" && subcommand === "check-stripe-keys")
|
|
1424
1632
|
return checkStripeKeys(flags);
|
|
1633
|
+
if (command === "payments" && subcommand === "check-stripe-cli")
|
|
1634
|
+
return checkStripeCli();
|
|
1635
|
+
if (command === "payments" && subcommand === "login-stripe-cli")
|
|
1636
|
+
return loginStripeCli(flags);
|
|
1637
|
+
if (command === "payments" && subcommand === "setup-stripe-localhost")
|
|
1638
|
+
return setupStripeLocalhost(flags);
|
|
1425
1639
|
if (command === "payments" && subcommand === "create-stripe-webhook")
|
|
1426
1640
|
return createStripeWebhook(flags);
|
|
1427
1641
|
if (command === "payments" && subcommand === "check-stripe-webhook-secret")
|