@butterbase/cli 0.1.0 → 0.1.1

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.
@@ -10,7 +10,7 @@ const program = new Command();
10
10
  program
11
11
  .name('butterbase')
12
12
  .description('Butterbase CLI - Backend as a Service')
13
- .version('0.1.0');
13
+ .version('0.1.1');
14
14
  // Init
15
15
  program
16
16
  .command('init [template]')
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AA+CA,wBAAsB,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,iBAuGlD"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAgEA,wBAAsB,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,iBAuGlD"}
@@ -7,6 +7,22 @@ import { fileURLToPath } from 'url';
7
7
  import { getMergedConfig } from '../lib/config.js';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = path.dirname(__filename);
10
+ /** Resolve package root whether running from src/ (tsx) or dist/ (published). */
11
+ function getCliPackageRoot() {
12
+ let dir = __dirname;
13
+ for (;;) {
14
+ const pkgJson = path.join(dir, 'package.json');
15
+ const templatesDir = path.join(dir, 'templates');
16
+ if (fs.existsSync(pkgJson) && fs.existsSync(templatesDir)) {
17
+ return dir;
18
+ }
19
+ const parent = path.dirname(dir);
20
+ if (parent === dir) {
21
+ throw new Error('Could not locate CLI package root (templates directory missing)');
22
+ }
23
+ dir = parent;
24
+ }
25
+ }
10
26
  async function replaceVariables(content, variables) {
11
27
  let result = content;
12
28
  for (const [key, value] of Object.entries(variables)) {
@@ -80,7 +96,7 @@ export async function initCommand(template) {
80
96
  const spinner = ora('Creating project...').start();
81
97
  try {
82
98
  // Get template directory
83
- const templateDir = path.join(__dirname, '../../templates', template);
99
+ const templateDir = path.join(getCliPackageRoot(), 'templates', template);
84
100
  if (!await fs.pathExists(templateDir)) {
85
101
  throw new Error(`Template "${template}" not found`);
86
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@butterbase/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Command-line tool for Butterbase project scaffolding and backend management",
5
5
  "type": "module",
6
6
  "bin": {
7
- "butterbase": "./dist/bin/butterbase.js"
7
+ "butterbase": "dist/bin/butterbase.js"
8
8
  },
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "README.md"
13
+ ],
9
14
  "scripts": {
10
15
  "build": "tsc",
11
16
  "dev": "tsc --watch",
package/bin/butterbase.ts DELETED
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import chalk from 'chalk';
4
- import { initCommand } from '../src/commands/init.js';
5
- import { loginCommand, logoutCommand, configGetCommand, configSetCommand } from '../src/commands/config.js';
6
- import { appsListCommand, appsCreateCommand, appsUseCommand, appsDeleteCommand } from '../src/commands/apps.js';
7
- import { schemaGetCommand, schemaApplyCommand } from '../src/commands/schema.js';
8
- import { functionsListCommand, functionsDeployCommand, functionsLogsCommand } from '../src/commands/functions.js';
9
- import { storageListCommand, storageUploadCommand, storageDeleteCommand } from '../src/commands/storage.js';
10
-
11
- const program = new Command();
12
-
13
- program
14
- .name('butterbase')
15
- .description('Butterbase CLI - Backend as a Service')
16
- .version('0.1.0');
17
-
18
- // Init
19
- program
20
- .command('init [template]')
21
- .description('Initialize a new Butterbase project')
22
- .action(initCommand);
23
-
24
- // Login/Logout
25
- program
26
- .command('login')
27
- .description('Authenticate with Butterbase')
28
- .action(loginCommand);
29
-
30
- program
31
- .command('logout')
32
- .description('Clear authentication credentials')
33
- .action(logoutCommand);
34
-
35
- // Config
36
- const config = program.command('config').description('Manage configuration');
37
-
38
- config
39
- .command('get')
40
- .description('Show current configuration')
41
- .action(configGetCommand);
42
-
43
- config
44
- .command('set <key> <value>')
45
- .description('Set a configuration value')
46
- .action(configSetCommand);
47
-
48
- // Apps
49
- const apps = program.command('apps').description('Manage apps');
50
-
51
- apps
52
- .command('list')
53
- .description('List all apps')
54
- .action(appsListCommand);
55
-
56
- apps
57
- .command('create [name]')
58
- .description('Create a new app')
59
- .action(appsCreateCommand);
60
-
61
- apps
62
- .command('use <app-id>')
63
- .description('Set the current app')
64
- .action(appsUseCommand);
65
-
66
- apps
67
- .command('delete <app-id>')
68
- .description('Delete an app')
69
- .action(appsDeleteCommand);
70
-
71
- // Schema
72
- const schema = program.command('schema').description('Manage database schema');
73
-
74
- schema
75
- .command('get')
76
- .description('Get current schema')
77
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
78
- .option('--output <file>', 'Save schema to file')
79
- .action(schemaGetCommand);
80
-
81
- schema
82
- .command('apply <file>')
83
- .description('Apply schema from file')
84
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
85
- .option('--dry-run', 'Preview changes without applying')
86
- .option('--name <name>', 'Migration name')
87
- .action(schemaApplyCommand);
88
-
89
- // Functions
90
- const functions = program.command('functions').description('Manage serverless functions');
91
-
92
- functions
93
- .command('list')
94
- .description('List deployed functions')
95
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
96
- .action(functionsListCommand);
97
-
98
- functions
99
- .command('deploy <file>')
100
- .description('Deploy a function')
101
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
102
- .option('--name <name>', 'Function name (defaults to filename)')
103
- .option('--trigger <type>', 'Trigger type (http, cron)', 'http')
104
- .option('--description <desc>', 'Function description')
105
- .action(functionsDeployCommand);
106
-
107
- functions
108
- .command('logs <function-name>')
109
- .description('View function logs')
110
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
111
- .option('--level <level>', 'Filter by log level (error, all)')
112
- .option('--limit <number>', 'Number of logs to fetch', '100')
113
- .action(functionsLogsCommand);
114
-
115
- // Storage
116
- const storage = program.command('storage').description('Manage file storage');
117
-
118
- storage
119
- .command('list')
120
- .description('List storage objects')
121
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
122
- .action(storageListCommand);
123
-
124
- storage
125
- .command('upload <file>')
126
- .description('Upload a file')
127
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
128
- .action(storageUploadCommand);
129
-
130
- storage
131
- .command('delete <object-id>')
132
- .description('Delete a storage object')
133
- .option('--app <app-id>', 'App ID (uses current app if not specified)')
134
- .action(storageDeleteCommand);
135
-
136
- // Parse arguments
137
- program.parse();
@@ -1,130 +0,0 @@
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
- }
@@ -1,50 +0,0 @@
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
- }
@@ -1,116 +0,0 @@
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
- }
@@ -1,151 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import prompts from 'prompts';
4
- import fs from 'fs-extra';
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
- import { getMergedConfig } from '../lib/config.js';
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
-
12
- interface TemplateVariables {
13
- PROJECT_NAME: string;
14
- APP_ID: string;
15
- API_URL: string;
16
- }
17
-
18
- async function replaceVariables(content: string, variables: TemplateVariables): Promise<string> {
19
- let result = content;
20
- for (const [key, value] of Object.entries(variables)) {
21
- result = result.replace(new RegExp(`{{${key}}}`, 'g'), value);
22
- }
23
- return result;
24
- }
25
-
26
- async function copyTemplate(
27
- templateDir: string,
28
- targetDir: string,
29
- variables: TemplateVariables
30
- ): Promise<void> {
31
- const files = await fs.readdir(templateDir, { withFileTypes: true });
32
-
33
- for (const file of files) {
34
- const sourcePath = path.join(templateDir, file.name);
35
- const targetPath = path.join(targetDir, file.name);
36
-
37
- if (file.isDirectory()) {
38
- await fs.ensureDir(targetPath);
39
- await copyTemplate(sourcePath, targetPath, variables);
40
- } else {
41
- const content = await fs.readFile(sourcePath, 'utf-8');
42
- const processedContent = await replaceVariables(content, variables);
43
- await fs.writeFile(targetPath, processedContent);
44
- }
45
- }
46
- }
47
-
48
- export async function initCommand(template?: string) {
49
- console.log(chalk.blue('🚀 Initialize Butterbase Project\n'));
50
-
51
- // Get project name
52
- const { projectName } = await prompts({
53
- type: 'text',
54
- name: 'projectName',
55
- message: 'Project name:',
56
- initial: 'my-butterbase-app',
57
- validate: (value) => value.length > 0 || 'Project name is required',
58
- });
59
-
60
- if (!projectName) {
61
- console.log(chalk.yellow('Cancelled'));
62
- process.exit(0);
63
- }
64
-
65
- // Select template
66
- if (!template) {
67
- const { selectedTemplate } = await prompts({
68
- type: 'select',
69
- name: 'selectedTemplate',
70
- message: 'Select a template:',
71
- choices: [
72
- { title: 'React + Vite', value: 'react-vite' },
73
- { title: 'Next.js (coming soon)', value: 'nextjs', disabled: true },
74
- { title: 'Vue + Vite (coming soon)', value: 'vue-vite', disabled: true },
75
- ],
76
- });
77
-
78
- if (!selectedTemplate) {
79
- console.log(chalk.yellow('Cancelled'));
80
- process.exit(0);
81
- }
82
-
83
- template = selectedTemplate;
84
- }
85
-
86
- // Get Butterbase configuration
87
- const config = await getMergedConfig();
88
-
89
- const { appId } = await prompts({
90
- type: 'text',
91
- name: 'appId',
92
- message: 'Butterbase App ID (leave empty to create later):',
93
- initial: config.currentApp || '',
94
- });
95
-
96
- const apiUrl = config.endpoint || 'http://localhost:4000';
97
-
98
- // Create project directory
99
- const targetDir = path.join(process.cwd(), projectName);
100
-
101
- if (await fs.pathExists(targetDir)) {
102
- console.log(chalk.red(`✗ Directory "${projectName}" already exists`));
103
- process.exit(1);
104
- }
105
-
106
- const spinner = ora('Creating project...').start();
107
-
108
- try {
109
- // Get template directory
110
- const templateDir = path.join(__dirname, '../../templates', template!);
111
-
112
- if (!await fs.pathExists(templateDir)) {
113
- throw new Error(`Template "${template}" not found`);
114
- }
115
-
116
- // Create target directory
117
- await fs.ensureDir(targetDir);
118
-
119
- // Copy template with variable replacement
120
- const variables: TemplateVariables = {
121
- PROJECT_NAME: projectName,
122
- APP_ID: appId || 'your-app-id',
123
- API_URL: apiUrl,
124
- };
125
-
126
- await copyTemplate(templateDir, targetDir, variables);
127
-
128
- // Create .env from .env.example
129
- const envExamplePath = path.join(targetDir, '.env.example');
130
- const envPath = path.join(targetDir, '.env');
131
- if (await fs.pathExists(envExamplePath)) {
132
- await fs.copy(envExamplePath, envPath);
133
- }
134
-
135
- spinner.succeed('Project created!');
136
-
137
- console.log(chalk.green('\n✓ Project initialized successfully!\n'));
138
- console.log(chalk.gray('Next steps:\n'));
139
- console.log(chalk.white(` cd ${projectName}`));
140
- console.log(chalk.white(` npm install`));
141
- console.log(chalk.white(` npm run dev`));
142
-
143
- if (!appId) {
144
- console.log(chalk.yellow('\n⚠ Remember to update .env with your Butterbase App ID'));
145
- }
146
- } catch (error) {
147
- spinner.fail('Failed to create project');
148
- console.error(chalk.red((error as Error).message));
149
- process.exit(1);
150
- }
151
- }
@@ -1,78 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import fs from 'fs-extra';
4
- import { getSchema, applySchema } 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
- console.log(chalk.gray('Or: butterbase schema get --app <app-id>'));
15
- process.exit(1);
16
- }
17
-
18
- return currentAppId;
19
- }
20
-
21
- export async function schemaGetCommand(options: { app?: string; output?: string }) {
22
- const appId = await requireAppId(options.app);
23
- const spinner = ora('Fetching schema...').start();
24
-
25
- try {
26
- const response: any = await getSchema(appId);
27
- spinner.stop();
28
-
29
- const schemaJson = JSON.stringify(response.schema, null, 2);
30
-
31
- if (options.output) {
32
- await fs.writeFile(options.output, schemaJson);
33
- console.log(chalk.green(`✓ Schema saved to ${options.output}`));
34
- } else {
35
- console.log(schemaJson);
36
- }
37
- } catch (error) {
38
- spinner.fail('Failed to fetch schema');
39
- console.error(chalk.red((error as Error).message));
40
- process.exit(1);
41
- }
42
- }
43
-
44
- export async function schemaApplyCommand(file: string, options: { app?: string; dryRun?: boolean; name?: string }) {
45
- const appId = await requireAppId(options.app);
46
-
47
- if (!await fs.pathExists(file)) {
48
- console.log(chalk.red(`✗ File not found: ${file}`));
49
- process.exit(1);
50
- }
51
-
52
- const spinner = ora('Reading schema file...').start();
53
-
54
- try {
55
- const schemaContent = await fs.readFile(file, 'utf-8');
56
- const schema = JSON.parse(schemaContent);
57
-
58
- spinner.text = options.dryRun ? 'Running dry-run...' : 'Applying schema...';
59
-
60
- const response: any = await applySchema(appId, schema, options.dryRun, options.name);
61
-
62
- spinner.stop();
63
-
64
- if (options.dryRun) {
65
- console.log(chalk.blue('\n📋 Dry-run results:\n'));
66
- console.log(JSON.stringify(response, null, 2));
67
- } else {
68
- console.log(chalk.green('\n✓ Schema applied successfully!'));
69
- if (response.migration_id) {
70
- console.log(chalk.gray(` Migration ID: ${response.migration_id}`));
71
- }
72
- }
73
- } catch (error) {
74
- spinner.fail(options.dryRun ? 'Dry-run failed' : 'Failed to apply schema');
75
- console.error(chalk.red((error as Error).message));
76
- process.exit(1);
77
- }
78
- }
@@ -1,117 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import fs from 'fs-extra';
4
- import { generateUploadUrl, listStorageObjects, deleteStorageObject } 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 storageListCommand(options: { app?: string }) {
21
- const appId = await requireAppId(options.app);
22
- const spinner = ora('Fetching storage objects...').start();
23
-
24
- try {
25
- const response: any = await listStorageObjects(appId);
26
- spinner.stop();
27
-
28
- if (!response.objects || response.objects.length === 0) {
29
- console.log(chalk.yellow('No files found'));
30
- return;
31
- }
32
-
33
- console.log(chalk.blue('\nStorage objects:\n'));
34
- for (const obj of response.objects) {
35
- console.log(chalk.bold(obj.filename));
36
- console.log(chalk.gray(` ID: ${obj.id}`));
37
- console.log(chalk.gray(` Size: ${(obj.size_bytes / 1024).toFixed(2)} KB`));
38
- console.log(chalk.gray(` Type: ${obj.content_type}`));
39
- console.log();
40
- }
41
- } catch (error) {
42
- spinner.fail('Failed to fetch storage objects');
43
- console.error(chalk.red((error as Error).message));
44
- process.exit(1);
45
- }
46
- }
47
-
48
- export async function storageUploadCommand(file: string, options: { app?: string }) {
49
- const appId = await requireAppId(options.app);
50
-
51
- if (!await fs.pathExists(file)) {
52
- console.log(chalk.red(`✗ File not found: ${file}`));
53
- process.exit(1);
54
- }
55
-
56
- const spinner = ora('Uploading file...').start();
57
-
58
- try {
59
- const stats = await fs.stat(file);
60
- const filename = file.split('/').pop()!;
61
-
62
- // Detect content type (basic implementation)
63
- let contentType = 'application/octet-stream';
64
- if (filename.endsWith('.png')) contentType = 'image/png';
65
- else if (filename.endsWith('.jpg') || filename.endsWith('.jpeg')) contentType = 'image/jpeg';
66
- else if (filename.endsWith('.pdf')) contentType = 'application/pdf';
67
- else if (filename.endsWith('.txt')) contentType = 'text/plain';
68
-
69
- // Get presigned upload URL
70
- const uploadData: any = await generateUploadUrl(appId, filename, contentType, stats.size);
71
-
72
- // Read file and upload to S3
73
- const fileBuffer = await fs.readFile(file);
74
-
75
- const uploadResponse = await fetch(uploadData.uploadUrl, {
76
- method: 'PUT',
77
- headers: {
78
- 'Content-Type': contentType,
79
- },
80
- body: fileBuffer,
81
- });
82
-
83
- if (!uploadResponse.ok) {
84
- throw new Error('Failed to upload file to storage');
85
- }
86
-
87
- spinner.succeed('File uploaded');
88
- console.log(chalk.green('\n✓ File uploaded successfully!'));
89
- console.log(chalk.gray(` Object ID: ${uploadData.objectId}`));
90
- console.log(chalk.gray(` Object Key: ${uploadData.objectKey}`));
91
- } catch (error) {
92
- spinner.fail('Failed to upload file');
93
- console.error(chalk.red((error as Error).message));
94
- process.exit(1);
95
- }
96
- }
97
-
98
- export async function storageDeleteCommand(objectId: string, options: { app?: string }) {
99
- const appId = await requireAppId(options.app);
100
-
101
- if (!objectId) {
102
- console.log(chalk.red('✗ Object ID is required'));
103
- console.log(chalk.gray('Usage: butterbase storage delete <object-id>'));
104
- process.exit(1);
105
- }
106
-
107
- const spinner = ora('Deleting object...').start();
108
-
109
- try {
110
- await deleteStorageObject(appId, objectId);
111
- spinner.succeed('Object deleted');
112
- } catch (error) {
113
- spinner.fail('Failed to delete object');
114
- console.error(chalk.red((error as Error).message));
115
- process.exit(1);
116
- }
117
- }
@@ -1,176 +0,0 @@
1
- import { getMergedConfig } from './config.js';
2
-
3
- export interface ApiError {
4
- error: string;
5
- details?: unknown;
6
- hint?: string;
7
- }
8
-
9
- /**
10
- * Get authorization headers
11
- */
12
- async function getHeaders(): Promise<HeadersInit> {
13
- const config = await getMergedConfig();
14
- const headers: HeadersInit = {
15
- 'Content-Type': 'application/json',
16
- };
17
-
18
- if (config.apiKey) {
19
- headers['Authorization'] = `Bearer ${config.apiKey}`;
20
- }
21
-
22
- return headers;
23
- }
24
-
25
- /**
26
- * Get base URL from config
27
- */
28
- async function getBaseUrl(): Promise<string> {
29
- const config = await getMergedConfig();
30
- return config.endpoint;
31
- }
32
-
33
- /**
34
- * Make a GET request
35
- */
36
- export async function apiGet<T>(path: string): Promise<T> {
37
- const baseUrl = await getBaseUrl();
38
- const headers = await getHeaders();
39
-
40
- const res = await fetch(`${baseUrl}${path}`, { headers });
41
- const body: any = await res.json();
42
-
43
- if (!res.ok) {
44
- throw new Error(body.error || body.message || 'Request failed');
45
- }
46
-
47
- return body as T;
48
- }
49
-
50
- /**
51
- * Make a POST request
52
- */
53
- export async function apiPost<T>(path: string, data: unknown): Promise<T> {
54
- const baseUrl = await getBaseUrl();
55
- const headers = await getHeaders();
56
-
57
- const res = await fetch(`${baseUrl}${path}`, {
58
- method: 'POST',
59
- headers,
60
- body: JSON.stringify(data),
61
- });
62
-
63
- const body: any = await res.json();
64
-
65
- if (!res.ok) {
66
- throw new Error(body.error || body.message || 'Request failed');
67
- }
68
-
69
- return body as T;
70
- }
71
-
72
- /**
73
- * Make a PATCH request
74
- */
75
- export async function apiPatch<T>(path: string, data: unknown): Promise<T> {
76
- const baseUrl = await getBaseUrl();
77
- const headers = await getHeaders();
78
-
79
- const res = await fetch(`${baseUrl}${path}`, {
80
- method: 'PATCH',
81
- headers,
82
- body: JSON.stringify(data),
83
- });
84
-
85
- const body: any = await res.json();
86
-
87
- if (!res.ok) {
88
- throw new Error(body.error || body.message || 'Request failed');
89
- }
90
-
91
- return body as T;
92
- }
93
-
94
- /**
95
- * Make a DELETE request
96
- */
97
- export async function apiDelete<T>(path: string): Promise<T> {
98
- const baseUrl = await getBaseUrl();
99
- const headers = await getHeaders();
100
-
101
- const res = await fetch(`${baseUrl}${path}`, {
102
- method: 'DELETE',
103
- headers,
104
- });
105
-
106
- // Handle 204 No Content
107
- if (res.status === 204) {
108
- return {} as T;
109
- }
110
-
111
- const body: any = await res.json();
112
-
113
- if (!res.ok) {
114
- throw new Error(body.error || body.message || 'Request failed');
115
- }
116
-
117
- return body as T;
118
- }
119
-
120
- // MCP tool wrappers
121
-
122
- export async function initApp(name: string) {
123
- return apiPost('/init', { name });
124
- }
125
-
126
- export async function listApps() {
127
- return apiGet('/apps');
128
- }
129
-
130
- export async function deleteApp(appId: string) {
131
- return apiDelete(`/apps/${appId}`);
132
- }
133
-
134
- export async function getSchema(appId: string) {
135
- return apiGet(`/v1/${appId}/schema`);
136
- }
137
-
138
- export async function applySchema(appId: string, schema: any, dryRun?: boolean, name?: string) {
139
- return apiPost(`/v1/${appId}/schema/apply`, { schema, dry_run: dryRun, name });
140
- }
141
-
142
- export async function deployFunction(appId: string, data: {
143
- name: string;
144
- code: string;
145
- description?: string;
146
- envVars?: Record<string, string>;
147
- timeoutMs?: number;
148
- memoryLimitMb?: number;
149
- trigger: { type: string; config?: any };
150
- }) {
151
- return apiPost(`/v1/${appId}/functions`, data);
152
- }
153
-
154
- export async function listFunctions(appId: string) {
155
- return apiGet(`/v1/${appId}/functions`);
156
- }
157
-
158
- export async function getFunctionLogs(appId: string, functionName: string, level?: string, limit?: number) {
159
- const params = new URLSearchParams();
160
- if (level) params.set('level', level);
161
- if (limit) params.set('limit', String(limit));
162
- const query = params.toString();
163
- return apiGet(`/v1/${appId}/functions/${functionName}/logs${query ? `?${query}` : ''}`);
164
- }
165
-
166
- export async function generateUploadUrl(appId: string, filename: string, contentType: string, sizeBytes: number) {
167
- return apiPost(`/storage/${appId}/upload`, { filename, contentType, sizeBytes });
168
- }
169
-
170
- export async function listStorageObjects(appId: string) {
171
- return apiGet(`/storage/${appId}/objects`);
172
- }
173
-
174
- export async function deleteStorageObject(appId: string, objectId: string) {
175
- return apiDelete(`/storage/${appId}/${objectId}`);
176
- }
package/src/lib/config.ts DELETED
@@ -1,112 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import os from 'os';
4
-
5
- export interface ButterbaseConfig {
6
- endpoint: string;
7
- apiKey?: string;
8
- currentApp?: string;
9
- apps?: Record<string, {
10
- id: string;
11
- name: string;
12
- apiUrl: string;
13
- }>;
14
- }
15
-
16
- const CONFIG_DIR = path.join(os.homedir(), '.butterbase');
17
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
18
- const PROJECT_CONFIG_FILE = '.butterbase/config.json';
19
-
20
- /**
21
- * Get the global config file path
22
- */
23
- export function getConfigPath(): string {
24
- return CONFIG_FILE;
25
- }
26
-
27
- /**
28
- * Ensure config directory exists
29
- */
30
- async function ensureConfigDir(): Promise<void> {
31
- await fs.ensureDir(CONFIG_DIR);
32
- }
33
-
34
- /**
35
- * Load global configuration
36
- */
37
- export async function loadConfig(): Promise<ButterbaseConfig> {
38
- await ensureConfigDir();
39
-
40
- if (await fs.pathExists(CONFIG_FILE)) {
41
- return await fs.readJson(CONFIG_FILE);
42
- }
43
-
44
- // Return default config
45
- return {
46
- endpoint: 'http://localhost:4000',
47
- };
48
- }
49
-
50
- /**
51
- * Save global configuration
52
- */
53
- export async function saveConfig(config: ButterbaseConfig): Promise<void> {
54
- await ensureConfigDir();
55
- await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
56
- }
57
-
58
- /**
59
- * Update a specific config value
60
- */
61
- export async function updateConfig(key: keyof ButterbaseConfig, value: any): Promise<void> {
62
- const config = await loadConfig();
63
- config[key] = value;
64
- await saveConfig(config);
65
- }
66
-
67
- /**
68
- * Load project-level configuration (from current directory)
69
- */
70
- export async function loadProjectConfig(): Promise<Partial<ButterbaseConfig> | null> {
71
- if (await fs.pathExists(PROJECT_CONFIG_FILE)) {
72
- return await fs.readJson(PROJECT_CONFIG_FILE);
73
- }
74
- return null;
75
- }
76
-
77
- /**
78
- * Save project-level configuration
79
- */
80
- export async function saveProjectConfig(config: Partial<ButterbaseConfig>): Promise<void> {
81
- await fs.ensureDir(path.dirname(PROJECT_CONFIG_FILE));
82
- await fs.writeJson(PROJECT_CONFIG_FILE, config, { spaces: 2 });
83
- }
84
-
85
- /**
86
- * Get merged configuration (project overrides global)
87
- */
88
- export async function getMergedConfig(): Promise<ButterbaseConfig> {
89
- const globalConfig = await loadConfig();
90
- const projectConfig = await loadProjectConfig();
91
-
92
- if (projectConfig) {
93
- return { ...globalConfig, ...projectConfig };
94
- }
95
-
96
- return globalConfig;
97
- }
98
-
99
- /**
100
- * Get the current app ID from config
101
- */
102
- export async function getCurrentAppId(): Promise<string | undefined> {
103
- const config = await getMergedConfig();
104
- return config.currentApp;
105
- }
106
-
107
- /**
108
- * Set the current app ID
109
- */
110
- export async function setCurrentAppId(appId: string): Promise<void> {
111
- await updateConfig('currentApp', appId);
112
- }
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": ".",
6
- "declaration": true,
7
- "declarationMap": true,
8
- "sourceMap": true,
9
- "lib": ["ES2022", "DOM"],
10
- "target": "ES2022",
11
- "module": "ES2022",
12
- "moduleResolution": "bundler",
13
- "esModuleInterop": true,
14
- "skipLibCheck": true,
15
- "strict": true,
16
- "resolveJsonModule": true
17
- },
18
- "include": ["src/**/*", "bin/**/*"],
19
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
20
- }