@byfungsi/funforge 0.1.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 +273 -0
- package/dist/__tests__/api.test.d.ts +5 -0
- package/dist/__tests__/api.test.d.ts.map +1 -0
- package/dist/__tests__/api.test.js +177 -0
- package/dist/__tests__/config.test.d.ts +5 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +58 -0
- package/dist/__tests__/mcp.test.d.ts +7 -0
- package/dist/__tests__/mcp.test.d.ts.map +1 -0
- package/dist/__tests__/mcp.test.js +142 -0
- package/dist/__tests__/project-config.test.d.ts +5 -0
- package/dist/__tests__/project-config.test.d.ts.map +1 -0
- package/dist/__tests__/project-config.test.js +122 -0
- package/dist/__tests__/tarball.test.d.ts +5 -0
- package/dist/__tests__/tarball.test.d.ts.map +1 -0
- package/dist/__tests__/tarball.test.js +113 -0
- package/dist/api.d.ts +157 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +165 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +129 -0
- package/dist/commands/apps.d.ts +29 -0
- package/dist/commands/apps.d.ts.map +1 -0
- package/dist/commands/apps.js +151 -0
- package/dist/commands/auth.d.ts +27 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +127 -0
- package/dist/commands/config.d.ts +31 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +287 -0
- package/dist/commands/deploy.d.ts +24 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +196 -0
- package/dist/commands/domains.d.ts +35 -0
- package/dist/commands/domains.d.ts.map +1 -0
- package/dist/commands/domains.js +217 -0
- package/dist/commands/env.d.ts +26 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +183 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +23 -0
- package/dist/credentials.d.ts +46 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +60 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +480 -0
- package/dist/project-config.d.ts +47 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +55 -0
- package/dist/tarball.d.ts +29 -0
- package/dist/tarball.d.ts.map +1 -0
- package/dist/tarball.js +148 -0
- package/package.json +45 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Commands
|
|
3
|
+
*
|
|
4
|
+
* Sync build settings between local funforge.json and server.
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* - funforge config push - Push local settings to server
|
|
8
|
+
* - funforge config pull - Pull server settings to local file
|
|
9
|
+
* - funforge config show - Show comparison between local and server
|
|
10
|
+
*/
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import ora from "ora";
|
|
13
|
+
import { ApiError, getAppDetails, updateApp, } from "../api.js";
|
|
14
|
+
import { isAuthenticated } from "../credentials.js";
|
|
15
|
+
import { readConfig, updateConfig, } from "../project-config.js";
|
|
16
|
+
/**
|
|
17
|
+
* Extract build settings from funforge.json config
|
|
18
|
+
*/
|
|
19
|
+
function extractBuildSettings(config) {
|
|
20
|
+
const settings = {};
|
|
21
|
+
if (config.port !== undefined) {
|
|
22
|
+
settings.port = config.port;
|
|
23
|
+
}
|
|
24
|
+
if (config.build) {
|
|
25
|
+
if (config.build.buildCommand !== undefined) {
|
|
26
|
+
settings.buildCommand = config.build.buildCommand || null;
|
|
27
|
+
}
|
|
28
|
+
if (config.build.installCommand !== undefined) {
|
|
29
|
+
settings.installCommand = config.build.installCommand || null;
|
|
30
|
+
}
|
|
31
|
+
if (config.build.startCommand !== undefined) {
|
|
32
|
+
settings.startCommand = config.build.startCommand || null;
|
|
33
|
+
}
|
|
34
|
+
if (config.build.nodeVersion !== undefined) {
|
|
35
|
+
const version = config.build.nodeVersion;
|
|
36
|
+
if (version === "18" || version === "20" || version === "22") {
|
|
37
|
+
settings.nodeVersion = version;
|
|
38
|
+
}
|
|
39
|
+
else if (!version) {
|
|
40
|
+
settings.nodeVersion = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return settings;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Format a setting value for display
|
|
48
|
+
*/
|
|
49
|
+
function formatValue(value) {
|
|
50
|
+
if (value === null || value === undefined || value === "") {
|
|
51
|
+
return chalk.gray("(not set)");
|
|
52
|
+
}
|
|
53
|
+
return chalk.cyan(String(value));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if two values are different (for highlighting changes)
|
|
57
|
+
*/
|
|
58
|
+
function isDifferent(local, server) {
|
|
59
|
+
const normalizedLocal = local ?? null;
|
|
60
|
+
const normalizedServer = server ?? null;
|
|
61
|
+
return normalizedLocal !== normalizedServer;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* funforge config push
|
|
65
|
+
*
|
|
66
|
+
* Push local funforge.json build settings to server.
|
|
67
|
+
* Local settings overwrite server settings.
|
|
68
|
+
*/
|
|
69
|
+
export async function configPushCommand() {
|
|
70
|
+
requireAuth();
|
|
71
|
+
// Read local config
|
|
72
|
+
const config = await readConfig();
|
|
73
|
+
if (!config?.appId) {
|
|
74
|
+
console.log(chalk.red("Not linked to an app."));
|
|
75
|
+
console.log(chalk.gray("Run `funforge link <app-id>` first."));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
// Extract build settings from local config
|
|
79
|
+
const settings = extractBuildSettings(config);
|
|
80
|
+
if (Object.keys(settings).length === 0) {
|
|
81
|
+
console.log(chalk.yellow("No build settings found in funforge.json"));
|
|
82
|
+
console.log(chalk.gray("Add a 'build' section or 'port' to funforge.json:"));
|
|
83
|
+
console.log(chalk.gray(`
|
|
84
|
+
{
|
|
85
|
+
"appId": "${config.appId}",
|
|
86
|
+
"build": {
|
|
87
|
+
"buildCommand": "npm run build",
|
|
88
|
+
"installCommand": "npm ci",
|
|
89
|
+
"startCommand": "npm start",
|
|
90
|
+
"nodeVersion": "20"
|
|
91
|
+
},
|
|
92
|
+
"port": 3000
|
|
93
|
+
}
|
|
94
|
+
`));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
console.log(chalk.bold(`Pushing config for ${config.appName ?? config.appSlug ?? "app"}...`));
|
|
98
|
+
console.log();
|
|
99
|
+
// Show what will be pushed
|
|
100
|
+
console.log(chalk.gray("Settings to push:"));
|
|
101
|
+
if (settings.port !== undefined) {
|
|
102
|
+
console.log(` Port: ${formatValue(settings.port)}`);
|
|
103
|
+
}
|
|
104
|
+
if (settings.buildCommand !== undefined) {
|
|
105
|
+
console.log(` Build command: ${formatValue(settings.buildCommand)}`);
|
|
106
|
+
}
|
|
107
|
+
if (settings.installCommand !== undefined) {
|
|
108
|
+
console.log(` Install command: ${formatValue(settings.installCommand)}`);
|
|
109
|
+
}
|
|
110
|
+
if (settings.startCommand !== undefined) {
|
|
111
|
+
console.log(` Start command: ${formatValue(settings.startCommand)}`);
|
|
112
|
+
}
|
|
113
|
+
if (settings.nodeVersion !== undefined) {
|
|
114
|
+
console.log(` Node version: ${formatValue(settings.nodeVersion)}`);
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
// Push to server
|
|
118
|
+
const spinner = ora("Pushing settings to server...").start();
|
|
119
|
+
try {
|
|
120
|
+
await updateApp(config.appId, settings);
|
|
121
|
+
spinner.succeed("Settings pushed successfully");
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(chalk.green("Local funforge.json settings are now synced to server."));
|
|
124
|
+
console.log(chalk.gray("These settings will be used for the next deployment."));
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
spinner.fail("Failed to push settings");
|
|
128
|
+
handleError(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* funforge config pull
|
|
133
|
+
*
|
|
134
|
+
* Pull server build settings to local funforge.json.
|
|
135
|
+
* Server settings are written to local file.
|
|
136
|
+
*/
|
|
137
|
+
export async function configPullCommand() {
|
|
138
|
+
requireAuth();
|
|
139
|
+
// Read local config
|
|
140
|
+
const config = await readConfig();
|
|
141
|
+
if (!config?.appId) {
|
|
142
|
+
console.log(chalk.red("Not linked to an app."));
|
|
143
|
+
console.log(chalk.gray("Run `funforge link <app-id>` first."));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
console.log(chalk.bold(`Pulling config for ${config.appName ?? config.appSlug ?? "app"}...`));
|
|
147
|
+
console.log();
|
|
148
|
+
// Fetch from server
|
|
149
|
+
const spinner = ora("Fetching settings from server...").start();
|
|
150
|
+
try {
|
|
151
|
+
const { app } = await getAppDetails(config.appId);
|
|
152
|
+
spinner.succeed("Settings fetched");
|
|
153
|
+
console.log();
|
|
154
|
+
// Build the updated config
|
|
155
|
+
const updatedConfig = {};
|
|
156
|
+
// Always sync port if set
|
|
157
|
+
if (app.port !== undefined) {
|
|
158
|
+
updatedConfig.port = app.port;
|
|
159
|
+
}
|
|
160
|
+
// Build build settings object
|
|
161
|
+
const build = {};
|
|
162
|
+
let hasBuildSettings = false;
|
|
163
|
+
if (app.buildCommand) {
|
|
164
|
+
build.buildCommand = app.buildCommand;
|
|
165
|
+
hasBuildSettings = true;
|
|
166
|
+
}
|
|
167
|
+
if (app.installCommand) {
|
|
168
|
+
build.installCommand = app.installCommand;
|
|
169
|
+
hasBuildSettings = true;
|
|
170
|
+
}
|
|
171
|
+
if (app.startCommand) {
|
|
172
|
+
build.startCommand = app.startCommand;
|
|
173
|
+
hasBuildSettings = true;
|
|
174
|
+
}
|
|
175
|
+
if (app.nodeVersion) {
|
|
176
|
+
build.nodeVersion = app.nodeVersion;
|
|
177
|
+
hasBuildSettings = true;
|
|
178
|
+
}
|
|
179
|
+
if (hasBuildSettings) {
|
|
180
|
+
updatedConfig.build = build;
|
|
181
|
+
}
|
|
182
|
+
// Show what will be written
|
|
183
|
+
console.log(chalk.gray("Settings from server:"));
|
|
184
|
+
console.log(` Port: ${formatValue(app.port)}`);
|
|
185
|
+
console.log(` Build command: ${formatValue(app.buildCommand)}`);
|
|
186
|
+
console.log(` Install command: ${formatValue(app.installCommand)}`);
|
|
187
|
+
console.log(` Start command: ${formatValue(app.startCommand)}`);
|
|
188
|
+
console.log(` Node version: ${formatValue(app.nodeVersion)}`);
|
|
189
|
+
console.log();
|
|
190
|
+
// Write to local file
|
|
191
|
+
await updateConfig(updatedConfig);
|
|
192
|
+
console.log(chalk.green("funforge.json updated with server settings."));
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
spinner.fail("Failed to pull settings");
|
|
196
|
+
handleError(error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* funforge config show
|
|
201
|
+
*
|
|
202
|
+
* Show comparison between local funforge.json and server settings.
|
|
203
|
+
*/
|
|
204
|
+
export async function configShowCommand() {
|
|
205
|
+
requireAuth();
|
|
206
|
+
// Read local config
|
|
207
|
+
const config = await readConfig();
|
|
208
|
+
if (!config?.appId) {
|
|
209
|
+
console.log(chalk.red("Not linked to an app."));
|
|
210
|
+
console.log(chalk.gray("Run `funforge link <app-id>` first."));
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
console.log(chalk.bold(`Config comparison for ${config.appName ?? config.appSlug ?? "app"}`));
|
|
214
|
+
console.log();
|
|
215
|
+
// Fetch from server
|
|
216
|
+
const spinner = ora("Fetching server settings...").start();
|
|
217
|
+
try {
|
|
218
|
+
const { app } = await getAppDetails(config.appId);
|
|
219
|
+
spinner.stop();
|
|
220
|
+
// Local values
|
|
221
|
+
const localPort = config.port;
|
|
222
|
+
const localBuildCommand = config.build?.buildCommand;
|
|
223
|
+
const localInstallCommand = config.build?.installCommand;
|
|
224
|
+
const localStartCommand = config.build?.startCommand;
|
|
225
|
+
const localNodeVersion = config.build?.nodeVersion;
|
|
226
|
+
// Server values
|
|
227
|
+
const serverPort = app.port;
|
|
228
|
+
const serverBuildCommand = app.buildCommand;
|
|
229
|
+
const serverInstallCommand = app.installCommand;
|
|
230
|
+
const serverStartCommand = app.startCommand;
|
|
231
|
+
const serverNodeVersion = app.nodeVersion;
|
|
232
|
+
// Table header
|
|
233
|
+
console.log(chalk.gray("Setting".padEnd(20)) +
|
|
234
|
+
chalk.gray("Local".padEnd(30)) +
|
|
235
|
+
chalk.gray("Server"));
|
|
236
|
+
console.log(chalk.gray("-".repeat(80)));
|
|
237
|
+
// Helper to print a row
|
|
238
|
+
const printRow = (name, local, server) => {
|
|
239
|
+
const different = isDifferent(local, server);
|
|
240
|
+
const marker = different ? chalk.yellow("*") : " ";
|
|
241
|
+
const localStr = formatValue(local).padEnd(30);
|
|
242
|
+
const serverStr = formatValue(server);
|
|
243
|
+
console.log(`${marker}${name.padEnd(19)}${localStr}${serverStr}`);
|
|
244
|
+
};
|
|
245
|
+
printRow("Port", localPort, serverPort);
|
|
246
|
+
printRow("Build command", localBuildCommand, serverBuildCommand);
|
|
247
|
+
printRow("Install command", localInstallCommand, serverInstallCommand);
|
|
248
|
+
printRow("Start command", localStartCommand, serverStartCommand);
|
|
249
|
+
printRow("Node version", localNodeVersion, serverNodeVersion);
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(chalk.gray("* = differs between local and server"));
|
|
252
|
+
console.log();
|
|
253
|
+
console.log(chalk.gray("Use `funforge config push` to sync local → server"));
|
|
254
|
+
console.log(chalk.gray("Use `funforge config pull` to sync server → local"));
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
spinner.fail("Failed to fetch server settings");
|
|
258
|
+
handleError(error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if user is authenticated, exit if not
|
|
263
|
+
*/
|
|
264
|
+
function requireAuth() {
|
|
265
|
+
if (!isAuthenticated()) {
|
|
266
|
+
console.log(chalk.red("Not authenticated."));
|
|
267
|
+
console.log(chalk.gray("Run `funforge login` first."));
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy Command
|
|
3
|
+
*
|
|
4
|
+
* Main deployment workflow:
|
|
5
|
+
* 1. Read funforge.json to get app ID
|
|
6
|
+
* 2. Create tarball of project files
|
|
7
|
+
* 3. Upload tarball to R2
|
|
8
|
+
* 4. Trigger deployment
|
|
9
|
+
* 5. Stream logs
|
|
10
|
+
*/
|
|
11
|
+
interface DeployOptions {
|
|
12
|
+
/** Skip confirmation prompt */
|
|
13
|
+
yes?: boolean;
|
|
14
|
+
/** Watch deployment logs */
|
|
15
|
+
watch?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* funforge deploy
|
|
19
|
+
*
|
|
20
|
+
* Deploy the current directory to FunForge.
|
|
21
|
+
*/
|
|
22
|
+
export declare function deployCommand(options?: DeployOptions): Promise<void>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=deploy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAeH,UAAU,aAAa;IACrB,+BAA+B;IAC/B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,4BAA4B;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAsGf"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy Command
|
|
3
|
+
*
|
|
4
|
+
* Main deployment workflow:
|
|
5
|
+
* 1. Read funforge.json to get app ID
|
|
6
|
+
* 2. Create tarball of project files
|
|
7
|
+
* 3. Upload tarball to R2
|
|
8
|
+
* 4. Trigger deployment
|
|
9
|
+
* 5. Stream logs
|
|
10
|
+
*/
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import ora from "ora";
|
|
13
|
+
import { ApiError, createDeployment, getDeployment, getUploadUrl, uploadTarball, } from "../api.js";
|
|
14
|
+
import { isAuthenticated } from "../credentials.js";
|
|
15
|
+
import { readConfig } from "../project-config.js";
|
|
16
|
+
import { createTarball, formatSize, readTarball } from "../tarball.js";
|
|
17
|
+
/**
|
|
18
|
+
* funforge deploy
|
|
19
|
+
*
|
|
20
|
+
* Deploy the current directory to FunForge.
|
|
21
|
+
*/
|
|
22
|
+
export async function deployCommand(options = {}) {
|
|
23
|
+
requireAuth();
|
|
24
|
+
// 1. Check project config
|
|
25
|
+
const config = await readConfig();
|
|
26
|
+
if (!config?.appId) {
|
|
27
|
+
console.log(chalk.red("Not linked to an app."));
|
|
28
|
+
console.log(chalk.gray("Run `funforge link <app-id>` first."));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
console.log(chalk.bold(`Deploying ${config.appName ?? config.appSlug ?? "app"}...`));
|
|
32
|
+
console.log();
|
|
33
|
+
// 2. Create tarball
|
|
34
|
+
const tarballSpinner = ora("Creating deployment package...").start();
|
|
35
|
+
let tarballResult;
|
|
36
|
+
try {
|
|
37
|
+
tarballResult = await createTarball(process.cwd());
|
|
38
|
+
tarballSpinner.succeed(`Package created: ${tarballResult.fileCount} files, ${formatSize(tarballResult.size)}`);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
tarballSpinner.fail("Failed to create package");
|
|
42
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// Check size limit (50MB)
|
|
46
|
+
const MAX_SIZE = 50 * 1024 * 1024;
|
|
47
|
+
if (tarballResult.size > MAX_SIZE) {
|
|
48
|
+
console.log(chalk.red(`Package too large: ${formatSize(tarballResult.size)} (max ${formatSize(MAX_SIZE)})`));
|
|
49
|
+
console.log(chalk.gray("Try adding more patterns to .funforgeignore"));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
// 3. Get upload URL
|
|
53
|
+
const uploadSpinner = ora("Preparing upload...").start();
|
|
54
|
+
let uploadInfo;
|
|
55
|
+
try {
|
|
56
|
+
uploadInfo = await getUploadUrl(config.appId, {
|
|
57
|
+
filename: "context.tar.gz",
|
|
58
|
+
contentLength: tarballResult.size,
|
|
59
|
+
sha256: tarballResult.sha256,
|
|
60
|
+
});
|
|
61
|
+
uploadSpinner.succeed("Upload prepared");
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
uploadSpinner.fail("Failed to prepare upload");
|
|
65
|
+
handleError(error);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// 4. Upload tarball
|
|
69
|
+
const uploadingSpinner = ora("Uploading...").start();
|
|
70
|
+
try {
|
|
71
|
+
const tarballBuffer = await readTarball(tarballResult.path);
|
|
72
|
+
await uploadTarball(uploadInfo.uploadUrl, tarballBuffer);
|
|
73
|
+
uploadingSpinner.succeed(`Uploaded ${formatSize(tarballResult.size)}`);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
uploadingSpinner.fail("Upload failed");
|
|
77
|
+
handleError(error);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// 5. Trigger deployment
|
|
81
|
+
const deploySpinner = ora("Starting deployment...").start();
|
|
82
|
+
let deployResult;
|
|
83
|
+
try {
|
|
84
|
+
deployResult = await createDeployment(config.appId, {
|
|
85
|
+
sourceId: uploadInfo.sourceId,
|
|
86
|
+
});
|
|
87
|
+
deploySpinner.succeed("Deployment started");
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
deploySpinner.fail("Failed to start deployment");
|
|
91
|
+
handleError(error);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(chalk.bold("Deployment:"));
|
|
96
|
+
console.log(` ID: ${deployResult.deployment.id}`);
|
|
97
|
+
console.log(` Status: ${formatStatus(deployResult.deployment.status)}`);
|
|
98
|
+
console.log();
|
|
99
|
+
// 6. Watch logs if requested
|
|
100
|
+
if (options.watch !== false) {
|
|
101
|
+
await watchDeployment(deployResult.deployment.id);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log(chalk.gray("Run with --watch to follow logs"));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Watch deployment status until completion
|
|
109
|
+
*/
|
|
110
|
+
async function watchDeployment(deploymentId) {
|
|
111
|
+
console.log(chalk.gray("Watching deployment..."));
|
|
112
|
+
console.log();
|
|
113
|
+
const spinner = ora("Building...").start();
|
|
114
|
+
let lastStatus = "";
|
|
115
|
+
while (true) {
|
|
116
|
+
try {
|
|
117
|
+
const { deployment } = await getDeployment(deploymentId);
|
|
118
|
+
if (deployment.status !== lastStatus) {
|
|
119
|
+
lastStatus = deployment.status;
|
|
120
|
+
spinner.text = formatStatus(deployment.status);
|
|
121
|
+
}
|
|
122
|
+
if (deployment.status === "live") {
|
|
123
|
+
spinner.succeed(chalk.green("Deployment live!"));
|
|
124
|
+
console.log();
|
|
125
|
+
// TODO: Show deployment URL
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (deployment.status === "failed" || deployment.status === "canceled") {
|
|
129
|
+
spinner.fail(chalk.red(`Deployment ${deployment.status}`));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
// Poll every 2 seconds
|
|
133
|
+
await sleep(2000);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
spinner.fail("Failed to get deployment status");
|
|
137
|
+
handleError(error);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Format deployment status for display
|
|
144
|
+
*/
|
|
145
|
+
function formatStatus(status) {
|
|
146
|
+
switch (status) {
|
|
147
|
+
case "pending":
|
|
148
|
+
return chalk.gray("Pending...");
|
|
149
|
+
case "queued":
|
|
150
|
+
return chalk.yellow("Queued...");
|
|
151
|
+
case "building":
|
|
152
|
+
return chalk.blue("Building...");
|
|
153
|
+
case "deploying":
|
|
154
|
+
return chalk.cyan("Deploying...");
|
|
155
|
+
case "live":
|
|
156
|
+
return chalk.green("Live");
|
|
157
|
+
case "stopped":
|
|
158
|
+
return chalk.gray("Stopped");
|
|
159
|
+
case "failed":
|
|
160
|
+
return chalk.red("Failed");
|
|
161
|
+
case "canceled":
|
|
162
|
+
return chalk.gray("Canceled");
|
|
163
|
+
default:
|
|
164
|
+
return status;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Check if user is authenticated, exit if not
|
|
169
|
+
*/
|
|
170
|
+
function requireAuth() {
|
|
171
|
+
if (!isAuthenticated()) {
|
|
172
|
+
console.log(chalk.red("Not authenticated."));
|
|
173
|
+
console.log(chalk.gray("Run `funforge login` first."));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
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
|
+
function sleep(ms) {
|
|
195
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
196
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Management Commands
|
|
3
|
+
*
|
|
4
|
+
* - domains list: List custom domains
|
|
5
|
+
* - domains add: Add a custom domain
|
|
6
|
+
* - domains remove: Remove a domain
|
|
7
|
+
* - domains verify: Verify DNS and provision SSL
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* funforge domains list
|
|
11
|
+
*
|
|
12
|
+
* List all custom domains for the linked app.
|
|
13
|
+
*/
|
|
14
|
+
export declare function domainsListCommand(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* funforge domains add <domain>
|
|
17
|
+
*
|
|
18
|
+
* Add a custom domain to the linked app.
|
|
19
|
+
*/
|
|
20
|
+
export declare function domainsAddCommand(domain: string, options: {
|
|
21
|
+
method?: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* funforge domains remove <domain>
|
|
25
|
+
*
|
|
26
|
+
* Remove a custom domain from the linked app.
|
|
27
|
+
*/
|
|
28
|
+
export declare function domainsRemoveCommand(domain: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* funforge domains verify <domain>
|
|
31
|
+
*
|
|
32
|
+
* Verify DNS and provision SSL for a custom domain.
|
|
33
|
+
*/
|
|
34
|
+
export declare function domainsVerifyCommand(domain: string): Promise<void>;
|
|
35
|
+
//# sourceMappingURL=domains.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/commands/domains.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqCH;;;;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"}
|