@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.
Files changed (59) hide show
  1. package/README.md +273 -0
  2. package/dist/__tests__/api.test.d.ts +5 -0
  3. package/dist/__tests__/api.test.d.ts.map +1 -0
  4. package/dist/__tests__/api.test.js +177 -0
  5. package/dist/__tests__/config.test.d.ts +5 -0
  6. package/dist/__tests__/config.test.d.ts.map +1 -0
  7. package/dist/__tests__/config.test.js +58 -0
  8. package/dist/__tests__/mcp.test.d.ts +7 -0
  9. package/dist/__tests__/mcp.test.d.ts.map +1 -0
  10. package/dist/__tests__/mcp.test.js +142 -0
  11. package/dist/__tests__/project-config.test.d.ts +5 -0
  12. package/dist/__tests__/project-config.test.d.ts.map +1 -0
  13. package/dist/__tests__/project-config.test.js +122 -0
  14. package/dist/__tests__/tarball.test.d.ts +5 -0
  15. package/dist/__tests__/tarball.test.d.ts.map +1 -0
  16. package/dist/__tests__/tarball.test.js +113 -0
  17. package/dist/api.d.ts +157 -0
  18. package/dist/api.d.ts.map +1 -0
  19. package/dist/api.js +165 -0
  20. package/dist/cli.d.ts +8 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +129 -0
  23. package/dist/commands/apps.d.ts +29 -0
  24. package/dist/commands/apps.d.ts.map +1 -0
  25. package/dist/commands/apps.js +151 -0
  26. package/dist/commands/auth.d.ts +27 -0
  27. package/dist/commands/auth.d.ts.map +1 -0
  28. package/dist/commands/auth.js +127 -0
  29. package/dist/commands/config.d.ts +31 -0
  30. package/dist/commands/config.d.ts.map +1 -0
  31. package/dist/commands/config.js +287 -0
  32. package/dist/commands/deploy.d.ts +24 -0
  33. package/dist/commands/deploy.d.ts.map +1 -0
  34. package/dist/commands/deploy.js +196 -0
  35. package/dist/commands/domains.d.ts +35 -0
  36. package/dist/commands/domains.d.ts.map +1 -0
  37. package/dist/commands/domains.js +217 -0
  38. package/dist/commands/env.d.ts +26 -0
  39. package/dist/commands/env.d.ts.map +1 -0
  40. package/dist/commands/env.js +183 -0
  41. package/dist/config.d.ts +22 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +23 -0
  44. package/dist/credentials.d.ts +46 -0
  45. package/dist/credentials.d.ts.map +1 -0
  46. package/dist/credentials.js +60 -0
  47. package/dist/index.d.ts +14 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +18 -0
  50. package/dist/mcp.d.ts +19 -0
  51. package/dist/mcp.d.ts.map +1 -0
  52. package/dist/mcp.js +480 -0
  53. package/dist/project-config.d.ts +47 -0
  54. package/dist/project-config.d.ts.map +1 -0
  55. package/dist/project-config.js +55 -0
  56. package/dist/tarball.d.ts +29 -0
  57. package/dist/tarball.d.ts.map +1 -0
  58. package/dist/tarball.js +148 -0
  59. package/package.json +45 -0
