@byfungsi/funforge 0.2.2 → 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/dist/__tests__/api.test.js +13 -3
- package/dist/__tests__/mcp.test.js +13 -1
- package/dist/api.d.ts +39 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +54 -0
- package/dist/cli.js +23 -1
- package/dist/commands/apps.d.ts +11 -2
- package/dist/commands/apps.d.ts.map +1 -1
- package/dist/commands/apps.js +71 -5
- package/dist/commands/control.d.ts +26 -0
- package/dist/commands/control.d.ts.map +1 -0
- package/dist/commands/control.js +91 -0
- package/dist/commands/deploy.js +8 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/mcp.js +246 -5
- package/dist/tiers.d.ts +38 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +168 -0
- package/package.json +4 -12
|
@@ -130,11 +130,17 @@ describe("api", () => {
|
|
|
130
130
|
app: { id: "new-app", name: "New App", slug: "new-app" },
|
|
131
131
|
}),
|
|
132
132
|
});
|
|
133
|
-
const result = await createApp({
|
|
133
|
+
const result = await createApp({
|
|
134
|
+
name: "New App",
|
|
135
|
+
tierKey: "cost-optimized.small",
|
|
136
|
+
});
|
|
134
137
|
expect(result.app.name).toBe("New App");
|
|
135
138
|
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/cli/apps"), expect.objectContaining({
|
|
136
139
|
method: "POST",
|
|
137
|
-
body: JSON.stringify({
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
name: "New App",
|
|
142
|
+
tierKey: "cost-optimized.small",
|
|
143
|
+
}),
|
|
138
144
|
}));
|
|
139
145
|
});
|
|
140
146
|
it("should create app with name and slug", async () => {
|
|
@@ -144,7 +150,11 @@ describe("api", () => {
|
|
|
144
150
|
app: { id: "new-app", name: "New App", slug: "custom-slug" },
|
|
145
151
|
}),
|
|
146
152
|
});
|
|
147
|
-
const result = await createApp({
|
|
153
|
+
const result = await createApp({
|
|
154
|
+
name: "New App",
|
|
155
|
+
slug: "custom-slug",
|
|
156
|
+
tierKey: "cost-optimized.small",
|
|
157
|
+
});
|
|
148
158
|
expect(result.app.slug).toBe("custom-slug");
|
|
149
159
|
});
|
|
150
160
|
});
|
|
@@ -124,19 +124,31 @@ describe("MCP Integration", () => {
|
|
|
124
124
|
"funforge_whoami",
|
|
125
125
|
"funforge_apps_list",
|
|
126
126
|
"funforge_apps_create",
|
|
127
|
+
"funforge_apps_delete",
|
|
127
128
|
"funforge_status",
|
|
128
129
|
"funforge_deploy",
|
|
130
|
+
"funforge_stop",
|
|
131
|
+
"funforge_start",
|
|
132
|
+
"funforge_cancel",
|
|
129
133
|
"funforge_env_list",
|
|
130
134
|
"funforge_env_set",
|
|
131
135
|
"funforge_env_unset",
|
|
132
136
|
"funforge_domains_list",
|
|
133
137
|
"funforge_domains_add",
|
|
138
|
+
"funforge_domains_remove",
|
|
139
|
+
"funforge_domains_verify",
|
|
134
140
|
];
|
|
135
141
|
it("should define all expected tools", () => {
|
|
136
142
|
// This is a documentation test to ensure we have all tools
|
|
137
|
-
expect(expectedTools).toHaveLength(
|
|
143
|
+
expect(expectedTools).toHaveLength(16);
|
|
138
144
|
expect(expectedTools).toContain("funforge_deploy");
|
|
139
145
|
expect(expectedTools).toContain("funforge_whoami");
|
|
146
|
+
expect(expectedTools).toContain("funforge_stop");
|
|
147
|
+
expect(expectedTools).toContain("funforge_start");
|
|
148
|
+
expect(expectedTools).toContain("funforge_cancel");
|
|
149
|
+
expect(expectedTools).toContain("funforge_apps_delete");
|
|
150
|
+
expect(expectedTools).toContain("funforge_domains_remove");
|
|
151
|
+
expect(expectedTools).toContain("funforge_domains_verify");
|
|
140
152
|
});
|
|
141
153
|
});
|
|
142
154
|
});
|
package/dist/api.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export declare function updateApp(appId: string, settings: UpdateAppSettings): P
|
|
|
105
105
|
export declare function createApp(data: {
|
|
106
106
|
name: string;
|
|
107
107
|
slug?: string;
|
|
108
|
+
tierKey: string;
|
|
108
109
|
}): Promise<{
|
|
109
110
|
app: App;
|
|
110
111
|
}>;
|
|
@@ -153,5 +154,43 @@ export declare function getDeployment(deploymentId: string): Promise<{
|
|
|
153
154
|
* Stream deployment logs (returns EventSource URL)
|
|
154
155
|
*/
|
|
155
156
|
export declare function getDeploymentLogsUrl(deploymentId: string): string;
|
|
157
|
+
/**
|
|
158
|
+
* Stop the current deployment for an app
|
|
159
|
+
*/
|
|
160
|
+
export declare function stopDeployment(appId: string): Promise<{
|
|
161
|
+
success: boolean;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* Start a stopped deployment for an app
|
|
165
|
+
*/
|
|
166
|
+
export declare function startDeployment(appId: string): Promise<{
|
|
167
|
+
success: boolean;
|
|
168
|
+
}>;
|
|
169
|
+
/**
|
|
170
|
+
* Cancel an in-progress deployment
|
|
171
|
+
*/
|
|
172
|
+
export declare function cancelDeployment(appId: string): Promise<{
|
|
173
|
+
success: boolean;
|
|
174
|
+
}>;
|
|
175
|
+
/**
|
|
176
|
+
* Delete an app
|
|
177
|
+
*/
|
|
178
|
+
export declare function deleteApp(appId: string): Promise<{
|
|
179
|
+
success: boolean;
|
|
180
|
+
}>;
|
|
181
|
+
/**
|
|
182
|
+
* Remove a custom domain from an app
|
|
183
|
+
*/
|
|
184
|
+
export declare function removeDomain(appId: string, domainId: string): Promise<{
|
|
185
|
+
success: boolean;
|
|
186
|
+
}>;
|
|
187
|
+
/**
|
|
188
|
+
* Verify DNS and provision SSL for a domain
|
|
189
|
+
*/
|
|
190
|
+
export declare function verifyDomain(appId: string, domainId: string): Promise<{
|
|
191
|
+
verified: boolean;
|
|
192
|
+
sslStatus?: string;
|
|
193
|
+
message?: string;
|
|
194
|
+
}>;
|
|
156
195
|
export {};
|
|
157
196
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.js
CHANGED
|
@@ -208,3 +208,57 @@ export function getDeploymentLogsUrl(deploymentId) {
|
|
|
208
208
|
const apiKey = getApiKey();
|
|
209
209
|
return `${config.apiUrl}/api/cli/deployments/${deploymentId}/logs?token=${apiKey}`;
|
|
210
210
|
}
|
|
211
|
+
// ============================================
|
|
212
|
+
// DEPLOYMENT CONTROL ENDPOINTS
|
|
213
|
+
// ============================================
|
|
214
|
+
/**
|
|
215
|
+
* Stop the current deployment for an app
|
|
216
|
+
*/
|
|
217
|
+
export async function stopDeployment(appId) {
|
|
218
|
+
return apiRequest(`/api/cli/apps/${appId}/stop`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Start a stopped deployment for an app
|
|
224
|
+
*/
|
|
225
|
+
export async function startDeployment(appId) {
|
|
226
|
+
return apiRequest(`/api/cli/apps/${appId}/start`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Cancel an in-progress deployment
|
|
232
|
+
*/
|
|
233
|
+
export async function cancelDeployment(appId) {
|
|
234
|
+
return apiRequest(`/api/cli/apps/${appId}/cancel`, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Delete an app
|
|
240
|
+
*/
|
|
241
|
+
export async function deleteApp(appId) {
|
|
242
|
+
return apiRequest(`/api/cli/apps/${appId}`, {
|
|
243
|
+
method: "DELETE",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// ============================================
|
|
247
|
+
// DOMAIN ENDPOINTS
|
|
248
|
+
// ============================================
|
|
249
|
+
/**
|
|
250
|
+
* Remove a custom domain from an app
|
|
251
|
+
*/
|
|
252
|
+
export async function removeDomain(appId, domainId) {
|
|
253
|
+
return apiRequest(`/api/cli/apps/${appId}/domains/${domainId}`, {
|
|
254
|
+
method: "DELETE",
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Verify DNS and provision SSL for a domain
|
|
259
|
+
*/
|
|
260
|
+
export async function verifyDomain(appId, domainId) {
|
|
261
|
+
return apiRequest(`/api/cli/apps/${appId}/domains/${domainId}/verify`, {
|
|
262
|
+
method: "POST",
|
|
263
|
+
});
|
|
264
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -10,10 +10,11 @@ import { Command } from "commander";
|
|
|
10
10
|
// Load version from package.json
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
const pkg = require("../package.json");
|
|
13
|
-
import { appsCreateCommand, appsListCommand, linkCommand, } from "./commands/apps.js";
|
|
13
|
+
import { appsCreateCommand, appsDeleteCommand, appsListCommand, linkCommand, } from "./commands/apps.js";
|
|
14
14
|
// Import commands
|
|
15
15
|
import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
|
|
16
16
|
import { configPullCommand, configPushCommand, configShowCommand, } from "./commands/config.js";
|
|
17
|
+
import { cancelCommand, startCommand, stopCommand, } from "./commands/control.js";
|
|
17
18
|
import { deployCommand } from "./commands/deploy.js";
|
|
18
19
|
import { domainsAddCommand, domainsListCommand, domainsRemoveCommand, domainsVerifyCommand, } from "./commands/domains.js";
|
|
19
20
|
import { envListCommand, envSetCommand, envUnsetCommand, } from "./commands/env.js";
|
|
@@ -47,7 +48,13 @@ appsCmd
|
|
|
47
48
|
.command("create <name>")
|
|
48
49
|
.description("Create a new app")
|
|
49
50
|
.option("-s, --slug <slug>", "Custom slug (subdomain)")
|
|
51
|
+
.option("-t, --tier <tier>", "Tier key (e.g., cost-optimized.small)")
|
|
50
52
|
.action(appsCreateCommand);
|
|
53
|
+
appsCmd
|
|
54
|
+
.command("delete <appId>")
|
|
55
|
+
.description("Delete an app")
|
|
56
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
57
|
+
.action(appsDeleteCommand);
|
|
51
58
|
program
|
|
52
59
|
.command("link [appId]")
|
|
53
60
|
.description("Link current directory to an app")
|
|
@@ -62,6 +69,21 @@ program
|
|
|
62
69
|
.option("--no-watch", "Don't watch deployment logs")
|
|
63
70
|
.action(deployCommand);
|
|
64
71
|
// ============================================
|
|
72
|
+
// DEPLOYMENT CONTROL COMMANDS
|
|
73
|
+
// ============================================
|
|
74
|
+
program
|
|
75
|
+
.command("stop")
|
|
76
|
+
.description("Stop the current deployment")
|
|
77
|
+
.action(stopCommand);
|
|
78
|
+
program
|
|
79
|
+
.command("start")
|
|
80
|
+
.description("Start a stopped deployment")
|
|
81
|
+
.action(startCommand);
|
|
82
|
+
program
|
|
83
|
+
.command("cancel")
|
|
84
|
+
.description("Cancel an in-progress deployment")
|
|
85
|
+
.action(cancelCommand);
|
|
86
|
+
// ============================================
|
|
65
87
|
// ENVIRONMENT VARIABLES
|
|
66
88
|
// ============================================
|
|
67
89
|
const envCmd = program
|
package/dist/commands/apps.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Apps Commands
|
|
3
3
|
*
|
|
4
4
|
* - apps list: List all apps
|
|
5
|
-
* - apps create: Create a new app
|
|
5
|
+
* - apps create: Create a new app (with interactive tier selection)
|
|
6
6
|
* - link: Link current directory to an app
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
@@ -14,10 +14,11 @@ export declare function appsListCommand(): Promise<void>;
|
|
|
14
14
|
/**
|
|
15
15
|
* funforge apps create <name>
|
|
16
16
|
*
|
|
17
|
-
* Create a new app.
|
|
17
|
+
* Create a new app with interactive tier selection.
|
|
18
18
|
*/
|
|
19
19
|
export declare function appsCreateCommand(name: string, options: {
|
|
20
20
|
slug?: string;
|
|
21
|
+
tier?: string;
|
|
21
22
|
}): Promise<void>;
|
|
22
23
|
/**
|
|
23
24
|
* funforge link [appId]
|
|
@@ -26,4 +27,12 @@ export declare function appsCreateCommand(name: string, options: {
|
|
|
26
27
|
* If no appId provided, shows interactive selector.
|
|
27
28
|
*/
|
|
28
29
|
export declare function linkCommand(appId?: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* funforge apps delete <appId>
|
|
32
|
+
*
|
|
33
|
+
* Delete an app (requires confirmation).
|
|
34
|
+
*/
|
|
35
|
+
export declare function appsDeleteCommand(appId: string, options: {
|
|
36
|
+
yes?: boolean;
|
|
37
|
+
}): Promise<void>;
|
|
29
38
|
//# sourceMappingURL=apps.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAgCrD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAyCf;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D/D;AAaD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|
package/dist/commands/apps.js
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
* Apps Commands
|
|
3
3
|
*
|
|
4
4
|
* - apps list: List all apps
|
|
5
|
-
* - apps create: Create a new app
|
|
5
|
+
* - apps create: Create a new app (with interactive tier selection)
|
|
6
6
|
* - link: Link current directory to an app
|
|
7
7
|
*/
|
|
8
|
+
import { confirm } from "@inquirer/prompts";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
import ora from "ora";
|
|
10
|
-
import { createApp, getApp, listApps } from "../api.js";
|
|
11
|
+
import { createApp, deleteApp, getApp, listApps } from "../api.js";
|
|
11
12
|
import { isAuthenticated } from "../credentials.js";
|
|
12
13
|
import { handleError } from "../errors.js";
|
|
13
14
|
import { readConfig, updateConfig } from "../project-config.js";
|
|
15
|
+
import { VALID_TIER_KEYS, getTierInfo, isValidTierKey, selectTier, } from "../tiers.js";
|
|
14
16
|
/**
|
|
15
17
|
* funforge apps list
|
|
16
18
|
*
|
|
@@ -44,17 +46,34 @@ export async function appsListCommand() {
|
|
|
44
46
|
/**
|
|
45
47
|
* funforge apps create <name>
|
|
46
48
|
*
|
|
47
|
-
* Create a new app.
|
|
49
|
+
* Create a new app with interactive tier selection.
|
|
48
50
|
*/
|
|
49
51
|
export async function appsCreateCommand(name, options) {
|
|
50
52
|
requireAuth();
|
|
51
|
-
|
|
53
|
+
// Get tier - from flag or interactive prompt
|
|
54
|
+
let tierKey;
|
|
55
|
+
if (options.tier) {
|
|
56
|
+
if (!isValidTierKey(options.tier)) {
|
|
57
|
+
console.log(chalk.red(`Invalid tier: ${options.tier}`));
|
|
58
|
+
console.log(chalk.gray(`Valid tiers: ${VALID_TIER_KEYS.join(", ")}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
tierKey = options.tier;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
tierKey = await selectTier();
|
|
65
|
+
}
|
|
66
|
+
const tierInfo = getTierInfo(tierKey);
|
|
67
|
+
const spinner = ora(`Creating app "${name}" with ${tierInfo?.name ?? tierKey} tier...`).start();
|
|
52
68
|
try {
|
|
53
|
-
const { app } = await createApp({ name, slug: options.slug });
|
|
69
|
+
const { app } = await createApp({ name, slug: options.slug, tierKey });
|
|
54
70
|
spinner.succeed(`App created!`);
|
|
55
71
|
console.log();
|
|
56
72
|
console.log(` Name: ${app.name}`);
|
|
57
73
|
console.log(` Slug: ${chalk.cyan(app.slug)}`);
|
|
74
|
+
if (tierInfo) {
|
|
75
|
+
console.log(` Tier: ${tierInfo.category}/${tierInfo.name} (${tierInfo.credits} credits/mo)`);
|
|
76
|
+
}
|
|
58
77
|
console.log(` ID: ${app.id}`);
|
|
59
78
|
console.log();
|
|
60
79
|
console.log(chalk.gray("Link this directory with:"));
|
|
@@ -133,3 +152,50 @@ function requireAuth() {
|
|
|
133
152
|
process.exit(1);
|
|
134
153
|
}
|
|
135
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* funforge apps delete <appId>
|
|
157
|
+
*
|
|
158
|
+
* Delete an app (requires confirmation).
|
|
159
|
+
*/
|
|
160
|
+
export async function appsDeleteCommand(appId, options) {
|
|
161
|
+
requireAuth();
|
|
162
|
+
// Get app info first
|
|
163
|
+
const infoSpinner = ora("Fetching app info...").start();
|
|
164
|
+
let app;
|
|
165
|
+
try {
|
|
166
|
+
const result = await getApp(appId);
|
|
167
|
+
app = result.app;
|
|
168
|
+
infoSpinner.stop();
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
infoSpinner.fail("Failed to fetch app");
|
|
172
|
+
handleError(error);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
console.log();
|
|
176
|
+
console.log(chalk.bold("App to delete:"));
|
|
177
|
+
console.log(` Name: ${app.name}`);
|
|
178
|
+
console.log(` Slug: ${chalk.cyan(app.slug)}`);
|
|
179
|
+
console.log(` ID: ${app.id}`);
|
|
180
|
+
console.log();
|
|
181
|
+
// Confirm deletion
|
|
182
|
+
if (!options.yes) {
|
|
183
|
+
const confirmed = await confirm({
|
|
184
|
+
message: `Are you sure you want to delete "${app.name}"? This cannot be undone.`,
|
|
185
|
+
default: false,
|
|
186
|
+
});
|
|
187
|
+
if (!confirmed) {
|
|
188
|
+
console.log(chalk.gray("Deletion canceled."));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const deleteSpinner = ora(`Deleting ${app.name}...`).start();
|
|
193
|
+
try {
|
|
194
|
+
await deleteApp(appId);
|
|
195
|
+
deleteSpinner.succeed(`App "${app.name}" deleted`);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
deleteSpinner.fail("Failed to delete app");
|
|
199
|
+
handleError(error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Control Commands
|
|
3
|
+
*
|
|
4
|
+
* - stop: Stop the current deployment
|
|
5
|
+
* - start: Start a stopped deployment
|
|
6
|
+
* - cancel: Cancel an in-progress deployment
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* funforge stop
|
|
10
|
+
*
|
|
11
|
+
* Stop the current deployment for the linked app.
|
|
12
|
+
*/
|
|
13
|
+
export declare function stopCommand(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* funforge start
|
|
16
|
+
*
|
|
17
|
+
* Start a stopped deployment for the linked app.
|
|
18
|
+
*/
|
|
19
|
+
export declare function startCommand(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* funforge cancel
|
|
22
|
+
*
|
|
23
|
+
* Cancel an in-progress deployment for the linked app.
|
|
24
|
+
*/
|
|
25
|
+
export declare function cancelCommand(): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/commands/control.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;;GAIG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAejD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAalD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAanD"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Control Commands
|
|
3
|
+
*
|
|
4
|
+
* - stop: Stop the current deployment
|
|
5
|
+
* - start: Start a stopped deployment
|
|
6
|
+
* - cancel: Cancel an in-progress deployment
|
|
7
|
+
*/
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
import { cancelDeployment, startDeployment, stopDeployment } from "../api.js";
|
|
11
|
+
import { isAuthenticated } from "../credentials.js";
|
|
12
|
+
import { handleError } from "../errors.js";
|
|
13
|
+
import { readConfig } from "../project-config.js";
|
|
14
|
+
/**
|
|
15
|
+
* funforge stop
|
|
16
|
+
*
|
|
17
|
+
* Stop the current deployment for the linked app.
|
|
18
|
+
*/
|
|
19
|
+
export async function stopCommand() {
|
|
20
|
+
requireAuth();
|
|
21
|
+
const appId = await getLinkedAppId();
|
|
22
|
+
const spinner = ora("Stopping deployment...").start();
|
|
23
|
+
try {
|
|
24
|
+
await stopDeployment(appId);
|
|
25
|
+
spinner.succeed("Deployment stopped");
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(chalk.gray("Use `funforge start` to restart the deployment"));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
spinner.fail("Failed to stop deployment");
|
|
31
|
+
handleError(error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* funforge start
|
|
36
|
+
*
|
|
37
|
+
* Start a stopped deployment for the linked app.
|
|
38
|
+
*/
|
|
39
|
+
export async function startCommand() {
|
|
40
|
+
requireAuth();
|
|
41
|
+
const appId = await getLinkedAppId();
|
|
42
|
+
const spinner = ora("Starting deployment...").start();
|
|
43
|
+
try {
|
|
44
|
+
await startDeployment(appId);
|
|
45
|
+
spinner.succeed("Deployment started");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
spinner.fail("Failed to start deployment");
|
|
49
|
+
handleError(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* funforge cancel
|
|
54
|
+
*
|
|
55
|
+
* Cancel an in-progress deployment for the linked app.
|
|
56
|
+
*/
|
|
57
|
+
export async function cancelCommand() {
|
|
58
|
+
requireAuth();
|
|
59
|
+
const appId = await getLinkedAppId();
|
|
60
|
+
const spinner = ora("Canceling deployment...").start();
|
|
61
|
+
try {
|
|
62
|
+
await cancelDeployment(appId);
|
|
63
|
+
spinner.succeed("Deployment canceled");
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
spinner.fail("Failed to cancel deployment");
|
|
67
|
+
handleError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the linked app ID or exit
|
|
72
|
+
*/
|
|
73
|
+
async function getLinkedAppId() {
|
|
74
|
+
const config = await readConfig();
|
|
75
|
+
if (!config?.appId) {
|
|
76
|
+
console.log(chalk.red("Not linked to an app."));
|
|
77
|
+
console.log(chalk.gray("Run `funforge link <app-id>` first."));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
return config.appId;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if user is authenticated, exit if not
|
|
84
|
+
*/
|
|
85
|
+
function requireAuth() {
|
|
86
|
+
if (!isAuthenticated()) {
|
|
87
|
+
console.log(chalk.red("Not authenticated."));
|
|
88
|
+
console.log(chalk.gray("Run `funforge login` first."));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -99,7 +99,7 @@ export async function deployCommand(options = {}) {
|
|
|
99
99
|
console.log();
|
|
100
100
|
// 6. Watch logs if requested
|
|
101
101
|
if (options.watch !== false) {
|
|
102
|
-
await watchDeployment(deployResult.deployment.id);
|
|
102
|
+
await watchDeployment(deployResult.deployment.id, config.appSlug);
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
105
105
|
console.log(chalk.gray("Run with --watch to follow logs"));
|
|
@@ -108,7 +108,7 @@ export async function deployCommand(options = {}) {
|
|
|
108
108
|
/**
|
|
109
109
|
* Watch deployment status until completion
|
|
110
110
|
*/
|
|
111
|
-
async function watchDeployment(deploymentId) {
|
|
111
|
+
async function watchDeployment(deploymentId, appSlug) {
|
|
112
112
|
console.log(chalk.gray("Watching deployment..."));
|
|
113
113
|
console.log();
|
|
114
114
|
const spinner = ora("Building...").start();
|
|
@@ -123,7 +123,12 @@ async function watchDeployment(deploymentId) {
|
|
|
123
123
|
if (deployment.status === "live") {
|
|
124
124
|
spinner.succeed(chalk.green("Deployment live!"));
|
|
125
125
|
console.log();
|
|
126
|
-
//
|
|
126
|
+
// Show deployment URL
|
|
127
|
+
if (appSlug) {
|
|
128
|
+
console.log(chalk.bold("Your app is live at:"));
|
|
129
|
+
console.log(` ${chalk.cyan(`https://sarana-${appSlug}.funforge.app`)}`);
|
|
130
|
+
console.log();
|
|
131
|
+
}
|
|
127
132
|
return;
|
|
128
133
|
}
|
|
129
134
|
if (deployment.status === "failed" || deployment.status === "canceled") {
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAmBpC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CA0EtD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CASjD"}
|
package/dist/errors.js
CHANGED
|
@@ -56,6 +56,17 @@ export function formatApiError(error) {
|
|
|
56
56
|
}
|
|
57
57
|
// 400 - Bad request / validation
|
|
58
58
|
if (error.statusCode === 400) {
|
|
59
|
+
const details = body?.error?.details;
|
|
60
|
+
if (details && Array.isArray(details) && details.length > 0) {
|
|
61
|
+
// Format Zod validation issues nicely
|
|
62
|
+
const issues = details
|
|
63
|
+
.map((d) => {
|
|
64
|
+
const field = d.path?.join(".") || "input";
|
|
65
|
+
return ` - ${field}: ${d.message}`;
|
|
66
|
+
})
|
|
67
|
+
.join("\n");
|
|
68
|
+
return [chalk.red(message || "Invalid input:"), chalk.gray(issues)].join("\n");
|
|
69
|
+
}
|
|
59
70
|
return chalk.red(message || "Invalid request.");
|
|
60
71
|
}
|
|
61
72
|
// 500+ - Server errors
|
package/dist/mcp.js
CHANGED
|
@@ -58,8 +58,24 @@ const TOOLS = [
|
|
|
58
58
|
type: "string",
|
|
59
59
|
description: "Custom slug/subdomain (optional, auto-generated if not provided)",
|
|
60
60
|
},
|
|
61
|
+
tierKey: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Tier key for the app. Cost-optimized tiers auto-sleep after 3h idle. Standard tiers are always-on. Options: cost-optimized.micro (10 credits/mo), cost-optimized.small (25 credits/mo), cost-optimized.medium (50 credits/mo), cost-optimized.large (100 credits/mo), cost-optimized.xlarge (125 credits/mo), standard.micro (50 credits/mo), standard.small (75 credits/mo), standard.medium (125 credits/mo), standard.large (200 credits/mo), standard.xlarge (250 credits/mo)",
|
|
64
|
+
enum: [
|
|
65
|
+
"cost-optimized.micro",
|
|
66
|
+
"cost-optimized.small",
|
|
67
|
+
"cost-optimized.medium",
|
|
68
|
+
"cost-optimized.large",
|
|
69
|
+
"cost-optimized.xlarge",
|
|
70
|
+
"standard.micro",
|
|
71
|
+
"standard.small",
|
|
72
|
+
"standard.medium",
|
|
73
|
+
"standard.large",
|
|
74
|
+
"standard.xlarge",
|
|
75
|
+
],
|
|
76
|
+
},
|
|
61
77
|
},
|
|
62
|
-
required: ["name"],
|
|
78
|
+
required: ["name", "tierKey"],
|
|
63
79
|
},
|
|
64
80
|
},
|
|
65
81
|
{
|
|
@@ -179,6 +195,98 @@ const TOOLS = [
|
|
|
179
195
|
required: ["domain"],
|
|
180
196
|
},
|
|
181
197
|
},
|
|
198
|
+
{
|
|
199
|
+
name: "funforge_domains_remove",
|
|
200
|
+
description: "Remove a custom domain from the linked app.",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
directory: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "Directory with funforge.json (defaults to cwd)",
|
|
207
|
+
},
|
|
208
|
+
domain: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "Domain name or domain ID to remove",
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
required: ["domain"],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "funforge_domains_verify",
|
|
218
|
+
description: "Verify DNS configuration and provision SSL for a custom domain.",
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: {
|
|
222
|
+
directory: {
|
|
223
|
+
type: "string",
|
|
224
|
+
description: "Directory with funforge.json (defaults to cwd)",
|
|
225
|
+
},
|
|
226
|
+
domain: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Domain name or domain ID to verify",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
required: ["domain"],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "funforge_stop",
|
|
236
|
+
description: "Stop the current deployment for the linked app. The app will be scaled down to 0 replicas.",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
directory: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Directory with funforge.json (defaults to cwd)",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
required: [],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "funforge_start",
|
|
250
|
+
description: "Start a stopped deployment for the linked app. Scales the app back up.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
directory: {
|
|
255
|
+
type: "string",
|
|
256
|
+
description: "Directory with funforge.json (defaults to cwd)",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: [],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "funforge_cancel",
|
|
264
|
+
description: "Cancel an in-progress deployment for the linked app. Use this to abort a build or deployment that is stuck.",
|
|
265
|
+
inputSchema: {
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: {
|
|
268
|
+
directory: {
|
|
269
|
+
type: "string",
|
|
270
|
+
description: "Directory with funforge.json (defaults to cwd)",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
required: [],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "funforge_apps_delete",
|
|
278
|
+
description: "Delete an app permanently. This will stop all deployments and remove all associated resources.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
appId: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "The app ID to delete",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
required: ["appId"],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
182
290
|
];
|
|
183
291
|
async function handleToolCall(name, args) {
|
|
184
292
|
try {
|
|
@@ -188,7 +296,7 @@ async function handleToolCall(name, args) {
|
|
|
188
296
|
case "funforge_apps_list":
|
|
189
297
|
return await handleAppsList();
|
|
190
298
|
case "funforge_apps_create":
|
|
191
|
-
return await handleAppsCreate(args.name, args.slug);
|
|
299
|
+
return await handleAppsCreate(args.name, args.tierKey, args.slug);
|
|
192
300
|
case "funforge_status":
|
|
193
301
|
return await handleStatus(args.directory);
|
|
194
302
|
case "funforge_deploy":
|
|
@@ -203,6 +311,18 @@ async function handleToolCall(name, args) {
|
|
|
203
311
|
return await handleDomainsList(args.directory);
|
|
204
312
|
case "funforge_domains_add":
|
|
205
313
|
return await handleDomainsAdd(args.directory, args.domain, args.verificationMethod);
|
|
314
|
+
case "funforge_domains_remove":
|
|
315
|
+
return await handleDomainsRemove(args.directory, args.domain);
|
|
316
|
+
case "funforge_domains_verify":
|
|
317
|
+
return await handleDomainsVerify(args.directory, args.domain);
|
|
318
|
+
case "funforge_stop":
|
|
319
|
+
return await handleStop(args.directory);
|
|
320
|
+
case "funforge_start":
|
|
321
|
+
return await handleStart(args.directory);
|
|
322
|
+
case "funforge_cancel":
|
|
323
|
+
return await handleCancel(args.directory);
|
|
324
|
+
case "funforge_apps_delete":
|
|
325
|
+
return await handleAppsDelete(args.appId);
|
|
206
326
|
default:
|
|
207
327
|
return errorResult(`Unknown tool: ${name}`);
|
|
208
328
|
}
|
|
@@ -240,15 +360,16 @@ async function handleAppsList() {
|
|
|
240
360
|
})),
|
|
241
361
|
});
|
|
242
362
|
}
|
|
243
|
-
async function handleAppsCreate(name, slug) {
|
|
363
|
+
async function handleAppsCreate(name, tierKey, slug) {
|
|
244
364
|
requireAuth();
|
|
245
|
-
const { app } = await createApp({ name, slug });
|
|
365
|
+
const { app } = await createApp({ name, slug, tierKey });
|
|
246
366
|
return successResult({
|
|
247
367
|
message: `App "${app.name}" created successfully`,
|
|
248
368
|
app: {
|
|
249
369
|
id: app.id,
|
|
250
370
|
name: app.name,
|
|
251
371
|
slug: app.slug,
|
|
372
|
+
tierKey,
|
|
252
373
|
},
|
|
253
374
|
});
|
|
254
375
|
}
|
|
@@ -410,6 +531,126 @@ async function handleDomainsAdd(directory, domain, verificationMethod) {
|
|
|
410
531
|
nextStep: "Add the DNS record above, then verify with funforge domains verify command",
|
|
411
532
|
});
|
|
412
533
|
}
|
|
534
|
+
async function handleDomainsRemove(directory, domain) {
|
|
535
|
+
requireAuth();
|
|
536
|
+
const dir = directory || process.cwd();
|
|
537
|
+
const appId = await getLinkedAppId(dir);
|
|
538
|
+
if (!appId) {
|
|
539
|
+
return errorResult(`No app linked in ${dir}.`);
|
|
540
|
+
}
|
|
541
|
+
const { domains } = await apiRequest(`/api/cli/apps/${appId}/domains`);
|
|
542
|
+
const cleanDomain = domain.toLowerCase();
|
|
543
|
+
const targetDomain = domains.find((d) => d.domain.toLowerCase() === cleanDomain || d.id === domain);
|
|
544
|
+
if (!targetDomain) {
|
|
545
|
+
return errorResult(`Domain not found: ${domain}. Use funforge_domains_list to see available domains.`);
|
|
546
|
+
}
|
|
547
|
+
await apiRequest(`/api/cli/apps/${appId}/domains/${targetDomain.id}`, {
|
|
548
|
+
method: "DELETE",
|
|
549
|
+
});
|
|
550
|
+
return successResult({
|
|
551
|
+
message: `Domain ${targetDomain.domain} removed`,
|
|
552
|
+
domainId: targetDomain.id,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
async function handleDomainsVerify(directory, domain) {
|
|
556
|
+
requireAuth();
|
|
557
|
+
const dir = directory || process.cwd();
|
|
558
|
+
const appId = await getLinkedAppId(dir);
|
|
559
|
+
if (!appId) {
|
|
560
|
+
return errorResult(`No app linked in ${dir}.`);
|
|
561
|
+
}
|
|
562
|
+
const { domains } = await apiRequest(`/api/cli/apps/${appId}/domains`);
|
|
563
|
+
const cleanDomain = domain.toLowerCase();
|
|
564
|
+
const targetDomain = domains.find((d) => d.domain.toLowerCase() === cleanDomain || d.id === domain);
|
|
565
|
+
if (!targetDomain) {
|
|
566
|
+
return errorResult(`Domain not found: ${domain}. Use funforge_domains_list to see available domains.`);
|
|
567
|
+
}
|
|
568
|
+
if (targetDomain.verified) {
|
|
569
|
+
return successResult({
|
|
570
|
+
message: `Domain ${targetDomain.domain} is already verified`,
|
|
571
|
+
domain: targetDomain.domain,
|
|
572
|
+
verified: true,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
const result = await apiRequest(`/api/cli/apps/${appId}/domains/${targetDomain.id}/verify`, { method: "POST" });
|
|
576
|
+
if (result.verified) {
|
|
577
|
+
return successResult({
|
|
578
|
+
message: `Domain ${targetDomain.domain} verified successfully`,
|
|
579
|
+
domain: targetDomain.domain,
|
|
580
|
+
verified: true,
|
|
581
|
+
sslStatus: result.sslStatus,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return successResult({
|
|
585
|
+
message: result.message || "Domain verification failed",
|
|
586
|
+
domain: targetDomain.domain,
|
|
587
|
+
verified: false,
|
|
588
|
+
hint: "Make sure your DNS record is correctly configured. DNS changes can take up to 48 hours to propagate.",
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
async function handleStop(directory) {
|
|
592
|
+
requireAuth();
|
|
593
|
+
const dir = directory || process.cwd();
|
|
594
|
+
const appId = await getLinkedAppId(dir);
|
|
595
|
+
if (!appId) {
|
|
596
|
+
return errorResult(`No app linked in ${dir}.`);
|
|
597
|
+
}
|
|
598
|
+
await apiRequest(`/api/cli/apps/${appId}/stop`, {
|
|
599
|
+
method: "POST",
|
|
600
|
+
});
|
|
601
|
+
return successResult({
|
|
602
|
+
message: "Deployment stopped",
|
|
603
|
+
appId,
|
|
604
|
+
note: "Use funforge_start to restart the deployment",
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
async function handleStart(directory) {
|
|
608
|
+
requireAuth();
|
|
609
|
+
const dir = directory || process.cwd();
|
|
610
|
+
const appId = await getLinkedAppId(dir);
|
|
611
|
+
if (!appId) {
|
|
612
|
+
return errorResult(`No app linked in ${dir}.`);
|
|
613
|
+
}
|
|
614
|
+
await apiRequest(`/api/cli/apps/${appId}/start`, {
|
|
615
|
+
method: "POST",
|
|
616
|
+
});
|
|
617
|
+
return successResult({
|
|
618
|
+
message: "Deployment started",
|
|
619
|
+
appId,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
async function handleCancel(directory) {
|
|
623
|
+
requireAuth();
|
|
624
|
+
const dir = directory || process.cwd();
|
|
625
|
+
const appId = await getLinkedAppId(dir);
|
|
626
|
+
if (!appId) {
|
|
627
|
+
return errorResult(`No app linked in ${dir}.`);
|
|
628
|
+
}
|
|
629
|
+
await apiRequest(`/api/cli/apps/${appId}/cancel`, {
|
|
630
|
+
method: "POST",
|
|
631
|
+
});
|
|
632
|
+
return successResult({
|
|
633
|
+
message: "Deployment canceled",
|
|
634
|
+
appId,
|
|
635
|
+
note: "The in-progress build or deployment has been canceled",
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async function handleAppsDelete(appId) {
|
|
639
|
+
requireAuth();
|
|
640
|
+
const { app } = await apiRequest(`/api/cli/apps/${appId}`);
|
|
641
|
+
await apiRequest(`/api/cli/apps/${appId}`, {
|
|
642
|
+
method: "DELETE",
|
|
643
|
+
});
|
|
644
|
+
return successResult({
|
|
645
|
+
message: `App "${app.name}" deleted successfully`,
|
|
646
|
+
app: {
|
|
647
|
+
id: app.id,
|
|
648
|
+
name: app.name,
|
|
649
|
+
slug: app.slug,
|
|
650
|
+
},
|
|
651
|
+
note: "All deployments and resources have been removed",
|
|
652
|
+
});
|
|
653
|
+
}
|
|
413
654
|
// ============================================
|
|
414
655
|
// HELPERS
|
|
415
656
|
// ============================================
|
|
@@ -453,7 +694,7 @@ function formatError(error) {
|
|
|
453
694
|
async function main() {
|
|
454
695
|
const server = new Server({
|
|
455
696
|
name: "funforge",
|
|
456
|
-
version: "0.
|
|
697
|
+
version: "0.3.0",
|
|
457
698
|
}, {
|
|
458
699
|
capabilities: {
|
|
459
700
|
tools: {},
|
package/dist/tiers.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FunForge Tier Selection
|
|
3
|
+
*
|
|
4
|
+
* Interactive tier picker for app creation.
|
|
5
|
+
*/
|
|
6
|
+
/** Tier configuration for display */
|
|
7
|
+
interface TierOption {
|
|
8
|
+
key: string;
|
|
9
|
+
name: string;
|
|
10
|
+
credits: number;
|
|
11
|
+
priceIdr: string;
|
|
12
|
+
memory: string;
|
|
13
|
+
cpu: string;
|
|
14
|
+
category: "cost-optimized" | "standard";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* All available tiers grouped by category
|
|
18
|
+
*
|
|
19
|
+
* Cost-Optimized: Auto-sleeps after 3h idle (cheaper)
|
|
20
|
+
* Standard: Always-on (higher availability)
|
|
21
|
+
*/
|
|
22
|
+
export declare const TIER_OPTIONS: TierOption[];
|
|
23
|
+
export declare const VALID_TIER_KEYS: string[];
|
|
24
|
+
export declare const DEFAULT_TIER = "cost-optimized.small";
|
|
25
|
+
/**
|
|
26
|
+
* Interactive tier selection prompt
|
|
27
|
+
*/
|
|
28
|
+
export declare function selectTier(): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a string is a valid tier key
|
|
31
|
+
*/
|
|
32
|
+
export declare function isValidTierKey(key: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Get tier display info by key
|
|
35
|
+
*/
|
|
36
|
+
export declare function getTierInfo(key: string): TierOption | undefined;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=tiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tiers.d.ts","sourceRoot":"","sources":["../src/tiers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,qCAAqC;AACrC,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,gBAAgB,GAAG,UAAU,CAAC;CACzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,UAAU,EA6FpC,CAAC;AAEF,eAAO,MAAM,eAAe,UAAiC,CAAC;AAC9D,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAanD;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAsClD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE/D"}
|
package/dist/tiers.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FunForge Tier Selection
|
|
3
|
+
*
|
|
4
|
+
* Interactive tier picker for app creation.
|
|
5
|
+
*/
|
|
6
|
+
import { select } from "@inquirer/prompts";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
/**
|
|
9
|
+
* All available tiers grouped by category
|
|
10
|
+
*
|
|
11
|
+
* Cost-Optimized: Auto-sleeps after 3h idle (cheaper)
|
|
12
|
+
* Standard: Always-on (higher availability)
|
|
13
|
+
*/
|
|
14
|
+
export const TIER_OPTIONS = [
|
|
15
|
+
// Cost-Optimized (auto-sleep after 3h idle)
|
|
16
|
+
{
|
|
17
|
+
key: "cost-optimized.micro",
|
|
18
|
+
name: "Micro",
|
|
19
|
+
credits: 10,
|
|
20
|
+
priceIdr: "Rp 10.000",
|
|
21
|
+
memory: "256MB",
|
|
22
|
+
cpu: "0.1",
|
|
23
|
+
category: "cost-optimized",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: "cost-optimized.small",
|
|
27
|
+
name: "Small",
|
|
28
|
+
credits: 25,
|
|
29
|
+
priceIdr: "Rp 25.000",
|
|
30
|
+
memory: "512MB",
|
|
31
|
+
cpu: "0.25",
|
|
32
|
+
category: "cost-optimized",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "cost-optimized.medium",
|
|
36
|
+
name: "Medium",
|
|
37
|
+
credits: 50,
|
|
38
|
+
priceIdr: "Rp 50.000",
|
|
39
|
+
memory: "1GB",
|
|
40
|
+
cpu: "0.5",
|
|
41
|
+
category: "cost-optimized",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "cost-optimized.large",
|
|
45
|
+
name: "Large",
|
|
46
|
+
credits: 100,
|
|
47
|
+
priceIdr: "Rp 100.000",
|
|
48
|
+
memory: "2GB",
|
|
49
|
+
cpu: "1.0",
|
|
50
|
+
category: "cost-optimized",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "cost-optimized.xlarge",
|
|
54
|
+
name: "X-Large",
|
|
55
|
+
credits: 125,
|
|
56
|
+
priceIdr: "Rp 125.000",
|
|
57
|
+
memory: "4GB",
|
|
58
|
+
cpu: "2.0",
|
|
59
|
+
category: "cost-optimized",
|
|
60
|
+
},
|
|
61
|
+
// Standard (always-on)
|
|
62
|
+
{
|
|
63
|
+
key: "standard.micro",
|
|
64
|
+
name: "Micro",
|
|
65
|
+
credits: 50,
|
|
66
|
+
priceIdr: "Rp 50.000",
|
|
67
|
+
memory: "256MB",
|
|
68
|
+
cpu: "0.1",
|
|
69
|
+
category: "standard",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "standard.small",
|
|
73
|
+
name: "Small",
|
|
74
|
+
credits: 75,
|
|
75
|
+
priceIdr: "Rp 75.000",
|
|
76
|
+
memory: "512MB",
|
|
77
|
+
cpu: "0.25",
|
|
78
|
+
category: "standard",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "standard.medium",
|
|
82
|
+
name: "Medium",
|
|
83
|
+
credits: 125,
|
|
84
|
+
priceIdr: "Rp 125.000",
|
|
85
|
+
memory: "1GB",
|
|
86
|
+
cpu: "0.5",
|
|
87
|
+
category: "standard",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: "standard.large",
|
|
91
|
+
name: "Large",
|
|
92
|
+
credits: 200,
|
|
93
|
+
priceIdr: "Rp 200.000",
|
|
94
|
+
memory: "2GB",
|
|
95
|
+
cpu: "1.0",
|
|
96
|
+
category: "standard",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "standard.xlarge",
|
|
100
|
+
name: "X-Large",
|
|
101
|
+
credits: 250,
|
|
102
|
+
priceIdr: "Rp 250.000",
|
|
103
|
+
memory: "4GB",
|
|
104
|
+
cpu: "2.0",
|
|
105
|
+
category: "standard",
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
export const VALID_TIER_KEYS = TIER_OPTIONS.map((t) => t.key);
|
|
109
|
+
export const DEFAULT_TIER = "cost-optimized.small";
|
|
110
|
+
/**
|
|
111
|
+
* Format tier for display in dropdown
|
|
112
|
+
*/
|
|
113
|
+
function formatTier(t) {
|
|
114
|
+
const nameCol = t.name.padEnd(8);
|
|
115
|
+
const creditsCol = `${t.credits.toString().padStart(3)} credits`;
|
|
116
|
+
const priceCol = `(${t.priceIdr})`;
|
|
117
|
+
const resourcesCol = `${t.memory}, ${t.cpu} CPU`;
|
|
118
|
+
return `${nameCol} ${creditsCol} ${priceCol.padEnd(14)} - ${resourcesCol}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Interactive tier selection prompt
|
|
122
|
+
*/
|
|
123
|
+
export async function selectTier() {
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(chalk.bold("Select a tier for your app:"));
|
|
126
|
+
console.log();
|
|
127
|
+
const costOptimized = TIER_OPTIONS.filter((t) => t.category === "cost-optimized");
|
|
128
|
+
const standard = TIER_OPTIONS.filter((t) => t.category === "standard");
|
|
129
|
+
const tierKey = await select({
|
|
130
|
+
message: "Tier",
|
|
131
|
+
choices: [
|
|
132
|
+
// Cost-Optimized group header
|
|
133
|
+
{
|
|
134
|
+
value: "_header_cost",
|
|
135
|
+
name: chalk.dim("── Cost-Optimized (auto-sleep after 3h idle) ──"),
|
|
136
|
+
disabled: true,
|
|
137
|
+
},
|
|
138
|
+
...costOptimized.map((t) => ({
|
|
139
|
+
value: t.key,
|
|
140
|
+
name: ` ${formatTier(t)}`,
|
|
141
|
+
})),
|
|
142
|
+
// Standard group header
|
|
143
|
+
{
|
|
144
|
+
value: "_header_standard",
|
|
145
|
+
name: chalk.dim("── Standard (always-on) ──"),
|
|
146
|
+
disabled: true,
|
|
147
|
+
},
|
|
148
|
+
...standard.map((t) => ({
|
|
149
|
+
value: t.key,
|
|
150
|
+
name: ` ${formatTier(t)}`,
|
|
151
|
+
})),
|
|
152
|
+
],
|
|
153
|
+
default: DEFAULT_TIER,
|
|
154
|
+
});
|
|
155
|
+
return tierKey;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if a string is a valid tier key
|
|
159
|
+
*/
|
|
160
|
+
export function isValidTierKey(key) {
|
|
161
|
+
return VALID_TIER_KEYS.includes(key);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get tier display info by key
|
|
165
|
+
*/
|
|
166
|
+
export function getTierInfo(key) {
|
|
167
|
+
return TIER_OPTIONS.find((t) => t.key === key);
|
|
168
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byfungsi/funforge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Deploy without git, without leaving your editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
"funforge": "./dist/cli.js",
|
|
10
10
|
"funforge-mcp": "./dist/mcp.js"
|
|
11
11
|
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
12
|
+
"files": ["dist", "README.md"],
|
|
16
13
|
"scripts": {
|
|
17
14
|
"build": "tsc",
|
|
18
15
|
"dev": "tsc --watch",
|
|
@@ -26,19 +23,14 @@
|
|
|
26
23
|
"release:publish": "pnpm run build && pnpm run test && npm publish --access public",
|
|
27
24
|
"release:dry": "pnpm run build && pnpm run test && npm publish --access public --dry-run"
|
|
28
25
|
},
|
|
29
|
-
"keywords": [
|
|
30
|
-
"cli",
|
|
31
|
-
"deploy",
|
|
32
|
-
"funforge",
|
|
33
|
-
"fungsi",
|
|
34
|
-
"mcp"
|
|
35
|
-
],
|
|
26
|
+
"keywords": ["cli", "deploy", "funforge", "fungsi", "mcp"],
|
|
36
27
|
"author": "Fungsi",
|
|
37
28
|
"license": "MIT",
|
|
38
29
|
"engines": {
|
|
39
30
|
"node": ">=18.0.0"
|
|
40
31
|
},
|
|
41
32
|
"dependencies": {
|
|
33
|
+
"@inquirer/prompts": "^8.2.0",
|
|
42
34
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
43
35
|
"chalk": "^5.4.1",
|
|
44
36
|
"commander": "^13.1.0",
|