@whop/cli 0.3.0 → 0.4.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/README.md +8 -0
- package/dist/{api-A6NYSDKW.js → api-7L74UEG4.js} +4 -2
- package/dist/{chunk-4WNH276N.js → chunk-2K4WBZA5.js} +4798 -250
- package/dist/chunk-2K4WBZA5.js.map +1 -0
- package/dist/index.js +476 -614
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4WNH276N.js.map +0 -1
- /package/dist/{api-A6NYSDKW.js.map → api-7L74UEG4.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
Cli_exports,
|
|
6
6
|
Identity,
|
|
7
7
|
OAuthError,
|
|
8
|
+
Openapi_exports,
|
|
8
9
|
Profile,
|
|
9
10
|
PromptCancelledError,
|
|
10
11
|
apiBaseUrl,
|
|
12
|
+
apps_spec_default,
|
|
11
13
|
buildTarget,
|
|
12
14
|
buildVersion,
|
|
13
15
|
chooseAuthMethod,
|
|
@@ -33,7 +35,7 @@ import {
|
|
|
33
35
|
switchProfile,
|
|
34
36
|
upsertProfile,
|
|
35
37
|
validateApiKey
|
|
36
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-2K4WBZA5.js";
|
|
37
39
|
import {
|
|
38
40
|
external_exports
|
|
39
41
|
} from "./chunk-KFCNNWPI.js";
|
|
@@ -312,7 +314,7 @@ async function loginAdapter(c2) {
|
|
|
312
314
|
const accountId = getActiveProfile()?.accountId ?? "";
|
|
313
315
|
if (accountId) {
|
|
314
316
|
try {
|
|
315
|
-
const { createWhopFetch: createWhopFetch2 } = await import("./api-
|
|
317
|
+
const { createWhopFetch: createWhopFetch2 } = await import("./api-7L74UEG4.js");
|
|
316
318
|
const fetch3 = createWhopFetch2();
|
|
317
319
|
const res = await fetch3(
|
|
318
320
|
new Request(
|
|
@@ -592,11 +594,11 @@ function buildAuthGroup() {
|
|
|
592
594
|
}
|
|
593
595
|
|
|
594
596
|
// src/apps/commands.ts
|
|
595
|
-
import { isCancel, select, text } from "@clack/prompts";
|
|
597
|
+
import { isCancel, select, spinner, text } from "@clack/prompts";
|
|
596
598
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
597
599
|
import { createHash } from "crypto";
|
|
598
600
|
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
|
|
599
|
-
import { join as join3, resolve as resolve2 } from "path";
|
|
601
|
+
import { join as join3, relative, resolve as resolve2 } from "path";
|
|
600
602
|
import chalk from "chalk";
|
|
601
603
|
|
|
602
604
|
// src/journey/client.ts
|
|
@@ -653,13 +655,6 @@ async function createAppBuild(input) {
|
|
|
653
655
|
async function getAppBuild(buildId) {
|
|
654
656
|
return makeWhopRequest("GET", `/app_builds/${buildId}`);
|
|
655
657
|
}
|
|
656
|
-
async function listAppBuilds(appId) {
|
|
657
|
-
const response = await makeWhopRequest(
|
|
658
|
-
"GET",
|
|
659
|
-
`/app_builds?app_id=${encodeURIComponent(appId)}&platform=web`
|
|
660
|
-
);
|
|
661
|
-
return response.data ?? [];
|
|
662
|
-
}
|
|
663
658
|
async function promoteAppBuild(buildId) {
|
|
664
659
|
return makeWhopRequest(
|
|
665
660
|
"POST",
|
|
@@ -767,7 +762,11 @@ var DLX_RUNNERS = {
|
|
|
767
762
|
bun: ["bunx"],
|
|
768
763
|
yarn: ["yarn", "dlx"]
|
|
769
764
|
};
|
|
770
|
-
var PM_PRIORITY = [
|
|
765
|
+
var PM_PRIORITY = [
|
|
766
|
+
"bun",
|
|
767
|
+
"pnpm",
|
|
768
|
+
"npm"
|
|
769
|
+
];
|
|
771
770
|
function isPmInstalled(pm) {
|
|
772
771
|
const result = spawnSync(pm, ["--version"], {
|
|
773
772
|
stdio: "ignore",
|
|
@@ -791,6 +790,34 @@ function pmInstallHint(pm) {
|
|
|
791
790
|
return "npm install -g yarn";
|
|
792
791
|
}
|
|
793
792
|
}
|
|
793
|
+
var TANSTACK_CREATE_FLAGS = [
|
|
794
|
+
"--framework",
|
|
795
|
+
"React",
|
|
796
|
+
"--deployment",
|
|
797
|
+
"cloudflare",
|
|
798
|
+
"--no-git",
|
|
799
|
+
"--no-install",
|
|
800
|
+
"--no-intent",
|
|
801
|
+
"--no-toolchain",
|
|
802
|
+
"--no-examples",
|
|
803
|
+
"--non-interactive"
|
|
804
|
+
];
|
|
805
|
+
function tanstackCreateCommand(pm, projectName) {
|
|
806
|
+
return [
|
|
807
|
+
...DLX_RUNNERS[pm],
|
|
808
|
+
"@tanstack/cli@latest",
|
|
809
|
+
"create",
|
|
810
|
+
projectName,
|
|
811
|
+
...TANSTACK_CREATE_FLAGS
|
|
812
|
+
].join(" ");
|
|
813
|
+
}
|
|
814
|
+
function outputTail(result) {
|
|
815
|
+
const combined = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
816
|
+
if (!combined) return "";
|
|
817
|
+
return `
|
|
818
|
+
|
|
819
|
+
${combined.split("\n").slice(-15).join("\n")}`;
|
|
820
|
+
}
|
|
794
821
|
function runTanstackCreate(options) {
|
|
795
822
|
const [runner, ...runnerArgs] = DLX_RUNNERS[options.pm];
|
|
796
823
|
const result = spawnSync(
|
|
@@ -802,26 +829,18 @@ function runTanstackCreate(options) {
|
|
|
802
829
|
options.projectName,
|
|
803
830
|
"--target-dir",
|
|
804
831
|
options.targetDir,
|
|
805
|
-
|
|
806
|
-
"React",
|
|
807
|
-
"--deployment",
|
|
808
|
-
"cloudflare",
|
|
809
|
-
"--no-git",
|
|
810
|
-
"--no-install",
|
|
811
|
-
"--no-intent",
|
|
812
|
-
"--no-toolchain",
|
|
813
|
-
"--no-examples",
|
|
814
|
-
"--non-interactive"
|
|
832
|
+
...TANSTACK_CREATE_FLAGS
|
|
815
833
|
],
|
|
816
834
|
{
|
|
817
|
-
stdio:
|
|
835
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
836
|
+
encoding: "utf-8",
|
|
818
837
|
shell: SPAWN_VIA_SHELL
|
|
819
838
|
}
|
|
820
839
|
);
|
|
821
840
|
if (result.error) throw result.error;
|
|
822
841
|
if (result.status !== 0) {
|
|
823
842
|
throw new Error(
|
|
824
|
-
`\`${options.pm === "npm" ? "npx" : options.pm} @tanstack/cli create\` failed (exit ${result.status}). Check your network connection and try again
|
|
843
|
+
`\`${options.pm === "npm" ? "npx" : options.pm} @tanstack/cli create\` failed (exit ${result.status}). Check your network connection and try again.${outputTail(result)}`
|
|
825
844
|
);
|
|
826
845
|
}
|
|
827
846
|
}
|
|
@@ -831,6 +850,11 @@ var VITE_CONFIG_NAMES = [
|
|
|
831
850
|
"vite.config.js",
|
|
832
851
|
"vite.config.mjs"
|
|
833
852
|
];
|
|
853
|
+
function findViteConfig(dir) {
|
|
854
|
+
return VITE_CONFIG_NAMES.map((name) => join2(dir, name)).find(
|
|
855
|
+
(path3) => existsSync2(path3)
|
|
856
|
+
);
|
|
857
|
+
}
|
|
834
858
|
var WRANGLER_CONFIG_NAMES = ["wrangler.jsonc", "wrangler.json"];
|
|
835
859
|
function cliDependencyVersion() {
|
|
836
860
|
return buildVersion ? `^${buildVersion}` : "latest";
|
|
@@ -867,9 +891,7 @@ function patchPackageJson(dir, manualSteps) {
|
|
|
867
891
|
`);
|
|
868
892
|
}
|
|
869
893
|
function patchViteConfig(dir, manualSteps) {
|
|
870
|
-
const file =
|
|
871
|
-
(path3) => existsSync2(path3)
|
|
872
|
-
);
|
|
894
|
+
const file = findViteConfig(dir);
|
|
873
895
|
if (!file) {
|
|
874
896
|
manualSteps.push(
|
|
875
897
|
"No vite config found (looked for vite.config.ts/.mts/.js/.mjs). Whop hosting serves Vite builds \u2014 create a vite config that builds with @cloudflare/vite-plugin (emitting dist/client and, for SSR, dist/server) and includes the whop() plugin from @whop/cli/vite in its plugins array."
|
|
@@ -962,26 +984,25 @@ function replaceTopLevelName(content, route) {
|
|
|
962
984
|
|
|
963
985
|
// src/apps/commands.ts
|
|
964
986
|
var BUILD_ARCHIVE = "dist/whop-build.zip";
|
|
965
|
-
var AppSchema = external_exports.object({
|
|
966
|
-
id: external_exports.string(),
|
|
967
|
-
name: external_exports.string(),
|
|
968
|
-
route: external_exports.string().nullable().optional(),
|
|
969
|
-
url: external_exports.string().optional(),
|
|
970
|
-
directory: external_exports.string().optional(),
|
|
971
|
-
install_failed: external_exports.boolean().optional()
|
|
972
|
-
});
|
|
973
987
|
var BuildSchema = external_exports.object({
|
|
974
988
|
id: external_exports.string(),
|
|
975
989
|
status: external_exports.string(),
|
|
976
990
|
is_production: external_exports.boolean().optional(),
|
|
977
991
|
url: external_exports.string().optional()
|
|
978
992
|
});
|
|
979
|
-
|
|
993
|
+
var DeployAbort = class extends Error {
|
|
994
|
+
constructor(options) {
|
|
995
|
+
super(options.message);
|
|
996
|
+
this.options = options;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
function detectProjectPackageManager(projectDir) {
|
|
980
1000
|
if (existsSync3(join3(projectDir, "bun.lock")) || existsSync3(join3(projectDir, "bun.lockb")))
|
|
981
1001
|
return "bun";
|
|
982
1002
|
if (existsSync3(join3(projectDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
983
1003
|
if (existsSync3(join3(projectDir, "yarn.lock"))) return "yarn";
|
|
984
|
-
return "npm";
|
|
1004
|
+
if (existsSync3(join3(projectDir, "package-lock.json"))) return "npm";
|
|
1005
|
+
return detectPackageManager() ?? "npm";
|
|
985
1006
|
}
|
|
986
1007
|
function hasScript(projectDir, script) {
|
|
987
1008
|
try {
|
|
@@ -995,7 +1016,7 @@ function hasScript(projectDir, script) {
|
|
|
995
1016
|
}
|
|
996
1017
|
var SPAWN_VIA_SHELL2 = process.platform === "win32";
|
|
997
1018
|
function runScript(projectDir, script, extraEnv = {}) {
|
|
998
|
-
const pm =
|
|
1019
|
+
const pm = detectProjectPackageManager(projectDir);
|
|
999
1020
|
const result = spawnSync2(pm, ["run", script], {
|
|
1000
1021
|
cwd: projectDir,
|
|
1001
1022
|
stdio: "inherit",
|
|
@@ -1013,501 +1034,341 @@ function runScript(projectDir, script, extraEnv = {}) {
|
|
|
1013
1034
|
);
|
|
1014
1035
|
}
|
|
1015
1036
|
}
|
|
1037
|
+
function runInstall(c2, projectDir) {
|
|
1038
|
+
const pm = detectProjectPackageManager(projectDir);
|
|
1039
|
+
const spin = c2.agent ? null : spinner();
|
|
1040
|
+
spin?.start(`Installing dependencies with ${pm}`);
|
|
1041
|
+
const result = spawnSync2(pm, ["install"], {
|
|
1042
|
+
cwd: projectDir,
|
|
1043
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1044
|
+
encoding: "utf-8",
|
|
1045
|
+
shell: SPAWN_VIA_SHELL2
|
|
1046
|
+
});
|
|
1047
|
+
if (result.error || result.status !== 0) {
|
|
1048
|
+
spin?.error(`${pm} install failed`);
|
|
1049
|
+
throw new Error(
|
|
1050
|
+
`\`${pm} install\` failed${result.status != null ? ` with exit code ${result.status}` : ""} \u2014 run it manually inside ${projectDir}, then re-run \`whop apps deploy\`.${outputTail(result)}`
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
spin?.stop(`Installed dependencies with ${pm}`);
|
|
1054
|
+
}
|
|
1016
1055
|
function log(c2, message) {
|
|
1017
1056
|
if (!c2.agent) console.log(message);
|
|
1018
1057
|
}
|
|
1019
|
-
function loadProject(dir) {
|
|
1020
|
-
const projectDir = dir ? resolve2(dir) : findProjectDir();
|
|
1021
|
-
if (!projectDir) {
|
|
1022
|
-
return {
|
|
1023
|
-
ok: false,
|
|
1024
|
-
error: {
|
|
1025
|
-
code: "NO_PROJECT",
|
|
1026
|
-
message: `No ${APP_CONFIG_FILENAME} found in this directory or any parent. Run \`whop apps create --existing\` to link this project to a Whop app.`,
|
|
1027
|
-
cta: {
|
|
1028
|
-
commands: [
|
|
1029
|
-
{
|
|
1030
|
-
command: "apps create --existing",
|
|
1031
|
-
description: "Link this project to a Whop app"
|
|
1032
|
-
}
|
|
1033
|
-
]
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
const config = readAppConfig(projectDir);
|
|
1039
|
-
if (!config) {
|
|
1040
|
-
return {
|
|
1041
|
-
ok: false,
|
|
1042
|
-
error: {
|
|
1043
|
-
code: "INVALID_CONFIG",
|
|
1044
|
-
message: `${join3(projectDir, APP_CONFIG_FILENAME)} is missing or malformed. Run \`whop apps create --existing\` to regenerate it.`
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
return { ok: true, projectDir, config };
|
|
1049
|
-
}
|
|
1050
1058
|
async function promptText(message, placeholder, initialValue) {
|
|
1051
1059
|
const value = await text({ message, placeholder, initialValue });
|
|
1052
1060
|
if (isCancel(value)) return null;
|
|
1053
1061
|
return String(value);
|
|
1054
1062
|
}
|
|
1063
|
+
function cancelled() {
|
|
1064
|
+
throw new DeployAbort({
|
|
1065
|
+
code: "CANCELLED",
|
|
1066
|
+
message: "Cancelled.",
|
|
1067
|
+
exitCode: 130
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1055
1070
|
function formatManualSteps(steps) {
|
|
1056
1071
|
return steps.map((step, i) => ` ${i + 1}. ${step}`).join("\n");
|
|
1057
1072
|
}
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1073
|
+
function formatAppList(apps) {
|
|
1074
|
+
const recent = [...apps].sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0)).slice(0, 10);
|
|
1075
|
+
return recent.map(
|
|
1076
|
+
(a) => ` ${a.id} ${a.name}${a.route ? ` (${a.route}.whop.app)` : ""}`
|
|
1077
|
+
).join("\n");
|
|
1078
|
+
}
|
|
1079
|
+
async function registerSpecCommands(group, tag) {
|
|
1080
|
+
const spec2 = filterSpecByTag(apps_spec_default, tag);
|
|
1081
|
+
const generated = await Openapi_exports.generateCommands(
|
|
1082
|
+
spec2,
|
|
1083
|
+
createWhopFetch()
|
|
1084
|
+
);
|
|
1085
|
+
for (const [name, entry] of generated) {
|
|
1086
|
+
if ("run" in entry) group.command(name, entry);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function ensurePackageManager() {
|
|
1090
|
+
const pm = detectPackageManager();
|
|
1091
|
+
if (pm) return pm;
|
|
1092
|
+
throw new DeployAbort({
|
|
1093
|
+
code: "NO_PACKAGE_MANAGER",
|
|
1094
|
+
message: `No package manager found (looked for bun, pnpm, npm). Install one and re-run \`whop apps deploy\`:
|
|
1095
|
+
|
|
1096
|
+
bun (recommended): ${pmInstallHint("bun")}
|
|
1097
|
+
pnpm: ${pmInstallHint("pnpm")}
|
|
1098
|
+
npm: ${pmInstallHint("npm")}`
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
async function ensureViteApp(c2, pm, dirOption) {
|
|
1102
|
+
const projectDir = dirOption ? resolve2(dirOption) : findProjectDir() ?? process.cwd();
|
|
1103
|
+
const isViteApp = existsSync3(join3(projectDir, "package.json")) && findViteConfig(projectDir) !== void 0;
|
|
1104
|
+
if (isViteApp) return { projectDir, freshlyScaffolded: false };
|
|
1105
|
+
if (c2.agent) {
|
|
1106
|
+
throw new DeployAbort({
|
|
1107
|
+
code: "NOT_A_VITE_APP",
|
|
1108
|
+
message: [
|
|
1109
|
+
`${projectDir} doesn't look like a Vite app (missing package.json or vite config).`,
|
|
1110
|
+
"",
|
|
1111
|
+
"Ask the user whether to scaffold a new app here, or locate their existing Vite app.",
|
|
1112
|
+
"",
|
|
1113
|
+
"To scaffold a new app (TanStack Start), run:",
|
|
1114
|
+
` ${tanstackCreateCommand(pm, "<app-name>")}`,
|
|
1115
|
+
` cd <app-name> && ${pm} install`,
|
|
1116
|
+
"",
|
|
1117
|
+
"Then re-run `whop apps deploy` from inside the app directory.",
|
|
1118
|
+
"Or, if a Vite app already exists somewhere else, re-run `whop apps deploy --dir <path-to-app>`."
|
|
1119
|
+
].join("\n"),
|
|
1120
|
+
retryable: true,
|
|
1121
|
+
cta: {
|
|
1122
|
+
commands: [
|
|
1123
|
+
{
|
|
1124
|
+
command: "apps deploy",
|
|
1125
|
+
description: "Re-run from inside a Vite app directory"
|
|
1126
|
+
}
|
|
1127
|
+
]
|
|
1128
|
+
}
|
|
1064
1129
|
});
|
|
1065
1130
|
}
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1131
|
+
const choice = await select({
|
|
1132
|
+
message: `${projectDir} doesn't look like a Vite app. What do you want to do?`,
|
|
1133
|
+
options: [
|
|
1134
|
+
{
|
|
1135
|
+
value: "scaffold",
|
|
1136
|
+
label: "Scaffold a new Whop app here (TanStack Start)"
|
|
1137
|
+
},
|
|
1138
|
+
{ value: "cancel", label: "Cancel \u2014 I'll cd into my app directory" }
|
|
1139
|
+
]
|
|
1140
|
+
});
|
|
1141
|
+
if (isCancel(choice) || choice === "cancel") cancelled();
|
|
1142
|
+
const scaffolded = await scaffoldNewApp(c2, pm);
|
|
1143
|
+
return { projectDir: scaffolded, freshlyScaffolded: true };
|
|
1144
|
+
}
|
|
1145
|
+
async function scaffoldNewApp(c2, pm) {
|
|
1146
|
+
const name = await promptText("What is your app called?", "My Site");
|
|
1147
|
+
if (name === null) cancelled();
|
|
1148
|
+
const slug = slugify(name);
|
|
1149
|
+
const routeInput = await promptText(
|
|
1150
|
+
"Which route should it live at? (your-route.whop.app)",
|
|
1151
|
+
slug,
|
|
1152
|
+
slug
|
|
1153
|
+
);
|
|
1154
|
+
if (routeInput === null) cancelled();
|
|
1155
|
+
const route = slugify(routeInput);
|
|
1156
|
+
if (!route) {
|
|
1157
|
+
throw new DeployAbort({
|
|
1158
|
+
code: "ROUTE_REQUIRED",
|
|
1159
|
+
message: `Couldn't derive a route from "${routeInput}" \u2014 use letters and numbers (e.g. my-site).`,
|
|
1160
|
+
retryable: true
|
|
1071
1161
|
});
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
${
|
|
1162
|
+
}
|
|
1163
|
+
const targetDir = resolve2(`./${route}`);
|
|
1164
|
+
if (existsSync3(targetDir) && readdirSync(targetDir).length > 0) {
|
|
1165
|
+
throw new DeployAbort({
|
|
1166
|
+
code: "TARGET_NOT_EMPTY",
|
|
1167
|
+
message: `Target directory is not empty: ${targetDir}. Pick a different route, or run \`whop apps deploy\` from an empty directory.`
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
let app;
|
|
1171
|
+
try {
|
|
1172
|
+
app = await createApp({ name, route });
|
|
1173
|
+
} catch (err) {
|
|
1174
|
+
throw new DeployAbort({
|
|
1175
|
+
code: "CREATE_FAILED",
|
|
1176
|
+
message: err instanceof Error ? err.message : "Failed to register the app",
|
|
1177
|
+
retryable: true
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
const spin = c2.agent ? null : spinner();
|
|
1181
|
+
spin?.start("Scaffolding the latest TanStack Start template");
|
|
1182
|
+
try {
|
|
1183
|
+
runTanstackCreate({ targetDir, projectName: route, pm });
|
|
1184
|
+
spin?.stop(`Scaffolded ${route} from the latest TanStack Start template`);
|
|
1185
|
+
} catch (err) {
|
|
1186
|
+
spin?.error("Scaffold failed");
|
|
1187
|
+
throw new DeployAbort({
|
|
1188
|
+
code: "SCAFFOLD_FAILED",
|
|
1189
|
+
message: `${err instanceof Error ? err.message : "Failed to scaffold the project"}
|
|
1078
1190
|
|
|
1079
|
-
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
if (!c2.agent && !c2.formatExplicit) {
|
|
1083
|
-
console.log(
|
|
1084
|
-
`${chalk.green.bold(`\u2713 Already linked to ${existing.name}`)} ${chalk.dim(`(${existing.app_id})`)} \u2014 wiring verified. Delete ${APP_CONFIG_FILENAME} to relink.`
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
return c2.ok({
|
|
1088
|
-
id: existing.app_id,
|
|
1089
|
-
name: existing.name,
|
|
1090
|
-
route: existing.route,
|
|
1091
|
-
directory: projectDir
|
|
1191
|
+
The app ${app.id} is already registered \u2014 scaffold into a fresh directory and re-run \`whop apps deploy --app ${app.id}\` there.`
|
|
1092
1192
|
});
|
|
1093
1193
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1194
|
+
writeAppConfig(targetDir, { app_id: app.id, name, route });
|
|
1195
|
+
return targetDir;
|
|
1196
|
+
}
|
|
1197
|
+
async function ensureAppLinked(c2, projectDir, appOption) {
|
|
1198
|
+
if (appOption) {
|
|
1199
|
+
let app;
|
|
1200
|
+
try {
|
|
1201
|
+
app = await getApp(appOption);
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
throw new DeployAbort({
|
|
1100
1204
|
code: "APP_NOT_FOUND",
|
|
1101
|
-
message: `
|
|
1205
|
+
message: `Could not load app ${appOption}: ${err instanceof Error ? err.message : "unknown error"}`,
|
|
1206
|
+
retryable: true
|
|
1102
1207
|
});
|
|
1103
1208
|
}
|
|
1209
|
+
const config2 = await withRoute(c2, app);
|
|
1210
|
+
writeAppConfig(projectDir, config2);
|
|
1211
|
+
log(
|
|
1212
|
+
c2,
|
|
1213
|
+
chalk.dim(
|
|
1214
|
+
`Linked ${config2.name} (${config2.app_id}) \u2192 ${APP_CONFIG_FILENAME}`
|
|
1215
|
+
)
|
|
1216
|
+
);
|
|
1217
|
+
return config2;
|
|
1104
1218
|
}
|
|
1105
|
-
|
|
1106
|
-
if (
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1219
|
+
const existing = readAppConfig(projectDir);
|
|
1220
|
+
if (existing) return existing;
|
|
1221
|
+
const apps = await listApps();
|
|
1222
|
+
if (c2.agent) {
|
|
1223
|
+
const reuse = apps.length > 0 ? `This account has existing apps \u2014 ask the user whether to reuse one:
|
|
1224
|
+
${formatAppList(apps)}
|
|
1225
|
+
|
|
1226
|
+
To reuse one: whop apps deploy --app <app_id>
|
|
1227
|
+
|
|
1228
|
+
` : "This account has no apps yet, so a new one must be created.\n\n";
|
|
1229
|
+
throw new DeployAbort({
|
|
1230
|
+
code: "NO_APP_LINKED",
|
|
1231
|
+
message: [
|
|
1232
|
+
`No ${APP_CONFIG_FILENAME} here \u2014 this project isn't linked to a Whop app yet.`,
|
|
1233
|
+
"",
|
|
1234
|
+
`${reuse}To create a new app, ask the user for a name and route (the route becomes <route>.whop.app), then run:`,
|
|
1235
|
+
' whop apps create --name "My Site" --route my-site',
|
|
1236
|
+
"",
|
|
1237
|
+
"Then deploy with the returned app id:",
|
|
1238
|
+
" whop apps deploy --app <app_id>"
|
|
1239
|
+
].join("\n"),
|
|
1240
|
+
retryable: true,
|
|
1241
|
+
cta: {
|
|
1242
|
+
commands: [
|
|
1243
|
+
{
|
|
1244
|
+
command: "apps create",
|
|
1245
|
+
description: "Create a new app (pass --name and --route)"
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
command: "apps deploy",
|
|
1249
|
+
description: "Re-run with --app <app_id> to link and deploy"
|
|
1250
|
+
}
|
|
1251
|
+
]
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
let selected = null;
|
|
1256
|
+
if (apps.length > 0) {
|
|
1257
|
+
const recent = [...apps].sort(
|
|
1258
|
+
(a, b) => (b.created_at ?? 0) - (a.created_at ?? 0)
|
|
1259
|
+
);
|
|
1260
|
+
const choice = await select({
|
|
1261
|
+
message: "Which app should this project deploy to?",
|
|
1262
|
+
options: [
|
|
1263
|
+
...recent.map((a) => ({
|
|
1264
|
+
value: a.id,
|
|
1265
|
+
label: `${a.name} (${a.id}${a.route ? `, ${a.route}.whop.app` : ""})`
|
|
1266
|
+
})),
|
|
1267
|
+
{ value: "__new__", label: "Create a new app" }
|
|
1268
|
+
]
|
|
1269
|
+
});
|
|
1270
|
+
if (isCancel(choice)) cancelled();
|
|
1271
|
+
if (choice !== "__new__") {
|
|
1272
|
+
selected = recent.find((a) => a.id === choice) ?? null;
|
|
1113
1273
|
}
|
|
1114
|
-
const value = await promptText("What is your app called?", "My Site");
|
|
1115
|
-
if (value === null)
|
|
1116
|
-
return c2.error({
|
|
1117
|
-
code: "CANCELLED",
|
|
1118
|
-
message: "Cancelled.",
|
|
1119
|
-
exitCode: 130
|
|
1120
|
-
});
|
|
1121
|
-
name = value;
|
|
1122
1274
|
}
|
|
1123
|
-
if (!
|
|
1124
|
-
const
|
|
1275
|
+
if (!selected) {
|
|
1276
|
+
const name = await promptText("What is your app called?", "My Site");
|
|
1277
|
+
if (name === null) cancelled();
|
|
1125
1278
|
const slug = slugify(name);
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1279
|
+
const routeInput = await promptText(
|
|
1280
|
+
"Which route should it live at? (your-route.whop.app)",
|
|
1281
|
+
slug,
|
|
1282
|
+
slug
|
|
1128
1283
|
);
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
})),
|
|
1137
|
-
{ value: "__new__", label: `Create a new app called "${name}"` }
|
|
1138
|
-
]
|
|
1284
|
+
if (routeInput === null) cancelled();
|
|
1285
|
+
const route = slugify(routeInput);
|
|
1286
|
+
if (!route) {
|
|
1287
|
+
throw new DeployAbort({
|
|
1288
|
+
code: "ROUTE_REQUIRED",
|
|
1289
|
+
message: `Couldn't derive a route from "${routeInput}" \u2014 use letters and numbers (e.g. my-site).`,
|
|
1290
|
+
retryable: true
|
|
1139
1291
|
});
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
linked = similar.find((a) => a.id === choice) ?? null;
|
|
1148
|
-
}
|
|
1149
|
-
} else if (similar.length > 0 && c2.agent) {
|
|
1150
|
-
return c2.error({
|
|
1151
|
-
code: "SIMILAR_APPS_FOUND",
|
|
1152
|
-
message: `Existing app(s) with a similar name: ${similar.map((a) => `${a.name} (${a.id})`).join(
|
|
1153
|
-
", "
|
|
1154
|
-
)}. Pass --link <app_id> to link one, or --name with a distinct name to create new.`,
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
selected = await createApp({ name, route });
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
throw new DeployAbort({
|
|
1297
|
+
code: "CREATE_FAILED",
|
|
1298
|
+
message: err instanceof Error ? err.message : "Failed to register the app",
|
|
1155
1299
|
retryable: true
|
|
1156
1300
|
});
|
|
1157
1301
|
}
|
|
1158
1302
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
)
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1303
|
+
const config = await withRoute(c2, selected);
|
|
1304
|
+
writeAppConfig(projectDir, config);
|
|
1305
|
+
log(
|
|
1306
|
+
c2,
|
|
1307
|
+
chalk.dim(
|
|
1308
|
+
`Linked ${config.name} (${config.app_id}) \u2192 ${APP_CONFIG_FILENAME}`
|
|
1309
|
+
)
|
|
1310
|
+
);
|
|
1311
|
+
return config;
|
|
1312
|
+
}
|
|
1313
|
+
async function withRoute(c2, app) {
|
|
1314
|
+
if (app.route) return { app_id: app.id, name: app.name, route: app.route };
|
|
1315
|
+
if (c2.agent) {
|
|
1316
|
+
throw new DeployAbort({
|
|
1317
|
+
code: "ROUTE_REQUIRED",
|
|
1318
|
+
message: `App ${app.id} (${app.name}) has no route \u2014 hosted apps are served from <route>.whop.app. Ask the user which route to claim, then run:
|
|
1319
|
+
whop apps update ${app.id} --route <route>
|
|
1320
|
+
whop apps deploy --app ${app.id}`,
|
|
1321
|
+
retryable: true,
|
|
1322
|
+
cta: {
|
|
1323
|
+
commands: [
|
|
1324
|
+
{
|
|
1325
|
+
command: `apps update ${app.id}`,
|
|
1326
|
+
description: "Set the route (pass --route)"
|
|
1327
|
+
}
|
|
1328
|
+
]
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1173
1331
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1332
|
+
const slug = slugify(app.name);
|
|
1333
|
+
const routeInput = await promptText(
|
|
1334
|
+
`${app.name} has no route yet. Which route should it live at? (your-route.whop.app)`,
|
|
1335
|
+
slug,
|
|
1336
|
+
slug
|
|
1337
|
+
);
|
|
1338
|
+
if (routeInput === null) cancelled();
|
|
1339
|
+
const route = slugify(routeInput);
|
|
1340
|
+
if (!route) {
|
|
1341
|
+
throw new DeployAbort({
|
|
1176
1342
|
code: "ROUTE_REQUIRED",
|
|
1177
|
-
message: `Couldn't derive a route from "${
|
|
1343
|
+
message: `Couldn't derive a route from "${routeInput}" \u2014 use letters and numbers (e.g. my-site).`,
|
|
1178
1344
|
retryable: true
|
|
1179
1345
|
});
|
|
1180
1346
|
}
|
|
1181
|
-
let whopApp;
|
|
1182
1347
|
try {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
} else {
|
|
1190
|
-
whopApp = await createApp({
|
|
1191
|
-
name,
|
|
1192
|
-
route,
|
|
1193
|
-
...options.companyId ? { company_id: options.companyId } : {}
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1348
|
+
const updated = await updateAppRoute(app.id, route);
|
|
1349
|
+
return {
|
|
1350
|
+
app_id: updated.id,
|
|
1351
|
+
name: updated.name,
|
|
1352
|
+
route: updated.route ?? route
|
|
1353
|
+
};
|
|
1196
1354
|
} catch (err) {
|
|
1197
|
-
|
|
1198
|
-
code: "
|
|
1199
|
-
message: err instanceof Error ? err.message : "Failed to
|
|
1355
|
+
throw new DeployAbort({
|
|
1356
|
+
code: "UPDATE_FAILED",
|
|
1357
|
+
message: err instanceof Error ? err.message : "Failed to set the route",
|
|
1200
1358
|
retryable: true
|
|
1201
1359
|
});
|
|
1202
1360
|
}
|
|
1203
|
-
writeAppConfig(projectDir, { app_id: whopApp.id, name, route });
|
|
1204
|
-
const { manualSteps } = whopifyProject({ targetDir: projectDir, route });
|
|
1205
|
-
if (manualSteps.length > 0) {
|
|
1206
|
-
return c2.error({
|
|
1207
|
-
code: "WHOPIFY_INCOMPLETE",
|
|
1208
|
-
message: `${name} is registered on Whop (${whopApp.id}) and ${APP_CONFIG_FILENAME} is written, but the project isn't fully wired for hosting yet. Complete these steps:
|
|
1209
|
-
|
|
1210
|
-
${formatManualSteps(manualSteps)}
|
|
1211
|
-
|
|
1212
|
-
Then run \`whop apps deploy\`. Re-running \`whop apps create --existing\` re-checks the wiring and applies anything it can.`
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
const url = whopApp.hosted_url ?? void 0;
|
|
1216
|
-
if (!c2.agent && !c2.formatExplicit) {
|
|
1217
|
-
console.log(
|
|
1218
|
-
[
|
|
1219
|
-
"",
|
|
1220
|
-
chalk.green.bold(`\u2713 ${linked ? "Linked" : "Created"} ${name}`),
|
|
1221
|
-
` ${chalk.dim(whopApp.id)}${url ? ` \u2192 ${chalk.cyan.underline(url)}` : ""}`,
|
|
1222
|
-
"",
|
|
1223
|
-
`${chalk.bold("Next")} ${chalk.cyan.bold("whop apps deploy")}`,
|
|
1224
|
-
""
|
|
1225
|
-
].join("\n")
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
return c2.ok({ id: whopApp.id, name, route, url, directory: projectDir });
|
|
1229
1361
|
}
|
|
1230
|
-
function buildAppGroup() {
|
|
1362
|
+
async function buildAppGroup() {
|
|
1231
1363
|
const app = Cli_exports.create("apps", {
|
|
1232
1364
|
description: "Build and deploy fully-hosted web apps on Whop (*.whop.app)"
|
|
1233
1365
|
});
|
|
1234
|
-
app
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
args: external_exports.object({
|
|
1238
|
-
name: external_exports.string().optional().describe("App name (e.g. 'My Site'; when linking, defaults to the linked app's name)")
|
|
1239
|
-
}),
|
|
1240
|
-
options: external_exports.object({
|
|
1241
|
-
route: external_exports.string().optional().describe("Subdomain route to claim (e.g. my-site \u2192 my-site.whop.app)"),
|
|
1242
|
-
dir: external_exports.string().optional().describe(
|
|
1243
|
-
"Directory to scaffold into (defaults to ./<route>); with --existing, the project directory (defaults to the current directory)"
|
|
1244
|
-
),
|
|
1245
|
-
existing: external_exports.boolean().optional().describe(
|
|
1246
|
-
"Wire up the existing Vite project here instead of scaffolding a new one"
|
|
1247
|
-
),
|
|
1248
|
-
link: external_exports.string().optional().describe(
|
|
1249
|
-
"Reuse an existing app by id (app_xxx) instead of creating a new one \u2014 works when scaffolding and with --existing"
|
|
1250
|
-
),
|
|
1251
|
-
company_id: external_exports.string().optional().describe(
|
|
1252
|
-
"Business account to create the app under (defaults to your active account)"
|
|
1253
|
-
),
|
|
1254
|
-
install: external_exports.boolean().optional().describe(
|
|
1255
|
-
"Install dependencies after scaffolding fresh (default: true)"
|
|
1256
|
-
),
|
|
1257
|
-
pm: external_exports.enum(["npm", "pnpm", "bun", "yarn"]).optional().describe(
|
|
1258
|
-
"Package manager to use (default: first of bun, pnpm, npm found on your PATH)"
|
|
1259
|
-
)
|
|
1260
|
-
}),
|
|
1261
|
-
output: AppSchema,
|
|
1262
|
-
outputPolicy: "agent-only",
|
|
1263
|
-
examples: [
|
|
1264
|
-
{
|
|
1265
|
-
args: { name: "My Site" },
|
|
1266
|
-
options: { route: "my-site" },
|
|
1267
|
-
description: "Scaffold 'My Site' into ./my-site, live at my-site.whop.app"
|
|
1268
|
-
},
|
|
1269
|
-
{
|
|
1270
|
-
args: { name: "My Site" },
|
|
1271
|
-
options: { existing: true },
|
|
1272
|
-
description: "Register + wire up the Vite project in the current directory"
|
|
1273
|
-
}
|
|
1274
|
-
],
|
|
1275
|
-
run: async (c2) => {
|
|
1276
|
-
const explicitDir = c2.options.dir ? resolve2(c2.options.dir) : void 0;
|
|
1277
|
-
if (c2.options.existing) {
|
|
1278
|
-
const linkedDir = explicitDir ?? findProjectDir();
|
|
1279
|
-
const projectDir = linkedDir && readAppConfig(linkedDir) ? linkedDir : explicitDir ?? process.cwd();
|
|
1280
|
-
return runRegisterFlow(c2, {
|
|
1281
|
-
dir: projectDir,
|
|
1282
|
-
name: c2.args.name,
|
|
1283
|
-
route: c2.options.route,
|
|
1284
|
-
link: c2.options.link,
|
|
1285
|
-
companyId: c2.options.company_id
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
if (!explicitDir && existsSync3(join3(process.cwd(), "package.json"))) {
|
|
1289
|
-
log(
|
|
1290
|
-
c2,
|
|
1291
|
-
chalk.dim(
|
|
1292
|
-
"Detected an existing project here \u2014 scaffolding a new app in a subdirectory. Pass --existing to wire up this project instead."
|
|
1293
|
-
)
|
|
1294
|
-
);
|
|
1295
|
-
}
|
|
1296
|
-
let linked = null;
|
|
1297
|
-
if (c2.options.link) {
|
|
1298
|
-
const apps = await listApps();
|
|
1299
|
-
linked = apps.find((a) => a.id === c2.options.link) ?? null;
|
|
1300
|
-
if (!linked) {
|
|
1301
|
-
return c2.error({
|
|
1302
|
-
code: "APP_NOT_FOUND",
|
|
1303
|
-
message: `No app ${c2.options.link} found on your account.`
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
let name = c2.args.name ?? linked?.name;
|
|
1308
|
-
if (!name) {
|
|
1309
|
-
if (c2.agent) {
|
|
1310
|
-
return c2.error({
|
|
1311
|
-
code: "NAME_REQUIRED",
|
|
1312
|
-
message: 'Pass a name: `whop apps create "My Site"`.',
|
|
1313
|
-
retryable: true
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
const value = await promptText("What is your app called?", "My Site");
|
|
1317
|
-
if (value === null)
|
|
1318
|
-
return c2.error({
|
|
1319
|
-
code: "CANCELLED",
|
|
1320
|
-
message: "Cancelled.",
|
|
1321
|
-
exitCode: 130
|
|
1322
|
-
});
|
|
1323
|
-
name = value;
|
|
1324
|
-
}
|
|
1325
|
-
let route = c2.options.route ?? linked?.route ?? slugify(name);
|
|
1326
|
-
if (!c2.options.route && !linked?.route && !c2.agent) {
|
|
1327
|
-
const value = await promptText(
|
|
1328
|
-
"Which route should it live at? (your-route.whop.app)",
|
|
1329
|
-
slugify(name),
|
|
1330
|
-
slugify(name)
|
|
1331
|
-
);
|
|
1332
|
-
if (value === null)
|
|
1333
|
-
return c2.error({
|
|
1334
|
-
code: "CANCELLED",
|
|
1335
|
-
message: "Cancelled.",
|
|
1336
|
-
exitCode: 130
|
|
1337
|
-
});
|
|
1338
|
-
route = slugify(value);
|
|
1339
|
-
}
|
|
1340
|
-
if (!route) {
|
|
1341
|
-
return c2.error({
|
|
1342
|
-
code: "ROUTE_REQUIRED",
|
|
1343
|
-
message: `Couldn't derive a route from "${name}" \u2014 pass --route with letters and numbers (e.g. --route my-site).`,
|
|
1344
|
-
retryable: true
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
let pm = c2.options.pm;
|
|
1348
|
-
if (pm && !isPmInstalled(pm)) {
|
|
1349
|
-
return c2.error({
|
|
1350
|
-
code: "PM_NOT_INSTALLED",
|
|
1351
|
-
message: `${pm} is not installed (or not on your PATH). Install it: ${pmInstallHint(pm)}`
|
|
1352
|
-
});
|
|
1353
|
-
}
|
|
1354
|
-
if (!pm) pm = detectPackageManager() ?? void 0;
|
|
1355
|
-
if (!pm) {
|
|
1356
|
-
if (c2.agent) {
|
|
1357
|
-
return c2.error({
|
|
1358
|
-
code: "NO_PACKAGE_MANAGER",
|
|
1359
|
-
message: `No package manager found (looked for bun, pnpm, npm). Install bun (recommended): ${pmInstallHint("bun")} \u2014 then re-run \`whop apps create\`.`
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
const choice = await select({
|
|
1363
|
-
message: "No package manager found (bun, pnpm, or npm). Which one do you want to install?",
|
|
1364
|
-
options: [
|
|
1365
|
-
{ value: "bun", label: "bun", hint: "recommended" },
|
|
1366
|
-
{ value: "pnpm", label: "pnpm" },
|
|
1367
|
-
{ value: "npm", label: "npm", hint: "comes with Node.js" }
|
|
1368
|
-
]
|
|
1369
|
-
});
|
|
1370
|
-
if (isCancel(choice)) {
|
|
1371
|
-
return c2.error({
|
|
1372
|
-
code: "CANCELLED",
|
|
1373
|
-
message: "Cancelled.",
|
|
1374
|
-
exitCode: 130
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
return c2.error({
|
|
1378
|
-
code: "NO_PACKAGE_MANAGER",
|
|
1379
|
-
message: `Install ${String(choice)} with:
|
|
1380
|
-
|
|
1381
|
-
${pmInstallHint(choice)}
|
|
1382
|
-
|
|
1383
|
-
then re-run \`whop apps create\`.`
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
const targetDir = resolve2(c2.options.dir ?? `./${route}`);
|
|
1387
|
-
if (existsSync3(targetDir) && readdirSync(targetDir).length > 0) {
|
|
1388
|
-
return c2.error({
|
|
1389
|
-
code: "TARGET_NOT_EMPTY",
|
|
1390
|
-
message: `Target directory is not empty: ${targetDir}. Choose an empty directory with --dir, or a different route.`
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
let whopApp;
|
|
1394
|
-
try {
|
|
1395
|
-
if (linked) {
|
|
1396
|
-
whopApp = linked;
|
|
1397
|
-
if (!linked.route || c2.options.route && c2.options.route !== linked.route) {
|
|
1398
|
-
whopApp = await updateAppRoute(linked.id, route);
|
|
1399
|
-
}
|
|
1400
|
-
} else {
|
|
1401
|
-
whopApp = await createApp({
|
|
1402
|
-
name,
|
|
1403
|
-
route,
|
|
1404
|
-
...c2.options.company_id ? { company_id: c2.options.company_id } : {}
|
|
1405
|
-
});
|
|
1406
|
-
}
|
|
1407
|
-
} catch (err) {
|
|
1408
|
-
return c2.error({
|
|
1409
|
-
code: "CREATE_FAILED",
|
|
1410
|
-
message: err instanceof Error ? err.message : "Failed to create the app on Whop",
|
|
1411
|
-
retryable: true
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
|
-
let whopifySteps = [];
|
|
1415
|
-
try {
|
|
1416
|
-
log(
|
|
1417
|
-
c2,
|
|
1418
|
-
chalk.dim("\nScaffolding with the latest TanStack Start template...")
|
|
1419
|
-
);
|
|
1420
|
-
runTanstackCreate({
|
|
1421
|
-
targetDir,
|
|
1422
|
-
projectName: route,
|
|
1423
|
-
pm,
|
|
1424
|
-
quiet: Boolean(c2.agent)
|
|
1425
|
-
});
|
|
1426
|
-
whopifySteps = whopifyProject({ targetDir, route }).manualSteps;
|
|
1427
|
-
} catch (err) {
|
|
1428
|
-
return c2.error({
|
|
1429
|
-
code: "SCAFFOLD_FAILED",
|
|
1430
|
-
message: err instanceof Error ? err.message : "Failed to scaffold the project"
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
1433
|
-
writeAppConfig(targetDir, { app_id: whopApp.id, name, route });
|
|
1434
|
-
if (whopifySteps.length > 0) {
|
|
1435
|
-
return c2.error({
|
|
1436
|
-
code: "WHOPIFY_INCOMPLETE",
|
|
1437
|
-
message: `Scaffolded ${targetDir} and registered ${whopApp.id}, but the project needs manual wiring:
|
|
1438
|
-
|
|
1439
|
-
${formatManualSteps(whopifySteps)}
|
|
1440
|
-
|
|
1441
|
-
Then run \`whop apps deploy\`. Re-running \`whop apps create --existing\` in the project re-checks the wiring.`
|
|
1442
|
-
});
|
|
1443
|
-
}
|
|
1444
|
-
let installFailed = false;
|
|
1445
|
-
if (c2.options.install !== false) {
|
|
1446
|
-
log(c2, chalk.dim(`
|
|
1447
|
-
Installing dependencies with ${pm}...`));
|
|
1448
|
-
const result = spawnSync2(pm, ["install"], {
|
|
1449
|
-
cwd: targetDir,
|
|
1450
|
-
stdio: c2.agent ? "ignore" : "inherit",
|
|
1451
|
-
shell: SPAWN_VIA_SHELL2
|
|
1452
|
-
});
|
|
1453
|
-
installFailed = Boolean(result.error) || result.status !== 0;
|
|
1454
|
-
if (installFailed) {
|
|
1455
|
-
log(
|
|
1456
|
-
c2,
|
|
1457
|
-
chalk.yellow(
|
|
1458
|
-
`
|
|
1459
|
-
${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
1460
|
-
)
|
|
1461
|
-
);
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
const url = whopApp.hosted_url ?? void 0;
|
|
1465
|
-
if (!c2.agent && !c2.formatExplicit) {
|
|
1466
|
-
console.log(
|
|
1467
|
-
[
|
|
1468
|
-
"",
|
|
1469
|
-
chalk.green.bold(`\u2713 ${name} is ready`),
|
|
1470
|
-
` ${chalk.dim(whopApp.id)}`,
|
|
1471
|
-
"",
|
|
1472
|
-
` ${chalk.bold("Directory")} ${targetDir}`,
|
|
1473
|
-
...url ? [
|
|
1474
|
-
` ${chalk.bold("URL")} ${chalk.cyan.underline(url)} ${chalk.dim("(after first deploy)")}`
|
|
1475
|
-
] : [],
|
|
1476
|
-
"",
|
|
1477
|
-
`${chalk.bold("Next")}`,
|
|
1478
|
-
` cd ${targetDir}`,
|
|
1479
|
-
` ${chalk.cyan.bold("whop apps dev")} ${chalk.dim("\u2192 local dev server")}`,
|
|
1480
|
-
` ${chalk.cyan.bold("whop apps deploy")} ${chalk.dim("\u2192 ship it live")}`,
|
|
1481
|
-
""
|
|
1482
|
-
].join("\n")
|
|
1483
|
-
);
|
|
1484
|
-
}
|
|
1485
|
-
return c2.ok({
|
|
1486
|
-
id: whopApp.id,
|
|
1487
|
-
name,
|
|
1488
|
-
route,
|
|
1489
|
-
url,
|
|
1490
|
-
directory: targetDir,
|
|
1491
|
-
install_failed: installFailed
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
});
|
|
1495
|
-
app.command("list", {
|
|
1496
|
-
description: "List the apps on your business account",
|
|
1497
|
-
output: external_exports.array(AppSchema),
|
|
1498
|
-
outputPolicy: "agent-only",
|
|
1499
|
-
run: async (c2) => {
|
|
1500
|
-
const apps = await listApps();
|
|
1501
|
-
return c2.ok(
|
|
1502
|
-
apps.map((a) => ({
|
|
1503
|
-
id: a.id,
|
|
1504
|
-
name: a.name,
|
|
1505
|
-
route: a.route,
|
|
1506
|
-
url: a.hosted_url ?? void 0
|
|
1507
|
-
}))
|
|
1508
|
-
);
|
|
1509
|
-
}
|
|
1366
|
+
await registerSpecCommands(app, "Apps");
|
|
1367
|
+
const builds = Cli_exports.create("builds", {
|
|
1368
|
+
description: "Manage app builds (list, inspect, promote)"
|
|
1510
1369
|
});
|
|
1370
|
+
await registerSpecCommands(builds, "App builds");
|
|
1371
|
+
app.command(builds);
|
|
1511
1372
|
app.command("dev", {
|
|
1512
1373
|
description: "Run the local dev server for this app",
|
|
1513
1374
|
hint: "Starts the project's dev script with WHOP_APP_ID set and a short-lived access token injected as WHOP_API_KEY (minted from your CLI credential), so server-side SDK calls work locally without env setup. An explicitly exported WHOP_API_KEY is used as-is.",
|
|
@@ -1515,10 +1376,24 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1515
1376
|
dir: external_exports.string().optional().describe("Project directory (defaults to the current directory)")
|
|
1516
1377
|
}),
|
|
1517
1378
|
run: async (c2) => {
|
|
1518
|
-
const
|
|
1519
|
-
|
|
1379
|
+
const projectDir = c2.options.dir ? resolve2(c2.options.dir) : findProjectDir();
|
|
1380
|
+
const config = projectDir ? readAppConfig(projectDir) : null;
|
|
1381
|
+
if (!projectDir || !config) {
|
|
1382
|
+
return c2.error({
|
|
1383
|
+
code: "NO_PROJECT",
|
|
1384
|
+
message: `No ${APP_CONFIG_FILENAME} found in this directory or any parent. Run \`whop apps deploy\` to link this project to a Whop app.`,
|
|
1385
|
+
cta: {
|
|
1386
|
+
commands: [
|
|
1387
|
+
{
|
|
1388
|
+
command: "apps deploy",
|
|
1389
|
+
description: "Link this project to a Whop app and ship it"
|
|
1390
|
+
}
|
|
1391
|
+
]
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1520
1395
|
const injected = {
|
|
1521
|
-
WHOP_APP_ID:
|
|
1396
|
+
WHOP_APP_ID: config.app_id
|
|
1522
1397
|
};
|
|
1523
1398
|
if (process.env.WHOP_API_KEY) {
|
|
1524
1399
|
log(
|
|
@@ -1548,9 +1423,9 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1548
1423
|
);
|
|
1549
1424
|
}
|
|
1550
1425
|
}
|
|
1551
|
-
log(c2, chalk.dim(`Starting dev server for ${
|
|
1426
|
+
log(c2, chalk.dim(`Starting dev server for ${config.name}...`));
|
|
1552
1427
|
try {
|
|
1553
|
-
runScript(
|
|
1428
|
+
runScript(projectDir, "dev", injected);
|
|
1554
1429
|
} catch (err) {
|
|
1555
1430
|
return c2.error({
|
|
1556
1431
|
code: "DEV_FAILED",
|
|
@@ -1562,11 +1437,14 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1562
1437
|
});
|
|
1563
1438
|
app.command("deploy", {
|
|
1564
1439
|
description: "Build, upload and ship this app live",
|
|
1565
|
-
hint: "
|
|
1440
|
+
hint: "The one-stop deploy: verifies the project is a Whop-ready Vite app (offering to scaffold or link one if not), builds, typechecks, uploads the build, and promotes it to production. Use --skip_promote to upload a preview-only build, then promote it later with `whop apps builds promote <build_id>`.",
|
|
1566
1441
|
options: external_exports.object({
|
|
1567
1442
|
dir: external_exports.string().optional().describe("Project directory (defaults to the current directory)"),
|
|
1568
|
-
|
|
1569
|
-
"
|
|
1443
|
+
app: external_exports.string().optional().describe(
|
|
1444
|
+
"Link this project to an app (app_xxx) before deploying \u2014 writes whop.app.json, replacing any existing link"
|
|
1445
|
+
),
|
|
1446
|
+
skip_promote: external_exports.boolean().optional().describe(
|
|
1447
|
+
"Upload the build without promoting it to production"
|
|
1570
1448
|
),
|
|
1571
1449
|
skip_typecheck: external_exports.boolean().optional().describe("Skip the typecheck step"),
|
|
1572
1450
|
skip_build: external_exports.boolean().optional().describe("Skip the build step and upload the existing dist/ output")
|
|
@@ -1576,20 +1454,45 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1576
1454
|
examples: [
|
|
1577
1455
|
{ description: "Build and ship to production" },
|
|
1578
1456
|
{
|
|
1579
|
-
options: {
|
|
1457
|
+
options: { app: "app_xxxxxxxx" },
|
|
1458
|
+
description: "Link this project to an existing app, then deploy"
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
options: { skip_promote: true },
|
|
1580
1462
|
description: "Upload a preview build without promoting"
|
|
1581
1463
|
}
|
|
1582
1464
|
],
|
|
1583
1465
|
run: async (c2) => {
|
|
1584
|
-
const project = loadProject(c2.options.dir);
|
|
1585
|
-
if (!project.ok) return c2.error(project.error);
|
|
1586
|
-
const { projectDir, config } = project;
|
|
1587
1466
|
try {
|
|
1467
|
+
const pm = ensurePackageManager();
|
|
1468
|
+
const { projectDir, freshlyScaffolded } = await ensureViteApp(
|
|
1469
|
+
c2,
|
|
1470
|
+
pm,
|
|
1471
|
+
c2.options.dir
|
|
1472
|
+
);
|
|
1473
|
+
const config = await ensureAppLinked(c2, projectDir, c2.options.app);
|
|
1474
|
+
const { manualSteps } = whopifyProject({
|
|
1475
|
+
targetDir: projectDir,
|
|
1476
|
+
route: config.route
|
|
1477
|
+
});
|
|
1478
|
+
if (manualSteps.length > 0) {
|
|
1479
|
+
return c2.error({
|
|
1480
|
+
code: "WHOPIFY_INCOMPLETE",
|
|
1481
|
+
message: `${config.name} is linked (${config.app_id}), but the project isn't fully wired for Whop hosting yet. Complete these steps:
|
|
1482
|
+
|
|
1483
|
+
${formatManualSteps(manualSteps)}
|
|
1484
|
+
|
|
1485
|
+
Then re-run \`whop apps deploy\` \u2014 it re-checks the wiring and applies anything it can.`
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1588
1488
|
if (!c2.options.skip_build) {
|
|
1589
|
-
|
|
1489
|
+
if (freshlyScaffolded || !existsSync3(join3(projectDir, "node_modules", "@whop", "cli"))) {
|
|
1490
|
+
runInstall(c2, projectDir);
|
|
1491
|
+
}
|
|
1492
|
+
log(c2, chalk.dim("[1/4] Building..."));
|
|
1590
1493
|
runScript(projectDir, "build", { WHOP_APP_ID: config.app_id });
|
|
1591
1494
|
if (!c2.options.skip_typecheck && hasScript(projectDir, "typecheck")) {
|
|
1592
|
-
log(c2, chalk.dim("[2/
|
|
1495
|
+
log(c2, chalk.dim("[2/4] Typechecking..."));
|
|
1593
1496
|
runScript(projectDir, "typecheck");
|
|
1594
1497
|
}
|
|
1595
1498
|
}
|
|
@@ -1600,26 +1503,50 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1600
1503
|
message: `No build archive at ${BUILD_ARCHIVE}. Add the whop() plugin from @whop/cli/vite to your vite config, then build. See https://whop.com/docs/apps/hosting.`
|
|
1601
1504
|
});
|
|
1602
1505
|
}
|
|
1603
|
-
log(c2, chalk.dim("[3/3] Uploading + creating build..."));
|
|
1604
1506
|
const zip = readFileSync3(archivePath);
|
|
1507
|
+
const sizeMb = (zip.byteLength / 1024 / 1024).toFixed(1);
|
|
1605
1508
|
const checksum = createHash("sha256").update(zip).digest("hex");
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
await
|
|
1618
|
-
|
|
1509
|
+
const spin = c2.agent ? null : spinner();
|
|
1510
|
+
spin?.start(`[3/4] Uploading build archive (${sizeMb} MB)`);
|
|
1511
|
+
let build;
|
|
1512
|
+
try {
|
|
1513
|
+
const fileId = await uploadBuildArchive(
|
|
1514
|
+
new Uint8Array(zip),
|
|
1515
|
+
`${config.route}-build.zip`
|
|
1516
|
+
);
|
|
1517
|
+
spin?.message("[3/4] Processing archive");
|
|
1518
|
+
await waitForFileReady(fileId);
|
|
1519
|
+
spin?.message("[3/4] Creating build");
|
|
1520
|
+
build = await createAppBuild({
|
|
1521
|
+
app_id: config.app_id,
|
|
1522
|
+
checksum,
|
|
1523
|
+
file_id: fileId
|
|
1524
|
+
});
|
|
1525
|
+
spin?.stop(`[3/4] Build uploaded (${build.id})`);
|
|
1526
|
+
} catch (err) {
|
|
1527
|
+
spin?.error("[3/4] Upload failed");
|
|
1528
|
+
throw err;
|
|
1529
|
+
}
|
|
1530
|
+
if (!c2.options.skip_promote) {
|
|
1531
|
+
const promoteSpin = c2.agent ? null : spinner();
|
|
1532
|
+
promoteSpin?.start("[4/4] Promoting to production");
|
|
1533
|
+
try {
|
|
1534
|
+
await promoteAppBuild(build.id);
|
|
1535
|
+
build = await waitForBuildPromotion(build.id);
|
|
1536
|
+
promoteSpin?.stop(
|
|
1537
|
+
build.is_production ? "[4/4] Promoted to production" : `[4/4] Promotion pending (status: ${build.status})`
|
|
1538
|
+
);
|
|
1539
|
+
} catch (err) {
|
|
1540
|
+
promoteSpin?.error("[4/4] Promote failed");
|
|
1541
|
+
throw err;
|
|
1542
|
+
}
|
|
1543
|
+
} else {
|
|
1544
|
+
log(c2, chalk.dim("[4/4] Skipping promotion (--skip_promote)"));
|
|
1619
1545
|
}
|
|
1620
1546
|
const promoted = build.is_production === true;
|
|
1621
1547
|
const productionUrl = promoted ? (await getApp(config.app_id)).hosted_url ?? void 0 : void 0;
|
|
1622
1548
|
if (!c2.agent && !c2.formatExplicit) {
|
|
1549
|
+
const cdTarget = relative(process.cwd(), projectDir) || ".";
|
|
1623
1550
|
console.log(
|
|
1624
1551
|
[
|
|
1625
1552
|
"",
|
|
@@ -1631,19 +1558,39 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1631
1558
|
...promoted && productionUrl ? [
|
|
1632
1559
|
` ${chalk.bold("Live")} ${chalk.cyan.underline(productionUrl)}`
|
|
1633
1560
|
] : [
|
|
1634
|
-
`${chalk.bold("Promote when ready")} ${chalk.cyan.bold(`whop apps promote ${build.id}`)}`
|
|
1561
|
+
`${chalk.bold("Promote when ready")} ${chalk.cyan.bold(`whop apps builds promote ${build.id}`)}`
|
|
1635
1562
|
],
|
|
1563
|
+
...freshlyScaffolded ? [
|
|
1564
|
+
"",
|
|
1565
|
+
chalk.bold("Next"),
|
|
1566
|
+
...cdTarget !== "." ? [` cd ${cdTarget}`] : [],
|
|
1567
|
+
` ${chalk.cyan.bold("whop apps dev")} ${chalk.dim("\u2192 local dev server")}`,
|
|
1568
|
+
` ${chalk.cyan.bold("whop apps deploy")} ${chalk.dim("\u2192 ship your changes")}`
|
|
1569
|
+
] : [],
|
|
1636
1570
|
""
|
|
1637
1571
|
].join("\n")
|
|
1638
1572
|
);
|
|
1639
1573
|
}
|
|
1640
|
-
return c2.ok(
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1574
|
+
return c2.ok(
|
|
1575
|
+
{
|
|
1576
|
+
id: build.id,
|
|
1577
|
+
status: build.status,
|
|
1578
|
+
is_production: promoted,
|
|
1579
|
+
url: productionUrl
|
|
1580
|
+
},
|
|
1581
|
+
promoted ? void 0 : {
|
|
1582
|
+
cta: {
|
|
1583
|
+
commands: [
|
|
1584
|
+
{
|
|
1585
|
+
command: `apps builds promote ${build.id}`,
|
|
1586
|
+
description: "Promote this build to production"
|
|
1587
|
+
}
|
|
1588
|
+
]
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
);
|
|
1646
1592
|
} catch (err) {
|
|
1593
|
+
if (err instanceof DeployAbort) return c2.error(err.options);
|
|
1647
1594
|
return c2.error({
|
|
1648
1595
|
code: "DEPLOY_FAILED",
|
|
1649
1596
|
message: err instanceof Error ? err.message : "Deploy failed",
|
|
@@ -1652,91 +1599,6 @@ ${pm} install failed \u2014 run it manually inside ${targetDir}.`
|
|
|
1652
1599
|
}
|
|
1653
1600
|
}
|
|
1654
1601
|
});
|
|
1655
|
-
app.command("promote", {
|
|
1656
|
-
description: "Promote a previously uploaded build to production",
|
|
1657
|
-
args: external_exports.object({
|
|
1658
|
-
build_id: external_exports.string().describe("The build to promote (apbu_xxx)")
|
|
1659
|
-
}),
|
|
1660
|
-
options: external_exports.object({
|
|
1661
|
-
dir: external_exports.string().optional().describe("Project directory (defaults to the current directory)")
|
|
1662
|
-
}),
|
|
1663
|
-
output: BuildSchema,
|
|
1664
|
-
outputPolicy: "agent-only",
|
|
1665
|
-
run: async (c2) => {
|
|
1666
|
-
const project = loadProject(c2.options.dir);
|
|
1667
|
-
if (!project.ok) return c2.error(project.error);
|
|
1668
|
-
try {
|
|
1669
|
-
await promoteAppBuild(c2.args.build_id);
|
|
1670
|
-
const build = await waitForBuildPromotion(c2.args.build_id);
|
|
1671
|
-
if (!build.is_production) {
|
|
1672
|
-
return c2.error({
|
|
1673
|
-
code: "PROMOTE_PENDING",
|
|
1674
|
-
message: `Build ${build.id} was accepted but is still processing (status: ${build.status}). It will go live once approved \u2014 re-run \`whop apps promote ${build.id}\` to confirm.`,
|
|
1675
|
-
retryable: true
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
const url = (await getApp(project.config.app_id)).hosted_url ?? void 0;
|
|
1679
|
-
if (!c2.agent && !c2.formatExplicit) {
|
|
1680
|
-
console.log(
|
|
1681
|
-
[
|
|
1682
|
-
"",
|
|
1683
|
-
chalk.green.bold("\u2713 Promoted to production"),
|
|
1684
|
-
` ${chalk.dim(build.id)}`,
|
|
1685
|
-
...url ? [` ${chalk.bold("Live")} ${chalk.cyan.underline(url)}`] : [],
|
|
1686
|
-
""
|
|
1687
|
-
].join("\n")
|
|
1688
|
-
);
|
|
1689
|
-
}
|
|
1690
|
-
return c2.ok({
|
|
1691
|
-
id: build.id,
|
|
1692
|
-
status: build.status,
|
|
1693
|
-
is_production: build.is_production,
|
|
1694
|
-
url
|
|
1695
|
-
});
|
|
1696
|
-
} catch (err) {
|
|
1697
|
-
return c2.error({
|
|
1698
|
-
code: "PROMOTE_FAILED",
|
|
1699
|
-
message: err instanceof Error ? err.message : "Promote failed",
|
|
1700
|
-
retryable: true
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
});
|
|
1705
|
-
app.command("builds", {
|
|
1706
|
-
description: "List builds for this app",
|
|
1707
|
-
options: external_exports.object({
|
|
1708
|
-
dir: external_exports.string().optional().describe("Project directory (defaults to the current directory)")
|
|
1709
|
-
}),
|
|
1710
|
-
output: external_exports.object({ builds: external_exports.array(BuildSchema) }),
|
|
1711
|
-
outputPolicy: "agent-only",
|
|
1712
|
-
run: async (c2) => {
|
|
1713
|
-
const project = loadProject(c2.options.dir);
|
|
1714
|
-
if (!project.ok) return c2.error(project.error);
|
|
1715
|
-
try {
|
|
1716
|
-
const builds = await listAppBuilds(project.config.app_id);
|
|
1717
|
-
const rows = builds.map((build) => ({
|
|
1718
|
-
id: build.id,
|
|
1719
|
-
status: build.status,
|
|
1720
|
-
is_production: build.is_production
|
|
1721
|
-
}));
|
|
1722
|
-
if (!c2.agent && !c2.formatExplicit) {
|
|
1723
|
-
console.log("");
|
|
1724
|
-
for (const row of rows) {
|
|
1725
|
-
const marker = row.is_production ? chalk.green("\u25CF production") : chalk.dim(`\u25CB ${row.status}`);
|
|
1726
|
-
console.log(` ${row.id} ${marker}`);
|
|
1727
|
-
}
|
|
1728
|
-
console.log("");
|
|
1729
|
-
}
|
|
1730
|
-
return c2.ok({ builds: rows });
|
|
1731
|
-
} catch (err) {
|
|
1732
|
-
return c2.error({
|
|
1733
|
-
code: "LIST_FAILED",
|
|
1734
|
-
message: err instanceof Error ? err.message : "Failed to list builds",
|
|
1735
|
-
retryable: true
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
});
|
|
1740
1602
|
return app;
|
|
1741
1603
|
}
|
|
1742
1604
|
|
|
@@ -2356,7 +2218,7 @@ ${c.success("\u2713 Your store is fully set up.")}`);
|
|
|
2356
2218
|
// package.json
|
|
2357
2219
|
var package_default = {
|
|
2358
2220
|
name: "@whop/cli",
|
|
2359
|
-
version: "0.
|
|
2221
|
+
version: "0.4.0",
|
|
2360
2222
|
description: "The Whop CLI \u2014 build and manage Whop apps from your terminal. Human and agent friendly.",
|
|
2361
2223
|
keywords: [
|
|
2362
2224
|
"agent",
|
|
@@ -2504,7 +2366,7 @@ var DEFAULT_MANIFEST_URL = "https://github.com/whopio/whop-public-cli/releases/l
|
|
|
2504
2366
|
function manifestUrl(env = process.env) {
|
|
2505
2367
|
return env.WHOP_CLI_MANIFEST_URL?.trim() || DEFAULT_MANIFEST_URL;
|
|
2506
2368
|
}
|
|
2507
|
-
var CHECK_INTERVAL_MS =
|
|
2369
|
+
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
2508
2370
|
function cachePath(env) {
|
|
2509
2371
|
return path.join(configDir(env), "update-check.json");
|
|
2510
2372
|
}
|
|
@@ -2874,7 +2736,7 @@ var HANDWRITTEN_GROUPS = [
|
|
|
2874
2736
|
name: "apps",
|
|
2875
2737
|
description: "Build and deploy fully-hosted web apps (*.whop.app)",
|
|
2876
2738
|
section: "get-started",
|
|
2877
|
-
register: (cli2) => cli2.command(buildAppGroup())
|
|
2739
|
+
register: async (cli2) => cli2.command(await buildAppGroup())
|
|
2878
2740
|
},
|
|
2879
2741
|
{
|
|
2880
2742
|
name: "upgrade",
|
|
@@ -2931,8 +2793,8 @@ var HANDWRITTEN_GROUP_NAMES = new Set(
|
|
|
2931
2793
|
);
|
|
2932
2794
|
var API_GROUPS = groups_default.map(([name, tag]) => ({ name, tag })).filter(({ name }) => !HANDWRITTEN_GROUP_NAMES.has(name));
|
|
2933
2795
|
var COMMAND_GROUPS = [...API_GROUPS, ...HANDWRITTEN_GROUPS];
|
|
2934
|
-
function registerHandwrittenGroups(cli2) {
|
|
2935
|
-
for (const group of HANDWRITTEN_GROUPS) group.register(cli2);
|
|
2796
|
+
async function registerHandwrittenGroups(cli2) {
|
|
2797
|
+
for (const group of HANDWRITTEN_GROUPS) await group.register(cli2);
|
|
2936
2798
|
}
|
|
2937
2799
|
async function setupAgents(cli2) {
|
|
2938
2800
|
for (const argv2 of [
|
|
@@ -3073,7 +2935,7 @@ cli.use(async (c2, next) => {
|
|
|
3073
2935
|
}
|
|
3074
2936
|
return next();
|
|
3075
2937
|
});
|
|
3076
|
-
registerHandwrittenGroups(cli);
|
|
2938
|
+
await registerHandwrittenGroups(cli);
|
|
3077
2939
|
for (const { name, tag } of API_GROUPS) {
|
|
3078
2940
|
cli.command(name, { fetch: fetch2, openapi: spec(tag) });
|
|
3079
2941
|
}
|