@byfungsi/funforge 0.2.0 → 0.2.3
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__/config.test.js +2 -2
- package/dist/api.d.ts +1 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +21 -7
- package/dist/cli.js +6 -1
- package/dist/commands/apps.d.ts +3 -2
- package/dist/commands/apps.d.ts.map +1 -1
- package/dist/commands/apps.js +24 -22
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +12 -2
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +2 -18
- package/dist/commands/deploy.js +2 -18
- package/dist/commands/domains.d.ts.map +1 -1
- package/dist/commands/domains.js +2 -18
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +2 -18
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +98 -0
- package/dist/mcp.js +21 -4
- package/dist/tiers.d.ts +38 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +168 -0
- package/package.json +19 -4
|
@@ -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
|
});
|
|
@@ -18,7 +18,7 @@ describe("config", () => {
|
|
|
18
18
|
expect(config.apiUrl).toBe("https://api.fungsi.app");
|
|
19
19
|
});
|
|
20
20
|
it("should have production auth URL", () => {
|
|
21
|
-
expect(config.authUrl).toBe("https://
|
|
21
|
+
expect(config.authUrl).toBe("https://funforge.fungsi.app");
|
|
22
22
|
});
|
|
23
23
|
it("should have 30s timeout", () => {
|
|
24
24
|
expect(config.timeout).toBe(30000);
|
|
@@ -31,7 +31,7 @@ describe("config", () => {
|
|
|
31
31
|
delete process.env.FUNFORGE_TIMEOUT;
|
|
32
32
|
const result = getConfig();
|
|
33
33
|
expect(result.apiUrl).toBe("https://api.fungsi.app");
|
|
34
|
-
expect(result.authUrl).toBe("https://
|
|
34
|
+
expect(result.authUrl).toBe("https://funforge.fungsi.app");
|
|
35
35
|
expect(result.timeout).toBe(30000);
|
|
36
36
|
});
|
|
37
37
|
it("should override apiUrl from environment", () => {
|
package/dist/api.d.ts
CHANGED
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,qBAAa,QAAS,SAAQ,KAAK;IAGxB,UAAU,EAAE,MAAM;IAClB,IAAI,CAAC,EAAE,OAAO;gBAFrB,OAAO,EAAE,MAAM,EACR,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,OAAO,YAAA;CAKxB;AAED,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAwCZ;AAMD,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,sBAAsB,CAAC,CA2BtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.js
CHANGED
|
@@ -78,14 +78,28 @@ export async function initDeviceAuth() {
|
|
|
78
78
|
*/
|
|
79
79
|
export async function pollDeviceAuth(deviceCode) {
|
|
80
80
|
const config = getConfig();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
let response;
|
|
82
|
+
try {
|
|
83
|
+
response = await fetch(`${config.apiUrl}/api/cli/auth/device/poll`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { "Content-Type": "application/json" },
|
|
86
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (fetchError) {
|
|
90
|
+
// Network error - throw with details
|
|
91
|
+
const err = fetchError;
|
|
92
|
+
throw new ApiError(`Network error: ${err.message}`, 0);
|
|
93
|
+
}
|
|
86
94
|
// Handle non-200 responses that contain error info
|
|
87
95
|
if (!response.ok) {
|
|
88
|
-
|
|
96
|
+
let data = {};
|
|
97
|
+
try {
|
|
98
|
+
data = (await response.json());
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// couldn't parse JSON
|
|
102
|
+
}
|
|
89
103
|
// Map OAuth error codes to our status
|
|
90
104
|
if (data.error === "access_denied") {
|
|
91
105
|
return { status: "denied" };
|
|
@@ -93,7 +107,7 @@ export async function pollDeviceAuth(deviceCode) {
|
|
|
93
107
|
if (data.error === "expired_token") {
|
|
94
108
|
return { status: "expired" };
|
|
95
109
|
}
|
|
96
|
-
throw new ApiError(
|
|
110
|
+
throw new ApiError(`Failed to poll auth status: ${data.error_description || data.error || "unknown"}`, response.status, data);
|
|
97
111
|
}
|
|
98
112
|
// API returns snake_case, map to camelCase
|
|
99
113
|
const data = (await response.json());
|
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Deploy without git, without leaving your editor.
|
|
6
6
|
*/
|
|
7
|
+
import { createRequire } from "node:module";
|
|
7
8
|
import chalk from "chalk";
|
|
8
9
|
import { Command } from "commander";
|
|
10
|
+
// Load version from package.json
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const pkg = require("../package.json");
|
|
9
13
|
import { appsCreateCommand, appsListCommand, linkCommand, } from "./commands/apps.js";
|
|
10
14
|
// Import commands
|
|
11
15
|
import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
|
|
@@ -18,7 +22,7 @@ const program = new Command();
|
|
|
18
22
|
program
|
|
19
23
|
.name("funforge")
|
|
20
24
|
.description("Deploy without git, without leaving your editor")
|
|
21
|
-
.version(
|
|
25
|
+
.version(pkg.version);
|
|
22
26
|
// ============================================
|
|
23
27
|
// AUTH COMMANDS
|
|
24
28
|
// ============================================
|
|
@@ -43,6 +47,7 @@ appsCmd
|
|
|
43
47
|
.command("create <name>")
|
|
44
48
|
.description("Create a new app")
|
|
45
49
|
.option("-s, --slug <slug>", "Custom slug (subdomain)")
|
|
50
|
+
.option("-t, --tier <tier>", "Tier key (e.g., cost-optimized.small)")
|
|
46
51
|
.action(appsCreateCommand);
|
|
47
52
|
program
|
|
48
53
|
.command("link [appId]")
|
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]
|
|
@@ -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;AAeH;;;;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"}
|
package/dist/commands/apps.js
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
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
|
import chalk from "chalk";
|
|
9
9
|
import ora from "ora";
|
|
10
|
-
import {
|
|
10
|
+
import { createApp, getApp, listApps } from "../api.js";
|
|
11
11
|
import { isAuthenticated } from "../credentials.js";
|
|
12
|
+
import { handleError } from "../errors.js";
|
|
12
13
|
import { readConfig, updateConfig } from "../project-config.js";
|
|
14
|
+
import { VALID_TIER_KEYS, getTierInfo, isValidTierKey, selectTier, } from "../tiers.js";
|
|
13
15
|
/**
|
|
14
16
|
* funforge apps list
|
|
15
17
|
*
|
|
@@ -43,17 +45,34 @@ export async function appsListCommand() {
|
|
|
43
45
|
/**
|
|
44
46
|
* funforge apps create <name>
|
|
45
47
|
*
|
|
46
|
-
* Create a new app.
|
|
48
|
+
* Create a new app with interactive tier selection.
|
|
47
49
|
*/
|
|
48
50
|
export async function appsCreateCommand(name, options) {
|
|
49
51
|
requireAuth();
|
|
50
|
-
|
|
52
|
+
// Get tier - from flag or interactive prompt
|
|
53
|
+
let tierKey;
|
|
54
|
+
if (options.tier) {
|
|
55
|
+
if (!isValidTierKey(options.tier)) {
|
|
56
|
+
console.log(chalk.red(`Invalid tier: ${options.tier}`));
|
|
57
|
+
console.log(chalk.gray(`Valid tiers: ${VALID_TIER_KEYS.join(", ")}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
tierKey = options.tier;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
tierKey = await selectTier();
|
|
64
|
+
}
|
|
65
|
+
const tierInfo = getTierInfo(tierKey);
|
|
66
|
+
const spinner = ora(`Creating app "${name}" with ${tierInfo?.name ?? tierKey} tier...`).start();
|
|
51
67
|
try {
|
|
52
|
-
const { app } = await createApp({ name, slug: options.slug });
|
|
68
|
+
const { app } = await createApp({ name, slug: options.slug, tierKey });
|
|
53
69
|
spinner.succeed(`App created!`);
|
|
54
70
|
console.log();
|
|
55
71
|
console.log(` Name: ${app.name}`);
|
|
56
72
|
console.log(` Slug: ${chalk.cyan(app.slug)}`);
|
|
73
|
+
if (tierInfo) {
|
|
74
|
+
console.log(` Tier: ${tierInfo.category}/${tierInfo.name} (${tierInfo.credits} credits/mo)`);
|
|
75
|
+
}
|
|
57
76
|
console.log(` ID: ${app.id}`);
|
|
58
77
|
console.log();
|
|
59
78
|
console.log(chalk.gray("Link this directory with:"));
|
|
@@ -132,20 +151,3 @@ function requireAuth() {
|
|
|
132
151
|
process.exit(1);
|
|
133
152
|
}
|
|
134
153
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Handle API errors
|
|
137
|
-
*/
|
|
138
|
-
function handleError(error) {
|
|
139
|
-
if (error instanceof ApiError) {
|
|
140
|
-
console.error(chalk.red(`Error ${error.statusCode}: ${error.message}`));
|
|
141
|
-
if (error.body &&
|
|
142
|
-
typeof error.body === "object" &&
|
|
143
|
-
"message" in error.body) {
|
|
144
|
-
console.error(chalk.gray(error.body.message));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
149
|
-
}
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH;;;;;GAKG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH;;;;;GAKG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAsGlD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CASnD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAcnD"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -54,7 +54,16 @@ export async function loginCommand() {
|
|
|
54
54
|
const expiresAt = startTime + auth.expiresIn * 1000;
|
|
55
55
|
while (Date.now() < expiresAt) {
|
|
56
56
|
await sleep(auth.interval * 1000);
|
|
57
|
-
|
|
57
|
+
let result;
|
|
58
|
+
try {
|
|
59
|
+
result = await pollDeviceAuth(auth.deviceCode);
|
|
60
|
+
}
|
|
61
|
+
catch (pollError) {
|
|
62
|
+
const err = pollError;
|
|
63
|
+
pollSpinner.fail("Poll failed");
|
|
64
|
+
console.error(chalk.red(err.message));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
58
67
|
if (result.status === "authorized") {
|
|
59
68
|
pollSpinner.succeed("Authenticated!");
|
|
60
69
|
// Save credentials
|
|
@@ -85,7 +94,8 @@ export async function loginCommand() {
|
|
|
85
94
|
}
|
|
86
95
|
catch (error) {
|
|
87
96
|
spinner.fail("Authentication failed");
|
|
88
|
-
|
|
97
|
+
const err = error;
|
|
98
|
+
console.error(chalk.red(err.message));
|
|
89
99
|
process.exit(1);
|
|
90
100
|
}
|
|
91
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoEH;;;;;GAKG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA+EvD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4EvD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAgFvD"}
|
package/dist/commands/config.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import ora from "ora";
|
|
13
|
-
import {
|
|
13
|
+
import { getAppDetails, updateApp } from "../api.js";
|
|
14
14
|
import { isAuthenticated } from "../credentials.js";
|
|
15
|
+
import { handleError } from "../errors.js";
|
|
15
16
|
import { readConfig, updateConfig, } from "../project-config.js";
|
|
16
17
|
/**
|
|
17
18
|
* Extract build settings from funforge.json config
|
|
@@ -268,20 +269,3 @@ function requireAuth() {
|
|
|
268
269
|
process.exit(1);
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Handle API errors
|
|
273
|
-
*/
|
|
274
|
-
function handleError(error) {
|
|
275
|
-
if (error instanceof ApiError) {
|
|
276
|
-
console.error(chalk.red(`Error ${error.statusCode}: ${error.message}`));
|
|
277
|
-
if (error.body &&
|
|
278
|
-
typeof error.body === "object" &&
|
|
279
|
-
"message" in error.body) {
|
|
280
|
-
console.error(chalk.gray(error.body.message));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
285
|
-
}
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import ora from "ora";
|
|
13
|
-
import {
|
|
13
|
+
import { createDeployment, getDeployment, getUploadUrl, uploadTarball, } from "../api.js";
|
|
14
14
|
import { isAuthenticated } from "../credentials.js";
|
|
15
|
+
import { handleError } from "../errors.js";
|
|
15
16
|
import { readConfig } from "../project-config.js";
|
|
16
17
|
import { createTarball, formatSize, readTarball } from "../tarball.js";
|
|
17
18
|
/**
|
|
@@ -174,23 +175,6 @@ function requireAuth() {
|
|
|
174
175
|
process.exit(1);
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Handle API errors
|
|
179
|
-
*/
|
|
180
|
-
function handleError(error) {
|
|
181
|
-
if (error instanceof ApiError) {
|
|
182
|
-
console.error(chalk.red(`Error ${error.statusCode}: ${error.message}`));
|
|
183
|
-
if (error.body &&
|
|
184
|
-
typeof error.body === "object" &&
|
|
185
|
-
"message" in error.body) {
|
|
186
|
-
console.error(chalk.gray(error.body.message));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
191
|
-
}
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
178
|
function sleep(ms) {
|
|
195
179
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
196
180
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/commands/domains.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/commands/domains.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsCH;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CA6CxD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCxE;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DxE"}
|
package/dist/commands/domains.js
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import ora from "ora";
|
|
11
|
-
import {
|
|
11
|
+
import { apiRequest } from "../api.js";
|
|
12
12
|
import { isAuthenticated } from "../credentials.js";
|
|
13
|
+
import { handleError } from "../errors.js";
|
|
13
14
|
import { readConfig } from "../project-config.js";
|
|
14
15
|
/**
|
|
15
16
|
* funforge domains list
|
|
@@ -198,20 +199,3 @@ function requireAuth() {
|
|
|
198
199
|
process.exit(1);
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Handle API errors
|
|
203
|
-
*/
|
|
204
|
-
function handleError(error) {
|
|
205
|
-
if (error instanceof ApiError) {
|
|
206
|
-
console.error(chalk.red(`Error ${error.statusCode}: ${error.message}`));
|
|
207
|
-
if (error.body &&
|
|
208
|
-
typeof error.body === "object" &&
|
|
209
|
-
"message" in error.body) {
|
|
210
|
-
console.error(chalk.gray(error.body.message));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
215
|
-
}
|
|
216
|
-
process.exit(1);
|
|
217
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/commands/env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/commands/env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH;;;;GAIG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAqDpD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDlE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCnE"}
|
package/dist/commands/env.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import ora from "ora";
|
|
10
|
-
import {
|
|
10
|
+
import { apiRequest } from "../api.js";
|
|
11
11
|
import { isAuthenticated } from "../credentials.js";
|
|
12
|
+
import { handleError } from "../errors.js";
|
|
12
13
|
import { readConfig } from "../project-config.js";
|
|
13
14
|
/**
|
|
14
15
|
* funforge env list
|
|
@@ -164,20 +165,3 @@ function requireAuth() {
|
|
|
164
165
|
process.exit(1);
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Handle API errors
|
|
169
|
-
*/
|
|
170
|
-
function handleError(error) {
|
|
171
|
-
if (error instanceof ApiError) {
|
|
172
|
-
console.error(chalk.red(`Error ${error.statusCode}: ${error.message}`));
|
|
173
|
-
if (error.body &&
|
|
174
|
-
typeof error.body === "object" &&
|
|
175
|
-
"message" in error.body) {
|
|
176
|
-
console.error(chalk.gray(error.body.message));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
181
|
-
}
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared error formatting for user-friendly CLI output.
|
|
5
|
+
*/
|
|
6
|
+
import { ApiError } from "./api.js";
|
|
7
|
+
/**
|
|
8
|
+
* Format an API error for user-friendly display.
|
|
9
|
+
*
|
|
10
|
+
* Handles known error codes with actionable hints:
|
|
11
|
+
* - 402: Billing/credits issues
|
|
12
|
+
* - 403: Plan limits
|
|
13
|
+
* - 409: Conflicts (deployment in progress)
|
|
14
|
+
* - 404: Not found
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatApiError(error: ApiError): string;
|
|
17
|
+
/**
|
|
18
|
+
* Handle any error and exit.
|
|
19
|
+
*
|
|
20
|
+
* Use this in catch blocks for consistent error output.
|
|
21
|
+
*/
|
|
22
|
+
export declare function handleError(error: unknown): never;
|
|
23
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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;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
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared error formatting for user-friendly CLI output.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { ApiError } from "./api.js";
|
|
8
|
+
/**
|
|
9
|
+
* Format an API error for user-friendly display.
|
|
10
|
+
*
|
|
11
|
+
* Handles known error codes with actionable hints:
|
|
12
|
+
* - 402: Billing/credits issues
|
|
13
|
+
* - 403: Plan limits
|
|
14
|
+
* - 409: Conflicts (deployment in progress)
|
|
15
|
+
* - 404: Not found
|
|
16
|
+
*/
|
|
17
|
+
export function formatApiError(error) {
|
|
18
|
+
const body = error.body;
|
|
19
|
+
const code = body?.error?.code;
|
|
20
|
+
const message = body?.error?.message || body?.message || error.message;
|
|
21
|
+
// 402 - Payment Required (billing/credits)
|
|
22
|
+
if (error.statusCode === 402 || code === "BILLING_ERROR") {
|
|
23
|
+
return [
|
|
24
|
+
chalk.red(message),
|
|
25
|
+
"",
|
|
26
|
+
chalk.yellow("Add credits at: https://funforge.fungsi.app/balance"),
|
|
27
|
+
].join("\n");
|
|
28
|
+
}
|
|
29
|
+
// 403 - Plan limits exceeded
|
|
30
|
+
if (code === "PLAN_LIMIT_EXCEEDED" || code === "APP_LIMIT_EXCEEDED") {
|
|
31
|
+
return [
|
|
32
|
+
chalk.red(message),
|
|
33
|
+
"",
|
|
34
|
+
chalk.yellow("Delete unused apps or upgrade your plan to continue."),
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
// 409 - Conflict (deployment in progress, etc.)
|
|
38
|
+
if (error.statusCode === 409 || code === "DEPLOYMENT_IN_PROGRESS") {
|
|
39
|
+
return [
|
|
40
|
+
chalk.red(message),
|
|
41
|
+
"",
|
|
42
|
+
chalk.gray("Wait for the current deployment to complete and try again."),
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
// 404 - Not found
|
|
46
|
+
if (error.statusCode === 404) {
|
|
47
|
+
return chalk.red(message || "Resource not found.");
|
|
48
|
+
}
|
|
49
|
+
// 401 - Unauthorized
|
|
50
|
+
if (error.statusCode === 401) {
|
|
51
|
+
return [
|
|
52
|
+
chalk.red(message || "Not authenticated."),
|
|
53
|
+
"",
|
|
54
|
+
chalk.gray("Run `funforge login` to authenticate."),
|
|
55
|
+
].join("\n");
|
|
56
|
+
}
|
|
57
|
+
// 400 - Bad request / validation
|
|
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
|
+
}
|
|
70
|
+
return chalk.red(message || "Invalid request.");
|
|
71
|
+
}
|
|
72
|
+
// 500+ - Server errors
|
|
73
|
+
if (error.statusCode >= 500) {
|
|
74
|
+
return [
|
|
75
|
+
chalk.red("Server error. Please try again later."),
|
|
76
|
+
chalk.gray(message),
|
|
77
|
+
].join("\n");
|
|
78
|
+
}
|
|
79
|
+
// Default: show the message
|
|
80
|
+
return chalk.red(message);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Handle any error and exit.
|
|
84
|
+
*
|
|
85
|
+
* Use this in catch blocks for consistent error output.
|
|
86
|
+
*/
|
|
87
|
+
export function handleError(error) {
|
|
88
|
+
if (error instanceof ApiError) {
|
|
89
|
+
console.error(formatApiError(error));
|
|
90
|
+
}
|
|
91
|
+
else if (error instanceof Error) {
|
|
92
|
+
console.error(chalk.red(error.message));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.error(chalk.red(String(error)));
|
|
96
|
+
}
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
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
|
{
|
|
@@ -188,7 +204,7 @@ async function handleToolCall(name, args) {
|
|
|
188
204
|
case "funforge_apps_list":
|
|
189
205
|
return await handleAppsList();
|
|
190
206
|
case "funforge_apps_create":
|
|
191
|
-
return await handleAppsCreate(args.name, args.slug);
|
|
207
|
+
return await handleAppsCreate(args.name, args.tierKey, args.slug);
|
|
192
208
|
case "funforge_status":
|
|
193
209
|
return await handleStatus(args.directory);
|
|
194
210
|
case "funforge_deploy":
|
|
@@ -240,15 +256,16 @@ async function handleAppsList() {
|
|
|
240
256
|
})),
|
|
241
257
|
});
|
|
242
258
|
}
|
|
243
|
-
async function handleAppsCreate(name, slug) {
|
|
259
|
+
async function handleAppsCreate(name, tierKey, slug) {
|
|
244
260
|
requireAuth();
|
|
245
|
-
const { app } = await createApp({ name, slug });
|
|
261
|
+
const { app } = await createApp({ name, slug, tierKey });
|
|
246
262
|
return successResult({
|
|
247
263
|
message: `App "${app.name}" created successfully`,
|
|
248
264
|
app: {
|
|
249
265
|
id: app.id,
|
|
250
266
|
name: app.name,
|
|
251
267
|
slug: app.slug,
|
|
268
|
+
tierKey,
|
|
252
269
|
},
|
|
253
270
|
});
|
|
254
271
|
}
|
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.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Deploy without git, without leaving your editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,22 +9,37 @@
|
|
|
9
9
|
"funforge": "./dist/cli.js",
|
|
10
10
|
"funforge-mcp": "./dist/mcp.js"
|
|
11
11
|
},
|
|
12
|
-
"files": [
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
13
16
|
"scripts": {
|
|
14
17
|
"build": "tsc",
|
|
15
18
|
"dev": "tsc --watch",
|
|
16
19
|
"typecheck": "tsc --noEmit",
|
|
17
20
|
"test": "vitest run",
|
|
18
21
|
"start": "node dist/cli.js",
|
|
19
|
-
"mcp": "node dist/mcp.js"
|
|
22
|
+
"mcp": "node dist/mcp.js",
|
|
23
|
+
"release:patch": "npm version patch --no-git-tag-version && pnpm run release:publish",
|
|
24
|
+
"release:minor": "npm version minor --no-git-tag-version && pnpm run release:publish",
|
|
25
|
+
"release:major": "npm version major --no-git-tag-version && pnpm run release:publish",
|
|
26
|
+
"release:publish": "pnpm run build && pnpm run test && npm publish --access public",
|
|
27
|
+
"release:dry": "pnpm run build && pnpm run test && npm publish --access public --dry-run"
|
|
20
28
|
},
|
|
21
|
-
"keywords": [
|
|
29
|
+
"keywords": [
|
|
30
|
+
"cli",
|
|
31
|
+
"deploy",
|
|
32
|
+
"funforge",
|
|
33
|
+
"fungsi",
|
|
34
|
+
"mcp"
|
|
35
|
+
],
|
|
22
36
|
"author": "Fungsi",
|
|
23
37
|
"license": "MIT",
|
|
24
38
|
"engines": {
|
|
25
39
|
"node": ">=18.0.0"
|
|
26
40
|
},
|
|
27
41
|
"dependencies": {
|
|
42
|
+
"@inquirer/prompts": "^8.2.0",
|
|
28
43
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
29
44
|
"chalk": "^5.4.1",
|
|
30
45
|
"commander": "^13.1.0",
|