mirror of https://github.com/buster-so/buster.git
top level imports and such moved
This commit is contained in:
parent
f9df968a15
commit
d123494372
28
CLAUDE.md
28
CLAUDE.md
|
@ -101,6 +101,34 @@ class UserService { // Never do this
|
|||
}
|
||||
```
|
||||
|
||||
### Import Patterns
|
||||
- **Always use top-level imports** - Import all dependencies at the top of the file
|
||||
- **No dynamic imports** - Avoid `await import()` in the middle of functions
|
||||
- **Rationale** - Prioritize code simplicity and consistency over premature optimization
|
||||
- **Exception** - Only use dynamic imports if you have clear evidence of measurable startup performance issues
|
||||
|
||||
```typescript
|
||||
// Good: Top-level imports
|
||||
import { runHeadless } from '../services/headless-handler';
|
||||
import { render } from 'ink';
|
||||
|
||||
export function handleCommand(options) {
|
||||
if (options.prompt) {
|
||||
return runHeadless(options);
|
||||
}
|
||||
return render(<Main />);
|
||||
}
|
||||
|
||||
// Bad: Dynamic imports without justification
|
||||
export async function handleCommand(options) {
|
||||
if (options.prompt) {
|
||||
const { runHeadless } = await import('../services/headless-handler');
|
||||
return runHeadless(options);
|
||||
}
|
||||
return render(<Main />);
|
||||
}
|
||||
```
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
### Environment Variables
|
||||
|
|
|
@ -3,6 +3,7 @@ import { render } from 'ink';
|
|||
import { Main } from '../commands/main/main';
|
||||
import { getCurrentVersion } from '../commands/update/update-handler';
|
||||
import { setupPreActionHook } from './hooks';
|
||||
import { runHeadless } from '../services/headless-handler';
|
||||
|
||||
interface RootOptions {
|
||||
cwd?: string;
|
||||
|
@ -32,7 +33,6 @@ program.action(async (options: RootOptions) => {
|
|||
// Check if running in headless mode
|
||||
if (options.prompt) {
|
||||
try {
|
||||
const { runHeadless } = await import('../services/headless-handler');
|
||||
const chatId = await runHeadless({
|
||||
prompt: options.prompt,
|
||||
...(options.chatId && { chatId: options.chatId }),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { createBusterSDK } from '@buster/sdk';
|
||||
import { Command } from 'commander';
|
||||
import { render } from 'ink';
|
||||
import { saveCredentials } from '../../utils/credentials';
|
||||
import { Auth } from './auth';
|
||||
|
||||
/**
|
||||
|
@ -33,9 +35,6 @@ export function createAuthCommand(): Command {
|
|||
|
||||
// If we have an API key in CI, just validate and save it without interactive UI
|
||||
if (isCIEnvironment && (options.apiKey || process.env.BUSTER_API_KEY)) {
|
||||
const { createBusterSDK } = await import('@buster/sdk');
|
||||
const { saveCredentials } = await import('../../utils/credentials');
|
||||
|
||||
const apiKey = options.apiKey || process.env.BUSTER_API_KEY;
|
||||
const host =
|
||||
options.host ||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { Command } from 'commander';
|
||||
import { render } from 'ink';
|
||||
import { DeployCommand } from './deploy';
|
||||
import { deployHandler } from './deploy-handler.js';
|
||||
import { DeployOptionsSchema } from './schemas';
|
||||
import { formatDeployError, getExitCode, isDeploymentValidationError } from './utils/errors.js';
|
||||
|
||||
/**
|
||||
* Creates the deploy command for deploying semantic models
|
||||
|
@ -32,15 +34,9 @@ export function createDeployCommand(): Command {
|
|||
render(<DeployCommand {...parsedOptions} />);
|
||||
} else {
|
||||
// Direct execution for cleaner CLI output
|
||||
const { deployHandler } = await import('./deploy-handler.js');
|
||||
await deployHandler(parsedOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
// Import the error formatter and type guard
|
||||
const { isDeploymentValidationError, formatDeployError, getExitCode } = await import(
|
||||
'./utils/errors.js'
|
||||
);
|
||||
|
||||
// Check if it's a DeploymentValidationError to handle it specially
|
||||
if (isDeploymentValidationError(error)) {
|
||||
// The error message already contains the formatted output
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { render } from 'ink-testing-library';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { HelloCommand } from './hello';
|
||||
|
||||
describe('HelloCommand', () => {
|
||||
it('should render greeting with default name', () => {
|
||||
const { lastFrame } = render(<HelloCommand name='World' />);
|
||||
|
||||
expect(lastFrame()).toContain('Hello, World!');
|
||||
expect(lastFrame()).toContain('Buster CLI');
|
||||
});
|
||||
|
||||
it('should render greeting in uppercase when flag is set', () => {
|
||||
const { lastFrame } = render(<HelloCommand name='Claude' uppercase={true} />);
|
||||
|
||||
expect(lastFrame()).toContain('HELLO, CLAUDE!');
|
||||
});
|
||||
|
||||
it('should render greeting in normal case when uppercase flag is false', () => {
|
||||
const { lastFrame } = render(<HelloCommand name='Claude' uppercase={false} />);
|
||||
|
||||
expect(lastFrame()).toContain('Hello, Claude!');
|
||||
expect(lastFrame()).not.toContain('HELLO, CLAUDE!');
|
||||
});
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
import chalk from 'chalk';
|
||||
import { Box, Text } from 'ink';
|
||||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface HelloCommandProps {
|
||||
name: string;
|
||||
uppercase?: boolean;
|
||||
}
|
||||
|
||||
export const HelloCommand: React.FC<HelloCommandProps> = ({ name, uppercase }) => {
|
||||
const greeting = `Hello, ${name}!`;
|
||||
const displayText = uppercase ? greeting.toUpperCase() : greeting;
|
||||
|
||||
useEffect(() => {
|
||||
// Exit after rendering
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Text color='green'>{chalk.bold('🚀 Buster CLI')}</Text>
|
||||
<Text>{displayText}</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
import chalk from 'chalk';
|
||||
import { Box, Text, useApp, useInput } from 'ink';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const InteractiveCommand: React.FC = () => {
|
||||
const [selectedOption, setSelectedOption] = useState(0);
|
||||
const { exit } = useApp();
|
||||
|
||||
const options = ['Create a new project', 'Deploy to production', 'Run tests', 'Exit'];
|
||||
|
||||
useInput((input, key) => {
|
||||
if (key.upArrow) {
|
||||
setSelectedOption((prev) => Math.max(0, prev - 1));
|
||||
} else if (key.downArrow) {
|
||||
setSelectedOption((prev) => Math.min(options.length - 1, prev + 1));
|
||||
} else if (key.return) {
|
||||
if (selectedOption === options.length - 1) {
|
||||
exit();
|
||||
} else {
|
||||
// Handle selection
|
||||
console.info(`\nYou selected: ${options[selectedOption]}`);
|
||||
exit();
|
||||
}
|
||||
} else if (input === 'q' || key.escape) {
|
||||
exit();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Box marginBottom={1}>
|
||||
<Text color='cyan' bold>
|
||||
🚀 Buster CLI - Interactive Mode
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Text dimColor>Use arrow keys to navigate, Enter to select, Q to quit</Text>
|
||||
<Box marginTop={1} flexDirection='column'>
|
||||
{options.map((option, index) => (
|
||||
<Box key={option}>
|
||||
<Text {...(selectedOption === index ? { color: 'green' } : {})}>
|
||||
{selectedOption === index ? chalk.bold('▶ ') : ' '}
|
||||
{option}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -14,6 +14,7 @@ import { AgentMessageComponent } from '../../components/message';
|
|||
import { SettingsForm } from '../../components/settings-form';
|
||||
import { ExpansionContext } from '../../hooks/use-expansion';
|
||||
import type { CliAgentMessage } from '../../services/analytics-engineer-handler';
|
||||
import { runAnalyticsEngineerAgent } from '../../services/analytics-engineer-handler';
|
||||
import type { Conversation } from '../../utils/conversation-history';
|
||||
import { loadConversation, saveModelMessages } from '../../utils/conversation-history';
|
||||
import { getCurrentChatId, initNewSession, setSessionChatId } from '../../utils/session';
|
||||
|
@ -137,9 +138,6 @@ export function Main() {
|
|||
// Save to disk
|
||||
await saveModelMessages(chatId, cwd, updatedModelMessages);
|
||||
|
||||
// Import and run the analytics engineer agent
|
||||
const { runAnalyticsEngineerAgent } = await import('../../services/analytics-engineer-handler');
|
||||
|
||||
// Create AbortController for this agent execution
|
||||
const abortController = new AbortController();
|
||||
abortControllerRef.current = abortController;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { spawn } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createWriteStream, existsSync } from 'node:fs';
|
||||
import { chmod, mkdir, rename, unlink } from 'node:fs/promises';
|
||||
import { createReadStream, createWriteStream, existsSync } from 'node:fs';
|
||||
import { chmod, mkdir, readFile, rename, rm, unlink } from 'node:fs/promises';
|
||||
import { arch, platform, tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
|
@ -102,7 +102,6 @@ async function downloadFile(url: string, destination: string): Promise<void> {
|
|||
* Verify file checksum
|
||||
*/
|
||||
async function verifyChecksum(filePath: string, expectedChecksum: string): Promise<boolean> {
|
||||
const { createReadStream } = await import('node:fs');
|
||||
const hash = createHash('sha256');
|
||||
const stream = createReadStream(filePath);
|
||||
|
||||
|
@ -315,7 +314,6 @@ export async function updateHandler(options: UpdateOptions): Promise<UpdateResul
|
|||
]);
|
||||
|
||||
// Read and validate checksum
|
||||
const { readFile } = await import('node:fs/promises');
|
||||
const checksumContent = await readFile(checksumPath, 'utf-8');
|
||||
|
||||
// SHA256 checksums are 64 hex characters
|
||||
|
@ -346,7 +344,6 @@ export async function updateHandler(options: UpdateOptions): Promise<UpdateResul
|
|||
await replaceBinary(extractedBinary);
|
||||
|
||||
// Clean up temp directory
|
||||
const { rm } = await import('node:fs/promises');
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
|
||||
return {
|
||||
|
@ -361,7 +358,6 @@ export async function updateHandler(options: UpdateOptions): Promise<UpdateResul
|
|||
} catch (error) {
|
||||
// Clean up on error
|
||||
try {
|
||||
const { rm } = await import('node:fs/promises');
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { Box, Text, useApp } from 'ink';
|
||||
import { useEffect } from 'react';
|
||||
import { AnimatedLogo } from '../components/animated-logo';
|
||||
|
||||
export function Welcome() {
|
||||
const { exit } = useApp();
|
||||
|
||||
// Auto-exit after a few seconds
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
exit();
|
||||
}, 5000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [exit]);
|
||||
|
||||
return (
|
||||
<Box paddingY={2} paddingX={2}>
|
||||
<Box marginRight={4}>
|
||||
<AnimatedLogo color='#7C3AED' />
|
||||
</Box>
|
||||
<Box flexDirection='column' justifyContent='center'>
|
||||
<Text bold>Welcome to Buster</Text>
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>Type / to use slash commands</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text dimColor>Type @ to mention files</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text dimColor>Ctrl-C to exit</Text>
|
||||
</Box>
|
||||
<Box marginTop={2}>
|
||||
<Text dimColor>/help for more</Text>
|
||||
</Box>
|
||||
<Box marginTop={2}>
|
||||
<Text color='#7C3AED'>"Run `buster` and fix all the errors"</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { getCredentials } from './credentials';
|
||||
|
||||
const ProxyConfigSchema = z.object({
|
||||
baseURL: z.string().url().describe('Base URL for the AI proxy endpoint'),
|
||||
|
@ -18,7 +19,6 @@ export type ProxyConfig = z.infer<typeof ProxyConfigSchema>;
|
|||
* API key comes from credentials (required)
|
||||
*/
|
||||
export async function getProxyConfig(): Promise<ProxyConfig> {
|
||||
const { getCredentials } = await import('./credentials');
|
||||
const creds = await getCredentials();
|
||||
|
||||
if (!creds?.apiKey) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
||||
import { mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { z } from 'zod';
|
||||
|
@ -205,7 +205,6 @@ export async function getLatestConversation(
|
|||
*/
|
||||
export async function deleteConversation(chatId: string, workingDirectory: string): Promise<void> {
|
||||
const filePath = getConversationFilePath(chatId, workingDirectory);
|
||||
const { unlink } = await import('node:fs/promises');
|
||||
await unlink(filePath);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { type VersionCache, VersionCacheSchema } from './version-schemas';
|
||||
|
@ -72,7 +72,6 @@ export async function getCachedVersion(): Promise<VersionCache | null> {
|
|||
*/
|
||||
export async function clearVersionCache(): Promise<void> {
|
||||
try {
|
||||
const { unlink } = await import('node:fs/promises');
|
||||
await unlink(CACHE_FILE);
|
||||
} catch {
|
||||
// Cache file might not exist, that's fine
|
||||
|
|
Loading…
Reference in New Issue