package/dist/cli.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FunForge CLI
4
+ *
5
+ * Deploy without git, without leaving your editor.
6
+ */
7
+ import chalk from "chalk";
8
+ import { Command } from "commander";
9
+ import { appsCreateCommand, appsListCommand, linkCommand, } from "./commands/apps.js";
10
+ // Import commands
11
+ import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
12
+ import { configPullCommand, configPushCommand, configShowCommand, } from "./commands/config.js";
13
+ import { deployCommand } from "./commands/deploy.js";
14
+ import { domainsAddCommand, domainsListCommand, domainsRemoveCommand, domainsVerifyCommand, } from "./commands/domains.js";
15
+ import { envListCommand, envSetCommand, envUnsetCommand, } from "./commands/env.js";
16
+ const program = new Command();
17
+ // CLI metadata
18
+ program
19
+ .name("funforge")
20
+ .description("Deploy without git, without leaving your editor")
21
+ .version("0.1.0");
22
+ // ============================================
23
+ // AUTH COMMANDS
24
+ // ============================================
25
+ program
26
+ .command("login")
27
+ .description("Authenticate with FunForge")
28
+ .action(loginCommand);
29
+ program
30
+ .command("logout")
31
+ .description("Clear stored credentials")
32
+ .action(logoutCommand);
33
+ program
34
+ .command("whoami")
35
+ .description("Show current authenticated user")
36
+ .action(whoamiCommand);
37
+ // ============================================
38
+ // APP COMMANDS
39
+ // ============================================
40
+ const appsCmd = program.command("apps").description("Manage apps");
41
+ appsCmd.command("list").description("List all apps").action(appsListCommand);
42
+ appsCmd
43
+ .command("create <name>")
44
+ .description("Create a new app")
45
+ .option("-s, --slug <slug>", "Custom slug (subdomain)")
46
+ .action(appsCreateCommand);
47
+ program
48
+ .command("link [appId]")
49
+ .description("Link current directory to an app")
50
+ .action(linkCommand);
51
+ // ============================================
52
+ // DEPLOY COMMAND
53
+ // ============================================
54
+ program
55
+ .command("deploy")
56
+ .description("Deploy the current directory")
57
+ .option("-y, --yes", "Skip confirmation prompt")
58
+ .option("--no-watch", "Don't watch deployment logs")
59
+ .action(deployCommand);
60
+ // ============================================
61
+ // ENVIRONMENT VARIABLES
62
+ // ============================================
63
+ const envCmd = program
64
+ .command("env")
65
+ .description("Manage environment variables");
66
+ envCmd
67
+ .command("list")
68
+ .description("List environment variables")
69
+ .action(envListCommand);
70
+ envCmd
71
+ .command("set <pairs...>")
72
+ .description("Set environment variables (KEY=VALUE)")
73
+ .action(envSetCommand);
74
+ envCmd
75
+ .command("unset <keys...>")
76
+ .description("Remove environment variables")
77
+ .action(envUnsetCommand);
78
+ // ============================================
79
+ // DOMAIN MANAGEMENT
80
+ // ============================================
81
+ const domainsCmd = program
82
+ .command("domains")
83
+ .description("Manage custom domains");
84
+ domainsCmd
85
+ .command("list")
86
+ .description("List custom domains")
87
+ .action(domainsListCommand);
88
+ domainsCmd
89
+ .command("add <domain>")
90
+ .description("Add a custom domain")
91
+ .option("-m, --method <method>", "Verification method (cname or txt)", "cname")
92
+ .action(domainsAddCommand);
93
+ domainsCmd
94
+ .command("remove <domain>")
95
+ .description("Remove a custom domain")
96
+ .action(domainsRemoveCommand);
97
+ domainsCmd
98
+ .command("verify <domain>")
99
+ .description("Verify DNS and provision SSL")
100
+ .action(domainsVerifyCommand);
101
+ // ============================================
102
+ // CONFIG COMMANDS
103
+ // ============================================
104
+ const configCmd = program
105
+ .command("config")
106
+ .description("Manage build settings (sync funforge.json with server)");
107
+ configCmd
108
+ .command("push")
109
+ .description("Push local funforge.json settings to server")
110
+ .action(configPushCommand);
111
+ configCmd
112
+ .command("pull")
113
+ .description("Pull server settings to local funforge.json")
114
+ .action(configPullCommand);
115
+ configCmd
116
+ .command("show")
117
+ .description("Show comparison between local and server settings")
118
+ .action(configShowCommand);
119
+ // ============================================
120
+ // ERROR HANDLING
121
+ // ============================================
122
+ program.on("command:*", () => {
123
+ console.error(chalk.red(`Unknown command: ${program.args.join(" ")}`));
124
+ console.log();
125
+ program.outputHelp();
126
+ process.exit(1);
127
+ });
128
+ // Parse and run
129
+ program.parse();
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Apps Commands
3
+ *
4
+ * - apps list: List all apps
5
+ * - apps create: Create a new app
6
+ * - link: Link current directory to an app
7
+ */
8
+ /**
9
+ * funforge apps list
10
+ *
11
+ * List all apps for the authenticated user.
12
+ */
13
+ export declare function appsListCommand(): Promise<void>;
14
+ /**
15
+ * funforge apps create <name>
16
+ *
17
+ * Create a new app.
18
+ */
19
+ export declare function appsCreateCommand(name: string, options: {
20
+ slug?: string;
21
+ }): Promise<void>;
22
+ /**
23
+ * funforge link [appId]
24
+ *
25
+ * Link current directory to an app.
26
+ * If no appId provided, shows interactive selector.
27
+ */
28
+ export declare function linkCommand(appId?: string): Promise<void>;
29
+ //# sourceMappingURL=apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;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,CAAA;CAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D/D"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Apps Commands
3
+ *
4
+ * - apps list: List all apps
5
+ * - apps create: Create a new app
6
+ * - link: Link current directory to an app
7
+ */
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ import { ApiError, createApp, getApp, listApps } from "../api.js";
11
+ import { isAuthenticated } from "../credentials.js";
12
+ import { readConfig, updateConfig } from "../project-config.js";
13
+ /**
14
+ * funforge apps list
15
+ *
16
+ * List all apps for the authenticated user.
17
+ */
18
+ export async function appsListCommand() {
19
+ requireAuth();
20
+ const spinner = ora("Fetching apps...").start();
21
+ try {
22
+ const { apps } = await listApps();
23
+ spinner.stop();
24
+ if (apps.length === 0) {
25
+ console.log(chalk.yellow("No apps found."));
26
+ console.log(chalk.gray("Create one with `funforge apps create <name>`"));
27
+ return;
28
+ }
29
+ console.log(chalk.bold(`Your apps (${apps.length}):`));
30
+ console.log();
31
+ for (const app of apps) {
32
+ console.log(` ${chalk.cyan(app.slug)} - ${app.name}`);
33
+ console.log(chalk.gray(` ID: ${app.id}`));
34
+ console.log(chalk.gray(` Created: ${new Date(app.createdAt).toLocaleDateString()}`));
35
+ console.log();
36
+ }
37
+ }
38
+ catch (error) {
39
+ spinner.fail("Failed to fetch apps");
40
+ handleError(error);
41
+ }
42
+ }
43
+ /**
44
+ * funforge apps create <name>
45
+ *
46
+ * Create a new app.
47
+ */
48
+ export async function appsCreateCommand(name, options) {
49
+ requireAuth();
50
+ const spinner = ora(`Creating app "${name}"...`).start();
51
+ try {
52
+ const { app } = await createApp({ name, slug: options.slug });
53
+ spinner.succeed(`App created!`);
54
+ console.log();
55
+ console.log(` Name: ${app.name}`);
56
+ console.log(` Slug: ${chalk.cyan(app.slug)}`);
57
+ console.log(` ID: ${app.id}`);
58
+ console.log();
59
+ console.log(chalk.gray("Link this directory with:"));
60
+ console.log(chalk.gray(` funforge link ${app.id}`));
61
+ }
62
+ catch (error) {
63
+ spinner.fail("Failed to create app");
64
+ handleError(error);
65
+ }
66
+ }
67
+ /**
68
+ * funforge link [appId]
69
+ *
70
+ * Link current directory to an app.
71
+ * If no appId provided, shows interactive selector.
72
+ */
73
+ export async function linkCommand(appId) {
74
+ requireAuth();
75
+ // Check if already linked
76
+ const existingConfig = await readConfig();
77
+ if (existingConfig?.appId) {
78
+ console.log(chalk.yellow(`Already linked to ${existingConfig.appName ?? existingConfig.appId}`));
79
+ console.log(chalk.gray("Edit funforge.json to change the link."));
80
+ return;
81
+ }
82
+ // If no appId, list apps and let user choose
83
+ if (!appId) {
84
+ const spinner = ora("Fetching apps...").start();
85
+ const { apps } = await listApps();
86
+ spinner.stop();
87
+ if (apps.length === 0) {
88
+ console.log(chalk.yellow("No apps found."));
89
+ console.log(chalk.gray("Create one with `funforge apps create <name>`"));
90
+ return;
91
+ }
92
+ console.log(chalk.bold("Select an app to link:"));
93
+ console.log();
94
+ apps.forEach((app, i) => {
95
+ console.log(` ${i + 1}. ${chalk.cyan(app.slug)} - ${app.name}`);
96
+ });
97
+ console.log();
98
+ console.log(chalk.gray("Run: funforge link <app-id>"));
99
+ console.log();
100
+ console.log("App IDs:");
101
+ apps.forEach((app) => {
102
+ console.log(` ${app.slug}: ${app.id}`);
103
+ });
104
+ return;
105
+ }
106
+ // Verify app exists
107
+ const spinner = ora("Verifying app...").start();
108
+ try {
109
+ const { app } = await getApp(appId);
110
+ spinner.stop();
111
+ // Save to funforge.json
112
+ await updateConfig({
113
+ appId: app.id,
114
+ appName: app.name,
115
+ appSlug: app.slug,
116
+ });
117
+ console.log(chalk.green(`Linked to ${app.name} (${app.slug})`));
118
+ console.log(chalk.gray("funforge.json created."));
119
+ }
120
+ catch (error) {
121
+ spinner.fail("Failed to link app");
122
+ handleError(error);
123
+ }
124
+ }
125
+ /**
126
+ * Check if user is authenticated, exit if not
127
+ */
128
+ function requireAuth() {
129
+ if (!isAuthenticated()) {
130
+ console.log(chalk.red("Not authenticated."));
131
+ console.log(chalk.gray("Run `funforge login` first."));
132
+ process.exit(1);
133
+ }
134
+ }
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
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Auth Commands
3
+ *
4
+ * - login: Authenticate via device flow
5
+ * - logout: Clear credentials
6
+ * - whoami: Show current user
7
+ */
8
+ /**
9
+ * funforge login
10
+ *
11
+ * Authenticate using device auth flow.
12
+ * Opens browser for user to confirm authorization.
13
+ */
14
+ export declare function loginCommand(): Promise<void>;
15
+ /**
16
+ * funforge logout
17
+ *
18
+ * Clear stored credentials.
19
+ */
20
+ export declare function logoutCommand(): Promise<void>;
21
+ /**
22
+ * funforge whoami
23
+ *
24
+ * Show current authenticated user.
25
+ */
26
+ export declare function whoamiCommand(): Promise<void>;
27
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +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,CA+FlD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CASnD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAcnD"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Auth Commands
3
+ *
4
+ * - login: Authenticate via device flow
5
+ * - logout: Clear credentials
6
+ * - whoami: Show current user
7
+ */
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ import { initDeviceAuth, pollDeviceAuth } from "../api.js";
11
+ import { getConfig } from "../config.js";
12
+ import { clearCredentials, getConfigPath, getCredentials, isAuthenticated, saveCredentials, } from "../credentials.js";
13
+ /**
14
+ * funforge login
15
+ *
16
+ * Authenticate using device auth flow.
17
+ * Opens browser for user to confirm authorization.
18
+ */
19
+ export async function loginCommand() {
20
+ if (isAuthenticated()) {
21
+ const creds = getCredentials();
22
+ console.log(chalk.yellow(`Already logged in as ${creds.email ?? "unknown"}`));
23
+ console.log(chalk.gray("Run `funforge logout` first to switch accounts."));
24
+ return;
25
+ }
26
+ const spinner = ora("Initializing authentication...").start();
27
+ try {
28
+ // Initialize device auth
29
+ const auth = await initDeviceAuth();
30
+ spinner.stop();
31
+ const config = getConfig();
32
+ const verificationUrl = `${config.authUrl}/cli/authorize?code=${auth.userCode}`;
33
+ // Display instructions
34
+ console.log();
35
+ console.log(chalk.bold("To authenticate, open this URL in your browser:"));
36
+ console.log();
37
+ console.log(chalk.cyan(` ${verificationUrl}`));
38
+ console.log();
39
+ console.log(chalk.gray(`Or enter this code manually: ${chalk.bold(auth.userCode)}`));
40
+ console.log();
41
+ // Try to open browser automatically
42
+ try {
43
+ const { default: open } = await import("open");
44
+ await open(verificationUrl);
45
+ console.log(chalk.gray("Browser opened automatically."));
46
+ }
47
+ catch {
48
+ // open package not available or failed, user will copy URL manually
49
+ console.log(chalk.gray("Copy and paste the URL above into your browser."));
50
+ }
51
+ // Poll for completion
52
+ const pollSpinner = ora("Waiting for authorization...").start();
53
+ const startTime = Date.now();
54
+ const expiresAt = startTime + auth.expiresIn * 1000;
55
+ while (Date.now() < expiresAt) {
56
+ await sleep(auth.interval * 1000);
57
+ const result = await pollDeviceAuth(auth.deviceCode);
58
+ if (result.status === "authorized") {
59
+ pollSpinner.succeed("Authenticated!");
60
+ // Save credentials
61
+ saveCredentials({
62
+ apiKey: result.apiKey,
63
+ userId: result.userId,
64
+ email: result.email,
65
+ });
66
+ console.log();
67
+ console.log(chalk.green(`Logged in as ${result.email}`));
68
+ return;
69
+ }
70
+ if (result.status === "denied") {
71
+ pollSpinner.fail("Authorization denied");
72
+ console.log(chalk.red("You denied the authorization request."));
73
+ process.exit(1);
74
+ }
75
+ if (result.status === "expired") {
76
+ pollSpinner.fail("Authorization expired");
77
+ console.log(chalk.red("The authorization code expired. Please try again."));
78
+ process.exit(1);
79
+ }
80
+ // status === "pending", continue polling
81
+ }
82
+ pollSpinner.fail("Authorization timed out");
83
+ console.log(chalk.red("Authorization timed out. Please try again."));
84
+ process.exit(1);
85
+ }
86
+ catch (error) {
87
+ spinner.fail("Authentication failed");
88
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
89
+ process.exit(1);
90
+ }
91
+ }
92
+ /**
93
+ * funforge logout
94
+ *
95
+ * Clear stored credentials.
96
+ */
97
+ export async function logoutCommand() {
98
+ if (!isAuthenticated()) {
99
+ console.log(chalk.yellow("Not logged in."));
100
+ return;
101
+ }
102
+ const creds = getCredentials();
103
+ clearCredentials();
104
+ console.log(chalk.green(`Logged out from ${creds.email ?? "account"}`));
105
+ }
106
+ /**
107
+ * funforge whoami
108
+ *
109
+ * Show current authenticated user.
110
+ */
111
+ export async function whoamiCommand() {
112
+ if (!isAuthenticated()) {
113
+ console.log(chalk.yellow("Not logged in."));
114
+ console.log(chalk.gray("Run `funforge login` to authenticate."));
115
+ process.exit(1);
116
+ }
117
+ const creds = getCredentials();
118
+ console.log(chalk.bold("Logged in as:"));
119
+ console.log(` Email: ${creds.email ?? "unknown"}`);
120
+ console.log(` User ID: ${creds.userId ?? "unknown"}`);
121
+ console.log(` Since: ${creds.savedAt ?? "unknown"}`);
122
+ console.log();
123
+ console.log(chalk.gray(`Config: ${getConfigPath()}`));
124
+ }
125
+ function sleep(ms) {
126
+ return new Promise((resolve) => setTimeout(resolve, ms));
127
+ }
@@ -0,0 +1,31 @@
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
+ /**
12
+ * funforge config push
13
+ *
14
+ * Push local funforge.json build settings to server.
15
+ * Local settings overwrite server settings.
16
+ */
17
+ export declare function configPushCommand(): Promise<void>;
18
+ /**
19
+ * funforge config pull
20
+ *
21
+ * Pull server build settings to local funforge.json.
22
+ * Server settings are written to local file.
23
+ */
24
+ export declare function configPullCommand(): Promise<void>;
25
+ /**
26
+ * funforge config show
27
+ *
28
+ * Show comparison between local funforge.json and server settings.
29
+ */
30
+ export declare function configShowCommand(): Promise<void>;
31
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwEH;;;;;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"}