@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,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Config Tests
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
8
|
+
import { configExists, getLinkedAppId, readConfig, updateConfig, writeConfig, } from "../project-config.js";
|
|
9
|
+
describe("project-config", () => {
|
|
10
|
+
let testDir;
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
testDir = join(tmpdir(), `funforge-config-test-${Date.now()}`);
|
|
13
|
+
await mkdir(testDir, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(testDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
describe("configExists", () => {
|
|
19
|
+
it("should return false when config does not exist", async () => {
|
|
20
|
+
const exists = await configExists(testDir);
|
|
21
|
+
expect(exists).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it("should return true when config exists", async () => {
|
|
24
|
+
await writeFile(join(testDir, "funforge.json"), "{}");
|
|
25
|
+
const exists = await configExists(testDir);
|
|
26
|
+
expect(exists).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("readConfig", () => {
|
|
30
|
+
it("should return null when config does not exist", async () => {
|
|
31
|
+
const config = await readConfig(testDir);
|
|
32
|
+
expect(config).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it("should read and parse config file", async () => {
|
|
35
|
+
const configData = {
|
|
36
|
+
appId: "app-123",
|
|
37
|
+
appName: "My App",
|
|
38
|
+
appSlug: "my-app",
|
|
39
|
+
};
|
|
40
|
+
await writeFile(join(testDir, "funforge.json"), JSON.stringify(configData));
|
|
41
|
+
const config = await readConfig(testDir);
|
|
42
|
+
expect(config).toEqual(configData);
|
|
43
|
+
});
|
|
44
|
+
it("should read config with build settings", async () => {
|
|
45
|
+
const configData = {
|
|
46
|
+
appId: "app-123",
|
|
47
|
+
build: {
|
|
48
|
+
buildCommand: "npm run build",
|
|
49
|
+
installCommand: "npm ci",
|
|
50
|
+
startCommand: "npm start",
|
|
51
|
+
nodeVersion: "20",
|
|
52
|
+
},
|
|
53
|
+
port: 3000,
|
|
54
|
+
};
|
|
55
|
+
await writeFile(join(testDir, "funforge.json"), JSON.stringify(configData));
|
|
56
|
+
const config = await readConfig(testDir);
|
|
57
|
+
expect(config?.build?.buildCommand).toBe("npm run build");
|
|
58
|
+
expect(config?.port).toBe(3000);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("writeConfig", () => {
|
|
62
|
+
it("should write config file with formatting", async () => {
|
|
63
|
+
const configData = {
|
|
64
|
+
appId: "app-456",
|
|
65
|
+
appName: "Test App",
|
|
66
|
+
};
|
|
67
|
+
await writeConfig(configData, testDir);
|
|
68
|
+
const content = await readFile(join(testDir, "funforge.json"), "utf-8");
|
|
69
|
+
expect(content).toContain('"appId": "app-456"');
|
|
70
|
+
expect(content).toContain('"appName": "Test App"');
|
|
71
|
+
// Should be formatted with 2-space indent
|
|
72
|
+
expect(content).toMatch(/^\{\n /);
|
|
73
|
+
});
|
|
74
|
+
it("should overwrite existing config", async () => {
|
|
75
|
+
await writeFile(join(testDir, "funforge.json"), JSON.stringify({ appId: "old-id" }));
|
|
76
|
+
await writeConfig({ appId: "new-id" }, testDir);
|
|
77
|
+
const config = await readConfig(testDir);
|
|
78
|
+
expect(config?.appId).toBe("new-id");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("updateConfig", () => {
|
|
82
|
+
it("should create config if it does not exist", async () => {
|
|
83
|
+
const updated = await updateConfig({ appId: "app-new" }, testDir);
|
|
84
|
+
expect(updated.appId).toBe("app-new");
|
|
85
|
+
const exists = await configExists(testDir);
|
|
86
|
+
expect(exists).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
it("should merge with existing config", async () => {
|
|
89
|
+
await writeConfig({
|
|
90
|
+
appId: "app-123",
|
|
91
|
+
appName: "Original Name",
|
|
92
|
+
appSlug: "original-slug",
|
|
93
|
+
}, testDir);
|
|
94
|
+
const updated = await updateConfig({ appName: "New Name" }, testDir);
|
|
95
|
+
expect(updated.appId).toBe("app-123");
|
|
96
|
+
expect(updated.appName).toBe("New Name");
|
|
97
|
+
expect(updated.appSlug).toBe("original-slug");
|
|
98
|
+
});
|
|
99
|
+
it("should add new fields to existing config", async () => {
|
|
100
|
+
await writeConfig({ appId: "app-123" }, testDir);
|
|
101
|
+
const updated = await updateConfig({ port: 8080 }, testDir);
|
|
102
|
+
expect(updated.appId).toBe("app-123");
|
|
103
|
+
expect(updated.port).toBe(8080);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe("getLinkedAppId", () => {
|
|
107
|
+
it("should return null when config does not exist", async () => {
|
|
108
|
+
const appId = await getLinkedAppId(testDir);
|
|
109
|
+
expect(appId).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
it("should return null when appId is not set", async () => {
|
|
112
|
+
await writeConfig({ appName: "Test" }, testDir);
|
|
113
|
+
const appId = await getLinkedAppId(testDir);
|
|
114
|
+
expect(appId).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
it("should return appId when set", async () => {
|
|
117
|
+
await writeConfig({ appId: "app-linked-123" }, testDir);
|
|
118
|
+
const appId = await getLinkedAppId(testDir);
|
|
119
|
+
expect(appId).toBe("app-linked-123");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tarball.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tarball.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tarball Creation Tests
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
8
|
+
import { createTarball, formatSize, readTarball } from "../tarball.js";
|
|
9
|
+
describe("tarball", () => {
|
|
10
|
+
let testDir;
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
// Create a temporary test directory
|
|
13
|
+
testDir = join(tmpdir(), `funforge-test-${Date.now()}`);
|
|
14
|
+
await mkdir(testDir, { recursive: true });
|
|
15
|
+
});
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
// Clean up test directory
|
|
18
|
+
await rm(testDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
describe("createTarball", () => {
|
|
21
|
+
it("should create tarball from directory with files", async () => {
|
|
22
|
+
// Create test files
|
|
23
|
+
await writeFile(join(testDir, "index.js"), "console.log('hello');");
|
|
24
|
+
await writeFile(join(testDir, "package.json"), '{"name": "test"}');
|
|
25
|
+
const result = await createTarball(testDir);
|
|
26
|
+
expect(result.fileCount).toBe(2);
|
|
27
|
+
expect(result.size).toBeGreaterThan(0);
|
|
28
|
+
expect(result.sha256).toMatch(/^[a-f0-9]{64}$/);
|
|
29
|
+
expect(result.path).toContain("funforge-");
|
|
30
|
+
expect(result.path).toContain(".tar.gz");
|
|
31
|
+
});
|
|
32
|
+
it("should exclude node_modules by default", async () => {
|
|
33
|
+
await writeFile(join(testDir, "index.js"), "// main file");
|
|
34
|
+
await mkdir(join(testDir, "node_modules", "some-package"), {
|
|
35
|
+
recursive: true,
|
|
36
|
+
});
|
|
37
|
+
await writeFile(join(testDir, "node_modules", "some-package", "index.js"), "// dep");
|
|
38
|
+
const result = await createTarball(testDir);
|
|
39
|
+
expect(result.fileCount).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
it("should exclude .git directory by default", async () => {
|
|
42
|
+
await writeFile(join(testDir, "index.js"), "// main");
|
|
43
|
+
await mkdir(join(testDir, ".git", "objects"), { recursive: true });
|
|
44
|
+
await writeFile(join(testDir, ".git", "config"), "[core]");
|
|
45
|
+
const result = await createTarball(testDir);
|
|
46
|
+
expect(result.fileCount).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
it("should exclude .env files by default", async () => {
|
|
49
|
+
await writeFile(join(testDir, "index.js"), "// main");
|
|
50
|
+
await writeFile(join(testDir, ".env"), "SECRET=xxx");
|
|
51
|
+
await writeFile(join(testDir, ".env.local"), "LOCAL=yyy");
|
|
52
|
+
const result = await createTarball(testDir);
|
|
53
|
+
expect(result.fileCount).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
it("should respect .gitignore patterns", async () => {
|
|
56
|
+
await writeFile(join(testDir, "index.js"), "// main");
|
|
57
|
+
await writeFile(join(testDir, "secret.txt"), "secret stuff");
|
|
58
|
+
await writeFile(join(testDir, ".gitignore"), "secret.txt");
|
|
59
|
+
const result = await createTarball(testDir);
|
|
60
|
+
// .gitignore itself is included (2 files: index.js + .gitignore)
|
|
61
|
+
// secret.txt is excluded by .gitignore pattern
|
|
62
|
+
expect(result.fileCount).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
it("should respect .funforgeignore patterns", async () => {
|
|
65
|
+
await writeFile(join(testDir, "index.js"), "// main");
|
|
66
|
+
await writeFile(join(testDir, "large-file.bin"), "x".repeat(1000));
|
|
67
|
+
await writeFile(join(testDir, ".funforgeignore"), "*.bin");
|
|
68
|
+
const result = await createTarball(testDir);
|
|
69
|
+
expect(result.fileCount).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
it("should include dotfiles except excluded ones", async () => {
|
|
72
|
+
await writeFile(join(testDir, "index.js"), "// main");
|
|
73
|
+
await writeFile(join(testDir, ".prettierrc"), "{}");
|
|
74
|
+
await writeFile(join(testDir, ".eslintrc.json"), "{}");
|
|
75
|
+
const result = await createTarball(testDir);
|
|
76
|
+
expect(result.fileCount).toBe(3);
|
|
77
|
+
});
|
|
78
|
+
it("should throw error when no files to include", async () => {
|
|
79
|
+
// Empty directory - all files will be excluded or none exist
|
|
80
|
+
await expect(createTarball(testDir)).rejects.toThrow("No files to include");
|
|
81
|
+
});
|
|
82
|
+
it("should handle nested directories", async () => {
|
|
83
|
+
await mkdir(join(testDir, "src", "components"), { recursive: true });
|
|
84
|
+
await writeFile(join(testDir, "src", "index.js"), "// entry");
|
|
85
|
+
await writeFile(join(testDir, "src", "components", "App.js"), "// app");
|
|
86
|
+
await writeFile(join(testDir, "package.json"), "{}");
|
|
87
|
+
const result = await createTarball(testDir);
|
|
88
|
+
expect(result.fileCount).toBe(3);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("readTarball", () => {
|
|
92
|
+
it("should read tarball as buffer", async () => {
|
|
93
|
+
await writeFile(join(testDir, "test.txt"), "hello world");
|
|
94
|
+
const { path } = await createTarball(testDir);
|
|
95
|
+
const buffer = await readTarball(path);
|
|
96
|
+
expect(Buffer.isBuffer(buffer)).toBe(true);
|
|
97
|
+
expect(buffer.length).toBeGreaterThan(0);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe("formatSize", () => {
|
|
101
|
+
it("should format bytes", () => {
|
|
102
|
+
expect(formatSize(500)).toBe("500 B");
|
|
103
|
+
});
|
|
104
|
+
it("should format kilobytes", () => {
|
|
105
|
+
expect(formatSize(1024)).toBe("1.0 KB");
|
|
106
|
+
expect(formatSize(1536)).toBe("1.5 KB");
|
|
107
|
+
});
|
|
108
|
+
it("should format megabytes", () => {
|
|
109
|
+
expect(formatSize(1024 * 1024)).toBe("1.0 MB");
|
|
110
|
+
expect(formatSize(1024 * 1024 * 2.5)).toBe("2.5 MB");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client
|
|
3
|
+
*
|
|
4
|
+
* HTTP client for FunForge API endpoints.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApiError extends Error {
|
|
7
|
+
statusCode: number;
|
|
8
|
+
body?: unknown | undefined;
|
|
9
|
+
constructor(message: string, statusCode: number, body?: unknown | undefined);
|
|
10
|
+
}
|
|
11
|
+
interface RequestOptions {
|
|
12
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
13
|
+
body?: unknown;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
/** Use API key auth (default: true) */
|
|
16
|
+
useAuth?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Make an API request
|
|
20
|
+
*/
|
|
21
|
+
export declare function apiRequest<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
22
|
+
export interface DeviceAuthInitResponse {
|
|
23
|
+
deviceCode: string;
|
|
24
|
+
userCode: string;
|
|
25
|
+
verificationUrl: string;
|
|
26
|
+
expiresIn: number;
|
|
27
|
+
interval: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DeviceAuthPollResponse {
|
|
30
|
+
status: "pending" | "authorized" | "expired" | "denied";
|
|
31
|
+
apiKey?: string;
|
|
32
|
+
userId?: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Initialize device auth flow
|
|
37
|
+
*/
|
|
38
|
+
export declare function initDeviceAuth(): Promise<DeviceAuthInitResponse>;
|
|
39
|
+
/**
|
|
40
|
+
* Poll for device auth completion
|
|
41
|
+
*/
|
|
42
|
+
export declare function pollDeviceAuth(deviceCode: string): Promise<DeviceAuthPollResponse>;
|
|
43
|
+
export interface App {
|
|
44
|
+
id: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
name: string;
|
|
47
|
+
sourceType: string;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
/** Subdomain (e.g., tenant-slug-app-slug.funforge.app) */
|
|
50
|
+
subdomain?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface AppDetails extends App {
|
|
53
|
+
/** Port the app listens on */
|
|
54
|
+
port?: number;
|
|
55
|
+
/** Custom build command */
|
|
56
|
+
buildCommand?: string | null;
|
|
57
|
+
/** Custom install command */
|
|
58
|
+
installCommand?: string | null;
|
|
59
|
+
/** Custom start command */
|
|
60
|
+
startCommand?: string | null;
|
|
61
|
+
/** Node.js version (18, 20, 22) */
|
|
62
|
+
nodeVersion?: string | null;
|
|
63
|
+
}
|
|
64
|
+
export interface ListAppsResponse {
|
|
65
|
+
apps: App[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* List user's apps
|
|
69
|
+
*/
|
|
70
|
+
export declare function listApps(): Promise<ListAppsResponse>;
|
|
71
|
+
/**
|
|
72
|
+
* Get app by ID
|
|
73
|
+
*/
|
|
74
|
+
export declare function getApp(appId: string): Promise<{
|
|
75
|
+
app: App;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Get app details including build settings
|
|
79
|
+
*/
|
|
80
|
+
export declare function getAppDetails(appId: string): Promise<{
|
|
81
|
+
app: AppDetails;
|
|
82
|
+
}>;
|
|
83
|
+
/** Build settings that can be updated */
|
|
84
|
+
export interface UpdateAppSettings {
|
|
85
|
+
/** Port the app listens on */
|
|
86
|
+
port?: number;
|
|
87
|
+
/** Custom build command (null clears) */
|
|
88
|
+
buildCommand?: string | null;
|
|
89
|
+
/** Custom install command (null clears) */
|
|
90
|
+
installCommand?: string | null;
|
|
91
|
+
/** Custom start command (null clears) */
|
|
92
|
+
startCommand?: string | null;
|
|
93
|
+
/** Node.js version (18, 20, 22) - null clears */
|
|
94
|
+
nodeVersion?: "18" | "20" | "22" | null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update app settings (build commands, port, etc.)
|
|
98
|
+
*/
|
|
99
|
+
export declare function updateApp(appId: string, settings: UpdateAppSettings): Promise<{
|
|
100
|
+
app: AppDetails;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Create a new app
|
|
104
|
+
*/
|
|
105
|
+
export declare function createApp(data: {
|
|
106
|
+
name: string;
|
|
107
|
+
slug?: string;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
app: App;
|
|
110
|
+
}>;
|
|
111
|
+
export interface UploadUrlResponse {
|
|
112
|
+
uploadUrl: string;
|
|
113
|
+
sourceId: string;
|
|
114
|
+
expiresIn: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get presigned URL for tarball upload
|
|
118
|
+
*/
|
|
119
|
+
export declare function getUploadUrl(appId: string, data: {
|
|
120
|
+
filename: string;
|
|
121
|
+
contentLength: number;
|
|
122
|
+
sha256?: string;
|
|
123
|
+
}): Promise<UploadUrlResponse>;
|
|
124
|
+
/**
|
|
125
|
+
* Upload tarball to presigned URL
|
|
126
|
+
*/
|
|
127
|
+
export declare function uploadTarball(uploadUrl: string, tarball: Buffer): Promise<void>;
|
|
128
|
+
export interface Deployment {
|
|
129
|
+
id: string;
|
|
130
|
+
appId: string;
|
|
131
|
+
status: string;
|
|
132
|
+
createdAt: string;
|
|
133
|
+
startedAt?: string;
|
|
134
|
+
completedAt?: string;
|
|
135
|
+
}
|
|
136
|
+
export interface DeployResponse {
|
|
137
|
+
deployment: Deployment;
|
|
138
|
+
logsUrl: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create deployment from uploaded source
|
|
142
|
+
*/
|
|
143
|
+
export declare function createDeployment(appId: string, data: {
|
|
144
|
+
sourceId: string;
|
|
145
|
+
}): Promise<DeployResponse>;
|
|
146
|
+
/**
|
|
147
|
+
* Get deployment status
|
|
148
|
+
*/
|
|
149
|
+
export declare function getDeployment(deploymentId: string): Promise<{
|
|
150
|
+
deployment: Deployment;
|
|
151
|
+
}>;
|
|
152
|
+
/**
|
|
153
|
+
* Stream deployment logs (returns EventSource URL)
|
|
154
|
+
*/
|
|
155
|
+
export declare function getDeploymentLogsUrl(deploymentId: string): string;
|
|
156
|
+
export {};
|
|
157
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client
|
|
3
|
+
*
|
|
4
|
+
* HTTP client for FunForge API endpoints.
|
|
5
|
+
*/
|
|
6
|
+
import { getConfig } from "./config.js";
|
|
7
|
+
import { getApiKey } from "./credentials.js";
|
|
8
|
+
export class ApiError extends Error {
|
|
9
|
+
statusCode;
|
|
10
|
+
body;
|
|
11
|
+
constructor(message, statusCode, body) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.statusCode = statusCode;
|
|
14
|
+
this.body = body;
|
|
15
|
+
this.name = "ApiError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Make an API request
|
|
20
|
+
*/
|
|
21
|
+
export async function apiRequest(path, options = {}) {
|
|
22
|
+
const config = getConfig();
|
|
23
|
+
const { method = "GET", body, headers = {}, useAuth = true } = options;
|
|
24
|
+
const url = `${config.apiUrl}${path}`;
|
|
25
|
+
const requestHeaders = {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
...headers,
|
|
28
|
+
};
|
|
29
|
+
if (useAuth) {
|
|
30
|
+
const apiKey = getApiKey();
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new ApiError("Not authenticated. Run `funforge login` first.", 401);
|
|
33
|
+
}
|
|
34
|
+
requestHeaders["Authorization"] = `Bearer ${apiKey}`;
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(url, {
|
|
37
|
+
method,
|
|
38
|
+
headers: requestHeaders,
|
|
39
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
40
|
+
signal: AbortSignal.timeout(config.timeout),
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
let errorBody;
|
|
44
|
+
try {
|
|
45
|
+
errorBody = await response.json();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
errorBody = await response.text();
|
|
49
|
+
}
|
|
50
|
+
throw new ApiError(`API request failed: ${response.status}`, response.status, errorBody);
|
|
51
|
+
}
|
|
52
|
+
return response.json();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Initialize device auth flow
|
|
56
|
+
*/
|
|
57
|
+
export async function initDeviceAuth() {
|
|
58
|
+
const config = getConfig();
|
|
59
|
+
const response = await fetch(`${config.apiUrl}/api/cli/auth/device/init`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new ApiError("Failed to initialize auth", response.status);
|
|
65
|
+
}
|
|
66
|
+
return response.json();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Poll for device auth completion
|
|
70
|
+
*/
|
|
71
|
+
export async function pollDeviceAuth(deviceCode) {
|
|
72
|
+
const config = getConfig();
|
|
73
|
+
const response = await fetch(`${config.apiUrl}/api/cli/auth/device/poll`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({ deviceCode }),
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new ApiError("Failed to poll auth status", response.status);
|
|
80
|
+
}
|
|
81
|
+
return response.json();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* List user's apps
|
|
85
|
+
*/
|
|
86
|
+
export async function listApps() {
|
|
87
|
+
return apiRequest("/api/cli/apps");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get app by ID
|
|
91
|
+
*/
|
|
92
|
+
export async function getApp(appId) {
|
|
93
|
+
return apiRequest(`/api/cli/apps/${appId}`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get app details including build settings
|
|
97
|
+
*/
|
|
98
|
+
export async function getAppDetails(appId) {
|
|
99
|
+
return apiRequest(`/api/cli/apps/${appId}`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Update app settings (build commands, port, etc.)
|
|
103
|
+
*/
|
|
104
|
+
export async function updateApp(appId, settings) {
|
|
105
|
+
return apiRequest(`/api/cli/apps/${appId}`, {
|
|
106
|
+
method: "PATCH",
|
|
107
|
+
body: settings,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create a new app
|
|
112
|
+
*/
|
|
113
|
+
export async function createApp(data) {
|
|
114
|
+
return apiRequest("/api/cli/apps", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: data,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get presigned URL for tarball upload
|
|
121
|
+
*/
|
|
122
|
+
export async function getUploadUrl(appId, data) {
|
|
123
|
+
return apiRequest(`/api/cli/apps/${appId}/upload`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
body: data,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Upload tarball to presigned URL
|
|
130
|
+
*/
|
|
131
|
+
export async function uploadTarball(uploadUrl, tarball) {
|
|
132
|
+
const response = await fetch(uploadUrl, {
|
|
133
|
+
method: "PUT",
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "application/gzip",
|
|
136
|
+
},
|
|
137
|
+
body: tarball,
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new ApiError("Failed to upload tarball", response.status);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Create deployment from uploaded source
|
|
145
|
+
*/
|
|
146
|
+
export async function createDeployment(appId, data) {
|
|
147
|
+
return apiRequest(`/api/cli/apps/${appId}/deploy`, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
body: data,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get deployment status
|
|
154
|
+
*/
|
|
155
|
+
export async function getDeployment(deploymentId) {
|
|
156
|
+
return apiRequest(`/api/cli/deployments/${deploymentId}`);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Stream deployment logs (returns EventSource URL)
|
|
160
|
+
*/
|
|
161
|
+
export function getDeploymentLogsUrl(deploymentId) {
|
|
162
|
+
const config = getConfig();
|
|
163
|
+
const apiKey = getApiKey();
|
|
164
|
+
return `${config.apiUrl}/api/cli/deployments/${deploymentId}/logs?token=${apiKey}`;
|
|
165
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG"}
|