@butterbase/cli 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 +255 -0
- package/bin/butterbase.ts +137 -0
- package/dist/bin/butterbase.d.ts +3 -0
- package/dist/bin/butterbase.d.ts.map +1 -0
- package/dist/bin/butterbase.js +112 -0
- package/dist/bin/butterbase.js.map +1 -0
- package/dist/src/commands/apps.d.ts +5 -0
- package/dist/src/commands/apps.d.ts.map +1 -0
- package/dist/src/commands/apps.js +113 -0
- package/dist/src/commands/apps.js.map +1 -0
- package/dist/src/commands/config.d.ts +5 -0
- package/dist/src/commands/config.d.ts.map +1 -0
- package/dist/src/commands/config.js +43 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/functions.d.ts +15 -0
- package/dist/src/commands/functions.d.ts.map +1 -0
- package/dist/src/commands/functions.js +94 -0
- package/dist/src/commands/functions.js.map +1 -0
- package/dist/src/commands/init.d.ts +2 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +118 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/schema.d.ts +10 -0
- package/dist/src/commands/schema.d.ts.map +1 -0
- package/dist/src/commands/schema.js +69 -0
- package/dist/src/commands/schema.js.map +1 -0
- package/dist/src/commands/storage.d.ts +10 -0
- package/dist/src/commands/storage.d.ts.map +1 -0
- package/dist/src/commands/storage.js +105 -0
- package/dist/src/commands/storage.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +44 -0
- package/dist/src/lib/api-client.d.ts.map +1 -0
- package/dist/src/lib/api-client.js +129 -0
- package/dist/src/lib/api-client.js.map +1 -0
- package/dist/src/lib/config.d.ts +47 -0
- package/dist/src/lib/config.d.ts.map +1 -0
- package/dist/src/lib/config.js +87 -0
- package/dist/src/lib/config.js.map +1 -0
- package/package.json +35 -0
- package/src/commands/apps.ts +130 -0
- package/src/commands/config.ts +50 -0
- package/src/commands/functions.ts +116 -0
- package/src/commands/init.ts +151 -0
- package/src/commands/schema.ts +78 -0
- package/src/commands/storage.ts +117 -0
- package/src/lib/api-client.ts +176 -0
- package/src/lib/config.ts +112 -0
- package/templates/react-vite/.env.example +2 -0
- package/templates/react-vite/README.md +38 -0
- package/templates/react-vite/index.html +13 -0
- package/templates/react-vite/package.json +25 -0
- package/templates/react-vite/src/App.css +33 -0
- package/templates/react-vite/src/App.tsx +77 -0
- package/templates/react-vite/src/index.css +8 -0
- package/templates/react-vite/src/lib.ts +6 -0
- package/templates/react-vite/src/main.tsx +10 -0
- package/templates/react-vite/tsconfig.json +21 -0
- package/templates/react-vite/vite.config.ts +6 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getMergedConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get authorization headers
|
|
4
|
+
*/
|
|
5
|
+
async function getHeaders() {
|
|
6
|
+
const config = await getMergedConfig();
|
|
7
|
+
const headers = {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
};
|
|
10
|
+
if (config.apiKey) {
|
|
11
|
+
headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
12
|
+
}
|
|
13
|
+
return headers;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get base URL from config
|
|
17
|
+
*/
|
|
18
|
+
async function getBaseUrl() {
|
|
19
|
+
const config = await getMergedConfig();
|
|
20
|
+
return config.endpoint;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Make a GET request
|
|
24
|
+
*/
|
|
25
|
+
export async function apiGet(path) {
|
|
26
|
+
const baseUrl = await getBaseUrl();
|
|
27
|
+
const headers = await getHeaders();
|
|
28
|
+
const res = await fetch(`${baseUrl}${path}`, { headers });
|
|
29
|
+
const body = await res.json();
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new Error(body.error || body.message || 'Request failed');
|
|
32
|
+
}
|
|
33
|
+
return body;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Make a POST request
|
|
37
|
+
*/
|
|
38
|
+
export async function apiPost(path, data) {
|
|
39
|
+
const baseUrl = await getBaseUrl();
|
|
40
|
+
const headers = await getHeaders();
|
|
41
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers,
|
|
44
|
+
body: JSON.stringify(data),
|
|
45
|
+
});
|
|
46
|
+
const body = await res.json();
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(body.error || body.message || 'Request failed');
|
|
49
|
+
}
|
|
50
|
+
return body;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Make a PATCH request
|
|
54
|
+
*/
|
|
55
|
+
export async function apiPatch(path, data) {
|
|
56
|
+
const baseUrl = await getBaseUrl();
|
|
57
|
+
const headers = await getHeaders();
|
|
58
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
59
|
+
method: 'PATCH',
|
|
60
|
+
headers,
|
|
61
|
+
body: JSON.stringify(data),
|
|
62
|
+
});
|
|
63
|
+
const body = await res.json();
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
throw new Error(body.error || body.message || 'Request failed');
|
|
66
|
+
}
|
|
67
|
+
return body;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Make a DELETE request
|
|
71
|
+
*/
|
|
72
|
+
export async function apiDelete(path) {
|
|
73
|
+
const baseUrl = await getBaseUrl();
|
|
74
|
+
const headers = await getHeaders();
|
|
75
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
76
|
+
method: 'DELETE',
|
|
77
|
+
headers,
|
|
78
|
+
});
|
|
79
|
+
// Handle 204 No Content
|
|
80
|
+
if (res.status === 204) {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
const body = await res.json();
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
throw new Error(body.error || body.message || 'Request failed');
|
|
86
|
+
}
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
89
|
+
// MCP tool wrappers
|
|
90
|
+
export async function initApp(name) {
|
|
91
|
+
return apiPost('/init', { name });
|
|
92
|
+
}
|
|
93
|
+
export async function listApps() {
|
|
94
|
+
return apiGet('/apps');
|
|
95
|
+
}
|
|
96
|
+
export async function deleteApp(appId) {
|
|
97
|
+
return apiDelete(`/apps/${appId}`);
|
|
98
|
+
}
|
|
99
|
+
export async function getSchema(appId) {
|
|
100
|
+
return apiGet(`/v1/${appId}/schema`);
|
|
101
|
+
}
|
|
102
|
+
export async function applySchema(appId, schema, dryRun, name) {
|
|
103
|
+
return apiPost(`/v1/${appId}/schema/apply`, { schema, dry_run: dryRun, name });
|
|
104
|
+
}
|
|
105
|
+
export async function deployFunction(appId, data) {
|
|
106
|
+
return apiPost(`/v1/${appId}/functions`, data);
|
|
107
|
+
}
|
|
108
|
+
export async function listFunctions(appId) {
|
|
109
|
+
return apiGet(`/v1/${appId}/functions`);
|
|
110
|
+
}
|
|
111
|
+
export async function getFunctionLogs(appId, functionName, level, limit) {
|
|
112
|
+
const params = new URLSearchParams();
|
|
113
|
+
if (level)
|
|
114
|
+
params.set('level', level);
|
|
115
|
+
if (limit)
|
|
116
|
+
params.set('limit', String(limit));
|
|
117
|
+
const query = params.toString();
|
|
118
|
+
return apiGet(`/v1/${appId}/functions/${functionName}/logs${query ? `?${query}` : ''}`);
|
|
119
|
+
}
|
|
120
|
+
export async function generateUploadUrl(appId, filename, contentType, sizeBytes) {
|
|
121
|
+
return apiPost(`/storage/${appId}/upload`, { filename, contentType, sizeBytes });
|
|
122
|
+
}
|
|
123
|
+
export async function listStorageObjects(appId) {
|
|
124
|
+
return apiGet(`/storage/${appId}/objects`);
|
|
125
|
+
}
|
|
126
|
+
export async function deleteStorageObject(appId, objectId) {
|
|
127
|
+
return apiDelete(`/storage/${appId}/${objectId}`);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../../src/lib/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface ButterbaseConfig {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
currentApp?: string;
|
|
5
|
+
apps?: Record<string, {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the global config file path
|
|
13
|
+
*/
|
|
14
|
+
export declare function getConfigPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Load global configuration
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadConfig(): Promise<ButterbaseConfig>;
|
|
19
|
+
/**
|
|
20
|
+
* Save global configuration
|
|
21
|
+
*/
|
|
22
|
+
export declare function saveConfig(config: ButterbaseConfig): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Update a specific config value
|
|
25
|
+
*/
|
|
26
|
+
export declare function updateConfig(key: keyof ButterbaseConfig, value: any): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Load project-level configuration (from current directory)
|
|
29
|
+
*/
|
|
30
|
+
export declare function loadProjectConfig(): Promise<Partial<ButterbaseConfig> | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Save project-level configuration
|
|
33
|
+
*/
|
|
34
|
+
export declare function saveProjectConfig(config: Partial<ButterbaseConfig>): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Get merged configuration (project overrides global)
|
|
37
|
+
*/
|
|
38
|
+
export declare function getMergedConfig(): Promise<ButterbaseConfig>;
|
|
39
|
+
/**
|
|
40
|
+
* Get the current app ID from config
|
|
41
|
+
*/
|
|
42
|
+
export declare function getCurrentAppId(): Promise<string | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* Set the current app ID
|
|
45
|
+
*/
|
|
46
|
+
export declare function setCurrentAppId(appId: string): Promise<void>;
|
|
47
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QACpB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAMD;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AASD;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAW5D;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,gBAAgB,EAAE,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAKnF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxF;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC,CASjE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAGnE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const CONFIG_DIR = path.join(os.homedir(), '.butterbase');
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
6
|
+
const PROJECT_CONFIG_FILE = '.butterbase/config.json';
|
|
7
|
+
/**
|
|
8
|
+
* Get the global config file path
|
|
9
|
+
*/
|
|
10
|
+
export function getConfigPath() {
|
|
11
|
+
return CONFIG_FILE;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Ensure config directory exists
|
|
15
|
+
*/
|
|
16
|
+
async function ensureConfigDir() {
|
|
17
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load global configuration
|
|
21
|
+
*/
|
|
22
|
+
export async function loadConfig() {
|
|
23
|
+
await ensureConfigDir();
|
|
24
|
+
if (await fs.pathExists(CONFIG_FILE)) {
|
|
25
|
+
return await fs.readJson(CONFIG_FILE);
|
|
26
|
+
}
|
|
27
|
+
// Return default config
|
|
28
|
+
return {
|
|
29
|
+
endpoint: 'http://localhost:4000',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Save global configuration
|
|
34
|
+
*/
|
|
35
|
+
export async function saveConfig(config) {
|
|
36
|
+
await ensureConfigDir();
|
|
37
|
+
await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Update a specific config value
|
|
41
|
+
*/
|
|
42
|
+
export async function updateConfig(key, value) {
|
|
43
|
+
const config = await loadConfig();
|
|
44
|
+
config[key] = value;
|
|
45
|
+
await saveConfig(config);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load project-level configuration (from current directory)
|
|
49
|
+
*/
|
|
50
|
+
export async function loadProjectConfig() {
|
|
51
|
+
if (await fs.pathExists(PROJECT_CONFIG_FILE)) {
|
|
52
|
+
return await fs.readJson(PROJECT_CONFIG_FILE);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Save project-level configuration
|
|
58
|
+
*/
|
|
59
|
+
export async function saveProjectConfig(config) {
|
|
60
|
+
await fs.ensureDir(path.dirname(PROJECT_CONFIG_FILE));
|
|
61
|
+
await fs.writeJson(PROJECT_CONFIG_FILE, config, { spaces: 2 });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get merged configuration (project overrides global)
|
|
65
|
+
*/
|
|
66
|
+
export async function getMergedConfig() {
|
|
67
|
+
const globalConfig = await loadConfig();
|
|
68
|
+
const projectConfig = await loadProjectConfig();
|
|
69
|
+
if (projectConfig) {
|
|
70
|
+
return { ...globalConfig, ...projectConfig };
|
|
71
|
+
}
|
|
72
|
+
return globalConfig;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the current app ID from config
|
|
76
|
+
*/
|
|
77
|
+
export async function getCurrentAppId() {
|
|
78
|
+
const config = await getMergedConfig();
|
|
79
|
+
return config.currentApp;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Set the current app ID
|
|
83
|
+
*/
|
|
84
|
+
export async function setCurrentAppId(appId) {
|
|
85
|
+
await updateConfig('currentApp', appId);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAapB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AACzD,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,eAAe;IAC5B,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,eAAe,EAAE,CAAC;IAExB,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QACrC,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IAED,wBAAwB;IACxB,OAAO;QACL,QAAQ,EAAE,uBAAuB;KAClC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAwB;IACvD,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAA2B,EAAE,KAAU;IACxE,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACpB,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC7C,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAiC;IACvE,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACtD,MAAM,EAAE,CAAC,SAAS,CAAC,mBAAmB,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,YAAY,GAAG,MAAM,UAAU,EAAE,CAAC;IACxC,MAAM,aAAa,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAEhD,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,EAAE,GAAG,YAAY,EAAE,GAAG,aAAa,EAAE,CAAC;IAC/C,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IACvC,OAAO,MAAM,CAAC,UAAU,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa;IACjD,MAAM,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;AAC1C,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@butterbase/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line tool for Butterbase project scaffolding and backend management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"butterbase": "./dist/bin/butterbase.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"test": "vitest run"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"butterbase",
|
|
16
|
+
"cli",
|
|
17
|
+
"backend",
|
|
18
|
+
"baas"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^12.0.0",
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"ora": "^8.0.0",
|
|
24
|
+
"prompts": "^2.4.2",
|
|
25
|
+
"fs-extra": "^11.2.0",
|
|
26
|
+
"dotenv": "^16.4.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.7.0",
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"@types/prompts": "^2.4.9",
|
|
32
|
+
"@types/fs-extra": "^11.0.4",
|
|
33
|
+
"vitest": "^3.1.1"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { initApp, listApps, deleteApp } from '../lib/api-client.js';
|
|
5
|
+
import { setCurrentAppId, getCurrentAppId } from '../lib/config.js';
|
|
6
|
+
|
|
7
|
+
export async function appsListCommand() {
|
|
8
|
+
const spinner = ora('Fetching apps...').start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const response: any = await listApps();
|
|
12
|
+
spinner.stop();
|
|
13
|
+
|
|
14
|
+
if (!response.apps || response.apps.length === 0) {
|
|
15
|
+
console.log(chalk.yellow('No apps found'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const currentAppId = await getCurrentAppId();
|
|
20
|
+
|
|
21
|
+
console.log(chalk.blue('\nYour apps:\n'));
|
|
22
|
+
for (const app of response.apps) {
|
|
23
|
+
const isCurrent = app.id === currentAppId;
|
|
24
|
+
const marker = isCurrent ? chalk.green('→') : ' ';
|
|
25
|
+
console.log(`${marker} ${chalk.bold(app.name)} ${chalk.gray(`(${app.id})`)}`);
|
|
26
|
+
if (isCurrent) {
|
|
27
|
+
console.log(chalk.gray(` Current app`));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
spinner.fail('Failed to fetch apps');
|
|
32
|
+
console.error(chalk.red((error as Error).message));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function appsCreateCommand(name: string) {
|
|
38
|
+
if (!name) {
|
|
39
|
+
const response = await prompts({
|
|
40
|
+
type: 'text',
|
|
41
|
+
name: 'name',
|
|
42
|
+
message: 'App name:',
|
|
43
|
+
validate: (value) => value.length > 0 || 'App name is required',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.name) {
|
|
47
|
+
console.log(chalk.yellow('Cancelled'));
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
name = response.name;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const spinner = ora(`Creating app "${name}"...`).start();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const response: any = await initApp(name);
|
|
58
|
+
spinner.succeed(`Created app "${name}"`);
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green('\n✓ App created successfully!'));
|
|
61
|
+
console.log(chalk.gray(` App ID: ${response.app_id}`));
|
|
62
|
+
console.log(chalk.gray(` API URL: ${response.api_url}`));
|
|
63
|
+
|
|
64
|
+
// Ask if they want to set it as current app
|
|
65
|
+
const { setCurrent } = await prompts({
|
|
66
|
+
type: 'confirm',
|
|
67
|
+
name: 'setCurrent',
|
|
68
|
+
message: 'Set as current app?',
|
|
69
|
+
initial: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (setCurrent) {
|
|
73
|
+
await setCurrentAppId(response.app_id);
|
|
74
|
+
console.log(chalk.green('✓ Set as current app'));
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
spinner.fail('Failed to create app');
|
|
78
|
+
console.error(chalk.red((error as Error).message));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function appsUseCommand(appId: string) {
|
|
84
|
+
if (!appId) {
|
|
85
|
+
console.log(chalk.red('✗ App ID is required'));
|
|
86
|
+
console.log(chalk.gray('Usage: butterbase apps use <app-id>'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await setCurrentAppId(appId);
|
|
91
|
+
console.log(chalk.green(`✓ Now using app: ${appId}`));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function appsDeleteCommand(appId: string) {
|
|
95
|
+
if (!appId) {
|
|
96
|
+
console.log(chalk.red('✗ App ID is required'));
|
|
97
|
+
console.log(chalk.gray('Usage: butterbase apps delete <app-id>'));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { confirm } = await prompts({
|
|
102
|
+
type: 'confirm',
|
|
103
|
+
name: 'confirm',
|
|
104
|
+
message: `Are you sure you want to delete app ${appId}? This cannot be undone.`,
|
|
105
|
+
initial: false,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!confirm) {
|
|
109
|
+
console.log(chalk.yellow('Cancelled'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const spinner = ora('Deleting app...').start();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await deleteApp(appId);
|
|
117
|
+
spinner.succeed('App deleted');
|
|
118
|
+
|
|
119
|
+
// Clear current app if it was deleted
|
|
120
|
+
const currentAppId = await getCurrentAppId();
|
|
121
|
+
if (currentAppId === appId) {
|
|
122
|
+
await setCurrentAppId('');
|
|
123
|
+
console.log(chalk.gray('Cleared current app'));
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
spinner.fail('Failed to delete app');
|
|
127
|
+
console.error(chalk.red((error as Error).message));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { updateConfig, loadConfig } from '../lib/config.js';
|
|
4
|
+
|
|
5
|
+
export async function loginCommand() {
|
|
6
|
+
console.log(chalk.blue('🔐 Butterbase Login\n'));
|
|
7
|
+
|
|
8
|
+
const { apiKey } = await prompts({
|
|
9
|
+
type: 'password',
|
|
10
|
+
name: 'apiKey',
|
|
11
|
+
message: 'Enter your Butterbase API key:',
|
|
12
|
+
validate: (value) => value.length > 0 || 'API key is required',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
console.log(chalk.yellow('Login cancelled'));
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await updateConfig('apiKey', apiKey);
|
|
21
|
+
|
|
22
|
+
console.log(chalk.green('✓ Successfully logged in!'));
|
|
23
|
+
console.log(chalk.gray(`Config saved to ~/.butterbase/config.json`));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function logoutCommand() {
|
|
27
|
+
await updateConfig('apiKey', undefined);
|
|
28
|
+
console.log(chalk.green('✓ Successfully logged out'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function configGetCommand() {
|
|
32
|
+
const config = await loadConfig();
|
|
33
|
+
console.log(chalk.blue('Current configuration:\n'));
|
|
34
|
+
console.log(JSON.stringify(config, null, 2));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function configSetCommand(key: string, value: string) {
|
|
38
|
+
const config = await loadConfig();
|
|
39
|
+
|
|
40
|
+
// Type-safe config update
|
|
41
|
+
if (key in config) {
|
|
42
|
+
(config as any)[key] = value;
|
|
43
|
+
await updateConfig(key as any, value);
|
|
44
|
+
console.log(chalk.green(`✓ Set ${key} = ${value}`));
|
|
45
|
+
} else {
|
|
46
|
+
console.log(chalk.red(`✗ Unknown config key: ${key}`));
|
|
47
|
+
console.log(chalk.gray('Valid keys: endpoint, apiKey, currentApp'));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { deployFunction, listFunctions, getFunctionLogs } from '../lib/api-client.js';
|
|
5
|
+
import { getCurrentAppId } from '../lib/config.js';
|
|
6
|
+
|
|
7
|
+
async function requireAppId(appId?: string): Promise<string> {
|
|
8
|
+
if (appId) return appId;
|
|
9
|
+
|
|
10
|
+
const currentAppId = await getCurrentAppId();
|
|
11
|
+
if (!currentAppId) {
|
|
12
|
+
console.log(chalk.red('✗ No app specified and no current app set'));
|
|
13
|
+
console.log(chalk.gray('Use: butterbase apps use <app-id>'));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return currentAppId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function functionsListCommand(options: { app?: string }) {
|
|
21
|
+
const appId = await requireAppId(options.app);
|
|
22
|
+
const spinner = ora('Fetching functions...').start();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response: any = await listFunctions(appId);
|
|
26
|
+
spinner.stop();
|
|
27
|
+
|
|
28
|
+
if (!response.functions || response.functions.length === 0) {
|
|
29
|
+
console.log(chalk.yellow('No functions found'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.blue('\nDeployed functions:\n'));
|
|
34
|
+
for (const func of response.functions) {
|
|
35
|
+
console.log(chalk.bold(func.name));
|
|
36
|
+
console.log(chalk.gray(` Trigger: ${func.trigger_type}`));
|
|
37
|
+
if (func.description) {
|
|
38
|
+
console.log(chalk.gray(` Description: ${func.description}`));
|
|
39
|
+
}
|
|
40
|
+
console.log();
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
spinner.fail('Failed to fetch functions');
|
|
44
|
+
console.error(chalk.red((error as Error).message));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function functionsDeployCommand(file: string, options: {
|
|
50
|
+
app?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
trigger?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
}) {
|
|
55
|
+
const appId = await requireAppId(options.app);
|
|
56
|
+
|
|
57
|
+
if (!await fs.pathExists(file)) {
|
|
58
|
+
console.log(chalk.red(`✗ File not found: ${file}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const functionName = options.name || file.replace(/\.(ts|js)$/, '').split('/').pop()!;
|
|
63
|
+
const spinner = ora(`Deploying function "${functionName}"...`).start();
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const code = await fs.readFile(file, 'utf-8');
|
|
67
|
+
|
|
68
|
+
const triggerType = options.trigger || 'http';
|
|
69
|
+
const trigger = { type: triggerType, config: {} };
|
|
70
|
+
|
|
71
|
+
await deployFunction(appId, {
|
|
72
|
+
name: functionName,
|
|
73
|
+
code,
|
|
74
|
+
description: options.description,
|
|
75
|
+
trigger,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
spinner.succeed(`Deployed function "${functionName}"`);
|
|
79
|
+
console.log(chalk.green('\n✓ Function deployed successfully!'));
|
|
80
|
+
console.log(chalk.gray(` Invoke URL: /v1/${appId}/fn/${functionName}`));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
spinner.fail('Failed to deploy function');
|
|
83
|
+
console.error(chalk.red((error as Error).message));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function functionsLogsCommand(functionName: string, options: {
|
|
89
|
+
app?: string;
|
|
90
|
+
level?: string;
|
|
91
|
+
limit?: number;
|
|
92
|
+
}) {
|
|
93
|
+
const appId = await requireAppId(options.app);
|
|
94
|
+
const spinner = ora('Fetching logs...').start();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const response: any = await getFunctionLogs(appId, functionName, options.level, options.limit);
|
|
98
|
+
spinner.stop();
|
|
99
|
+
|
|
100
|
+
if (!response.logs || response.logs.length === 0) {
|
|
101
|
+
console.log(chalk.yellow('No logs found'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(chalk.blue(`\nLogs for ${functionName}:\n`));
|
|
106
|
+
for (const log of response.logs) {
|
|
107
|
+
const timestamp = new Date(log.timestamp).toLocaleString();
|
|
108
|
+
const levelColor = log.level === 'error' ? chalk.red : chalk.gray;
|
|
109
|
+
console.log(`${chalk.gray(timestamp)} ${levelColor(log.level.toUpperCase())} ${log.message}`);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
spinner.fail('Failed to fetch logs');
|
|
113
|
+
console.error(chalk.red((error as Error).message));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|